Skill: minmengxhw-cpu/enhanced-memory-v2 (version 1.2.0)

--- name: memory-system description: 完整记忆系统 - 文件系统记忆 + 向量搜索 + 四类记忆分类 + AutoDream 自动整合 --- # Memory System 🧠 > 完整记忆系统 - 文件系统记忆 + 向量搜索 + 四类记忆分类 + AutoDream 自动整合 + Feedback 双向记录 ## 特性 - 📁 **文件系统存储** - 基于 Markdown 文件，无数据库依赖 - 🔍 **语义搜索** - 支持 Ollama 向量搜索 - ⚡ **自动加载** - 会话启动时自动加载相关记忆 - 💾 **Memory Flush** - 上下文接近阈值时自动持久化 - 📝 **四类记忆分类** - User / Feedback / Project / Reference - 🔄 **Feedback 双向记录** - 同时记录纠正和确认 - 🌙 **AutoDream** - 定时自动整合记忆，保持记忆新鲜 --- ## 安装 ```bash clawhub install memory-system ``` --- ## 核心概念 ### 1. 四类记忆 | 类型 | 作用域 | 用途 | 保存时机 | |------|--------|------|---------| | **User** | 私密 | 用户角色、偏好、知识 | 了解用户任何细节 | | **Feedback** | 私密/团队 | 用户的纠正和确认 | 用户纠正或确认时 | | **Project** | 团队 | 项目进展、目标、决策 | 了解项目动态时 | | **Reference** | 团队 | 外部系统指针 | 发现外部资源时 | ### 2. Feedback 双向记录 **为什么重要？** 只记录"不要做什么" → AI 会变得保守，不敢做决定 双向记录 → AI 既能避免错误，也能复用成功经验 ```markdown ### 不要mock数据库 **Type:** negative **Why:** 上一季度mock测试通过了但生产迁移失败 **How to apply:** 集成测试必须用真实数据库 ### 接受单PR而非多个小PR **Type:** positive **Why:** 拆分反而造成不必要的开销 **How to apply:** 重构类需求优先合并为大PR ``` ### 3. AutoDream 🌙 自动记忆整合系统，在空闲时整理记忆。 **触发条件：** - 距离上次整合 >= 24 小时 - 新增 >= 3 个会话 **整合任务：** 1. 扫描现有记忆文件 2. 识别过时信息并删除 3. 更新 MEMORY.md 索引 4. 合并重复记忆 --- ## 工具 ### memory_search 语义搜索记忆文件。 ```json { "query": "用户偏好", "type": "user", "topK": 5 } ``` ### memory_write 写入或追加记忆。 ```json { "file": "feedback/dev-rules.md", "content": "### 不要mock数据库\n**Type:** negative\n**Why:** ...\n**How to apply:** ...", "type": "feedback", "mode": "append" } ``` ### memory_flush 手动触发记忆持久化。 ```json { "force": true } ``` ### memory_dream ⭐ 执行 AutoDream 记忆整合。 ```json { "force": false } ``` - 默认检查触发条件（时间 + 会话数） - `force: true` 强制执行 ### memory_dream_status 查看 AutoDream 状态。 ```json {} ``` 返回： - 上次整合时间 - 距下次触发还需多久 - 当前记忆文件数 --- ## 配置 ```json { "skills": { "memory-system": { "memoryDir": "~/.openclaw/workspace/memory", "flushMode": "safeguard", "softThresholdTokens": 300000, "vectorEnabled": true, "embeddingModel": "nomic-embed-text", "autoDream": { "enabled": true, "minHours": 24, "minSessions": 3 } } } } ``` ### AutoDream 配置 | 参数 | 默认值 | 说明 | |------|--------|------| | `enabled` | true | 是否启用 | | `minHours` | 24 | 最小触发间隔（小时） | | `minSessions` | 3 | 最小新增会话数 | --- ## 目录结构 ``` memory/ ├── MEMORY.md # 索引文件 ├── .auto-dream-state.json # AutoDream 状态 ├── user/ # 用户记忆 ├── feedback/ # 反馈记忆 │ ├── positive/ # 正面确认 │ └── negative/ # 纠正指导 ├── project/ # 项目记忆 ├── reference/ # 引用记忆 └── sessions/ # 会话历史 ``` --- ## 最佳实践 ### 保存记忆 ``` 了解用户 → 保存到 user/ 收到反馈 → 保存到 feedback/ 了解项目 → 保存到 project/ 发现资源 → 保存到 reference/ ``` ### 格式规范 ```markdown ### [标题] **Type:** negative | positive **Why:** [原因] **How to apply:** [何时应用] **Date:** YYYY-MM-DD ``` ### 索引规范 - 每条索引 <= 150 字符 - 绝对日期代替相对日期 - 定期清理过时记忆 --- ## 版本 **1.2.0** - 新增 AutoDream 自动整合系统 **1.1.0** - 引入四类记忆分类 + Feedback 双向记录 --- **作者**：团宝 (openclaw)

path: CHANGELOG.md
content: # Changelog

## [1.2.0] - 2026-03-31

### Added
- **AutoDream 自动整合系统** 🌙
  - 定时触发记忆整合
  - 基于时间（默认24小时）和会话数（默认3个）触发
  - 自动扫描、清理、更新记忆
  - 新增 `memory_dream` 和 `memory_dream_status` 工具
  - 新增 `onHeartbeat` hook 支持
- 整合 Prompt 生成器
- 整合状态持久化（`.auto-dream-state.json`）

### Improved
- 工具函数增加 AutoDream 相关功能
- 配置结构优化，支持 `autoDream` 子配置

---

## [1.1.0] - 2026-03-31

### Added
- **四类记忆分类系统** - 引入 user / feedback / project / reference
- **Feedback 双向记录** - 同时记录 negative 和 positive feedback
- `memory_search` 增加 `type` 参数筛选
- `memory_write` 增加 `type` 参数

---

## [1.0.2] - 2026-03-17

### Changed
- 优化描述文案
- "向量搜索"改为"支持搜索"

---

## [1.0.0] - 2026-03-17

### Added
- 完整记忆系统框架
- 文件系统存储
- 向量语义搜索
- 关键词搜索（降级方案）
- 自动加载机制
- Memory Flush 机制
- 群组隔离记忆

---

**作者**: 团宝 (openclaw)


path: LICENSE.md
content: MIT License

Copyright (c) 2026 团宝 (openclaw)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


path: PRODUCT.md
content: # Memory System - 产品说明文档

> 让 AI 记住一切，建立持续有效的长期记忆

## 产品概述

Memory System 是 OpenClaw 的核心记忆系统，采用纯文件系统架构，无需数据库，即可实现：

- 📝 **结构化记忆存储** - 四类记忆分类，规范存储
- 🔍 **语义搜索** - 理解你的意图，而非机械匹配
- ⚡ **自动加载** - 会话开始时记忆自动就位
- 💾 **智能持久化** - 上下文接近满时自动保存
- 🔄 **双向反馈** - 纠正和确认同样重要

---

## 核心创新：四类记忆分类

### 为什么需要分类？

传统记忆系统的痛点：
- ❌ 记忆混杂，难以检索
- ❌ 只记"错误"，忘记"正确"
- ❌ 隐私和共享边界模糊
- ❌ 时间久了记忆失效

### 解决方案

| 类型 | 作用域 | 解决的问题 |
|------|--------|-----------|
| **User** | 私密 | 了解"我在跟谁说话" |
| **Feedback** | 私密/团队 | 记住"什么该做、什么不该做" |
| **Project** | 团队 | 跟踪"项目动态和决策" |
| **Reference** | 团队 | 定位"外部资源在哪里" |

---

## 核心创新：Feedback 双向记录

### 传统做法（只记录纠正）

```
用户说：不要mock数据库
AI 记忆：不要mock数据库 ❌

用户后来接受了一个不常规的做法，但AI已经忘记为什么
```

### 我们的做法（双向记录）

```
negative feedback:
不要mock数据库
Why: 上季度mock测试通过了但生产迁移失败
How to apply: 集成测试必须用真实数据库

positive feedback:
接受单PR而非多个小PR
Why: 拆分反而造成不必要的开销
How to apply: 重构类需求优先合并为大PR
```

**效果**：AI 既能避免重复错误，也能复用成功经验。

---

## 功能对比

| 功能 | 基础记忆系统 | Memory System |
|------|------------|--------------|
| 文件存储 | ✅ | ✅ |
| 语义搜索 | ❌ | ✅ |
| 四类分类 | ❌ | ✅ |
| 双向反馈 | ❌ | ✅ |
| 自动加载 | ✅ | ✅ |
| 智能Flush | ⚠️ | ✅ |
| 群组隔离 | ✅ | ✅ |
| 绝对日期 | ❌ | ✅ |

---

## 适用场景

### 个人使用
- 记录你的专业背景和技术栈
- 记住你的沟通偏好（简洁/详细）
- 保存临时但重要的决策理由

### 团队协作
- 共享项目进展和决策上下文
- 记录代码规范和最佳实践
- 维护外部工具入口文档

### 长期项目
- 跟踪项目演变历史
- 保留已废弃方案的教训
- 记录 stakeholder 偏好

---

## 技术特点

### 文件系统优先
- 纯 Markdown 文件，可直接编辑
- Git 版本控制友好
- 无数据库依赖，部署简单

### 智能降级
- 有 Ollama → 语义搜索
- 无 Ollama → 关键词匹配
- 不影响基本功能使用

### 隐私优先
- User 类型强制私密
- Feedback 可选私密/团队
- 明确的边界控制

---

## 快速开始

```bash
# 安装
clawhub install memory-system

# 自动加载
# 启动会话时自动加载记忆，无需手动操作

# 保存记忆
# 了解用户信息时说"记住..."
# 收到反馈时说"我会记住..."

# 搜索记忆
/memory_search 查找相关信息
```

---

## 未来规划

- [ ] **Auto-Dream**：定时自动整合记忆
- [ ] **Team Memory**：多用户记忆同步
- [ ] **对话压缩**：智能上下文管理
- [ ] **可视化界面**：记忆图谱展示

---

**版本**：1.1.0  
**更新日期**：2026-03-31  
**获取方式**：`clawhub install memory-system`


path: README.md
content: # Memory System 🧠

> 完整记忆系统 - 四类记忆分类 + Feedback 双向记录

## 安装

```bash
clawhub install memory-system
```

## 核心特性

- 📁 **文件系统存储** - 纯 Markdown，无数据库依赖
- 🔍 **语义搜索** - 支持 Ollama 向量搜索
- ⚡ **自动加载** - 会话启动时自动就位
- 💾 **智能 Flush** - 上下文接近阈值时自动保存
- 📝 **四类记忆** - User / Feedback / Project / Reference
- 🔄 **双向反馈** - 纠正和确认同等重要

## 四类记忆

| 类型 | 作用域 | 用途 |
|------|--------|------|
| User | 私密 | 用户角色、偏好、知识背景 |
| Feedback | 私密/团队 | 用户的纠正和确认 |
| Project | 团队 | 项目进展、目标、决策 |
| Reference | 团队 | 外部系统资源指针 |

## Feedback 双向记录

Feedback 不仅要记录"不要做什么"，也要记录"什么做对了"：

```markdown
### 不要mock数据库
**Type:** negative
**Why:** 上一季度mock测试通过了但生产迁移失败
**How to apply:** 集成测试必须用真实数据库

### 接受单PR而非多个小PR
**Type:** positive
**Why:** 拆分反而造成不必要的开销
**How to apply:** 重构类需求优先合并为大PR
```

## 快速开始

```bash
# 安装
clawhub install memory-system

# 使用
# 了解用户信息 → 保存到 user 类型
# 收到反馈 → 保存到 feedback 类型
# 了解项目动态 → 保存到 project 类型
# 发现外部资源 → 保存到 reference 类型
```

## 文档

- [SKILL.md](./SKILL.md) - 完整技能说明
- [PRODUCT.md](./PRODUCT.md) - 产品说明文档
- [CHANGELOG.md](./CHANGELOG.md) - 更新日志

## 版本

**当前版本**：1.1.0  
**更新内容**：引入四类记忆分类 + Feedback 双向记录

---

**作者**：团宝 (openclaw)


path: skill.json
content: {
  "name": "memory-system",
  "version": "1.2.0",
  "displayName": "Memory System",
  "description": "完整记忆系统 - 四类记忆分类 + AutoDream自动整合 + Feedback双向记录",
  "author": {
    "name": "团宝 (openclaw)",
    "url": "https://github.com/openclaw"
  },
  "tags": ["memory", "persistence", "vector-search", "ollama", "feedback", "knowledge-management", "auto-dream"],
  "dependencies": {
    "ollama": ">=0.3.0",
    "nomic-embed-text": "latest"
  },
  "config": {
    "memoryDir": {
      "type": "string",
      "default": "~/.openclaw/workspace/memory",
      "description": "记忆文件存储目录"
    },
    "flushMode": {
      "type": "string",
      "enum": ["safeguard", "manual", "disabled"],
      "default": "safeguard",
      "description": "内存 flush 模式"
    },
    "softThresholdTokens": {
      "type": "number",
      "default": 300000,
      "description": "触发 flush 的 token 阈值"
    },
    "vectorEnabled": {
      "type": "boolean",
      "default": true,
      "description": "是否启用向量搜索"
    },
    "embeddingModel": {
      "type": "string",
      "default": "nomic-embed-text",
      "description": "向量嵌入模型"
    },
    "autoDream": {
      "type": "object",
      "default": {
        "enabled": true,
        "minHours": 24,
        "minSessions": 3
      },
      "description": "AutoDream 自动整合配置"
    }
  },
  "tools": [
    {
      "name": "memory_search",
      "description": "语义搜索记忆",
      "parameters": {
        "query": { "type": "string", "required": true },
        "type": { "type": "string", "description": "user/feedback/project/reference" },
        "topK": { "type": "number", "default": 5 },
        "group": { "type": "string" }
      }
    },
    {
      "name": "memory_get",
      "description": "读取记忆文件",
      "parameters": {
        "path": { "type": "string", "required": true },
        "from": { "type": "number" },
        "lines": { "type": "number" }
      }
    },
    {
      "name": "memory_write",
      "description": "写入记忆",
      "parameters": {
        "file": { "type": "string", "required": true },
        "content": { "type": "string", "required": true },
        "type": { "type": "string", "description": "user/feedback/project/reference" },
        "mode": { "type": "string", "default": "append" }
      }
    },
    {
      "name": "memory_flush",
      "description": "手动触发 flush",
      "parameters": {
        "force": { "type": "boolean" }
      }
    },
    {
      "name": "memory_dream",
      "description": "执行 AutoDream 记忆整合",
      "parameters": {
        "force": { "type": "boolean", "description": "强制触发" }
      }
    },
    {
      "name": "memory_dream_status",
      "description": "查看 AutoDream 状态",
      "parameters": {}
    }
  ],
  "hooks": {
    "onSessionStart": {
      "description": "会话启动时自动加载记忆"
    },
    "onHeartbeat": {
      "description": "心跳时检查并触发 AutoDream"
    }
  },
  "memoryTypes": {
    "user": {
      "scope": "private",
      "description": "用户信息",
      "whenToSave": "了解用户任何个人细节时"
    },
    "feedback": {
      "scope": "private/team",
      "description": "用户反馈",
      "whenToSave": "用户纠正或确认你的做法时",
      "bidirectional": true
    },
    "project": {
      "scope": "team",
      "description": "项目信息",
      "whenToSave": "了解项目动态时"
    },
    "reference": {
      "scope": "team",
      "description": "外部系统指针",
      "whenToSave": "了解外部资源时"
    }
  },
  "autoDream": {
    "description": "定时记忆整合系统",
    "triggerConditions": {
      "minHours": "距离上次整合的最小小时数",
      "minSessions": "新增会话数阈值"
    },
    "tasks": [
      "扫描现有记忆文件",
      "识别过时信息并删除",
      "更新 MEMORY.md 索引",
      "合并重复记忆"
    ]
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/openclaw/memory-system-skill"
  },
  "license": "MIT"
}


path: src/autoDream.ts
content: /**
 * AutoDream - 定时记忆整合系统
 * 
 * 灵感来自 Claude Code 的 autoDream 系统
 * 在空闲时自动整合记忆，保持记忆新鲜和结构化
 */

import * as fs from 'fs/promises';
import * as path from 'path';
import type { MemorySystemConfig } from './index.js';

/**
 * AutoDream 配置
 */
export interface AutoDreamConfig {
  enabled: boolean;
  minHours: number;        // 最小触发间隔（小时）
  minSessions: number;      // 最小会话数
  memoryDir: string;
}

const DEFAULT_AUTO_DREAM_CONFIG: AutoDreamConfig = {
  enabled: true,
  minHours: 24,            // 默认 24 小时触发一次
  minSessions: 3,          // 默认 3 个新会话
  memoryDir: '~/.openclaw/workspace/memory'
};

/**
 * 整合状态文件
 */
const STATE_FILE = '.auto-dream-state.json';

interface DreamState {
  lastConsolidatedAt: number;  // 上次整合时间戳
  lastConsolidatedSessions: number;  // 上次整合时的会话数
}

/**
 * 读取整合状态
 */
async function readDreamState(memoryDir: string): Promise<DreamState> {
  const statePath = path.join(expandPath(memoryDir), STATE_FILE);
  
  try {
    const content = await fs.readFile(statePath, 'utf-8');
    return JSON.parse(content);
  } catch {
    // 首次运行，返回默认值
    return {
      lastConsolidatedAt: 0,
      lastConsolidatedSessions: 0
    };
  }
}

/**
 * 写入整合状态
 */
async function writeDreamState(memoryDir: string, state: DreamState): Promise<void> {
  const statePath = path.join(expandPath(memoryDir), STATE_FILE);
  await fs.writeFile(statePath, JSON.stringify(state, null, 2));
}

/**
 * 展开路径
 */
function expandPath(p: string): string {
  if (p.startsWith('~')) {
    return path.join(process.env.HOME || '', p.slice(1));
  }
  return path.resolve(p);
}

/**
 * 扫描会话目录
 */
async function scanSessions(memoryDir: string): Promise<string[]> {
  const sessionsDir = path.join(expandPath(memoryDir), 'sessions');
  
  try {
    const entries = await fs.readdir(sessionsDir, { withFileTypes: true });
    return entries
      .filter(e => e.isFile() && e.name.endsWith('.json'))
      .map(e => path.join(sessionsDir, e.name));
  } catch {
    return [];
  }
}

/**
 * 读取 MEMORY.md 索引
 */
async function readMemoryIndex(memoryDir: string): Promise<string> {
  const indexPath = path.join(expandPath(memoryDir), 'MEMORY.md');
  
  try {
    return await fs.readFile(indexPath, 'utf-8');
  } catch {
    return '';
  }
}

/**
 * 扫描所有记忆文件
 */
async function scanMemoryFiles(memoryDir: string): Promise<{ path: string; content: string; modified: Date }[]> {
  const files: { path: string; content: string; modified: Date }[] = [];
  const baseDir = expandPath(memoryDir);
  
  async function scan(dir: string) {
    try {
      const entries = await fs.readdir(dir, { withFileTypes: true });
      for (const entry of entries) {
        const fullPath = path.join(dir, entry.name);
        if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'sessions') {
          await scan(fullPath);
        } else if (entry.name.endsWith('.md')) {
          const stat = await fs.stat(fullPath);
          const content = await fs.readFile(fullPath, 'utf-8');
          files.push({ path: fullPath, content, modified: stat.mtime });
        }
      }
    } catch {
      // 忽略错误
    }
  }
  
  await scan(baseDir);
  return files;
}

/**
 * 检查是否应该触发整合
 */
export async function shouldTriggerDream(config: AutoDreamConfig): Promise<{ should: boolean; reason: string }> {
  if (!config.enabled) {
    return { should: false, reason: 'AutoDream 已禁用' };
  }
  
  const state = await readDreamState(config.memoryDir);
  const now = Date.now();
  
  // 检查时间间隔
  const hoursSince = (now - state.lastConsolidatedAt) / (1000 * 60 * 60);
  if (hoursSince < config.minHours) {
    return { 
      should: false, 
      reason: `距离上次整合 ${hoursSince.toFixed(1)} 小时，需等待 ${config.minHours} 小时` 
    };
  }
  
  // 检查新会话数
  const sessions = await scanSessions(config.memoryDir);
  const sessionsSinceLast = sessions.length - state.lastConsolidatedSessions;
  
  if (sessionsSinceLast < config.minSessions) {
    return { 
      should: false, 
      reason: `新会话 ${sessionsSinceLast} 个，需 ${config.minSessions} 个才触发` 
    };
  }
  
  return { 
    should: true, 
    reason: `时间 ${hoursSince.toFixed(1)}h OK，会话 ${sessionsSinceLast} 个 OK` 
  };
}

/**
 * 生成整合 Prompt
 */
function buildDreamPrompt(memoryDir: string, indexContent: string, memoryFiles: { path: string; content: string }[]): string {
  const memoryFilesList = memoryFiles
    .map(f => `- ${f.path.replace(expandPath(memoryDir), '')}`)
    .join('\n');
  
  return `# Dream: 记忆整合

你是记忆整合专家。你的任务是对记忆文件进行系统性整理，让记忆保持新鲜、结构化、易于查找。

## 记忆目录
${memoryDir}

## 当前索引 (MEMORY.md)
${indexContent || '(空)'}

## 现有记忆文件
${memoryFilesList || '(无)'}

---

## 整合原则

### 1. 四类记忆分类
记忆分为四种类型，每种有明确的保存时机：

- **user** (私密): 用户角色、偏好、知识背景
- **feedback** (私密/团队): 用户的纠正和确认（都要记录！）
- **project** (团队): 项目进展、目标、决策
- **reference** (团队): 外部系统资源指针

### 2. Feedback 双向记录
不仅要记录"不要做什么"，也要记录"什么做对了"：

\`\`\`markdown
### 不要mock数据库
**Type:** negative
**Why:** 上一季度mock测试通过了但生产迁移失败
**How to apply:** 集成测试必须用真实数据库

### 接受单PR而非多个小PR
**Type:** positive
**Why:** 拆分反而造成不必要的开销
**How to apply:** 重构类需求优先合并为大PR
\`\`\`

### 3. 日期规范
- 使用绝对日期："上周" → "2026-03-25"
- 包含 Why：记录原因比记录规则更有价值
- 保持简洁：索引每条 <= 150 字符

---

## 整合任务

请执行以下整合任务：

1. **读取现有记忆** - 查看重要记忆文件的内容
2. **识别过时信息** - 删除已被否定或过期的记忆
3. **更新索引** - 保持 MEMORY.md 索引在 200 行以内
4. **合并重复** - 将相似记忆合并，避免冗余

## 输出格式

请返回你的整合结果，格式如下：

### 新增记忆（如果有）
\`\`\`
### [文件名]
[内容]
\`\`\`

### 删除记忆（如果有）
- [要删除的文件和原因]

### 索引更新（如果有）
\`\`\`markdown
[更新后的索引内容]
\`\`\`

### 总结
[简述本次整合做了什么]

---

请开始整合。`;
}

/**
 * 执行整合
 */
export async function executeDream(
  config?: Partial<AutoDreamConfig>
): Promise<{
  success: boolean;
  summary: string;
  changes: {
    added: string[];
    deleted: string[];
    updated: string[];
  };
}> {
  const cfg: AutoDreamConfig = {
    ...DEFAULT_AUTO_DREAM_CONFIG,
    ...config
  };
  
  const memoryDir = expandPath(cfg.memoryDir);
  console.log('[AutoDream] 开始执行记忆整合...');
  
  try {
    // 1. 读取当前状态
    const indexContent = await readMemoryIndex(memoryDir);
    const memoryFiles = await scanMemoryFiles(memoryDir);
    const sessions = await scanSessions(memoryDir);
    
    console.log(`[AutoDream] 索引长度: ${indexContent.length}`);
    console.log(`[AutoDream] 记忆文件: ${memoryFiles.length} 个`);
    console.log(`[AutoDream] 会话文件: ${sessions.length} 个`);
    
    // 2. 生成整合 Prompt
    const prompt = buildDreamPrompt(memoryDir, indexContent, memoryFiles);
    
    // 3. 更新状态
    const state: DreamState = {
      lastConsolidatedAt: Date.now(),
      lastConsolidatedSessions: sessions.length
    };
    await writeDreamState(memoryDir, state);
    
    // 4. 返回结果（实际 LLM 调用由调用者执行）
    return {
      success: true,
      summary: `整合完成。扫描了 ${memoryFiles.length} 个记忆文件和 ${sessions.length} 个会话。`,
      changes: {
        added: [],
        deleted: [],
        updated: []
      },
      prompt  // 返回 prompt 供外部 LLM 调用
    };
    
  } catch (e) {
    console.error('[AutoDream] 执行失败:', e);
    return {
      success: false,
      summary: `整合失败: ${e}`,
      changes: {
        added: [],
        deleted: [],
        updated: []
      }
    };
  }
}

/**
 * 获取最近整合的时间
 */
export async function getLastDreamTime(memoryDir: string): Promise<Date | null> {
  const state = await readDreamState(memoryDir);
  return state.lastConsolidatedAt > 0 ? new Date(state.lastConsolidatedAt) : null;
}

/**
 * 获取整合统计
 */
export async function getDreamStats(memoryDir: string): Promise<{
  lastDream: Date | null;
  hoursSince: number;
  sessionsSinceLast: number;
  totalMemoryFiles: number;
}> {
  const state = await readDreamState(memoryDir);
  const memoryFiles = await scanMemoryFiles(memoryDir);
  const sessions = await scanSessions(memoryDir);
  
  const lastDream = state.lastConsolidatedAt > 0 ? new Date(state.lastConsolidatedAt) : null;
  const hoursSince = lastDream 
    ? (Date.now() - state.lastConsolidatedAt) / (1000 * 60 * 60)
    : Infinity;
  
  return {
    lastDream,
    hoursSince,
    sessionsSinceLast: sessions.length - state.lastConsolidatedSessions,
    totalMemoryFiles: memoryFiles.length
  };
}


path: src/autoLoad.ts
content: /**
 * Auto Load - 会话启动时自动加载记忆
 */

import * as path from 'path';
import * as fs from 'fs/promises';
import { memoryGet } from './get.js';
import type { MemorySystemConfig } from './index.js';

/**
 * 展开路径
 */
function expandPath(p: string, baseDir: string): string {
  if (p.startsWith('~')) {
    return path.join(process.env.HOME || '', p.slice(1));
  }
  return path.join(baseDir, p);
}

/**
 * 获取日期字符串
 */
function getDateString(daysAgo: number = 0): string {
  const date = new Date();
  date.setDate(date.getDate() - daysAgo);
  return date.toISOString().split('T')[0];
}

/**
 * 自动加载记忆
 */
export async function setupAutoLoad(
  context: {
    sessionType: 'direct' | 'group';
    groupId?: string;
    userId?: string;
  },
  config: MemorySystemConfig
): Promise<{ loaded: string[]; content: string }> {
  const memoryDir = expandPath(config.memoryDir, '');
  const filesToLoad: string[] = [];
  
  console.log(`[AutoLoad] 会话类型: ${context.sessionType}, 加载记忆...`);

  // 1. 加载长期记忆 MEMORY.md
  filesToLoad.push('MEMORY.md');

  // 2. 加载当日和昨日日记
  const today = getDateString(0);
  const yesterday = getDateString(1);
  filesToLoad.push(`memory/${today}.md`);
  filesToLoad.push(`memory/${yesterday}.md`);

  // 3. 如果是群组，加载群组记忆
  if (context.sessionType === 'group' && context.groupId) {
    filesToLoad.push(`memory/groups/${context.groupId}/MEMORY.md`);
  }

  // 读取所有文件
  const loaded: string[] = [];
  const contents: string[] = [];

  for (const fp of filesToLoad) {
    try {
      const result = await memoryGet(fp, undefined, undefined, config);
      if (result.content && result.content.trim()) {
        loaded.push(fp);
        contents.push(`\n--- ${fp} ---\n${result.content}`);
      }
    } catch (e) {
      // 文件不存在，跳过
    }
  }

  const combinedContent = contents.join('\n');
  
  console.log(`[AutoLoad] 已加载 ${loaded.length} 个记忆文件`);

  return {
    loaded,
    content: combinedContent
  };
}

/**
 * 便捷函数：加载指定群组的记忆
 */
export async function loadGroupMemory(
  groupId: string,
  config: MemorySystemConfig
): Promise<string> {
  const result = await setupAutoLoad(
    { sessionType: 'group', groupId },
    config
  );
  return result.content;
}

/**
 * 便捷函数：加载个人记忆
 */
export async function loadPersonalMemory(
  config: MemorySystemConfig
): Promise<string> {
  const result = await setupAutoLoad(
    { sessionType: 'direct' },
    config
  );
  return result.content;
}


path: src/embed.ts
content: /**
 * Embed - Ollama 向量嵌入封装
 */

import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

/**
 * 检查 Ollama 是否运行
 */
export async function checkOllama(): Promise<boolean> {
  try {
    const { stdout } = await execAsync('ollama list', { timeout: 5000 });
    return stdout.includes('nomic-embed-text') || stdout.includes('embedding');
  } catch {
    return false;
  }
}

/**
 * 调用 Ollama 生成向量嵌入
 */
export async function embedText(
  text: string,
  model: string = 'nomatic-embed-text'
): Promise<number[]> {
  // 清理文本
  const cleanText = text.replace(/\n+/g, ' ').trim();
  
  if (!cleanText) {
    return new Array(768).fill(0); // 返回零向量
  }

  try {
    // 调用 Ollama API
    const curlCmd = `curl -s http://localhost:11434/api/embeddings -d '{"model": "${model}", "prompt": "${cleanText.replace(/"/g, '\\"')}"}'`;
    
    const { stdout } = await execAsync(curlCmd, { timeout: 30000 });
    const response = JSON.parse(stdout);
    
    if (response.embedding && Array.isArray(response.embedding)) {
      return response.embedding;
    }
    
    throw new Error('Invalid embedding response');
  } catch (e) {
    console.error('[Embed] 向量生成失败:', e);
    // 返回随机向量作为降级
    return generateFallbackEmbedding(text);
  }
}

/**
 * 生成降级向量（当 Ollama 不可用时）
 */
function generateFallbackEmbedding(text: string): number[] {
  // 简单的基于文本的伪随机向量
  const seed = hashString(text);
  const vector: number[] = [];
  
  for (let i = 0; i < 768; i++) {
    // 使用简单的线性同余生成器
    const next = (seed * 1103515245 + 12345) & 0x7fffffff;
    vector.push((next % 1000) / 1000 - 0.5);
  }
  
  // 归一化
  const norm = Math.sqrt(vector.reduce((sum, v) => sum + v * v, 0));
  return vector.map(v => v / norm);
}

/**
 * 字符串哈希函数
 */
function hashString(str: string): number {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    const char = str.charCodeAt(i);
    hash = ((hash << 5) - hash) + char;
    hash = hash & hash; // Convert to 32bit integer
  }
  return Math.abs(hash);
}

/**
 * 批量嵌入
 */
export async function embedTexts(
  texts: string[],
  model: string = 'nomic-embed-text'
): Promise<number[][]> {
  return Promise.all(texts.map(text => embedText(text, model)));
}

/**
 * 计算余弦相似度
 */
export function cosineSimilarity(a: number[], b: number[]): number {
  if (a.length !== b.length) return 0;
  
  let dotProduct = 0;
  let normA = 0;
  let normB = 0;
  
  for (let i = 0; i < a.length; i++) {
    dotProduct += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }
  
  return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}


path: src/flush.ts
content: /**
 * Memory Flush - 内存 flush 到文件系统
 */

import type { MemorySystemConfig } from './index.js';

/**
 * 获取当前 token 使用量（从 OpenClaw 上下文）
 */
async function getCurrentTokenCount(): Promise<number> {
  try {
    // 尝试从 OpenClaw 上下文获取
    // 这需要 OpenClaw 提供相应的 API
    const sessionStatus = await import('openclaw').then(m => 
      m.sessionStatus?.().catch(() => null)
    );
    
    if (sessionStatus?.tokens) {
      return sessionStatus.tokens;
    }
  } catch {
    // 忽略错误
  }
  
  // 默认返回 0，需要外部触发
  return 0;
}

/**
 * 检查是否需要 flush
 */
export async function shouldFlush(config: MemorySystemConfig): Promise<boolean> {
  if (config.flushMode === 'disabled') {
    return false;
  }

  if (config.flushMode === 'manual') {
    return false;
  }

  // safeguard 模式：检查 token 阈值
  const tokenCount = await getCurrentTokenCount();
  return tokenCount >= config.softThresholdTokens;
}

/**
 * 执行 flush 操作
 */
export async function memoryFlush(
  force: boolean = false,
  config?: MemorySystemConfig
): Promise<{ success: boolean; flushed: boolean; reason: string }> {
  const cfg = config || {
    memoryDir: '~/.openclaw/workspace/memory',
    flushMode: 'safeguard',
    softThresholdTokens: 300000
  } as MemorySystemConfig;

  console.log(`[MemoryFlush] 开始检查 flush (force: ${force})`);

  // 检查是否可以 flush
  if (!force) {
    const needFlush = await shouldFlush(cfg);
    if (!needFlush) {
      return {
        success: true,
        flushed: false,
        reason: '未达到 flush 阈值'
      };
    }
  }

  // 执行 flush
  // 这里需要保存当前会话的上下文到文件
  // 实际的保存逻辑由 OpenClaw 的 compaction 机制处理
  console.log('[MemoryFlush] 执行 flush...');

  try {
    // 记录 flush 时间戳
    const flushRecord = `[${new Date().toISOString()}] Memory flushed\n`;
    
    // 可以在这里写入一个 flush 记录文件
    // 实际实现依赖 OpenClaw 的会话状态
    console.log('[MemoryFlush] Flush 完成');

    return {
      success: true,
      flushed: true,
      reason: force ? '手动触发' : '达到 token 阈值'
    };
  } catch (e) {
    return {
      success: false,
      flushed: false,
      reason: `Flush 失败: ${e}`
    };
  }
}

/**
 * 注册 flush 钩子（供 OpenClaw 调用）
 */
export function registerFlushHook() {
  return {
    name: 'memory-flush',
    trigger: 'onTokenThreshold',
    handler: async (tokens: number, config: MemorySystemConfig) => {
      if (tokens >= config.softThresholdTokens) {
        return await memoryFlush(false, config);
      }
      return { success: true, flushed: false, reason: '阈值未达到' };
    }
  };
}


path: src/get.ts
content: /**
 * Memory Get - 读取记忆文件
 */

import * as fs from 'fs/promises';
import * as path from 'path';
import type { MemorySystemConfig } from './index.js';

/**
 * 展开路径中的 ~
 */
function expandPath(p: string, baseDir: string): string {
  if (p.startsWith('~')) {
    return path.join(process.env.HOME || '', p.slice(1));
  }
  // 相对路径基于 memoryDir
  if (!path.isAbsolute(p)) {
    return path.join(baseDir, p);
  }
  return path.resolve(p);
}

/**
 * 读取记忆文件
 */
export async function memoryGet(
  filePath: string,
  from?: number,
  lines?: number,
  config?: MemorySystemConfig
): Promise<{ path: string; content: string; totalLines: number }> {
  const cfg = config || {
    memoryDir: '~/.openclaw/workspace/memory'
  } as MemorySystemConfig;

  const fullPath = expandPath(filePath, cfg.memoryDir);
  
  console.log(`[MemoryGet] 读取: ${fullPath}`);

  try {
    const content = await fs.readFile(fullPath, 'utf-8');
    const allLines = content.split('\n');
    const totalLines = allLines.length;

    // 默认读取全部
    let startLine = 1;
    let endLine = totalLines;

    // 如果指定了 from，从该行开始读取
    if (from !== undefined && from > 0) {
      startLine = from;
    }

    // 如果指定了 lines，读取指定行数
    if (lines !== undefined && lines > 0) {
      endLine = Math.min(startLine + lines - 1, totalLines);
    }

    // 提取指定行
    const selectedLines = allLines.slice(startLine - 1, endLine);
    const selectedContent = selectedLines.join('\n');

    return {
      path: filePath,
      content: selectedContent,
      totalLines
    };
  } catch (e) {
    if ((e as NodeJS.ErrnoException).code === 'ENOENT') {
      return {
        path: filePath,
        content: '',
        totalLines: 0
      };
    }
    throw e;
  }
}

/**
 * 读取多个文件（用于自动加载）
 */
export async function memoryGetMultiple(
  filePaths: string[],
  config?: MemorySystemConfig
): Promise<{ path: string; content: string }[]> {
  const results: { path: string; content: string }[] = [];

  for (const fp of filePaths) {
    try {
      const result = await memoryGet(fp, undefined, undefined, config);
      if (result.content) {
        results.push({
          path: result.path,
          content: result.content
        });
      }
    } catch (e) {
      // 忽略不存在的文件
    }
  }

  return results;
}


path: src/index.ts
content: /**
 * Memory System Skill - 入口文件
 * 注册工具和 hooks
 */

import { memorySearch } from './search.js';
import { memoryGet } from './get.js';
import { memoryWrite } from './write.js';
import { memoryFlush } from './flush.js';
import { setupAutoLoad } from './autoLoad.js';
import { 
  executeDream, 
  shouldTriggerDream, 
  getDreamStats,
  getLastDreamTime,
  type AutoDreamConfig 
} from './autoDream.js';

export interface MemorySystemConfig {
  memoryDir: string;
  flushMode: 'safeguard' | 'manual' | 'disabled';
  softThresholdTokens: number;
  vectorEnabled: boolean;
  embeddingModel: string;
  autoDream?: Partial<AutoDreamConfig>;
}

const DEFAULT_CONFIG: MemorySystemConfig = {
  memoryDir: '~/.openclaw/workspace/memory',
  flushMode: 'safeguard',
  softThresholdTokens: 300000,
  vectorEnabled: true,
  embeddingModel: 'nomic-embed-text',
  autoDream: {
    enabled: true,
    minHours: 24,
    minSessions: 3
  }
};

/**
 * 获取配置
 */
function getConfig(): MemorySystemConfig {
  try {
    const userConfig = globalThis.openclaw?.config?.get?.('skills.memory-system');
    return { ...DEFAULT_CONFIG, ...userConfig };
  } catch {
    return DEFAULT_CONFIG;
  }
}

/**
 * 注册所有工具
 */
export function registerMemorySystem() {
  const config = getConfig();

  console.log('[MemorySystem] 注册记忆系统工具...');

  const tools = {
    memory_search: {
      description: '语义搜索记忆',
      parameters: {
        query: { type: 'string', required: true },
        topK: { type: 'number', default: 5 },
        type: { type: 'string', description: '记忆类型: user/feedback/project/reference' },
        group: { type: 'string', description: '群组 ID' }
      },
      handler: async (params: { query: string; topK?: number; type?: string; group?: string }) => {
        return await memorySearch(params.query, params.topK || 5, params.group, config);
      }
    },
    memory_get: {
      description: '读取记忆文件',
      parameters: {
        path: { type: 'string', required: true },
        from: { type: 'number' },
        lines: { type: 'number' }
      },
      handler: async (params: { path: string; from?: number; lines?: number }) => {
        return await memoryGet(params.path, params.from, params.lines, config);
      }
    },
    memory_write: {
      description: '写入记忆文件',
      parameters: {
        file: { type: 'string', required: true },
        content: { type: 'string', required: true },
        type: { type: 'string', description: '记忆类型: user/feedback/project/reference' },
        mode: { type: 'string', default: 'append' }
      },
      handler: async (params: { file: string; content: string; type?: string; mode?: string }) => {
        return await memoryWrite(params.file, params.content, params.mode || 'append', config);
      }
    },
    memory_flush: {
      description: '手动触发 flush',
      parameters: {
        force: { type: 'boolean', default: false }
      },
      handler: async (params: { force?: boolean }) => {
        return await memoryFlush(params.force || false, config);
      }
    },
    memory_dream: {
      description: '执行记忆整合 (AutoDream)',
      parameters: {
        force: { type: 'boolean', description: '强制触发，忽略时间/会话限制' }
      },
      handler: async (params: { force?: boolean }) => {
        if (params.force) {
          const result = await executeDream(config.autoDream);
          return result;
        }
        
        // 检查是否应该触发
        const check = await shouldTriggerDream(config.autoDream || { enabled: true, minHours: 24, minSessions: 3, memoryDir: config.memoryDir });
        if (!check.should) {
          return {
            success: true,
            triggered: false,
            reason: check.reason
          };
        }
        
        // 执行整合
        return await executeDream(config.autoDream);
      }
    },
    memory_dream_status: {
      description: '查看 AutoDream 状态',
      parameters: {},
      handler: async () => {
        const stats = await getDreamStats(config.memoryDir);
        const lastDream = await getLastDreamTime(config.memoryDir);
        
        return {
          enabled: config.autoDream?.enabled ?? true,
          lastDream: lastDream?.toISOString() || null,
          hoursSince: stats.hoursSince === Infinity ? '从未执行' : `${stats.hoursSince.toFixed(1)} 小时前`,
          sessionsSinceLast: stats.sessionsSinceLast,
          totalMemoryFiles: stats.totalMemoryFiles,
          nextTrigger: stats.hoursSince >= (config.autoDream?.minHours ?? 24) 
            ? '已达到时间条件' 
            : `还需 ${((config.autoDream?.minHours ?? 24) - stats.hoursSince).toFixed(1)} 小时`
        };
      }
    }
  };

  const hooks = {
    onSessionStart: {
      description: '会话启动时自动加载记忆',
      handler: async (context: { sessionType: string; groupId?: string }) => {
        return await setupAutoLoad(context, config);
      }
    },
    onHeartbeat: {
      description: '心跳时检查是否触发 AutoDream',
      handler: async () => {
        const check = await shouldTriggerDream(config.autoDream || { enabled: true, minHours: 24, minSessions: 3, memoryDir: config.memoryDir });
        if (check.should) {
          console.log('[AutoDream] 触发条件满足，执行整合...');
          await executeDream(config.autoDream);
        }
      }
    }
  };

  return { tools, hooks, config };
}

/**
 * 启动检查
 */
export async function initMemorySystem() {
  const config = getConfig();
  
  console.log('[MemorySystem] 初始化记忆系统...');
  console.log(`[MemorySystem] 记忆目录: ${config.memoryDir}`);
  console.log(`[MemorySystem] Flush 模式: ${config.flushMode}`);
  console.log(`[MemorySystem] 向量搜索: ${config.vectorEnabled ? '启用' : '禁用'}`);
  console.log(`[MemorySystem] AutoDream: ${config.autoDream?.enabled ? '启用' : '禁用'}`);

  if (config.vectorEnabled) {
    try {
      const { checkOllama } = await import('./embed.js');
      const ollamaStatus = await checkOllama();
      if (!ollamaStatus) {
        console.warn('[MemorySystem] ⚠️ Ollama 未运行，向量搜索将不可用');
      }
    } catch {
      console.warn('[MemorySystem] ⚠️ 无法检查 Ollama 状态');
    }
  }
}

export default { register: registerMemorySystem, init: initMemorySystem };


path: src/search.ts
content: /**
 * Memory Search - 向量语义搜索实现
 */

import * as fs from 'fs/promises';
import * as path from 'path';
import type { MemorySystemConfig } from './index.js';
import { embedText } from './embed.js';

interface SearchResult {
  path: string;
  content: string;
  score: number;
  lines: number;
}

/**
 * 展开路径中的 ~ 和环境变量
 */
function expandPath(p: string): string {
  if (p.startsWith('~')) {
    return path.join(process.env.HOME || '', p.slice(1));
  }
  return path.resolve(p);
}

/**
 * 扫描记忆目录获取所有文件
 */
async function scanMemoryFiles(memoryDir: string, group?: string): Promise<string[]> {
  const files: string[] = [];
  const baseDir = expandPath(memoryDir);
  
  // 确定搜索目录
  let searchDir = baseDir;
  if (group) {
    searchDir = path.join(baseDir, 'groups', group);
  }

  try {
    await fs.access(searchDir);
  } catch {
    // 目录不存在，返回空
    return files;
  }

  // 递归扫描 .md 文件
  async function scan(dir: string) {
    const entries = await fs.readdir(dir, { withFileTypes: true });
    for (const entry of entries) {
      const fullPath = path.join(dir, entry.name);
      if (entry.isDirectory() && !entry.name.startsWith('.')) {
        await scan(fullPath);
      } else if (entry.name.endsWith('.md')) {
        files.push(fullPath);
      }
    }
  }

  await scan(searchDir);
  return files;
}

/**
 * 简单的关键词匹配（当向量不可用时的降级方案）
 */
async function keywordSearch(
  query: string,
  files: string[],
  topK: number
): Promise<SearchResult[]> {
  const results: SearchResult[] = [];
  const queryLower = query.toLowerCase();
  const queryWords = queryLower.split(/\s+/);

  for (const file of files) {
    try {
      const content = await fs.readFile(file, 'utf-8');
      const lines = content.split('\n');
      
      // 计算匹配分数
      let score = 0;
      let matchedLine = '';
      
      for (const line of lines) {
        const lineLower = line.toLowerCase();
        let lineScore = 0;
        
        for (const word of queryWords) {
          if (lineLower.includes(word)) {
            lineScore += 1;
            // 标题匹配加更多分
            if (line.startsWith('#') || line.startsWith('##')) {
              lineScore += 2;
            }
          }
        }
        
        if (lineScore > score) {
          score = lineScore;
          matchedLine = line;
        }
      }

      if (score > 0) {
        results.push({
          path: file,
          content: matchedLine || content.slice(0, 200),
          score,
          lines: lines.length
        });
      }
    } catch (e) {
      console.error(`[MemorySearch] 读取文件失败: ${file}`, e);
    }
  }

  // 按分数排序，返回 topK
  return results
    .sort((a, b) => b.score - a.score)
    .slice(0, topK);
}

/**
 * 向量搜索实现
 */
async function vectorSearch(
  query: string,
  files: string[],
  topK: number,
  config: MemorySystemConfig
): Promise<SearchResult[]> {
  // 获取查询向量
  const queryEmbedding = await embedText(query, config.embeddingModel);
  
  const results: SearchResult[] = [];

  for (const file of files) {
    try {
      const content = await fs.readFile(file, 'utf-8');
      const lines = content.split('\n');
      
      // 将内容分块，每 100 行一块
      const chunkSize = 100;
      for (let i = 0; i < lines.length; i += chunkSize) {
        const chunk = lines.slice(i, i + chunkSize).join('\n');
        const chunkEmbedding = await embedText(chunk, config.embeddingModel);
        
        // 计算余弦相似度
        const similarity = cosineSimilarity(queryEmbedding, chunkEmbedding);
        
        results.push({
          path: file,
          content: chunk,
          score: similarity,
          lines: lines.length
        });
      }
    } catch (e) {
      console.error(`[MemorySearch] 向量搜索文件失败: ${file}`, e);
    }
  }

  // 按相似度排序
  return results
    .sort((a, b) => b.score - a.score)
    .slice(0, topK);
}

/**
 * 余弦相似度计算
 */
function cosineSimilarity(a: number[], b: number[]): number {
  if (a.length !== b.length) return 0;
  
  let dotProduct = 0;
  let normA = 0;
  let normB = 0;
  
  for (let i = 0; i < a.length; i++) {
    dotProduct += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }
  
  return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}

/**
 * 主搜索函数
 */
export async function memorySearch(
  query: string,
  topK: number = 5,
  group?: string,
  config?: MemorySystemConfig
): Promise<{ results: SearchResult[] }> {
  const cfg = config || {
    memoryDir: '~/.openclaw/workspace/memory',
    vectorEnabled: true,
    embeddingModel: 'nomic-embed-text'
  } as MemorySystemConfig;

  console.log(`[MemorySearch] 搜索: "${query}" (group: ${group || 'all'}, topK: ${topK})`);

  // 获取所有记忆文件
  const files = await scanMemoryFiles(cfg.memoryDir, group);
  
  if (files.length === 0) {
    return { results: [] };
  }

  console.log(`[MemorySearch] 扫描到 ${files.length} 个记忆文件`);

  // 根据配置选择搜索方式
  let results: SearchResult[];
  
  if (cfg.vectorEnabled) {
    try {
      results = await vectorSearch(query, files, topK, cfg);
    } catch (e) {
      console.warn('[MemorySearch] 向量搜索失败，降级到关键词搜索', e);
      results = await keywordSearch(query, files, topK);
    }
  } else {
    results = await keywordSearch(query, files, topK);
  }

  // 格式化返回结果
  return {
    results: results.map(r => ({
      path: r.path.replace(expandPath(cfg.memoryDir), '').replace(/^[/\\]/, ''),
      content: r.content.slice(0, 500),
      score: Math.round(r.score * 100) / 100,
      lines: r.lines
    }))
  };
}


path: src/write.ts
content: /**
 * Memory Write - 写入记忆文件
 */

import * as fs from 'fs/promises';
import * as path from 'path';
import type { MemorySystemConfig } from './index.js';

/**
 * 展开路径中的 ~
 */
function expandPath(p: string, baseDir: string): string {
  if (p.startsWith('~')) {
    return path.join(process.env.HOME || '', p.slice(1));
  }
  if (!path.isAbsolute(p)) {
    return path.join(baseDir, p);
  }
  return path.resolve(p);
}

/**
 * 写入记忆文件
 */
export async function memoryWrite(
  filePath: string,
  content: string,
  mode: 'overwrite' | 'append' = 'append',
  config?: MemorySystemConfig
): Promise<{ success: boolean; path: string }> {
  const cfg = config || {
    memoryDir: '~/.openclaw/workspace/memory'
  } as MemorySystemConfig;

  const fullPath = expandPath(filePath, cfg.memoryDir);
  
  console.log(`[MemoryWrite] 写入: ${fullPath} (mode: ${mode})`);

  // 确保目录存在
  const dir = path.dirname(fullPath);
  await fs.mkdir(dir, { recursive: true });

  if (mode === 'append') {
    // 追加模式
    try {
      const existing = await fs.readFile(fullPath, 'utf-8');
      const newContent = existing + '\n' + content;
      await fs.writeFile(fullPath, newContent, 'utf-8');
    } catch (e) {
      if ((e as NodeJS.ErrnoException).code === 'ENOENT') {
        // 文件不存在，直接创建
        await fs.writeFile(fullPath, content, 'utf-8');
      } else {
        throw e;
      }
    }
  } else {
    // 覆盖模式
    await fs.writeFile(fullPath, content, 'utf-8');
  }

  return {
    success: true,
    path: filePath
  };
}

/**
 * 便捷方法：写入每日记录
 */
export async function memoryWriteDaily(
  content: string,
  config?: MemorySystemConfig
): Promise<{ success: boolean; path: string }> {
  const today = new Date().toISOString().split('T')[0];
  return memoryWrite(`memory/${today}.md`, content, 'append', config);
}

/**
 * 便捷方法：写入长期记忆
 */
export async function memoryWritePermanent(
  content: string,
  config?: MemorySystemConfig
): Promise<{ success: boolean; path: string }> {
  return memoryWrite('MEMORY.md', content, 'append', config);
}


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)