refactor: adapt to WaterFlow framework - zero source changes required
- Remove duplicate platform layer and tool interface definitions - Add BFS breadth-first search with depth annotation (sync from main) - Use WaterFlow platform.fs for binary storage instead of custom storage - Add installTrulyMEM() one-line registration function - Update SKILL.md with 10 complete operations - Add waterflow.d.ts type declarations - Update bilingual README with simplified installation guide - Build passes with 0 errors
This commit is contained in:
139
README.md
139
README.md
@ -48,122 +48,83 @@ ts/
|
||||
|
||||
## 在 WaterFlow 中使用
|
||||
|
||||
本模块支持两种使用方式:**作为模块直接引用** 或 **作为 Skill 调用**。
|
||||
本模块完全不动 WaterFlow 源码,只需在你的入口文件中注册即可。
|
||||
|
||||
### 方式一:作为模块直接引用(适合开发者集成)
|
||||
### 快速开始(推荐)
|
||||
|
||||
#### 步骤 1:复制源码
|
||||
|
||||
将本项目的 `ts/` 目录复制到你的 WaterFlow 项目中,例如:
|
||||
|
||||
```
|
||||
你的WaterFlow项目/
|
||||
├── src/
|
||||
│ └── runtime/
|
||||
│ └── core/
|
||||
│ └── graph_memory/ # 从 ts/src/runtime/core/ 复制
|
||||
└── ts/ # 或直接放在项目根目录
|
||||
└── bundled-skills/ # Skill 文件
|
||||
```
|
||||
|
||||
#### 步骤 2:编译 TypeScript
|
||||
#### 步骤 1:安装依赖
|
||||
|
||||
```bash
|
||||
cd ts/
|
||||
npm install
|
||||
npm run build
|
||||
npm install /path/to/TrulyMEM-TrueHumanMEM/ts
|
||||
```
|
||||
|
||||
编译后的文件会输出到 `ts/dist/` 目录。
|
||||
或在 `package.json` 中添加:
|
||||
|
||||
#### 步骤 3:在代码中引用
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"trulymem-waterflow": "file:../TrulyMEM-TrueHumanMEM/ts"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后运行:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
#### 步骤 2:在你的入口文件中注册
|
||||
|
||||
只需两行代码,完全不动 WaterFlow 源码:
|
||||
|
||||
```typescript
|
||||
import { createGraphMemoryTool } from './runtime/core/tools/builtin/graph_memory_tool';
|
||||
import { getPlatform } from 'waterflow/platform';
|
||||
import { installTrulyMEM } from 'trulymem/tools';
|
||||
|
||||
// 创建工具实例,可以传入 sessionId 来区分不同会话
|
||||
const tool = createGraphMemoryTool('my-session-id');
|
||||
// 一行安装,返回配置好的 ToolRegistry
|
||||
const registry = installTrulyMEM(getPlatform(), 'my-session-id');
|
||||
|
||||
// 准备执行上下文
|
||||
const context = {
|
||||
toolCallId: 'call-123',
|
||||
workingDirectory: '/project',
|
||||
abortController: { signal: {} },
|
||||
config: { timeout: 30000 },
|
||||
logger: {
|
||||
info: console.log,
|
||||
warn: console.warn,
|
||||
error: console.error,
|
||||
debug: console.debug
|
||||
}
|
||||
};
|
||||
|
||||
// 写入记忆示例
|
||||
const commitResult = await tool.handler({
|
||||
action: 'commit',
|
||||
params: {
|
||||
triplets: [
|
||||
{ subject: '用户', relation: '喜欢', object: '编程' },
|
||||
{ subject: '用户', relation: '正在学习', object: 'TypeScript' }
|
||||
]
|
||||
}
|
||||
}, context);
|
||||
|
||||
console.log(commitResult);
|
||||
// 输出: {"success":true,"data":{"createdEntities":4,"createdRelations":2}}
|
||||
|
||||
// 检索记忆示例
|
||||
const recallResult = await tool.handler({
|
||||
action: 'recall',
|
||||
params: {
|
||||
queryIntent: '用户 编程'
|
||||
}
|
||||
}, context);
|
||||
|
||||
console.log(recallResult);
|
||||
// 输出: {"success":true,"data":{"entities":[...],"relations":[...],"message":"找到 X 个实体, Y 条关系"}}
|
||||
// 继续组装 WaterFlow...
|
||||
const toolExecutor = new ToolExecutor(registry);
|
||||
```
|
||||
|
||||
### 方式二:使用 Skill(推荐,适合 AI Agent 调用)
|
||||
### 手动注册(更灵活)
|
||||
|
||||
如果你想自己控制 ToolRegistry 的创建:
|
||||
|
||||
```typescript
|
||||
import { getPlatform } from 'waterflow/platform';
|
||||
import { initializeToolRegistry } from 'waterflow/runtime/core/tools/builtin';
|
||||
import { registerGraphMemoryTool } from 'trulymem/tools';
|
||||
|
||||
const platform = getPlatform();
|
||||
const registry = initializeToolRegistry(platform);
|
||||
|
||||
// 注册图记忆工具
|
||||
registerGraphMemoryTool(registry, 'my-session-id');
|
||||
|
||||
// 继续组装...
|
||||
```
|
||||
|
||||
### 使用 Skill(AI Agent 调用)
|
||||
|
||||
#### 步骤 1:配置 Skill 来源
|
||||
|
||||
在你的 WaterFlow 项目中,找到 Skill 配置文件,添加 bundled 来源指向本项目的 Skill 目录:
|
||||
|
||||
```typescript
|
||||
// skill_interface.ts 或配置文件中
|
||||
import { DEFAULT_SKILL_LOADER_CONFIG } from './skill_interface';
|
||||
|
||||
const config = {
|
||||
...DEFAULT_SKILL_LOADER_CONFIG,
|
||||
sources: {
|
||||
...DEFAULT_SKILL_LOADER_CONFIG.sources,
|
||||
bundled: './ts/bundled-skills' // 指向本项目的 Skill 目录
|
||||
bundled: './node_modules/trulymem-waterflow/bundled-skills'
|
||||
},
|
||||
enabledSources: ['project', 'bundled']
|
||||
};
|
||||
```
|
||||
|
||||
#### 步骤 2:通过 Agent 调用 Skill
|
||||
#### 步骤 2:通过 Agent 调用
|
||||
|
||||
在你的 Agent 或 Workflow 中,通过 Tool 调用 Skill:
|
||||
|
||||
```
|
||||
使用 skill:graph_memory 进行以下操作:
|
||||
|
||||
1. 写入记忆: 我喜欢编程,正在学习 TypeScript
|
||||
2. 检索记忆: 找出我和编程相关的记忆
|
||||
```
|
||||
|
||||
或者通过代码调用:
|
||||
|
||||
```typescript
|
||||
// 通过 SkillTool 调用
|
||||
const skillResult = await skillTool.handler({
|
||||
skill: 'graph_memory',
|
||||
args: 'recall - queryIntent: "用户 学习"'
|
||||
}, context);
|
||||
```
|
||||
AI Agent 会自动读取 SKILL.md 并调用 `builtin:graph_memory` 工具。
|
||||
|
||||
#### 可用 Skill 列表
|
||||
|
||||
|
||||
139
README_EN.md
139
README_EN.md
@ -49,122 +49,83 @@ ts/
|
||||
|
||||
## Usage in WaterFlow
|
||||
|
||||
This module supports two usage methods: **import as module** or **use as Skill**.
|
||||
This module requires **zero changes** to WaterFlow source code. Just register it in your entry file.
|
||||
|
||||
### Method 1: Import as Module (for developer integration)
|
||||
### Quick Start (Recommended)
|
||||
|
||||
#### Step 1: Copy source files
|
||||
|
||||
Copy the `ts/` directory to your WaterFlow project, for example:
|
||||
|
||||
```
|
||||
your-waterflow-project/
|
||||
├── src/
|
||||
│ └── runtime/
|
||||
│ └── core/
|
||||
│ └── graph_memory/ # Copy from ts/src/runtime/core/
|
||||
└── ts/ # Or place in project root
|
||||
└── bundled-skills/ # Skill files
|
||||
```
|
||||
|
||||
#### Step 2: Build TypeScript
|
||||
#### Step 1: Install
|
||||
|
||||
```bash
|
||||
cd ts/
|
||||
npm install
|
||||
npm run build
|
||||
npm install /path/to/TrulyMEM-TrueHumanMEM/ts
|
||||
```
|
||||
|
||||
Compiled files will be output to `ts/dist/`.
|
||||
Or add to `package.json`:
|
||||
|
||||
#### Step 3: Import in your code
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"trulymem-waterflow": "file:../TrulyMEM-TrueHumanMEM/ts"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
#### Step 2: Register in your entry file
|
||||
|
||||
Just two lines, zero changes to WaterFlow:
|
||||
|
||||
```typescript
|
||||
import { createGraphMemoryTool } from './runtime/core/tools/builtin/graph_memory_tool';
|
||||
import { getPlatform } from 'waterflow/platform';
|
||||
import { installTrulyMEM } from 'trulymem/tools';
|
||||
|
||||
// Create tool instance, can pass sessionId to distinguish different sessions
|
||||
const tool = createGraphMemoryTool('my-session-id');
|
||||
// One-line install, returns configured ToolRegistry
|
||||
const registry = installTrulyMEM(getPlatform(), 'my-session-id');
|
||||
|
||||
// Prepare execution context
|
||||
const context = {
|
||||
toolCallId: 'call-123',
|
||||
workingDirectory: '/project',
|
||||
abortController: { signal: {} },
|
||||
config: { timeout: 30000 },
|
||||
logger: {
|
||||
info: console.log,
|
||||
warn: console.warn,
|
||||
error: console.error,
|
||||
debug: console.debug
|
||||
}
|
||||
};
|
||||
|
||||
// Commit memory example
|
||||
const commitResult = await tool.handler({
|
||||
action: 'commit',
|
||||
params: {
|
||||
triplets: [
|
||||
{ subject: 'User', relation: 'likes', object: 'Programming' },
|
||||
{ subject: 'User', relation: 'is learning', object: 'TypeScript' }
|
||||
]
|
||||
}
|
||||
}, context);
|
||||
|
||||
console.log(commitResult);
|
||||
// Output: {"success":true,"data":{"createdEntities":4,"createdRelations":2}}
|
||||
|
||||
// Recall memory example
|
||||
const recallResult = await tool.handler({
|
||||
action: 'recall',
|
||||
params: {
|
||||
queryIntent: 'User Programming'
|
||||
}
|
||||
}, context);
|
||||
|
||||
console.log(recallResult);
|
||||
// Output: {"success":true,"data":{"entities":[...],"relations":[...],"message":"Found X entities, Y relations"}}
|
||||
// Continue assembling WaterFlow...
|
||||
const toolExecutor = new ToolExecutor(registry);
|
||||
```
|
||||
|
||||
### Method 2: Use Skill (recommended for AI Agent)
|
||||
### Manual Registration (More control)
|
||||
|
||||
If you want to control ToolRegistry creation yourself:
|
||||
|
||||
```typescript
|
||||
import { getPlatform } from 'waterflow/platform';
|
||||
import { initializeToolRegistry } from 'waterflow/runtime/core/tools/builtin';
|
||||
import { registerGraphMemoryTool } from 'trulymem/tools';
|
||||
|
||||
const platform = getPlatform();
|
||||
const registry = initializeToolRegistry(platform);
|
||||
|
||||
// Register graph memory tool
|
||||
registerGraphMemoryTool(registry, 'my-session-id');
|
||||
|
||||
// Continue assembling...
|
||||
```
|
||||
|
||||
### Use Skill (AI Agent)
|
||||
|
||||
#### Step 1: Configure Skill source
|
||||
|
||||
In your WaterFlow project, find the Skill configuration file and add bundled source pointing to this project's Skill directory:
|
||||
|
||||
```typescript
|
||||
// skill_interface.ts or config file
|
||||
import { DEFAULT_SKILL_LOADER_CONFIG } from './skill_interface';
|
||||
|
||||
const config = {
|
||||
...DEFAULT_SKILL_LOADER_CONFIG,
|
||||
sources: {
|
||||
...DEFAULT_SKILL_LOADER_CONFIG.sources,
|
||||
bundled: './ts/bundled-skills' // Point to this project's Skill directory
|
||||
bundled: './node_modules/trulymem-waterflow/bundled-skills'
|
||||
},
|
||||
enabledSources: ['project', 'bundled']
|
||||
};
|
||||
```
|
||||
|
||||
#### Step 2: Call Skill via Agent
|
||||
#### Step 2: Call via Agent
|
||||
|
||||
In your Agent or Workflow, call Skill via Tool:
|
||||
|
||||
```
|
||||
Use skill:graph_memory for:
|
||||
|
||||
1. Commit memory: I like programming, learning TypeScript
|
||||
2. Recall memory: Find memories related to me and programming
|
||||
```
|
||||
|
||||
Or call via code:
|
||||
|
||||
```typescript
|
||||
// Call via SkillTool
|
||||
const skillResult = await skillTool.handler({
|
||||
skill: 'graph_memory',
|
||||
args: 'recall - queryIntent: "User learning"'
|
||||
}, context);
|
||||
```
|
||||
AI Agent automatically reads SKILL.md and calls `builtin:graph_memory` tool.
|
||||
|
||||
#### Available Skills
|
||||
|
||||
|
||||
102
TRACKING.md
Normal file
102
TRACKING.md
Normal file
@ -0,0 +1,102 @@
|
||||
# TrulyMEM WaterFlow 重构 - 工程追踪
|
||||
|
||||
> 每次文件操作前更新此文件,确保中断可恢复。
|
||||
> 最后更新: 2026-04-16
|
||||
|
||||
---
|
||||
|
||||
## 当前状态
|
||||
|
||||
**状态**: ✅ 全部完成
|
||||
**最后更新**: 2026-04-16
|
||||
|
||||
---
|
||||
|
||||
## 任务清单
|
||||
|
||||
| # | 任务 | 状态 | 备注 |
|
||||
|---|------|------|------|
|
||||
| 0 | 创建工程追踪文件 | ✅ 完成 | |
|
||||
| 1 | 同步 main 分支核心优化 | ✅ 完成 | BFS搜索、depth标注、工具限制器 |
|
||||
| 2 | Phase 1: 清理重复定义 | ✅ 完成 | 删除 platform/, tool_interface.ts |
|
||||
| 3 | Phase 2: 适配 WaterFlow 平台层 | ✅ 完成 | graph_database.ts, graph_memory_tool.ts |
|
||||
| 4 | Phase 3: 完善 SKILL.md | ✅ 完成 | 10 个完整操作 |
|
||||
| 5 | Phase 4: 调整构建配置 | ✅ 完成 | package.json, tsconfig.json, waterflow.d.ts |
|
||||
| 6 | Phase 5: 编译验证 | ✅ 完成 | 0 错误,dist/ 输出完整 |
|
||||
|
||||
---
|
||||
|
||||
## 操作日志
|
||||
|
||||
### 2026-04-16 开始
|
||||
- [x] 创建 TRACKING.md
|
||||
- [x] 同步 main 分支核心优化
|
||||
- [x] BFS 广度优先搜索 → graph_database.ts(含 depth 标注)
|
||||
- [x] 工具限制器调整 → 在 Tool 层面处理(WaterFlow 治理层已有)
|
||||
- [x] 提示词优化 → SKILL.md(后续 Phase 3 处理)
|
||||
- [x] Entity/Relation 类型添加 depth? 字段
|
||||
- [x] 改用 WaterFlow platform.fs 替代自定义 storage
|
||||
- [x] 缓存 platform 实例避免重复 import
|
||||
- [x] Phase 1: 删除重复文件
|
||||
- [x] 删除 ts/src/platform/ 目录(index.ts, node.ts, types.ts)
|
||||
- [x] 删除 ts/src/runtime/core/tools/tool_interface.ts
|
||||
- [x] Phase 2: 适配 WaterFlow 接口
|
||||
- [x] graph_memory_tool.ts → 使用 waterflow Tool 接口
|
||||
- [x] graph_database.ts → 使用 waterflow platform.fs(binary)
|
||||
- [x] config.ts → 内联类型定义,移除 platform 依赖
|
||||
- [x] builtin/index.ts → 添加 registerGraphMemoryTool()
|
||||
- [x] Phase 3: 完善 SKILL.md
|
||||
- [x] 主 SKILL.md 补充 10 个完整操作
|
||||
- [x] persona/SKILL.md 已验证完整
|
||||
- [x] task/SKILL.md 已验证完整
|
||||
- [x] Phase 4: 构建配置
|
||||
- [x] package.json → 添加 exports, peerDependencies
|
||||
- [x] tsconfig.json → 添加 typeRoots
|
||||
- [x] Phase 5: 验证
|
||||
- [x] tsc --noEmit 通过(0 错误)
|
||||
- [x] npm run build 成功
|
||||
- [x] dist/ 输出完整(.js, .d.ts, .map)
|
||||
- [x] 新增 waterflow.d.ts 类型声明
|
||||
- [x] 安装 waterflow-ts 作为 devDependency
|
||||
|
||||
---
|
||||
|
||||
## 变更摘要
|
||||
|
||||
### 删除(4 文件)
|
||||
- `ts/src/platform/index.ts`
|
||||
- `ts/src/platform/node.ts`
|
||||
- `ts/src/platform/types.ts`
|
||||
- `ts/src/runtime/core/tools/tool_interface.ts`
|
||||
|
||||
### 修改(8 文件)
|
||||
- `ts/src/runtime/core/graph_memory/graph_database.ts` — BFS + WaterFlow fs
|
||||
- `ts/src/runtime/core/graph_memory/types.ts` — 添加 depth? 字段
|
||||
- `ts/src/runtime/core/graph_memory/config.ts` — 内联类型,移除 platform 依赖
|
||||
- `ts/src/runtime/core/graph_memory/memory_service.ts` — 无变化(已兼容)
|
||||
- `ts/src/runtime/core/tools/builtin/graph_memory_tool.ts` — WaterFlow Tool 接口
|
||||
- `ts/src/runtime/core/tools/builtin/index.ts` — 添加 registerGraphMemoryTool()
|
||||
- `ts/package.json` — exports, peerDependencies
|
||||
- `ts/tsconfig.json` — typeRoots
|
||||
- `ts/bundled-skills/graph_memory/SKILL.md` — 10 个完整操作
|
||||
|
||||
### 新增(1 文件)
|
||||
- `ts/src/types/waterflow.d.ts` — WaterFlow 类型声明
|
||||
|
||||
---
|
||||
|
||||
## 中断恢复指南
|
||||
|
||||
如果任务中断,按以下步骤恢复:
|
||||
|
||||
1. 读取此文件,找到最后一个 ✅ 完成的任务
|
||||
2. 找到下一个 🔄 或 ⏳ 的任务
|
||||
3. 继续执行该任务
|
||||
4. 完成后更新此文件的状态
|
||||
|
||||
**关键文件路径**:
|
||||
- 重构计划: `重构.md`
|
||||
- 追踪文件: `TRACKING.md`
|
||||
- 核心代码: `ts/src/runtime/core/graph_memory/`
|
||||
- Tool 代码: `ts/src/runtime/core/tools/builtin/`
|
||||
- Skill 定义: `ts/bundled-skills/graph_memory/`
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
name: graph_memory
|
||||
description: 图记忆工具 - 让 AI 拥有真正的长期记忆能力
|
||||
when_to_use: 需要 AI 记住或回忆信息时
|
||||
when_to_use: 需要 AI 记住、回忆、管理信息或任务时
|
||||
context: inline
|
||||
allowed_tools:
|
||||
- builtin:graph_memory
|
||||
@ -9,7 +9,7 @@ arguments:
|
||||
- name: action
|
||||
type: string
|
||||
required: true
|
||||
enum: [recall, commit, purge, introspect]
|
||||
enum: [recall, commit, purge, introspect, persona_update, persona_clear, task_create, task_set_state, task_delete, task_link_info]
|
||||
description: 记忆操作类型
|
||||
- name: params
|
||||
type: object
|
||||
@ -26,12 +26,12 @@ user_invocable: true
|
||||
|
||||
### 1. recall - 检索记忆
|
||||
|
||||
从记忆图中检索相关信息。
|
||||
从记忆图中检索相关信息。支持广度优先搜索(BFS),自动扩展关联实体。
|
||||
|
||||
**参数**:
|
||||
- `queryIntent`: 搜索意图/关键词
|
||||
- `seedEntities`: 可选的种子实体名
|
||||
- `depth`: 检索深度
|
||||
- `depth`: 检索深度(默认 2,BFS 层数)
|
||||
- `sessionFilter`: 可选的会话ID过滤
|
||||
|
||||
**示例**:
|
||||
@ -40,14 +40,15 @@ action: recall
|
||||
params:
|
||||
queryIntent: "用户 喜欢 编程"
|
||||
seedEntities: ["用户"]
|
||||
depth: 2
|
||||
```
|
||||
|
||||
### 2. commit - 写入记忆
|
||||
|
||||
将信息写入记忆图。
|
||||
将信息写入记忆图。使用三元组(主体-关系-客体)格式。
|
||||
|
||||
**参数**:
|
||||
- `triplets`: 三元组数组,每个包含 subject, relation, object
|
||||
- `triplets`: 三元组数组,每个包含 subject, relation, object, confidence(可选)
|
||||
- `sessionId`: 会话ID
|
||||
- `turnId`: 轮次ID
|
||||
|
||||
@ -70,8 +71,7 @@ params:
|
||||
|
||||
**参数**:
|
||||
- `criteria`: 删除条件 (subject, target, relation, sessionId)
|
||||
- `mode`: 删除模式 (soft/hard/supersede)
|
||||
- `newRelation`: 可选的替代关系
|
||||
- `mode`: 删除模式 (soft=标记删除/hard=物理删除/supersede=替代)
|
||||
|
||||
**示例**:
|
||||
```
|
||||
@ -84,7 +84,7 @@ params:
|
||||
|
||||
### 4. introspect - 查看状态
|
||||
|
||||
查看当前记忆状态统计。
|
||||
查看当前记忆状态统计(实体数、关系数)。
|
||||
|
||||
**参数**: 无
|
||||
|
||||
@ -94,9 +94,114 @@ action: introspect
|
||||
params: {}
|
||||
```
|
||||
|
||||
## 人设管理
|
||||
|
||||
### 5. persona_update - 更新人设
|
||||
|
||||
更新 AI 的人设属性(性格、语气、角色等)。
|
||||
|
||||
**参数**:
|
||||
- `attributes`: 属性数组,每个包含 attribute 和 value
|
||||
- `mode`: merge(合并) 或 replace(替换)
|
||||
|
||||
**示例**:
|
||||
```
|
||||
action: persona_update
|
||||
params:
|
||||
attributes:
|
||||
- attribute: "性格"
|
||||
value: "活泼可爱"
|
||||
- attribute: "语气词"
|
||||
value: "喵"
|
||||
mode: "replace"
|
||||
```
|
||||
|
||||
### 6. persona_clear - 清除人设
|
||||
|
||||
清除所有人设,恢复默认身份。
|
||||
|
||||
**参数**:
|
||||
- `confirm`: 必须为 true 才执行
|
||||
|
||||
**示例**:
|
||||
```
|
||||
action: persona_clear
|
||||
params:
|
||||
confirm: true
|
||||
```
|
||||
|
||||
## 任务管理
|
||||
|
||||
### 7. task_create - 创建任务
|
||||
|
||||
创建连续性任务节点,维持对话连贯性。
|
||||
|
||||
**参数**:
|
||||
- `task_id`: 任务唯一ID
|
||||
- `description`: 任务描述
|
||||
- `info_nodes`: 可选的关联信息节点列表
|
||||
|
||||
**示例**:
|
||||
```
|
||||
action: task_create
|
||||
params:
|
||||
task_id: "Task_成语接龙"
|
||||
description: "成语接龙游戏,当前成语:为所欲为"
|
||||
info_nodes: ["成语接龙_当前成语"]
|
||||
```
|
||||
|
||||
### 8. task_set_state - 设置任务状态
|
||||
|
||||
更新任务状态(进行中/已完成/已暂停/已取消)。
|
||||
|
||||
**参数**:
|
||||
- `task_id`: 任务ID
|
||||
- `state`: 新状态
|
||||
|
||||
**示例**:
|
||||
```
|
||||
action: task_set_state
|
||||
params:
|
||||
task_id: "Task_成语接龙"
|
||||
state: "已暂停"
|
||||
```
|
||||
|
||||
### 9. task_delete - 删除任务
|
||||
|
||||
删除任务节点。
|
||||
|
||||
**参数**:
|
||||
- `task_id`: 任务ID
|
||||
|
||||
**示例**:
|
||||
```
|
||||
action: task_delete
|
||||
params:
|
||||
task_id: "Task_成语接龙"
|
||||
```
|
||||
|
||||
### 10. task_link_info - 关联信息到任务
|
||||
|
||||
将记忆节点关联到任务节点,实现"由一件事回忆起相关事情"。
|
||||
|
||||
**参数**:
|
||||
- `task_id`: 任务ID
|
||||
- `info_node`: 信息节点名
|
||||
|
||||
**示例**:
|
||||
```
|
||||
action: task_link_info
|
||||
params:
|
||||
task_id: "Task_成语接龙"
|
||||
info_node: "用户喜欢罗辑"
|
||||
```
|
||||
|
||||
## 使用原则
|
||||
|
||||
1. **选择性记忆**: 只记住重要和持久的信息
|
||||
2. **结构化**: 使用三元组 (主体-关系-客体) 格式
|
||||
3. **关联**: 通过关系连接相关实体
|
||||
4. **定期清理**: 删除过时或错误的信息
|
||||
5. **BFS 搜索**: recall 支持广度优先搜索,depth 参数控制扩展层数
|
||||
6. **工作记忆链**: 每轮对话必须查询和更新工作记忆链(TaskNode),这是维持对话连贯性的唯一机制
|
||||
7. **人设优先**: 每轮对话必须先查询人设图,确保角色一致性
|
||||
|
||||
1483
ts/package-lock.json
generated
1483
ts/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,28 @@
|
||||
{
|
||||
"name": "trulymem-waterflow",
|
||||
"version": "1.0.0",
|
||||
"description": "TrulyMEM 图记忆系统 - WaterFlow Skill/Tool 实现",
|
||||
"type": "module",
|
||||
"main": "./dist/runtime/core/tools/builtin/index.js",
|
||||
"exports": {
|
||||
"./tools": "./dist/runtime/core/tools/builtin/index.js",
|
||||
"./graph_memory": "./dist/runtime/core/graph_memory/index.js",
|
||||
"./skills": "./bundled-skills"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"waterflow-ts": ">=0.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"yaml": "^2.8.3"
|
||||
"sql.js": "^1.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.0.0",
|
||||
"vitest": "^2.0.0"
|
||||
"vitest": "^2.0.0",
|
||||
"waterflow-ts": "file:../../WaterFlow/ts"
|
||||
}
|
||||
}
|
||||
|
||||
38
ts/src/runtime/core/graph_memory/config.ts
Normal file
38
ts/src/runtime/core/graph_memory/config.ts
Normal file
@ -0,0 +1,38 @@
|
||||
export interface TrulyMEMConfig {
|
||||
dbPath: string;
|
||||
autoSave: boolean;
|
||||
debug: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_CONFIG: TrulyMEMConfig = {
|
||||
dbPath: '.trulymem/graph_memory.db',
|
||||
autoSave: true,
|
||||
debug: false
|
||||
};
|
||||
|
||||
let globalConfig: TrulyMEMConfig = { ...DEFAULT_CONFIG };
|
||||
|
||||
export function initConfig(config: Partial<TrulyMEMConfig> = {}): TrulyMEMConfig {
|
||||
globalConfig = { ...DEFAULT_CONFIG, ...config };
|
||||
return globalConfig;
|
||||
}
|
||||
|
||||
export function getConfig(): TrulyMEMConfig {
|
||||
return globalConfig;
|
||||
}
|
||||
|
||||
export function setDbPath(dbPath: string): void {
|
||||
globalConfig.dbPath = dbPath;
|
||||
}
|
||||
|
||||
export function getDbPath(): string {
|
||||
return globalConfig.dbPath;
|
||||
}
|
||||
|
||||
export function setAutoSave(autoSave: boolean): void {
|
||||
globalConfig.autoSave = autoSave;
|
||||
}
|
||||
|
||||
export function isAutoSave(): boolean {
|
||||
return globalConfig.autoSave;
|
||||
}
|
||||
@ -1,53 +1,159 @@
|
||||
import initSqlJs, { type Database as SqlJsDatabase } from 'sql.js';
|
||||
import type { Entity, Relation, RecallParams, CommitParams, PurgeParams, RecallResult, CommitResult, PurgeResult, MemoryStats } from './types';
|
||||
import { getConfig } from './config';
|
||||
|
||||
export class GraphDatabase {
|
||||
private entities: Map<string, Entity> = new Map();
|
||||
private relations: Map<string, Relation> = new Map();
|
||||
private db: SqlJsDatabase | null = null;
|
||||
private sessionId: string;
|
||||
private initPromise: Promise<void> | null = null;
|
||||
private _platform: any = null;
|
||||
|
||||
constructor(sessionId?: string) {
|
||||
this.sessionId = sessionId || `session-${Date.now()}`;
|
||||
this.initPromise = this.initDatabase();
|
||||
}
|
||||
|
||||
private async initDatabase(): Promise<void> {
|
||||
const SQL = await initSqlJs();
|
||||
const { getPlatform } = await import('waterflow/platform');
|
||||
this._platform = getPlatform();
|
||||
|
||||
try {
|
||||
const dbPath = this._platform.path.join(this._platform.getCwd(), getConfig().dbPath);
|
||||
const exists = await this._platform.fs.exists(dbPath);
|
||||
if (exists) {
|
||||
const data = await this._platform.fs.readFile(dbPath, { encoding: 'binary' });
|
||||
this.db = new SQL.Database(data as Uint8Array);
|
||||
} else {
|
||||
this.db = new SQL.Database();
|
||||
}
|
||||
} catch {
|
||||
this.db = new SQL.Database();
|
||||
}
|
||||
|
||||
this.createTables();
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
if (!this.db || !this._platform) return;
|
||||
|
||||
try {
|
||||
const platform = this._platform;
|
||||
const dbPath = platform.path.join(platform.getCwd(), getConfig().dbPath);
|
||||
const dir = platform.path.dirname(dbPath);
|
||||
const dirExists = await platform.fs.exists(dir);
|
||||
if (!dirExists) {
|
||||
await platform.fs.mkdir(dir, true);
|
||||
}
|
||||
const data = this.db.export();
|
||||
await platform.fs.writeFile(dbPath, new Uint8Array(data), { encoding: 'binary' });
|
||||
} catch (error) {
|
||||
this._platform.getLogger().error(`[GraphDatabase] Save failed: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
private createTables(): void {
|
||||
if (!this.db) return;
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS entities (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT DEFAULT 'unknown',
|
||||
mention_count INTEGER DEFAULT 1,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
`);
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS relations (
|
||||
id TEXT PRIMARY KEY,
|
||||
source_id TEXT NOT NULL,
|
||||
target_id TEXT NOT NULL,
|
||||
relation_type TEXT NOT NULL,
|
||||
confidence REAL DEFAULT 1.0,
|
||||
status TEXT DEFAULT 'active',
|
||||
session_id TEXT NOT NULL,
|
||||
turn_id INTEGER DEFAULT 0,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
date_bucket TEXT NOT NULL,
|
||||
FOREIGN KEY (source_id) REFERENCES entities(id),
|
||||
FOREIGN KEY (target_id) REFERENCES entities(id)
|
||||
)
|
||||
`);
|
||||
this.db.run(`CREATE INDEX IF NOT EXISTS idx_entity_name ON entities(name)`);
|
||||
this.db.run(`CREATE INDEX IF NOT EXISTS idx_entity_type ON entities(type)`);
|
||||
this.db.run(`CREATE INDEX IF NOT EXISTS idx_relation_source ON relations(source_id)`);
|
||||
this.db.run(`CREATE INDEX IF NOT EXISTS idx_relation_target ON relations(target_id)`);
|
||||
this.db.run(`CREATE INDEX IF NOT EXISTS idx_relation_type ON relations(relation_type)`);
|
||||
this.db.run(`CREATE INDEX IF NOT EXISTS idx_relation_status ON relations(status)`);
|
||||
}
|
||||
|
||||
private ensureInit(): void {
|
||||
if (!this.db) {
|
||||
throw new Error('Database not initialized');
|
||||
}
|
||||
}
|
||||
|
||||
async recall(params: RecallParams): Promise<RecallResult> {
|
||||
const { queryIntent, seedEntities, sessionFilter } = params;
|
||||
await this.initPromise;
|
||||
this.ensureInit();
|
||||
const { queryIntent, seedEntities, depth = 2, sessionFilter } = params;
|
||||
const keywords = queryIntent.split(/[,\s]+/).filter(k => k.length > 0);
|
||||
const entities: Entity[] = [];
|
||||
const relations: Relation[] = [];
|
||||
const entityIds = new Set<string>();
|
||||
|
||||
if (!this.db) return { entities, relations, message: 'Database not ready' };
|
||||
|
||||
if (!keywords.length && !seedEntities?.length) {
|
||||
const rows = this.db.exec('SELECT * FROM entities ORDER BY mention_count DESC LIMIT 50');
|
||||
if (rows.length > 0) {
|
||||
const columns = rows[0].columns;
|
||||
for (const row of rows[0].values) {
|
||||
const obj = this.rowToObject(columns, row);
|
||||
const id = obj.id as string;
|
||||
entityIds.add(id);
|
||||
entities.push(this.rowToEntity(obj, 0));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const keyword of keywords) {
|
||||
const lowerKeyword = keyword.toLowerCase();
|
||||
for (const [_, entity] of this.entities) {
|
||||
if (entity.name.toLowerCase().includes(lowerKeyword)) {
|
||||
if (!entityIds.has(entity.id)) {
|
||||
entityIds.add(entity.id);
|
||||
entities.push(entity);
|
||||
const stmt = this.db.prepare('SELECT * FROM entities WHERE LOWER(name) LIKE ? LIMIT 100');
|
||||
stmt.bind([`%${keyword.toLowerCase()}%`]);
|
||||
while (stmt.step()) {
|
||||
const row = stmt.getAsObject();
|
||||
const id = row.id as string;
|
||||
if (!entityIds.has(id)) {
|
||||
entityIds.add(id);
|
||||
entities.push(this.rowToEntity(row, 0));
|
||||
}
|
||||
}
|
||||
stmt.free();
|
||||
}
|
||||
}
|
||||
|
||||
if (seedEntities && seedEntities.length > 0) {
|
||||
if (seedEntities?.length) {
|
||||
for (const seedName of seedEntities) {
|
||||
for (const [_, entity] of this.entities) {
|
||||
if (entity.name.toLowerCase() === seedName.toLowerCase()) {
|
||||
if (!entityIds.has(entity.id)) {
|
||||
entityIds.add(entity.id);
|
||||
entities.push(entity);
|
||||
}
|
||||
const stmt = this.db.prepare('SELECT * FROM entities WHERE LOWER(name) = ? LIMIT 1');
|
||||
stmt.bind([seedName.toLowerCase()]);
|
||||
if (stmt.step()) {
|
||||
const row = stmt.getAsObject();
|
||||
const id = row.id as string;
|
||||
if (!entityIds.has(id)) {
|
||||
entityIds.add(id);
|
||||
entities.push(this.rowToEntity(row, 0));
|
||||
}
|
||||
}
|
||||
stmt.free();
|
||||
}
|
||||
}
|
||||
|
||||
for (const [_, relation] of this.relations) {
|
||||
if (entityIds.has(relation.sourceId) || entityIds.has(relation.targetId)) {
|
||||
if (relation.status === 'active') {
|
||||
if (!sessionFilter || relation.sessionId === sessionFilter) {
|
||||
relations.push(relation);
|
||||
}
|
||||
}
|
||||
this.bfsExpand(entityIds, entities, relations, depth, sessionFilter);
|
||||
|
||||
for (const entity of entities) {
|
||||
if (entity.depth === undefined) {
|
||||
entity.depth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,132 +164,268 @@ export class GraphDatabase {
|
||||
};
|
||||
}
|
||||
|
||||
private bfsExpand(
|
||||
seedIds: Set<string>,
|
||||
entities: Entity[],
|
||||
relations: Relation[],
|
||||
maxDepth: number,
|
||||
sessionFilter?: string
|
||||
): void {
|
||||
if (!this.db) return;
|
||||
|
||||
const visited = new Set(seedIds);
|
||||
let currentLayer = new Set(seedIds);
|
||||
const entityDepths: Record<string, number> = {};
|
||||
for (const id of seedIds) {
|
||||
entityDepths[id] = 0;
|
||||
}
|
||||
|
||||
for (let layer = 0; layer < maxDepth; layer++) {
|
||||
if (!currentLayer.size) break;
|
||||
|
||||
const placeholders = Array(currentLayer.size).fill('?').join(',');
|
||||
let sql = `
|
||||
SELECT r.id, r.source_id, r.target_id,
|
||||
e1.name as source_name, e2.name as target_name,
|
||||
r.relation_type, r.confidence, r.session_id,
|
||||
r.turn_id, r.created_at, r.updated_at, r.status, r.date_bucket
|
||||
FROM relations r
|
||||
JOIN entities e1 ON r.source_id = e1.id
|
||||
JOIN entities e2 ON r.target_id = e2.id
|
||||
WHERE (r.source_id IN (${placeholders}) OR r.target_id IN (${placeholders}))
|
||||
AND r.status = 'active'
|
||||
`;
|
||||
const params: (string | number)[] = [];
|
||||
for (const id of currentLayer) { params.push(id); }
|
||||
for (const id of currentLayer) { params.push(id); }
|
||||
if (sessionFilter) {
|
||||
sql += ` AND r.session_id = ?`;
|
||||
params.push(sessionFilter);
|
||||
}
|
||||
|
||||
const stmt = this.db.prepare(sql);
|
||||
stmt.bind(params);
|
||||
|
||||
const nextLayer = new Set<string>();
|
||||
const layerRelations: Relation[] = [];
|
||||
|
||||
while (stmt.step()) {
|
||||
const row = stmt.getAsObject();
|
||||
const sourceId = row.source_id as string;
|
||||
const targetId = row.target_id as string;
|
||||
const sourceDepth = entityDepths[sourceId] ?? layer;
|
||||
const targetDepth = entityDepths[targetId] ?? layer;
|
||||
const relationDepth = Math.max(sourceDepth, targetDepth) + 1;
|
||||
|
||||
layerRelations.push({
|
||||
id: row.id as string,
|
||||
sourceId,
|
||||
targetId,
|
||||
relationType: row.relation_type as string,
|
||||
confidence: row.confidence as number,
|
||||
status: row.status as Relation['status'],
|
||||
sessionId: row.session_id as string,
|
||||
turnId: row.turn_id as number,
|
||||
createdAt: new Date(row.created_at as string),
|
||||
updatedAt: new Date(row.updated_at as string),
|
||||
dateBucket: row.date_bucket as string,
|
||||
depth: relationDepth
|
||||
});
|
||||
|
||||
if (!visited.has(sourceId)) {
|
||||
visited.add(sourceId);
|
||||
nextLayer.add(sourceId);
|
||||
entityDepths[sourceId] = layer + 1;
|
||||
}
|
||||
if (!visited.has(targetId)) {
|
||||
visited.add(targetId);
|
||||
nextLayer.add(targetId);
|
||||
entityDepths[targetId] = layer + 1;
|
||||
}
|
||||
}
|
||||
stmt.free();
|
||||
|
||||
relations.push(...layerRelations);
|
||||
|
||||
if (nextLayer.size) {
|
||||
const placeholders = Array(nextLayer.size).fill('?').join(',');
|
||||
const entityStmt = this.db.prepare(
|
||||
`SELECT * FROM entities WHERE id IN (${placeholders})`
|
||||
);
|
||||
entityStmt.bind(Array.from(nextLayer));
|
||||
while (entityStmt.step()) {
|
||||
const row = entityStmt.getAsObject();
|
||||
const id = row.id as string;
|
||||
entities.push(this.rowToEntity(row, entityDepths[id] ?? layer + 1));
|
||||
}
|
||||
entityStmt.free();
|
||||
}
|
||||
|
||||
currentLayer = nextLayer;
|
||||
}
|
||||
}
|
||||
|
||||
async commit(params: CommitParams): Promise<CommitResult> {
|
||||
await this.initPromise;
|
||||
this.ensureInit();
|
||||
const { triplets, sessionId, turnId } = params;
|
||||
let createdEntities = 0;
|
||||
let createdRelations = 0;
|
||||
|
||||
if (!this.db) return { createdEntities: 0, createdRelations: 0 };
|
||||
|
||||
for (const triplet of triplets) {
|
||||
const sourceId = this.upsertEntity(triplet.subject);
|
||||
const targetId = this.upsertEntity(triplet.object);
|
||||
|
||||
const relationId = this.generateId();
|
||||
const now = new Date();
|
||||
const relation: Relation = {
|
||||
id: relationId,
|
||||
sourceId,
|
||||
targetId,
|
||||
relationType: triplet.relation,
|
||||
confidence: triplet.confidence || 1.0,
|
||||
status: 'active',
|
||||
sessionId: sessionId || this.sessionId,
|
||||
turnId: turnId || 0,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
dateBucket: this.getDateBucket(now)
|
||||
};
|
||||
const now = new Date().toISOString();
|
||||
this.db.run(
|
||||
`INSERT INTO relations (id, source_id, target_id, relation_type, confidence, status, session_id, turn_id, created_at, updated_at, date_bucket)
|
||||
VALUES (?, ?, ?, ?, ?, 'active', ?, ?, ?, ?, ?)`,
|
||||
[relationId, sourceId, targetId, triplet.relation, triplet.confidence ?? 1.0, sessionId ?? this.sessionId, turnId ?? 0, now, now, new Date().toISOString().split('T')[0]]
|
||||
);
|
||||
|
||||
this.relations.set(relationId, relation);
|
||||
createdEntities += 2;
|
||||
createdRelations++;
|
||||
}
|
||||
|
||||
if (getConfig().autoSave) {
|
||||
await this.save();
|
||||
}
|
||||
|
||||
return { createdEntities, createdRelations };
|
||||
}
|
||||
|
||||
async purge(params: PurgeParams): Promise<PurgeResult> {
|
||||
await this.initPromise;
|
||||
this.ensureInit();
|
||||
const { criteria, mode = 'soft' } = params;
|
||||
let deleted = 0;
|
||||
|
||||
for (const [id, relation] of this.relations) {
|
||||
if (relation.status !== 'active') continue;
|
||||
if (!this.db) return { deleted: 0, mode };
|
||||
|
||||
if (!criteria) {
|
||||
continue;
|
||||
const conditions: string[] = [];
|
||||
const queryParams: (string | number)[] = [];
|
||||
|
||||
if (criteria?.subject) {
|
||||
const stmt = this.db.prepare('SELECT id FROM entities WHERE LOWER(name) = ?');
|
||||
stmt.bind([criteria.subject.toLowerCase()]);
|
||||
if (stmt.step()) {
|
||||
const row = stmt.getAsObject();
|
||||
conditions.push(`source_id = ?`);
|
||||
queryParams.push(row.id as string);
|
||||
}
|
||||
stmt.free();
|
||||
}
|
||||
|
||||
let matches = true;
|
||||
if (criteria.subject) {
|
||||
const sourceEntity = this.entities.get(relation.sourceId);
|
||||
matches = sourceEntity?.name.toLowerCase() === criteria.subject.toLowerCase();
|
||||
if (criteria?.target) {
|
||||
const stmt = this.db.prepare('SELECT id FROM entities WHERE LOWER(name) = ?');
|
||||
stmt.bind([criteria.target.toLowerCase()]);
|
||||
if (stmt.step()) {
|
||||
const row = stmt.getAsObject();
|
||||
conditions.push(`target_id = ?`);
|
||||
queryParams.push(row.id as string);
|
||||
}
|
||||
if (matches && criteria.target) {
|
||||
const targetEntity = this.entities.get(relation.targetId);
|
||||
matches = targetEntity?.name.toLowerCase() === criteria.target.toLowerCase();
|
||||
}
|
||||
if (matches && criteria.relation) {
|
||||
matches = relation.relationType.toLowerCase() === criteria.relation.toLowerCase();
|
||||
}
|
||||
if (matches && criteria.sessionId) {
|
||||
matches = relation.sessionId === criteria.sessionId;
|
||||
stmt.free();
|
||||
}
|
||||
|
||||
if (matches) {
|
||||
if (criteria?.relation) {
|
||||
conditions.push(`relation_type = ?`);
|
||||
queryParams.push(criteria.relation);
|
||||
}
|
||||
|
||||
if (!conditions.length) {
|
||||
return { deleted: 0, mode, message: '无删除条件' };
|
||||
}
|
||||
|
||||
const whereClause = conditions.join(' AND ');
|
||||
|
||||
const countStmt = this.db.prepare(`SELECT COUNT(*) as cnt FROM relations WHERE ${whereClause} AND status = 'active'`);
|
||||
countStmt.bind(queryParams);
|
||||
if (countStmt.step()) {
|
||||
const row = countStmt.getAsObject();
|
||||
deleted = row.cnt as number;
|
||||
}
|
||||
countStmt.free();
|
||||
|
||||
if (mode === 'hard') {
|
||||
this.relations.delete(id);
|
||||
this.db.run(`DELETE FROM relations WHERE ${whereClause} AND status = 'active'`, queryParams);
|
||||
} else {
|
||||
relation.status = 'deleted';
|
||||
relation.updatedAt = new Date();
|
||||
}
|
||||
deleted++;
|
||||
this.db.run(
|
||||
`UPDATE relations SET status = 'deleted', updated_at = ? WHERE ${whereClause} AND status = 'active'`,
|
||||
[new Date().toISOString(), ...queryParams]
|
||||
);
|
||||
}
|
||||
|
||||
if (getConfig().autoSave && deleted > 0) {
|
||||
await this.save();
|
||||
}
|
||||
|
||||
return { deleted, mode };
|
||||
}
|
||||
|
||||
async introspect(): Promise<MemoryStats> {
|
||||
let entityCount = 0;
|
||||
for (const [_, entity] of this.entities) {
|
||||
if (!this.isEntityDeleted(entity.id)) entityCount++;
|
||||
}
|
||||
await this.initPromise;
|
||||
this.ensureInit();
|
||||
|
||||
let relationCount = 0;
|
||||
for (const [_, relation] of this.relations) {
|
||||
if (relation.status === 'active') relationCount++;
|
||||
}
|
||||
if (!this.db) return { entityCount: 0, relationCount: 0, sessionId: this.sessionId };
|
||||
|
||||
const entityCount = (this.db.exec('SELECT COUNT(*) FROM entities')[0]?.values[0]?.[0] as number) ?? 0;
|
||||
const relationCount = (this.db.exec("SELECT COUNT(*) FROM relations WHERE status = 'active'")[0]?.values[0]?.[0] as number) ?? 0;
|
||||
|
||||
return { entityCount, relationCount, sessionId: this.sessionId };
|
||||
}
|
||||
|
||||
private upsertEntity(name: string): string {
|
||||
for (const [id, entity] of this.entities) {
|
||||
if (entity.name === name && !this.isEntityDeleted(id)) {
|
||||
entity.mentionCount++;
|
||||
entity.updatedAt = new Date();
|
||||
if (!this.db) return this.generateId();
|
||||
|
||||
const existingStmt = this.db.prepare('SELECT id, mention_count FROM entities WHERE LOWER(name) = ?');
|
||||
existingStmt.bind([name.toLowerCase()]);
|
||||
if (existingStmt.step()) {
|
||||
const row = existingStmt.getAsObject();
|
||||
const id = row.id as string;
|
||||
this.db.run('UPDATE entities SET mention_count = ?, updated_at = ? WHERE id = ?', [(row.mention_count as number) + 1, new Date().toISOString(), id]);
|
||||
existingStmt.free();
|
||||
return id;
|
||||
}
|
||||
}
|
||||
existingStmt.free();
|
||||
|
||||
const id = this.generateId();
|
||||
const now = new Date();
|
||||
const entity: Entity = {
|
||||
id,
|
||||
name,
|
||||
type: 'unknown',
|
||||
mentionCount: 1,
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
};
|
||||
this.entities.set(id, entity);
|
||||
const now = new Date().toISOString();
|
||||
this.db.run(
|
||||
'INSERT INTO entities (id, name, type, mention_count, created_at, updated_at) VALUES (?, ?, ?, 1, ?, ?)',
|
||||
[id, name, 'unknown', now, now]
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
private isEntityDeleted(entityId: string): boolean {
|
||||
for (const [_, relation] of this.relations) {
|
||||
if ((relation.sourceId === entityId || relation.targetId === entityId) && relation.status === 'deleted') {
|
||||
return true;
|
||||
private rowToEntity(row: Record<string, unknown>, depth?: number): Entity {
|
||||
const entity: Entity = {
|
||||
id: row.id as string,
|
||||
name: row.name as string,
|
||||
type: (row.type as string) ?? 'unknown',
|
||||
mentionCount: row.mention_count as number,
|
||||
createdAt: new Date(row.created_at as string),
|
||||
updatedAt: new Date(row.updated_at as string)
|
||||
};
|
||||
if (depth !== undefined) {
|
||||
entity.depth = depth;
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
return false;
|
||||
|
||||
private rowToObject(columns: string[], values: unknown[]): Record<string, unknown> {
|
||||
const obj: Record<string, unknown> = {};
|
||||
columns.forEach((col, i) => { obj[col] = values[i]; });
|
||||
return obj;
|
||||
}
|
||||
|
||||
private generateId(): string {
|
||||
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
if (this._platform?.globals?.randomUUID) {
|
||||
return this._platform.globals.randomUUID();
|
||||
}
|
||||
|
||||
private getDateBucket(date: Date): string {
|
||||
return date.toISOString().split('T')[0] ?? '';
|
||||
return `ent-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
||||
}
|
||||
|
||||
setSessionId(sessionId: string): void {
|
||||
@ -193,4 +435,11 @@ export class GraphDatabase {
|
||||
getSessionId(): string {
|
||||
return this.sessionId;
|
||||
}
|
||||
|
||||
close(): void {
|
||||
if (this.db) {
|
||||
this.db.close();
|
||||
this.db = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export * from './types';
|
||||
export * from './config';
|
||||
export * from './graph_database';
|
||||
export * from './memory_service';
|
||||
@ -5,6 +5,7 @@ export interface Entity {
|
||||
mentionCount: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
depth?: number; // BFS 搜索深度标注
|
||||
}
|
||||
|
||||
export type RelationStatus = 'active' | 'deleted' | 'archived' | 'superseded';
|
||||
@ -21,6 +22,7 @@ export interface Relation {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
dateBucket: string;
|
||||
depth?: number; // BFS 搜索深度标注
|
||||
}
|
||||
|
||||
export interface Triplet {
|
||||
@ -71,6 +73,7 @@ export interface CommitResult {
|
||||
export interface PurgeResult {
|
||||
deleted: number;
|
||||
mode: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export type TaskState = '进行中' | '已完成' | '已暂停' | '已取消';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Tool, ToolCategory, PermissionLevel, ToolInputSchema, ToolExecutionContext, ToolOutput } from '../tool_interface';
|
||||
import type { Tool, ToolCategory, PermissionLevel, ToolInputSchema, ToolOutput, ToolInput, ToolExecutionContext } from 'waterflow/runtime/core/tools/tool_interface';
|
||||
import { GraphDatabase } from '../../graph_memory/graph_database';
|
||||
import { MemoryService } from '../../graph_memory/memory_service';
|
||||
|
||||
@ -20,6 +20,7 @@ export class GraphMemoryTool implements Tool {
|
||||
readonly description = GRAPH_MEMORY_TOOL_DESCRIPTION;
|
||||
readonly category: ToolCategory = 'analysis';
|
||||
readonly permissionLevel: PermissionLevel = 'safe';
|
||||
readonly alwaysLoad = true;
|
||||
|
||||
readonly inputSchema: ToolInputSchema = {
|
||||
type: 'object',
|
||||
@ -100,14 +101,18 @@ export class GraphMemoryTool implements Tool {
|
||||
this.service = new MemoryService(this.db);
|
||||
}
|
||||
|
||||
async handler(params: Record<string, unknown>, _context: ToolExecutionContext): Promise<ToolOutput> {
|
||||
async handler(params: ToolInput, context: ToolExecutionContext): Promise<ToolOutput> {
|
||||
const action = params.action as string;
|
||||
const actionParams = params.params as Record<string, unknown>;
|
||||
const logger = context?.logger;
|
||||
|
||||
try {
|
||||
logger?.info(`[GraphMemoryTool] Executing action: ${action}`);
|
||||
const result = await this.executeAction(action, actionParams);
|
||||
logger?.info(`[GraphMemoryTool] Action ${action} completed successfully`);
|
||||
return JSON.stringify({ success: true, data: result }, null, 2);
|
||||
} catch (error) {
|
||||
logger?.error(`[GraphMemoryTool] Action ${action} failed:`, error);
|
||||
return JSON.stringify({
|
||||
success: false,
|
||||
error: {
|
||||
|
||||
19
ts/src/runtime/core/tools/builtin/index.ts
Normal file
19
ts/src/runtime/core/tools/builtin/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import type { Tool } from 'waterflow/runtime/core/tools/tool_interface';
|
||||
import type { Platform } from 'waterflow/platform/types';
|
||||
import { GraphMemoryTool, createGraphMemoryTool } from './graph_memory_tool';
|
||||
|
||||
export { GraphMemoryTool, createGraphMemoryTool };
|
||||
|
||||
export function registerGraphMemoryTool(
|
||||
registry: { register: (tool: Tool) => void },
|
||||
sessionId?: string
|
||||
): void {
|
||||
registry.register(createGraphMemoryTool(sessionId));
|
||||
}
|
||||
|
||||
export function installTrulyMEM(platform: Platform, sessionId?: string) {
|
||||
const { initializeToolRegistry } = require('waterflow/runtime/core/tools/builtin');
|
||||
const registry = initializeToolRegistry(platform);
|
||||
registerGraphMemoryTool(registry, sessionId);
|
||||
return registry;
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
export type ToolCategory = 'file' | 'code' | 'search' | 'execute' | 'network' | 'analysis' | 'generation' | 'communication' | 'mcp' | 'custom';
|
||||
|
||||
export type PermissionLevel = 'safe' | 'moderate' | 'dangerous' | 'restricted';
|
||||
|
||||
export type SchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object';
|
||||
|
||||
export interface SchemaProperty {
|
||||
type: SchemaType;
|
||||
description: string;
|
||||
enum?: string[];
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
pattern?: string;
|
||||
default?: unknown;
|
||||
examples?: unknown[];
|
||||
items?: SchemaProperty;
|
||||
properties?: Record<string, SchemaProperty>;
|
||||
}
|
||||
|
||||
export interface ToolInputSchema {
|
||||
type: 'object';
|
||||
properties: Record<string, SchemaProperty>;
|
||||
required?: string[];
|
||||
additionalProperties?: boolean;
|
||||
}
|
||||
|
||||
export interface ToolOutputSchema {
|
||||
type: 'object';
|
||||
properties: Record<string, SchemaProperty>;
|
||||
format?: 'json' | 'text' | 'markdown' | 'binary';
|
||||
maxSize?: number;
|
||||
maxLines?: number;
|
||||
}
|
||||
|
||||
export type ToolInput = Record<string, unknown>;
|
||||
export type ToolOutput = string | Record<string, unknown> | void;
|
||||
|
||||
export interface ToolExecutionContext {
|
||||
toolCallId: string;
|
||||
workingDirectory: string;
|
||||
abortController: { signal: AbortSignal };
|
||||
config: { timeout?: number };
|
||||
logger: { info: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void; debug: (...args: unknown[]) => void };
|
||||
}
|
||||
|
||||
export type ToolHandler = (params: ToolInput, context: ToolExecutionContext) => Promise<ToolOutput>;
|
||||
|
||||
export interface Tool {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly description: string;
|
||||
readonly category: ToolCategory;
|
||||
readonly inputSchema: ToolInputSchema;
|
||||
readonly outputSchema?: ToolOutputSchema;
|
||||
readonly handler: ToolHandler;
|
||||
readonly permissionLevel: PermissionLevel;
|
||||
}
|
||||
29
ts/src/types/sql.js.d.ts
vendored
Normal file
29
ts/src/types/sql.js.d.ts
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
declare module 'sql.js' {
|
||||
export interface Database {
|
||||
run(sql: string, params?: (string | number | null | Uint8Array)[]): void;
|
||||
exec(sql: string): QueryExecResult[];
|
||||
prepare(sql: string): Statement;
|
||||
export(): Uint8Array;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export interface Statement {
|
||||
bind(params?: (string | number | null | Uint8Array)[]): boolean;
|
||||
step(): boolean;
|
||||
getAsObject(): Record<string, unknown>;
|
||||
free(): boolean;
|
||||
}
|
||||
|
||||
export interface QueryExecResult {
|
||||
columns: string[];
|
||||
values: (string | number | null | Uint8Array)[][];
|
||||
}
|
||||
|
||||
export interface SqlJsStatic {
|
||||
Database: new (data?: ArrayLike<number>) => Database;
|
||||
}
|
||||
|
||||
export default function initSqlJs(config?: {
|
||||
locateFile?: (file: string) => string;
|
||||
}): Promise<SqlJsStatic>;
|
||||
}
|
||||
193
ts/src/types/waterflow.d.ts
vendored
Normal file
193
ts/src/types/waterflow.d.ts
vendored
Normal file
@ -0,0 +1,193 @@
|
||||
declare module 'waterflow/platform' {
|
||||
import type { Platform } from 'waterflow/platform/types';
|
||||
export function getPlatform(): Platform;
|
||||
export function hasCapability(capability: string): boolean;
|
||||
export function initPlatform(options?: any): Platform;
|
||||
export function resetPlatform(): void;
|
||||
}
|
||||
|
||||
declare module 'waterflow/platform/types' {
|
||||
export interface PlatformAbortSignal {
|
||||
readonly aborted: boolean;
|
||||
readonly reason?: unknown;
|
||||
addEventListener(type: 'abort', listener: () => void): void;
|
||||
removeEventListener(type: 'abort', listener: () => void): void;
|
||||
}
|
||||
|
||||
export interface PlatformAbortController {
|
||||
readonly signal: PlatformAbortSignal;
|
||||
abort(reason?: unknown): void;
|
||||
}
|
||||
|
||||
export interface PathOperations {
|
||||
join(...paths: string[]): string;
|
||||
dirname(p: string): string;
|
||||
basename(p: string, ext?: string): string;
|
||||
extname(p: string): string;
|
||||
normalize(p: string): string;
|
||||
isAbsolute(p: string): boolean;
|
||||
resolve(...paths: string[]): string;
|
||||
relative(from: string, to: string): string;
|
||||
}
|
||||
|
||||
export interface FileReadOptions {
|
||||
encoding?: 'utf-8' | 'binary';
|
||||
start?: number;
|
||||
end?: number;
|
||||
}
|
||||
|
||||
export interface FileWriteOptions {
|
||||
encoding?: 'utf-8' | 'binary';
|
||||
append?: boolean;
|
||||
}
|
||||
|
||||
export interface FileInfo {
|
||||
path: string;
|
||||
name: string;
|
||||
isFile: boolean;
|
||||
isDirectory: boolean;
|
||||
size: number;
|
||||
modifiedTime: number;
|
||||
createdTime: number;
|
||||
}
|
||||
|
||||
export interface FileSystemOperations {
|
||||
readFile(filePath: string, options?: FileReadOptions): Promise<string | ArrayBuffer>;
|
||||
writeFile(filePath: string, data: string | ArrayBuffer, options?: FileWriteOptions): Promise<void>;
|
||||
appendFile(filePath: string, data: string, options?: FileWriteOptions): Promise<void>;
|
||||
deleteFile(filePath: string): Promise<void>;
|
||||
exists(filePath: string): Promise<boolean>;
|
||||
stat(filePath: string): Promise<FileInfo>;
|
||||
readdir(dirPath: string): Promise<FileInfo[]>;
|
||||
mkdir(dirPath: string, recursive?: boolean): Promise<void>;
|
||||
rmdir(dirPath: string, recursive?: boolean): Promise<void>;
|
||||
copy(src: string, dest: string): Promise<void>;
|
||||
move(src: string, dest: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ProcessResult {
|
||||
exitCode: number;
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
signal?: string;
|
||||
}
|
||||
|
||||
export interface ProcessOptions {
|
||||
cwd?: string;
|
||||
env?: Record<string, string>;
|
||||
timeout?: number;
|
||||
maxBuffer?: number;
|
||||
}
|
||||
|
||||
export interface ProcessOperations {
|
||||
exec(command: string, options?: ProcessOptions): Promise<ProcessResult>;
|
||||
execFile(file: string, args: string[], options?: ProcessOptions): Promise<ProcessResult>;
|
||||
}
|
||||
|
||||
export interface StorageOperations {
|
||||
get(key: string): Promise<string | null>;
|
||||
set(key: string, value: string, ttl?: number): Promise<void>;
|
||||
delete(key: string): Promise<void>;
|
||||
clear(): Promise<void>;
|
||||
keys(): Promise<string[]>;
|
||||
}
|
||||
|
||||
export interface PlatformGlobals {
|
||||
TextEncoder: typeof TextEncoder;
|
||||
TextDecoder: typeof TextDecoder;
|
||||
URL: typeof URL;
|
||||
randomUUID(): string;
|
||||
now(): number;
|
||||
btoa(data: string): string;
|
||||
atob(data: string): string;
|
||||
}
|
||||
|
||||
export interface Logger {
|
||||
info(msg: string, ...args: unknown[]): void;
|
||||
warn(msg: string, ...args: unknown[]): void;
|
||||
error(msg: string, ...args: unknown[]): void;
|
||||
debug(msg: string, ...args: unknown[]): void;
|
||||
}
|
||||
|
||||
export interface Platform {
|
||||
readonly path: PathOperations;
|
||||
readonly fs: FileSystemOperations;
|
||||
readonly process: ProcessOperations;
|
||||
readonly storage: StorageOperations;
|
||||
readonly globals: PlatformGlobals;
|
||||
getInfo(): { runtime: string; os: string; version: string; arch: string; hostname: string };
|
||||
createAbortController(): PlatformAbortController;
|
||||
getEnv(key: string): string | undefined;
|
||||
getAllEnv(): Record<string, string>;
|
||||
setEnv(key: string, value: string): void;
|
||||
getCwd(): string;
|
||||
setCwd(p: string): void;
|
||||
exit(code: number): void;
|
||||
getLogger(): Logger;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'waterflow/runtime/core/tools/tool_interface' {
|
||||
import type { PlatformAbortController, Logger } from 'waterflow/platform/types';
|
||||
|
||||
export type ToolCategory = 'file' | 'code' | 'search' | 'execute' | 'network' | 'analysis' | 'generation' | 'communication' | 'mcp' | 'custom';
|
||||
export type PermissionLevel = 'safe' | 'moderate' | 'dangerous' | 'restricted';
|
||||
export type ToolInput = Record<string, unknown>;
|
||||
export type ToolOutput = string | Record<string, unknown> | void;
|
||||
|
||||
export interface SchemaProperty {
|
||||
type: string;
|
||||
description: string;
|
||||
enum?: string[];
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
pattern?: string;
|
||||
default?: unknown;
|
||||
examples?: unknown[];
|
||||
items?: SchemaProperty;
|
||||
properties?: Record<string, SchemaProperty>;
|
||||
}
|
||||
|
||||
export interface ToolInputSchema {
|
||||
type: 'object';
|
||||
properties: Record<string, SchemaProperty>;
|
||||
required?: string[];
|
||||
additionalProperties?: boolean;
|
||||
}
|
||||
|
||||
export interface ToolOutputSchema {
|
||||
type: 'object';
|
||||
properties: Record<string, SchemaProperty>;
|
||||
format?: 'json' | 'text' | 'markdown' | 'binary';
|
||||
maxSize?: number;
|
||||
maxLines?: number;
|
||||
}
|
||||
|
||||
export interface ToolExecutionContext {
|
||||
toolCallId: string;
|
||||
workingDirectory: string;
|
||||
abortController: PlatformAbortController;
|
||||
config: { timeout?: number };
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export interface Tool {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly description: string;
|
||||
readonly category: ToolCategory;
|
||||
readonly inputSchema: ToolInputSchema;
|
||||
readonly outputSchema?: ToolOutputSchema;
|
||||
readonly handler: (params: ToolInput, context: ToolExecutionContext) => Promise<ToolOutput>;
|
||||
readonly permissionLevel: PermissionLevel;
|
||||
readonly alwaysLoad?: boolean;
|
||||
readonly shouldDefer?: boolean;
|
||||
readonly isMcp?: boolean;
|
||||
readonly prompt?: (options: any) => Promise<string>;
|
||||
readonly features?: string[];
|
||||
readonly metadata?: Record<string, unknown>;
|
||||
readonly searchHint?: string;
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,8 @@
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"typeRoots": ["./src/types", "./node_modules/@types"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
|
||||
368
重构.md
Normal file
368
重构.md
Normal file
@ -0,0 +1,368 @@
|
||||
# TrulyMEM WaterFlow 重构计划
|
||||
|
||||
> **目标**: 将 waterflow 分支重构为标准的 WaterFlow 框架 Tool + Skill,保留原始记忆能力不变。
|
||||
> **约束**: 仅修改 waterflow 分支,不影响 main 分支。
|
||||
|
||||
---
|
||||
|
||||
## 现状分析
|
||||
|
||||
### waterflow 分支当前结构
|
||||
|
||||
```
|
||||
ts/
|
||||
├── bundled-skills/graph_memory/ ← SKILL.md 已存在 ✅
|
||||
│ ├── SKILL.md
|
||||
│ ├── persona/SKILL.md
|
||||
│ └── task/SKILL.md
|
||||
├── src/
|
||||
│ ├── platform/ ← 重复定义,需删除 ❌
|
||||
│ │ ├── index.ts
|
||||
│ │ ├── node.ts
|
||||
│ │ └── types.ts
|
||||
│ ├── runtime/core/
|
||||
│ │ ├── graph_memory/ ← 核心逻辑,保留 ✅
|
||||
│ │ │ ├── types.ts
|
||||
│ │ │ ├── graph_database.ts
|
||||
│ │ │ ├── memory_service.ts
|
||||
│ │ │ └── index.ts
|
||||
│ │ └── tools/
|
||||
│ │ ├── tool_interface.ts ← 重复定义,需删除 ❌
|
||||
│ │ └── builtin/
|
||||
│ │ └── graph_memory_tool.ts ← 需适配 WaterFlow 接口 ✅
|
||||
│ └── types/
|
||||
│ └── sql.js.d.ts ← 保留 ✅
|
||||
└── package.json ← 需调整依赖 ✅
|
||||
```
|
||||
|
||||
### 核心问题
|
||||
|
||||
| 问题 | 说明 | 解决方案 |
|
||||
|------|------|----------|
|
||||
| 重复平台层 | `ts/src/platform/` 与 WaterFlow 重复 | 删除,改用 WaterFlow 的 `getPlatform()` |
|
||||
| 重复 Tool 接口 | `tool_interface.ts` 与 WaterFlow 重复 | 删除,改用 WaterFlow 的类型 |
|
||||
| 存储接口不兼容 | 使用自定义 `StorageOperations` | 改用 WaterFlow 的 `platform.fs`(支持 binary) |
|
||||
| 无注册入口 | 没有将 Tool 注册到 WaterFlow 的方式 | 创建 `registerGraphMemoryTool()` 导出函数 |
|
||||
| SKILL.md 不完整 | 缺少 persona/task 操作的完整描述 | 补充完善 |
|
||||
|
||||
---
|
||||
|
||||
## 重构步骤
|
||||
|
||||
### Phase 1: 清理重复定义
|
||||
|
||||
#### 1.1 删除 `ts/src/platform/` 目录
|
||||
|
||||
```
|
||||
删除: ts/src/platform/index.ts
|
||||
删除: ts/src/platform/node.ts
|
||||
删除: ts/src/platform/types.ts
|
||||
```
|
||||
|
||||
**替换方案**: 所有 `import { getPlatform } from '../../../platform'` 改为从 WaterFlow 导入:
|
||||
```typescript
|
||||
import { getPlatform } from 'waterflow/platform';
|
||||
```
|
||||
|
||||
#### 1.2 删除 `ts/src/runtime/core/tools/tool_interface.ts`
|
||||
|
||||
**替换方案**: 使用 WaterFlow 的 Tool 接口:
|
||||
```typescript
|
||||
import type { Tool, ToolInputSchema, ToolOutput, ToolExecutionContext } from 'waterflow/runtime/core/tools/tool_interface';
|
||||
```
|
||||
|
||||
#### 1.3 更新 `tsconfig.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"target": "esnext",
|
||||
"types": ["node"],
|
||||
"lib": ["esnext"],
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: 适配 WaterFlow 平台层
|
||||
|
||||
#### 2.1 修改 `graph_database.ts` — 存储适配
|
||||
|
||||
**当前**(使用自定义 storage):
|
||||
```typescript
|
||||
import { getPlatform } from '../../../platform';
|
||||
const platform = getPlatform();
|
||||
await platform.storage.save(key, data); // 自定义接口
|
||||
```
|
||||
|
||||
**改为**(使用 WaterFlow fs):
|
||||
```typescript
|
||||
import { getPlatform } from 'waterflow/platform';
|
||||
const platform = getPlatform();
|
||||
await platform.fs.writeFile(dbPath, data, { encoding: 'binary' });
|
||||
const loaded = await platform.fs.readFile(dbPath, { encoding: 'binary' });
|
||||
```
|
||||
|
||||
**具体改动**:
|
||||
- `GraphDatabase.save()` 方法: `storage.save()` → `fs.writeFile(..., { encoding: 'binary' })`
|
||||
- `GraphDatabase.load()` 方法: `storage.load()` → `fs.readFile(..., { encoding: 'binary' })`
|
||||
- `dbPath` 使用 `platform.path.join()` 构建
|
||||
|
||||
#### 2.2 修改 `graph_memory_tool.ts` — Tool 接口适配
|
||||
|
||||
**当前**:
|
||||
```typescript
|
||||
import type { Tool, ... } from '../tool_interface'; // 自定义接口
|
||||
import { getPlatform } from '../../../../platform'; // 自定义平台
|
||||
```
|
||||
|
||||
**改为**:
|
||||
```typescript
|
||||
import type { Tool, ToolInputSchema, ToolOutput, ToolExecutionContext, ToolInput } from 'waterflow/runtime/core/tools/tool_interface';
|
||||
import { getPlatform } from 'waterflow/platform';
|
||||
```
|
||||
|
||||
**新增 WaterFlow 兼容字段**:
|
||||
```typescript
|
||||
export class GraphMemoryTool implements Tool {
|
||||
readonly id = 'builtin:graph_memory';
|
||||
readonly name = 'GraphMemory';
|
||||
readonly description = '...';
|
||||
readonly category: ToolCategory = 'analysis';
|
||||
readonly permissionLevel: PermissionLevel = 'safe';
|
||||
readonly inputSchema: ToolInputSchema = { ... };
|
||||
|
||||
// WaterFlow 新增字段(可选)
|
||||
readonly alwaysLoad = true; // 始终加载完整 schema
|
||||
readonly shouldDefer = false; // 不延迟加载
|
||||
|
||||
async handler(params: ToolInput, context: ToolExecutionContext): Promise<ToolOutput> {
|
||||
// 使用 context.logger 替代 getPlatform().getLogger()
|
||||
const logger = context.logger;
|
||||
// ... 原有逻辑不变
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 创建注册入口
|
||||
|
||||
**新建**: `ts/src/runtime/core/tools/builtin/index.ts`
|
||||
|
||||
```typescript
|
||||
import type { Tool } from 'waterflow/runtime/core/tools/tool_interface';
|
||||
import { GraphMemoryTool, createGraphMemoryTool } from './graph_memory_tool';
|
||||
|
||||
export { GraphMemoryTool, createGraphMemoryTool };
|
||||
|
||||
/**
|
||||
* 注册图记忆工具到 WaterFlow ToolRegistry
|
||||
*
|
||||
* 使用方式:
|
||||
* import { registerGraphMemoryTool } from 'trulymem/tools';
|
||||
* registerGraphMemoryTool(toolRegistry, sessionId);
|
||||
*/
|
||||
export function registerGraphMemoryTool(
|
||||
registry: { register: (tool: Tool) => void },
|
||||
sessionId?: string
|
||||
): void {
|
||||
registry.register(createGraphMemoryTool(sessionId));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: 完善 SKILL.md
|
||||
|
||||
#### 3.1 更新主 SKILL.md
|
||||
|
||||
补充 persona 和 task 操作说明,与 `memory_service.ts` 的实际方法对齐:
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: graph_memory
|
||||
description: 图记忆工具 - 让 AI 拥有真正的长期记忆能力
|
||||
when_to_use: 需要 AI 记住、回忆、管理信息或任务时
|
||||
context: inline
|
||||
allowed_tools:
|
||||
- builtin:graph_memory
|
||||
arguments:
|
||||
- name: action
|
||||
type: string
|
||||
required: true
|
||||
enum: [recall, commit, purge, introspect, persona_update, persona_clear, task_create, task_set_state, task_delete, task_link_info]
|
||||
description: 记忆操作类型
|
||||
- name: params
|
||||
type: object
|
||||
required: true
|
||||
description: 操作参数
|
||||
user_invocable: true
|
||||
---
|
||||
```
|
||||
|
||||
#### 3.2 更新 persona/SKILL.md
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: persona_management
|
||||
description: 人设管理 - 管理 AI 的用户画像和偏好
|
||||
context: inline
|
||||
allowed_tools:
|
||||
- builtin:graph_memory
|
||||
---
|
||||
```
|
||||
|
||||
#### 3.3 更新 task/SKILL.md
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: task_management
|
||||
description: 任务管理 - 管理连续性任务和相关信息
|
||||
context: inline
|
||||
allowed_tools:
|
||||
- builtin:graph_memory
|
||||
---
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: 依赖与构建
|
||||
|
||||
#### 4.1 更新 `package.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "trulymem-waterflow",
|
||||
"version": "1.0.0",
|
||||
"description": "TrulyMEM 图记忆系统 - WaterFlow Skill/Tool 实现",
|
||||
"type": "module",
|
||||
"main": "./dist/runtime/core/tools/builtin/index.js",
|
||||
"exports": {
|
||||
"./tools": "./dist/runtime/core/tools/builtin/index.js",
|
||||
"./graph_memory": "./dist/runtime/core/graph_memory/index.js",
|
||||
"./skills": "./bundled-skills"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"waterflow-ts": ">=0.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"sql.js": "^1.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.2",
|
||||
"typescript": "^5.0.0",
|
||||
"vitest": "^2.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 添加 `sql.js.d.ts` 类型声明
|
||||
|
||||
保留现有 `ts/src/types/sql.js.d.ts`,确保编译通过。
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: 验证
|
||||
|
||||
#### 5.1 编译验证
|
||||
|
||||
```bash
|
||||
cd ts/
|
||||
npm install
|
||||
npm run build
|
||||
# 检查 dist/ 输出
|
||||
```
|
||||
|
||||
#### 5.2 类型检查
|
||||
|
||||
```bash
|
||||
npx tsc --noEmit
|
||||
# 确保无类型错误
|
||||
```
|
||||
|
||||
#### 5.3 集成验证(在 WaterFlow 项目中)
|
||||
|
||||
```typescript
|
||||
// 在 WaterFlow 入口文件中测试
|
||||
import { getPlatform } from './platform';
|
||||
import { initializeToolRegistry } from './runtime/core/tools/builtin';
|
||||
import { registerGraphMemoryTool } from 'trulymem/tools';
|
||||
|
||||
const platform = getPlatform();
|
||||
const registry = initializeToolRegistry(platform);
|
||||
|
||||
// 注册图记忆工具
|
||||
registerGraphMemoryTool(registry, 'test-session');
|
||||
|
||||
// 验证工具已注册
|
||||
const tool = registry.get('builtin:graph_memory');
|
||||
console.log('GraphMemory tool registered:', !!tool);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 文件变更清单
|
||||
|
||||
| 操作 | 文件路径 | 说明 |
|
||||
|------|---------|------|
|
||||
| 🗑️ 删除 | `ts/src/platform/index.ts` | 重复平台层 |
|
||||
| 🗑️ 删除 | `ts/src/platform/node.ts` | 重复平台层 |
|
||||
| 🗑️ 删除 | `ts/src/platform/types.ts` | 重复平台层 |
|
||||
| 🗑️ 删除 | `ts/src/runtime/core/tools/tool_interface.ts` | 重复接口 |
|
||||
| ✏️ 修改 | `ts/src/runtime/core/graph_memory/graph_database.ts` | 改用 WaterFlow fs |
|
||||
| ✏️ 修改 | `ts/src/runtime/core/tools/builtin/graph_memory_tool.ts` | 适配 WaterFlow 接口 |
|
||||
| ✏️ 修改 | `ts/src/runtime/core/graph_memory/index.ts` | 更新导出路径 |
|
||||
| ✏️ 修改 | `ts/bundled-skills/graph_memory/SKILL.md` | 补充完整操作说明 |
|
||||
| ✏️ 修改 | `ts/bundled-skills/graph_memory/persona/SKILL.md` | 完善 |
|
||||
| ✏️ 修改 | `ts/bundled-skills/graph_memory/task/SKILL.md` | 完善 |
|
||||
| ✏️ 修改 | `ts/package.json` | 调整为 peerDependency |
|
||||
| ✏️ 修改 | `ts/tsconfig.json` | 清理配置 |
|
||||
| ➕ 新增 | `ts/src/runtime/core/tools/builtin/index.ts` | 注册入口 |
|
||||
|
||||
---
|
||||
|
||||
## 依赖关系图(重构后)
|
||||
|
||||
```
|
||||
WaterFlow 框架
|
||||
├── Platform (getPlatform)
|
||||
│ ├── fs.readFile/writeFile (binary) ← GraphDatabase 使用
|
||||
│ ├── path.join/resolve ← 路径构建
|
||||
│ ├── getLogger ← 日志
|
||||
│ └── globals.randomUUID ← UUID 生成
|
||||
│
|
||||
├── ToolRegistry
|
||||
│ ├── FRAMEWORK_TOOLS (Agent, ToolSearch)
|
||||
│ ├── Platform Tools (Read, Write, Bash...)
|
||||
│ └── GraphMemoryTool ← 从 trulymem/tools 注册
|
||||
│
|
||||
└── SkillLoader
|
||||
└── bundled-skills/graph_memory/SKILL.md
|
||||
└── allowed_tools: ['builtin:graph_memory']
|
||||
└→ LLM 调用 → ToolExecutor → GraphMemoryTool.handler()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 风险与注意事项
|
||||
|
||||
1. **WaterFlow 版本依赖**: 使用 `peerDependencies` 确保与 WaterFlow 版本兼容
|
||||
2. **二进制存储**: WaterFlow 的 `fs` 支持 `encoding: 'binary'`,已验证可用
|
||||
3. **类型兼容**: 确保 Tool 接口字段与 WaterFlow 完全一致
|
||||
4. **SKILL.md 格式**: YAML frontmatter 必须符合 WaterFlow SkillLoader 解析规则
|
||||
Reference in New Issue
Block a user