Skill: minmengxhw-cpu/memory-system-pro-v2 (version 2.0.1)

--- name: memory-system description: 完整记忆系统 - 四类记忆分类 + AutoDream自动整合 + MiniMax LLM驱动 + Feedback双向记录 --- # Memory System 🧠 > 完整记忆系统 - 四类记忆分类 + AutoDream 自动整合 + MiniMax LLM 驱动 + Feedback 双向记录 **版本**: 2.0.0 | **作者**: 团宝 (openclaw) --- ## 核心特性 | 特性 | 说明 | |------|------| | 📁 **文件系统存储** | 纯 Markdown，无数据库，可直接编辑 | | 🔍 **语义搜索** | Ollama 向量搜索 + 关键词降级 | | ⚡ **自动加载** | 会话启动时自动读取相关记忆 | | 📝 **四类记忆** | User / Feedback / Project / Reference | | 🔄 **Feedback 双向** | 纠正 (negative) + 确认 (positive) | | 🌙 **AutoDream** | 定时自动整合，MiniMax LLM 驱动 | | 💾 **智能 Flush** | 上下文接近阈值时自动保存 | --- ## AutoDream：自动记忆整合 ### 工作原理 ``` 定时触发 (每天 22:00) 或 手动触发 ↓ 扫描所有记忆文件 + 会话历史 ↓ 调用 MiniMax LLM 分析 ↓ 自动执行整合： - 新增遗漏的记忆 - 删除过时的记忆 - 更新 MEMORY.md 索引 ↓ 通知用户整合结果 ``` ### 触发条件 | 条件 | 默认值 | 说明 | |------|--------|------| | 时间间隔 | ≥ 24 小时 | 距离上次整合的时间 | | 新会话数 | ≥ 3 个 | 新增的会话数 | ### 整合能力 - ✅ 扫描现有记忆文件 - ✅ 识别过时信息 - ✅ 合并重复记忆 - ✅ 更新 MEMORY.md 索引 - ✅ 使用绝对日期 - ✅ 四类记忆分类 --- ## 四类记忆分类 ### 1. User（用户）记忆 **作用域**: 私密 **用途**: 了解用户角色、偏好、知识背景 ```markdown ## user/李明.md - 民盟上海市委宣传部副部长 - 技术背景：AI、编程 - 偏好：简洁回复，中文为主 ``` ### 2. Feedback（反馈）记忆 ⭐ **作用域**: 私密 / 团队 **用途**: 记录用户的纠正和确认 ```markdown ## feedback/开发规范.md ### 集成测试必须用真实数据库 **Type:** negative **Why:** 上季度mock测试通过但生产迁移失败 **How to apply:** 禁止在集成测试中 mock 数据库层 **Date:** 2026-03-15 ### 单PR优于多个小PR **Type:** positive **Why:** 拆分反而造成不必要的开销 **How to apply:** 重构类需求优先合并为大PR **Date:** 2026-03-20 ``` ### 3. Project（项目）记忆 **作用域**: 团队 **用途**: 项目进展、目标、决策 ```markdown ## project/民盟宣传系统.md - 阶段：二期开发 - 目标：6月底前完成 - 当前：AI播客监控系统 ``` ### 4. Reference（引用）记忆 **作用域**: 团队 **用途**: 外部系统入口 ```markdown ## reference/工具入口.md - Bug追踪：Linear 项目 "MINMENG" - 文档：Confluence /minmeng-space ``` --- ## 安装 ```bash clawhub install memory-system ``` --- ## 工具列表 ### memory_search 语义搜索记忆 ```json { "query": "用户对代码规范的偏好", "type": "feedback", "topK": 5 } ``` ### memory_write 写入记忆（写入后自动检查是否触发 AutoDream） ```json { "file": "feedback/dev-rules.md", "content": "### 集成测试...\n**Type:** negative...", "type": "feedback", "mode": "append" } ``` ### memory_flush 手动触发持久化 ```json { "force": true } ``` ### memory_dream 执行 AutoDream 整合 ```json { "force": false } ``` - `force: false` - 检查条件，满足后执行 - `force: true` - 强制立即执行 ### memory_dream_status 查看整合状态 ```json {} ``` 返回： ```json { "enabled": true, "lastDream": "2026-03-30T22:00:00Z", "hoursSince": 21.5, "sessionsSinceLast": 5, "totalMemoryFiles": 32, "nextTrigger": "已达时间条件" } ``` --- ## 配置 ```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, "apiKey": "env:MINIMAX_CODING_API_KEY" } } } } ``` --- ## 目录结构 ``` memory/ ├── MEMORY.md # 索引文件 ├── .auto-dream-state.json # 整合状态 ├── user/ # 用户记忆 │ └── [用户ID].md ├── feedback/ # 反馈记忆 │ ├── positive/ # 正面确认 │ └── negative/ # 纠正指导 ├── project/ # 项目记忆 │ └── [项目名].md ├── reference/ # 引用记忆 │ └── external.md └── sessions/ # 会话历史 └── [日期].json ``` --- ## 与 HEARTBEAT 集成 AutoDream 与 OpenClaw HEARTBEAT 深度集成： ```markdown ## 🌙 AutoDream 记忆整合 (每天 22:00) - 自动检查触发条件 - 静默执行，无变化时通知 - 手动触发：说"执行记忆整合" ``` --- ## 最佳实践 ### 保存时机 | 类型 | 保存时机 | 示例 | |------|---------|------| | User | 了解用户信息 | "我是数据科学家" | | Feedback | 用户纠正/确认 | "不要那样做" / "perfect" | | Project | 了解项目动态 | "在做X项目" | | Reference | 发现外部资源 | "bug在Linear跟踪" | ### 格式规范 ```markdown ### [标题] **Type:** negative | positive **Why:** [原因] **How to apply:** [何时应用] **Date:** YYYY-MM-DD ``` ### 索引规范 - 每条索引 <= 150 字符 - 使用绝对日期 - 按 user / feedback / project / reference 分类 --- ## 技术架构 ``` memory-system (v2.0.0) ├── src/ │ ├── index.ts # 入口 + 工具注册 │ ├── search.ts # 向量/关键词搜索 │ ├── write.ts # 写入 + 触发检查 │ ├── get.ts # 读取 │ ├── flush.ts # 内存持久化 │ ├── autoLoad.ts # 自动加载 │ ├── embed.ts # 向量嵌入 │ └── autoDream.ts # AutoDream LLM整合 └── HEARTBEAT.md # 定时触发集成 ``` --- **版本**: 2.0.0 | **更新**: AutoDream 与记忆系统深度整合

path: CHANGELOG.md
content: # Changelog

## [2.0.1] - 2026-03-31

### Fixed
- **memory_write 后触发检查** - 写入后自动检查是否满足 AutoDream 触发条件
- **HEARTBEAT hook 完整实现** - onHeartbeat 真正执行 dream 检查和触发
- **新增文件内容** - 从 LLM 输出正确解析文件内容
- **API Key 配置** - 支持从 config 或环境变量读取
- **类型验证** - memory_write 增加 type 参数验证

### Improved
- prompt 格式优化，更容易解析
- 详细的日志输出
- 错误处理完善

---

## [2.0.0] - 2026-03-31

### Added
- **AutoDream 与 HEARTBEAT 深度集成**
  - 每天 22:00 自动执行记忆整合
  - 静默执行，无变化时不打扰
  - 手动触发：说"执行记忆整合"
- **memory_write 后自动检查**
  - 写入记忆后自动检查是否满足 dream 触发条件
- **integrations.heartbeat 配置**
  - 官方支持心跳触发

### Changed
- 统一 skill 描述：四类记忆 + AutoDream + Feedback 双向

---

## [1.3.0] - 2026-03-31

### Added
- **AutoDream LLM 整合调用** - 调用 MiniMax API 执行真正的记忆整合
- 支持从环境变量 `MINIMAX_CODING_API_KEY` 读取 API Key
- 解析 LLM 返回结果，自动新增/删除/更新记忆文件

---

## [1.2.0] - 2026-03-31

### Added
- **AutoDream 自动整合系统** 🌙
  - 定时触发记忆整合
  - 基于时间（默认24小时）和会话数（默认3个）触发
  - 自动扫描、清理、更新记忆
  - 新增 `memory_dream` 和 `memory_dream_status` 工具

---

## [1.1.0] - 2026-03-31

### Added
- **四类记忆分类系统** - 引入 user / feedback / project / reference
- **Feedback 双向记录** - 同时记录 negative 和 positive feedback

---

## [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 Pro

> 让 AI 拥有持续进化的记忆能力

**版本**: 2.0.0 | **发布**: 2026-03-31

---

## 一句话介绍

Memory System Pro 是一个具有**自动记忆整合**能力的 AI 记忆系统。区别于传统记忆系统的"存储-检索"模式，它引入了类似 Claude Code AutoDream 的自动整理机制，让记忆始终保持新鲜、结构化。

---

## 核心创新

### 1. AutoDream：自动记忆整合 🌙

传统记忆系统的痛点：
- ❌ 记忆越积越多，难以维护
- ❌ 过时记忆造成误导
- ❌ 索引混乱，难以检索

AutoDream 的解决方案：
- ✅ **定时自动执行** - 每天 22:00 自动整合
- ✅ **LLM 驱动** - MiniMax M2.7 模型智能分析
- ✅ **自动清理** - 删除过时、重复记忆
- ✅ **索引自动更新** - 保持 MEMORY.md 整洁

### 2. 四类记忆分类

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

### 3. Feedback 双向记录

传统做法的盲区：
- 只记录"错误"，忘记"正确"
- AI 变得过于保守

我们的做法：
```markdown
# Negative - 避免重复错误
不要mock数据库
Why: mock测试通过了但生产迁移失败

# Positive - 复用成功经验
接受单PR而非多个小PR
Why: 拆分反而造成不必要的开销
```

---

## 功能对比

| 功能 | 基础笔记 | Memory System Pro |
|------|----------|------------------|
| 存储方式 | 手动 | 自动 + 手动 |
| 搜索 | 关键词 | 语义 + 关键词 |
| 分类 | 无 | 四类分类 |
| 反馈记录 | 单向 | 双向 |
| 记忆整合 | 手动 | **自动** |
| LLM 驱动 | 无 | ✅ MiniMax |
| HEARTBEAT | 无 | ✅ 集成 |

---

## 技术架构

```
┌─────────────────────────────────────────────────────┐
│                   OpenClaw                          │
│  ┌─────────────────────────────────────────────┐   │
│  │              HEARTBEAT                      │   │
│  │         每天 22:00 触发                    │   │
│  └─────────────────┬───────────────────────────┘   │
│                    ↓                               │
│  ┌─────────────────────────────────────────────┐   │
│  │           Memory System (v2.0)              │   │
│  │  ┌─────────────────────────────────────┐   │   │
│  │  │         AutoDream Engine            │   │   │
│  │  │  1. 扫描记忆文件                    │   │   │
│  │  │  2. 调用 MiniMax LLM                │   │   │
│  │  │  3. 分析/整合/清理                  │   │   │
│  │  │  4. 更新索引                        │   │   │
│  │  └─────────────────────────────────────┘   │   │
│  │                                            │   │
│  │  ┌──────────┐  ┌──────────┐  ┌────────┐  │   │
│  │  │ Search   │  │ Write    │  │ Dream  │  │   │
│  │  │ 语义搜索  │  │ 写入+检查 │  │ 整合   │  │   │
│  │  └──────────┘  └──────────┘  └────────┘  │   │
│  └─────────────────────────────────────────────┘   │
│                    ↓                               │
│  ┌─────────────────────────────────────────────┐   │
│  │           ~/.openclaw/workspace/            │   │
│  │              memory/                        │   │
│  │  ├── MEMORY.md (索引)                       │   │
│  │  ├── user/                                  │   │
│  │  ├── feedback/ (positive/negative/)          │   │
│  │  ├── project/                               │   │
│  │  ├── reference/                             │   │
│  │  └── sessions/ (会话历史)                   │   │
│  └─────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────┘
```

---

## 使用场景

### 场景 1：日常对话
```
用户: 我是民盟上海市委宣传部的
AI: (保存到 user 类型)
    记住了，您是民盟上海市委宣传部副部长

用户: 以后播客报告发到播客群就行
AI: (保存到 feedback 类型)
    好的，已记录您的偏好设置
```

### 场景 2：项目协作
```
用户: 我们现在在做民盟史料库项目
AI: (保存到 project 类型)
    已记录。民盟史料库项目，当前阶段...

用户: 报告格式参考这个文档
AI: (保存到 reference 类型)
    已记录，报告格式参考文档位置...
```

### 场景 3：AutoDream 整合
```
(每天 22:00 HEARTBEAT 自动触发)

AI: [AutoDream 开始执行]
    - 扫描了 32 个记忆文件
    - 识别出 3 个可能过时的记忆
    - 更新了 MEMORY.md 索引
    整合完成。
```

---

## 成本估算

| 项目 | 消耗 | 说明 |
|------|-------|------|
| AutoDream 单次 | ~40K tokens | 扫描 + LLM 调用 |
| 每天 1 次 | ¥0.6 / 天 | 极低成本 |
| 每月 | ~¥18 / 月 | 可忽略不计 |

---

## 安装与配置

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

### 配置
```json
{
  "skills": {
    "memory-system": {
      "memoryDir": "~/.openclaw/workspace/memory",
      "autoDream": {
        "enabled": true,
        "minHours": 24,
        "minSessions": 3
      }
    }
  }
}
```

### HEARTBEAT 集成
已在 HEARTBEAT.md 中自动配置：
- 每天 22:00 检查并执行
- 静默执行，无变化不打扰
- 手动触发：说"执行记忆整合"

---

## 未来规划

- [ ] **Team Memory** - 多用户记忆同步
- [ ] **记忆可视化** - 生成记忆图谱
- [ ] **智能过期** - 自动归档过时记忆
- [ ] **对话压缩** - 智能上下文管理

---

## 关于版本

| 版本 | 日期 | 主要更新 |
|------|------|---------|
| 1.0.0 | 2026-03-17 | 基础记忆系统 |
| 1.1.0 | 2026-03-31 | 四类分类 + Feedback 双向 |
| 1.3.0 | 2026-03-31 | AutoDream LLM 整合 |
| **2.0.0** | **2026-03-31** | **HEARTBEAT 深度集成** |

---

**获取方式**: `clawhub install memory-system`  
**Skill ID**: enhanced-memory-v3  
**作者**: 团宝 (openclaw)


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": "2.0.1",
  "displayName": "Memory System",
  "description": "完整记忆系统 - 四类记忆分类 + AutoDream LLM整合 + Feedback双向记录",
  "author": {
    "name": "团宝 (openclaw)",
    "url": "https://github.com/openclaw"
  },
  "tags": ["memory", "persistence", "vector-search", "ollama", "feedback", "knowledge-management", "auto-dream", "llm"],
  "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": "写入记忆（写入后可触发AutoDream检查）",
      "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 记忆整合（与HEARTBEAT集成）",
      "parameters": {
        "force": { "type": "boolean", "description": "强制触发" }
      }
    },
    {
      "name": "memory_dream_status",
      "description": "查看 AutoDream 状态",
      "parameters": {}
    }
  ],
  "hooks": {
    "onSessionStart": {
      "description": "会话启动时自动加载记忆"
    },
    "onHeartbeat": {
      "description": "心跳时检查并触发 AutoDream"
    }
  },
  "integrations": {
    "heartbeat": {
      "enabled": true,
      "cron": "0 22 * * *",
      "description": "每天22:00自动执行记忆整合"
    }
  },
  "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": "定时记忆整合系统",
    "llm": "MiniMax-M2.7",
    "triggerConditions": {
      "minHours": "距离上次整合的最小小时数（默认24）",
      "minSessions": "新增会话数阈值（默认3）"
    },
    "tasks": [
      "扫描现有记忆文件",
      "调用MiniMax LLM分析",
      "识别过时信息并删除",
      "更新 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';

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

const DEFAULT_AUTO_DREAM_CONFIG: AutoDreamConfig = {
  enabled: true,
  minHours: 24,
  minSessions: 3,
  memoryDir: '~/.openclaw/workspace/memory'
};

/**
 * MiniMax API 调用
 */
async function callMiniMax(prompt: string, [REDACTED_SECRET]): Promise<string> {
  const response = await fetch('https://api.minimaxi.com/anthropic/v1/messages', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${apiKey}`,
      'anthropic-version': '2023-06-01'
    },
    body: JSON.stringify({
      model: 'MiniMax-M2.7',
      max_tokens: 8192,
      messages: [
        { role: 'user', content: prompt }
      ]
    })
  });

  if (!response.ok) {
    throw new Error(`MiniMax API error: ${response.status}`);
  }

  const data = await response.json() as { content?: Array<{ type: string; text?: string }> };
  return data.content?.find(c => c.type === 'text')?.text || '';
}

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

interface DreamState {
  lastConsolidatedAt: number;
  lastConsolidatedSessions: number;
}

interface DreamFile {
  path: string;
  content: string;
}

interface DreamResult {
  added: DreamFile[];
  deleted: string[];
  updated: string[];
  summary: string;
  newIndexContent?: string;
}

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

/**
 * 读取整合状态
 */
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));
}

/**
 * 扫描会话目录
 */
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; relativePath: string; content: string; modified: Date }[]> {
  const files: { path: string; relativePath: 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');
          const relativePath = fullPath.replace(baseDir + '/', '');
          files.push({ path: fullPath, relativePath, content, modified: stat.mtime });
        }
      }
    } catch {
      // 忽略错误
    }
  }
  
  await scan(baseDir);
  return files;
}

/**
 * 获取最近修改的文件
 */
async function getRecentlyModifiedFiles(
  memoryDir: string, 
  since: number
): Promise<{ path: string; relativePath: string; content: string }[]> {
  const files = await scanMemoryFiles(memoryDir);
  return files
    .filter(f => f.modified.getTime() > since)
    .map(f => ({ path: f.path, relativePath: f.relativePath, content: f.content }));
}

/**
 * 解析 LLM 返回的整合结果
 */
function parseDreamResult(llmOutput: string): DreamResult {
  const result: DreamResult = {
    added: [],
    deleted: [],
    updated: [],
    summary: ''
  };

  // 提取新增记忆块
  // 格式: ### NEW: filename.md\n 内容
  const newFileRegex = /###\s*NEW:\s*([^\n]+)\n([\s\S]*?)(?=###\s*(?:DELETE|UPDATE|NEW)|```|###\s*总结|$)/gi;
  let match;
  while ((match = newFileRegex.exec(llmOutput)) !== null) {
    const fileName = match[1].trim();
    const fileContent = match[2].trim();
    if (fileName && fileContent) {
      result.added.push({ path: fileName, content: fileContent });
    }
  }

  // 如果没找到 NEW: 格式，尝试查找文件名作为标题
  if (result.added.length === 0) {
    const mdBlockRegex = /```markdown\n([\s\S]*?)```/g;
    const indexBlocks: string[] = [];
    let blockMatch;
    while ((blockMatch = mdBlockRegex.exec(llmOutput)) !== null) {
      indexBlocks.push(blockMatch[1]);
    }
    
    // 检查是否有关于索引的更新
    if (indexBlocks.length > 0) {
      result.updated.push('MEMORY.md');
      result.newIndexContent = indexBlocks[indexBlocks.length - 1];
    }
  }

  // 提取删除文件名
  // 格式: ### DELETE: filename.md 或 - [filename.md]: 原因
  const deleteFileRegex = /###\s*DELETE:\s*([^\n]+)/gi;
  while ((match = deleteFileRegex.exec(llmOutput)) !== null) {
    const fileName = match[1].trim();
    if (fileName) result.deleted.push(fileName);
  }

  // 也支持列表格式: - [filename.md]
  const listDeleteRegex = /-\s*\[[^\]]+\.md\]:/gi;
  const deleteListMatches = llmOutput.match(listDeleteRegex);
  if (deleteListMatches) {
    for (const m of deleteListMatches) {
      const fnMatch = m.match(/\[([^\]]+\.md)\]/);
      if (fnMatch && !result.deleted.includes(fnMatch[1])) {
        result.deleted.push(fnMatch[1]);
      }
    }
  }

  // 提取总结
  const summaryMatch = llmOutput.match(/\*\*总结[:：]?\s*([\s\S]+)$/im);
  if (summaryMatch) {
    result.summary = summaryMatch[1].trim().slice(0, 200);
  } else {
    // 取最后一段非代码块的内容
    const parts = llmOutput.split(/```/);
    if (parts.length > 1) {
      result.summary = parts[parts.length - 1].trim().slice(0, 200);
    } else {
      result.summary = llmOutput.slice(-300).trim().split('\n').slice(-5).join(' ');
    }
  }

  return result;
}

/**
 * 检查是否应该触发整合
 */
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(
  indexContent: string, 
  recentFiles: { relativePath: string; content: string }[],
  oldFiles: { relativePath: string; content: string }[],
  stats: { totalFiles: number; recentCount: number; oldCount: number }
): string {
  const today = new Date().toISOString().split('T')[0];

  const recentFilesSection = recentFiles
    .map(f => `## ${f.relativePath}\n${f.content.slice(0, 3000)}`)
    .join('\n\n---\n\n');

  const oldFilesSection = oldFiles.length > 0
    ? oldFiles
        .map(f => `## ${f.relativePath} (建议检查是否过时)\n${f.content.slice(0, 1500)}`)
        .join('\n\n---\n\n')
    : '(无早期文件)';

  return `# Dream: 记忆整合

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

## 当前统计
- 总记忆文件: ${stats.totalFiles} 个
- 新增/修改文件: ${stats.recentCount} 个
- 早期文件: ${stats.oldCount} 个
- 整合日期: ${today}

---

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

---

## 最近新增/修改的记忆文件
${recentFilesSection || '(无新文件)'}

---

## 早期记忆文件（请检查是否过时）
${oldFilesSection}

---

## 整合原则

### 1. 四类记忆分类
- **user** (私密): 用户角色、偏好，知识背景
- **feedback** (negative/positive): 用户的纠正和确认
- **project** (团队): 项目进展、目标、决策
- **reference** (team): 外部系统资源指针

### 2. Feedback 双向记录格式
\`\`\`markdown
### [规则名称]
**Type:** negative | positive
**Why:** [原因]
**How to apply:** [何时应用]
**Date:** ${today}
\`\`\`

### 3. 索引规范
- 每条 <= 150 字符
- 使用绝对日期
- 按 user / feedback / project / reference 分类

---

## 输出格式（严格按此格式）

### DELETE: 要删除的文件名.md
(如无需删除，请写"无")

### NEW: 要新增的文件名.md
文件内容...
(如无需新增，请写"无")

### UPDATE: MEMORY.md
\`\`\`markdown
# Memory Index

## User
- [用户角色](user/xxx.md) — 一句话描述

## Feedback
...

## Project
...

## Reference
...
\`\`\`
(如无需更新索引，请写"无")

### **总结**: 50字以内简述本次整合内容

---

请开始整合。`;
}

/**
 * 执行整合
 */
export async function executeDream(
  config?: Partial<AutoDreamConfig>,
  apiKey?: string
): Promise<{
  success: boolean;
  triggered: boolean;
  summary: string;
  changes: { added: string[]; deleted: string[]; updated: string[]; summary: string };
  reason?: string;
}> {
  const cfg: AutoDreamConfig = {
    ...DEFAULT_AUTO_DREAM_CONFIG,
    ...config
  };
  
  const memoryDir = expandPath(cfg.memoryDir);
  const resolvedApiKey = apiKey || process.env.MINIMAX_CODING_API_KEY;
  
  console.log('[AutoDream] 开始执行记忆整合...');
  
  try {
    // 1. 读取状态
    const state = await readDreamState(memoryDir);
    const indexContent = await readMemoryIndex(memoryDir);
    const allFiles = await scanMemoryFiles(memoryDir);
    const sessions = await scanSessions(memoryDir);
    
    // 2. 获取最近修改的文件
    const recentFiles = await getRecentlyModifiedFiles(memoryDir, state.lastConsolidatedAt);
    const oldFiles = allFiles
      .filter(f => !recentFiles.some(r => r.path === f.path))
      .map(f => ({ relativePath: f.relativePath, content: f.content }));
    
    console.log(`[AutoDream] 总文件: ${allFiles.length}, 新文件: ${recentFiles.length}, 旧文件: ${oldFiles.length}`);
    
    // 3. 生成 Prompt
    const prompt = buildDreamPrompt(indexContent, recentFiles, oldFiles, {
      totalFiles: allFiles.length,
      recentCount: recentFiles.length,
      oldCount: oldFiles.length
    });
    
    // 4. 调用 LLM
    if (!resolvedApiKey) {
      return {
        success: false,
        triggered: false,
        summary: 'API Key 未配置',
        changes: { added: [], deleted: [], updated: [], summary: '未执行' },
        reason: 'missing_api_key'
      };
    }
    
    console.log('[AutoDream] 调用 MiniMax LLM...');
    const llmOutput = await callMiniMax(prompt, resolvedApiKey);
    console.log(`[AutoDream] LLM 返回 ${llmOutput.length} 字符`);
    
    // 5. 解析结果
    const changes = parseDreamResult(llmOutput);
    console.log('[AutoDream] 解析结果:', JSON.stringify(changes, null, 2));
    
    // 6. 应用更改 - 新增文件
    for (const file of changes.added) {
      try {
        const fullPath = path.join(memoryDir, file.path);
        await fs.mkdir(path.dirname(fullPath), { recursive: true });
        await fs.writeFile(fullPath, file.content, 'utf-8');
        console.log(`[AutoDream] 创建文件: ${file.path}`);
      } catch (e) {
        console.error(`[AutoDream] 创建文件失败: ${file.path}`, e);
      }
    }
    
    // 7. 应用更改 - 删除文件
    for (const filePath of changes.deleted) {
      try {
        const fullPath = path.join(memoryDir, filePath);
        await fs.unlink(fullPath);
        console.log(`[AutoDream] 删除文件: ${filePath}`);
      } catch (e) {
        console.error(`[AutoDream] 删除文件失败: ${filePath}`, e);
      }
    }
    
    // 8. 更新索引
    if (changes.newIndexContent) {
      await fs.writeFile(path.join(memoryDir, 'MEMORY.md'), changes.newIndexContent, 'utf-8');
      console.log('[AutoDream] 更新 MEMORY.md');
    }
    
    // 9. 更新状态
    await writeDreamState(memoryDir, {
      lastConsolidatedAt: Date.now(),
      lastConsolidatedSessions: sessions.length
    });
    
    console.log('[AutoDream] 整合完成');
    
    return {
      success: true,
      triggered: true,
      summary: changes.summary || '整合完成',
      changes: {
        added: changes.added.map(f => f.path),
        deleted: changes.deleted,
        updated: changes.updated,
        summary: changes.summary
      }
    };
    
  } catch (e) {
    console.error('[AutoDream] 执行失败:', e);
    return {
      success: false,
      triggered: false,
      summary: `整合失败: ${e}`,
      changes: { added: [], deleted: [], updated: [], summary: `失败: ${e}` }
    };
  }
}

/**
 * 获取最近整合的时间
 */
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?: {
    enabled: boolean;
    minHours: number;
    minSessions: number;
    apiKey?: string;
  };
}

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;
  }
}

/**
 * 获取 API Key
 */
function getApiKey(): string | undefined {
  // 1. 先从 config 读取
  const config = getConfig();
  if (config.autoDream?.apiKey) {
    // 支持 env: 前缀，从环境变量读取
    if (config.autoDream.apiKey.startsWith('env:')) {
      const envVar = config.autoDream.apiKey.slice(4);
      return process.env[envVar];
    }
    return config.autoDream.apiKey;
  }
  // 2. 回退到环境变量
  return process.env.MINIMAX_CODING_API_KEY;
}

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

  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: '写入记忆文件（支持类型验证，写入后自动检查 AutoDream 触发条件）',
      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 as 'overwrite' | 'append' || 'append', params.type, 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, apiKey);
          return result;
        }
        
        // 检查是否应该触发
        const check = await shouldTriggerDream({
          enabled: config.autoDream?.enabled ?? true,
          minHours: config.autoDream?.minHours ?? 24,
          minSessions: config.autoDream?.minSessions ?? 3,
          memoryDir: config.memoryDir
        });
        
        if (!check.should) {
          return {
            success: true,
            triggered: false,
            reason: check.reason,
            canTriggerNow: false
          };
        }
        
        // 执行整合
        const result = await executeDream(config.autoDream, apiKey);
        return {
          ...result,
          canTriggerNow: true
        };
      }
    },
    memory_dream_status: {
      description: '查看 AutoDream 状态',
      parameters: {},
      handler: async () => {
        const stats = await getDreamStats(config.memoryDir);
        const lastDream = await getLastDreamTime(config.memoryDir);
        const check = await shouldTriggerDream({
          enabled: config.autoDream?.enabled ?? true,
          minHours: config.autoDream?.minHours ?? 24,
          minSessions: config.autoDream?.minSessions ?? 3,
          memoryDir: 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,
          canTriggerNow: check.should,
          nextTriggerReason: check.reason
        };
      }
    }
  };

  // 注册 hooks - 这些会被 OpenClaw 在适当的时机调用
  const hooks = {
    onSessionStart: {
      description: '会话启动时自动加载记忆',
      handler: async (context: { sessionType: string; groupId?: string }) => {
        console.log('[MemorySystem] onSessionStart hook triggered');
        return await setupAutoLoad(context as { sessionType: 'direct' | 'group'; groupId?: string; userId?: string }, config);
      }
    },
    onHeartbeat: {
      description: '心跳时检查并触发 AutoDream',
      handler: async () => {
        console.log('[MemorySystem] onHeartbeat hook triggered - checking AutoDream');
        
        if (!config.autoDream?.enabled) {
          return { triggered: false, reason: 'AutoDream 已禁用' };
        }
        
        const check = await shouldTriggerDream({
          enabled: config.autoDream.enabled,
          minHours: config.autoDream.minHours || 24,
          minSessions: config.autoDream.minSessions || 3,
          memoryDir: config.memoryDir
        });
        
        if (!check.should) {
          console.log(`[MemorySystem] AutoDream 不满足触发条件: ${check.reason}`);
          return { triggered: false, reason: check.reason };
        }
        
        console.log('[MemorySystem] AutoDream 触发条件满足，执行整合...');
        const result = await executeDream(config.autoDream, apiKey);
        
        return {
          triggered: result.triggered,
          success: result.success,
          summary: result.summary,
          changes: result.changes
        };
      }
    },
    onSessionEnd: {
      description: '会话结束时自动保存记忆',
      handler: async () => {
        console.log('[MemorySystem] onSessionEnd hook triggered');
        // 可以在这里做清理或最终保存
        return { success: true };
      }
    }
  };

  return { tools, hooks, config };
}

/**
 * 启动检查
 */
export async function initMemorySystem() {
  const config = getConfig();
  const [REDACTED_SECRET]);
  
  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 ? '启用' : '禁用'}`);
  console.log(`[MemorySystem] API Key: ${apiKey ? '已配置' : '未配置'}`);

  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';
import { shouldTriggerDream } from './autoDream.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);
}

/**
 * 记忆类型白名单
 */
const VALID_MEMORY_TYPES = ['user', 'feedback', 'project', 'reference'];

/**
 * 验证记忆类型
 */
function validateMemoryType(type?: string): string | null {
  if (!type) return null;
  if (!VALID_MEMORY_TYPES.includes(type)) {
    return `Invalid type: ${type}. Must be one of: ${VALID_MEMORY_TYPES.join(', ')}`;
  }
  return null;
}

/**
 * 写入记忆文件
 */
export async function memoryWrite(
  filePath: string,
  content: string,
  mode: 'overwrite' | 'append' = 'append',
  memoryType?: string,
  config?: MemorySystemConfig
): Promise<{ 
  success: boolean; 
  path: string;
  typeValidation?: string;
  dreamCheck?: { should: boolean; reason: string };
}> {
  const cfg = config || {
    memoryDir: '~/.openclaw/workspace/memory',
    autoDream: { enabled: true, minHours: 24, minSessions: 3 }
  } as MemorySystemConfig;

  const fullPath = expandPath(filePath, cfg.memoryDir);
  
  // 验证类型
  const typeValidation = validateMemoryType(memoryType);
  
  console.log(`[MemoryWrite] 写入: ${fullPath} (mode: ${mode}, type: ${memoryType || 'auto'})`);

  // 确保目录存在
  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');
  }

  // 检查是否应该触发 AutoDream
  let dreamCheck = { should: false, reason: '' };
  if (cfg.autoDream?.enabled) {
    const check = await shouldTriggerDream({
      enabled: cfg.autoDream.enabled,
      minHours: cfg.autoDream.minHours || 24,
      minSessions: cfg.autoDream.minSessions || 3,
      memoryDir: cfg.memoryDir || '~/.openclaw/workspace/memory'
    });
    dreamCheck = check;
  }

  return {
    success: true,
    path: filePath,
    typeValidation: typeValidation || undefined,
    dreamCheck
  };
}

/**
 * 便捷方法：写入每日记录
 */
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', undefined, config);
}

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

/**
 * 便捷方法：写入用户记忆
 */
export async function memoryWriteUser(
  content: string,
  userId: string,
  config?: MemorySystemConfig
): Promise<{ success: boolean; path: string }> {
  return memoryWrite(`user/${userId}.md`, content, 'append', 'user', config);
}

/**
 * 便捷方法：写入反馈记忆
 */
export async function memoryWriteFeedback(
  content: string,
  subtype: 'positive' | 'negative',
  config?: MemorySystemConfig
): Promise<{ success: boolean; path: string }> {
  return memoryWrite(`feedback/${subtype}/auto.md`, content, 'append', 'feedback', config);
}

/**
 * 便捷方法：写入项目记忆
 */
export async function memoryWriteProject(
  content: string,
  projectName: string,
  config?: MemorySystemConfig
): Promise<{ success: boolean; path: string }> {
  return memoryWrite(`project/${projectName}.md`, content, 'append', 'project', config);
}

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


path: tools.json
content: {
  "tools": [
    {
      "id": "memory_search",
      "name": "memory_search",
      "description": "语义搜索记忆系统。使用向量嵌入匹配相关记忆片段。",
      "category": "memory",
      "parameters": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "description": "搜索查询内容"
          },
          "topK": {
            "type": "number",
            "description": "返回最相似的 K 条结果",
            "default": 5
          },
          "group": {
            "type": "string",
            "description": "群组 ID（可选，指定则只在群组记忆里搜索）",
            "optional": true
          }
        },
        "required": ["query"]
      },
      "returns": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": { "type": "string" },
                "content": { "type": "string" },
                "score": { "type": "number" },
                "lines": { "type": "number" }
              }
            }
          }
        }
      }
    },
    {
      "id": "memory_get",
      "name": "memory_get",
      "description": "读取记忆文件片段。支持指定行号范围。",
      "category": "memory",
      "parameters": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "文件路径，相对于 memoryDir"
          },
          "from": {
            "type": "number",
            "description": "起始行号（1-based）",
            "optional": true
          },
          "lines": {
            "type": "number",
            "description": "读取行数",
            "optional": true
          }
        },
        "required": ["path"]
      },
      "returns": {
        "type": "object",
        "properties": {
          "path": { "type": "string" },
          "content": { "type": "string" },
          "totalLines": { "type": "number" }
        }
      }
    },
    {
      "id": "memory_write",
      "name": "memory_write",
      "description": "写入记忆文件",
      "category": "memory",
      "parameters": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "文件路径，相对于 memoryDir"
          },
          "content": {
            "type": "string",
            "description": "写入内容"
          },
          "mode": {
            "type": "string",
            "enum": ["overwrite", "append"],
            "default": "append",
            "description": "写入模式"
          }
        },
        "required": ["path", "content"]
      }
    },
    {
      "id": "memory_flush",
      "name": "memory_flush",
      "description": "手动触发内存 flush 到文件系统",
      "category": "memory",
      "parameters": {
        "type": "object",
        "properties": {
          "force": {
            "type": "boolean",
            "default": false,
            "description": "强制 flush，忽略阈值"
          }
        }
      }
    }
  ]
}


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