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:
root
2026-04-16 11:48:58 +08:00
parent e8c275c8f8
commit 13e3c662ac
17 changed files with 2822 additions and 356 deletions

139
README.md
View File

@ -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');
// 继续组装...
```
### 使用 SkillAI 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 列表

View File

@ -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
View 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.fsbinary
- [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/`

View File

@ -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`: 检索深度(默认 2BFS 层数)
- `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

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

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

View File

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

View File

@ -1,3 +1,4 @@
export * from './types';
export * from './config';
export * from './graph_database';
export * from './memory_service';

View File

@ -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 = '进行中' | '已完成' | '已暂停' | '已取消';

View File

@ -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: {

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

View File

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

View File

@ -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
View 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 解析规则