ultra-memory 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,328 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ ultra-memory: 操作日志写入脚本
4
+ 每次工具调用、文件变更、命令执行后调用,追加写入 ops.jsonl
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import json
10
+ import re
11
+ import argparse
12
+ from datetime import datetime, timezone
13
+ from pathlib import Path
14
+
15
+ if sys.stdout.encoding != "utf-8":
16
+ sys.stdout.reconfigure(encoding="utf-8")
17
+ if sys.stderr.encoding != "utf-8":
18
+ sys.stderr.reconfigure(encoding="utf-8")
19
+
20
+ ULTRA_MEMORY_HOME = Path(os.environ.get("ULTRA_MEMORY_HOME", Path.home() / ".ultra-memory"))
21
+
22
+ # 敏感词正则(防止记录密码/密钥)
23
+ SENSITIVE_PATTERNS = [
24
+ r'(?i)(password|passwd|pwd)\s*[=:]\s*\S+',
25
+ r'(?i)(api[_-]?key|secret[_-]?key|access[_-]?token)\s*[=:]\s*\S+',
26
+ r'(?i)bearer\s+[A-Za-z0-9\-._~+/]+=*',
27
+ r'[A-Za-z0-9]{32,}', # 长随机字符串(可能是 token)
28
+ ]
29
+
30
+ OP_TYPES = [
31
+ "tool_call", "file_write", "file_read",
32
+ "bash_exec", "reasoning", "user_instruction",
33
+ "decision", "error", "milestone"
34
+ ]
35
+
36
+ # 扩展标签体系:覆盖 setup/code/test/debug/refactor/deploy/config/data/api/ui 十大类
37
+ AUTO_TAGS = {
38
+ # setup — 环境初始化、依赖安装
39
+ "pip install": ["setup", "dependency"],
40
+ "pip3 install": ["setup", "dependency"],
41
+ "npm install": ["setup", "dependency"],
42
+ "yarn add": ["setup", "dependency"],
43
+ "pnpm add": ["setup", "dependency"],
44
+ "conda install": ["setup", "dependency"],
45
+ "apt install": ["setup", "dependency"],
46
+ "brew install": ["setup", "dependency"],
47
+ "初始化": ["setup"],
48
+ "安装": ["setup", "dependency"],
49
+ # code — 代码编写
50
+ "def ": ["code"],
51
+ "class ": ["code"],
52
+ "function ": ["code"],
53
+ "import ": ["code"],
54
+ "async def": ["code"],
55
+ "const ": ["code"],
56
+ "let ": ["code"],
57
+ "写代码": ["code"],
58
+ "实现": ["code"],
59
+ # test — 测试
60
+ "pytest": ["test"],
61
+ "unittest": ["test"],
62
+ "jest ": ["test"],
63
+ "vitest": ["test"],
64
+ "测试": ["test"],
65
+ "test_": ["test"],
66
+ "assert": ["test"],
67
+ "expect(": ["test"],
68
+ # debug — 调试
69
+ "debug": ["debug"],
70
+ "breakpoint": ["debug"],
71
+ "traceback": ["debug"],
72
+ "调试": ["debug"],
73
+ "排查": ["debug"],
74
+ "修复": ["debug"],
75
+ "fix": ["debug"],
76
+ # refactor — 重构
77
+ "refactor": ["refactor"],
78
+ "重构": ["refactor"],
79
+ "rename": ["refactor"],
80
+ "extract": ["refactor"],
81
+ "移动": ["refactor"],
82
+ # deploy — 部署
83
+ "docker": ["deploy"],
84
+ "kubectl": ["deploy"],
85
+ "helm ": ["deploy"],
86
+ "deploy": ["deploy"],
87
+ "部署": ["deploy"],
88
+ "发布": ["deploy"],
89
+ "release": ["deploy"],
90
+ "nginx": ["deploy"],
91
+ # config — 配置
92
+ ".env": ["config"],
93
+ "config": ["config"],
94
+ "配置": ["config"],
95
+ "settings": ["config"],
96
+ "yaml": ["config"],
97
+ "toml": ["config"],
98
+ # data — 数据处理
99
+ "dataframe": ["data"],
100
+ "pandas": ["data"],
101
+ "数据": ["data"],
102
+ "clean_": ["data"],
103
+ "preprocess": ["data"],
104
+ "csv": ["data"],
105
+ "json": ["data"],
106
+ "database": ["data"],
107
+ "sql": ["data"],
108
+ # api — 接口调用
109
+ "requests.": ["api"],
110
+ "fetch(": ["api"],
111
+ "axios": ["api"],
112
+ "curl ": ["api"],
113
+ "http": ["api"],
114
+ "endpoint": ["api"],
115
+ "接口": ["api"],
116
+ # ui — 界面
117
+ "component": ["ui"],
118
+ "vue": ["ui"],
119
+ "react": ["ui"],
120
+ "css": ["ui"],
121
+ "html": ["ui"],
122
+ "界面": ["ui"],
123
+ "页面": ["ui"],
124
+ # vcs — 版本控制
125
+ "git ": ["vcs"],
126
+ "commit": ["vcs"],
127
+ "branch": ["vcs"],
128
+ "merge": ["vcs"],
129
+ # error — 错误
130
+ "error": ["error"],
131
+ "exception": ["error"],
132
+ "traceback": ["error"],
133
+ "failed": ["error"],
134
+ "失败": ["error"],
135
+ "报错": ["error"],
136
+ # milestone — 里程碑
137
+ "✅": ["milestone"],
138
+ "完成": ["milestone"],
139
+ "done": ["milestone"],
140
+ "finished": ["milestone"],
141
+ }
142
+
143
+ # bash 命令意图识别:命令前缀 -> 标签
144
+ BASH_INTENT_MAP = [
145
+ (r'^pip3?\s+install', ["setup", "dependency"]),
146
+ (r'^npm\s+(install|i\b)', ["setup", "dependency"]),
147
+ (r'^yarn\s+add', ["setup", "dependency"]),
148
+ (r'^pytest|^python\s+-m\s+pytest', ["test"]),
149
+ (r'^jest|^npx\s+jest', ["test"]),
150
+ (r'^git\s+commit', ["vcs"]),
151
+ (r'^git\s+push', ["vcs", "deploy"]),
152
+ (r'^git\s+', ["vcs"]),
153
+ (r'^docker\s+', ["deploy"]),
154
+ (r'^kubectl\s+', ["deploy"]),
155
+ (r'^curl\s+', ["api"]),
156
+ (r'^python3?\s+\S+\.py', ["code"]),
157
+ (r'^node\s+', ["code"]),
158
+ (r'^cat\s+|^head\s+|^tail\s+', ["file_read"]),
159
+ (r'^mkdir|^touch|^cp\s+|^mv\s+', ["setup"]),
160
+ (r'^rm\s+', ["error"]), # 删除操作归为需要注意的操作
161
+ ]
162
+
163
+ # 文件扩展名 -> 标签
164
+ FILE_EXT_TAG_MAP = {
165
+ ".py": ["code"],
166
+ ".js": ["code"],
167
+ ".ts": ["code"],
168
+ ".jsx": ["code", "ui"],
169
+ ".tsx": ["code", "ui"],
170
+ ".vue": ["ui"],
171
+ ".html": ["ui"],
172
+ ".css": ["ui"],
173
+ ".scss": ["ui"],
174
+ ".less": ["ui"],
175
+ ".json": ["config"],
176
+ ".yaml": ["config"],
177
+ ".yml": ["config"],
178
+ ".toml": ["config"],
179
+ ".env": ["config"],
180
+ ".md": ["data"],
181
+ ".sql": ["data"],
182
+ ".csv": ["data"],
183
+ ".parquet": ["data"],
184
+ ".sh": ["deploy"],
185
+ ".dockerfile": ["deploy"],
186
+ ".tf": ["deploy"],
187
+ "dockerfile": ["deploy"],
188
+ ".test.py": ["test"],
189
+ ".spec.ts": ["test"],
190
+ ".spec.js": ["test"],
191
+ "_test.go": ["test"],
192
+ }
193
+
194
+
195
+ def sanitize(text: str) -> str:
196
+ """过滤敏感信息"""
197
+ if not text:
198
+ return text
199
+ for pattern in SENSITIVE_PATTERNS:
200
+ text = re.sub(pattern, "[REDACTED]", text)
201
+ return text
202
+
203
+
204
+ def auto_tag(summary: str, detail: dict, op_type: str = "") -> list[str]:
205
+ """根据内容自动打标签(关键词 + bash命令意图 + 文件扩展名)"""
206
+ tags = set()
207
+ combined = summary.lower() + " " + json.dumps(detail, ensure_ascii=False).lower()
208
+
209
+ # 1. 关键词匹配(扩展标签体系)
210
+ for keyword, kw_tags in AUTO_TAGS.items():
211
+ if keyword.lower() in combined:
212
+ tags.update(kw_tags)
213
+
214
+ # 2. bash_exec 类型:解析命令意图
215
+ if op_type == "bash_exec":
216
+ cmd = detail.get("cmd", summary).strip()
217
+ for pattern, intent_tags in BASH_INTENT_MAP:
218
+ if re.match(pattern, cmd, re.IGNORECASE):
219
+ tags.update(intent_tags)
220
+ break
221
+
222
+ # 3. file_write 类型:根据文件扩展名自动分类
223
+ if op_type == "file_write":
224
+ file_path = detail.get("path", "")
225
+ if file_path:
226
+ lower_path = file_path.lower()
227
+ # 先检查特殊全名(如 Dockerfile)
228
+ base = lower_path.split("/")[-1].split("\\")[-1]
229
+ if base in FILE_EXT_TAG_MAP:
230
+ tags.update(FILE_EXT_TAG_MAP[base])
231
+ else:
232
+ # 检查复合扩展名(如 .test.py)
233
+ for ext, ext_tags in FILE_EXT_TAG_MAP.items():
234
+ if lower_path.endswith(ext):
235
+ tags.update(ext_tags)
236
+ break
237
+
238
+ return list(tags)
239
+
240
+
241
+ def log_op(
242
+ session_id: str,
243
+ op_type: str,
244
+ summary: str,
245
+ detail: dict = None,
246
+ tags: list = None,
247
+ ):
248
+ session_dir = ULTRA_MEMORY_HOME / "sessions" / session_id
249
+ if not session_dir.exists():
250
+ print(f"[ultra-memory] ⚠️ 会话不存在: {session_id},跳过记录")
251
+ return
252
+
253
+ ops_file = session_dir / "ops.jsonl"
254
+ meta_file = session_dir / "meta.json"
255
+
256
+ # 读取当前序号
257
+ seq = 0
258
+ if meta_file.exists():
259
+ with open(meta_file, encoding="utf-8") as f:
260
+ meta = json.load(f)
261
+ seq = meta.get("op_count", 0) + 1
262
+ else:
263
+ meta = {}
264
+
265
+ detail = detail or {}
266
+ summary = sanitize(summary)
267
+ detail = json.loads(sanitize(json.dumps(detail, ensure_ascii=False)))
268
+
269
+ auto_tags = auto_tag(summary, detail, op_type)
270
+ all_tags = list(set((tags or []) + auto_tags))
271
+
272
+ entry = {
273
+ "ts": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
274
+ "seq": seq,
275
+ "type": op_type,
276
+ "summary": summary[:200], # 摘要限长
277
+ "detail": detail,
278
+ "tags": all_tags,
279
+ "compressed": False,
280
+ }
281
+
282
+ # 追加写入(append-only,永不覆盖)
283
+ with open(ops_file, "a", encoding="utf-8") as f:
284
+ f.write(json.dumps(entry, ensure_ascii=False) + "\n")
285
+
286
+ # 更新 meta
287
+ meta["op_count"] = seq
288
+ meta["last_op_at"] = entry["ts"]
289
+ if op_type == "milestone":
290
+ meta["last_milestone"] = summary
291
+ with open(meta_file, "w", encoding="utf-8") as f:
292
+ json.dump(meta, f, ensure_ascii=False, indent=2)
293
+
294
+ # 自动提取结构化实体(写入 semantic/entities.jsonl)
295
+ try:
296
+ import sys as _sys
297
+ _scripts_dir = Path(__file__).parent
298
+ if str(_scripts_dir) not in _sys.path:
299
+ _sys.path.insert(0, str(_scripts_dir))
300
+ from extract_entities import extract_and_store
301
+ extract_and_store(session_id, dict(entry))
302
+ except Exception:
303
+ pass # 实体提取失败不影响主流程
304
+
305
+ # 检查是否需要触发压缩
306
+ should_compress = False
307
+ if seq > 0 and seq % 50 == 0:
308
+ should_compress = True
309
+ print(f"[ultra-memory] ⚡ 操作日志已达 {seq} 条,建议触发摘要压缩")
310
+ print(f"[ultra-memory] 运行: python3 scripts/summarize.py --session {session_id}")
311
+
312
+ print(f"[ultra-memory] 📝 [{seq}] {op_type}: {summary[:60]}")
313
+ if should_compress:
314
+ print("[ultra-memory] COMPRESS_SUGGESTED")
315
+
316
+
317
+ if __name__ == "__main__":
318
+ parser = argparse.ArgumentParser(description="记录操作日志")
319
+ parser.add_argument("--session", required=True, help="会话 ID")
320
+ parser.add_argument("--type", required=True, choices=OP_TYPES, dest="op_type")
321
+ parser.add_argument("--summary", required=True, help="操作摘要(中英文均可)")
322
+ parser.add_argument("--detail", default="{}", help="详情 JSON 字符串")
323
+ parser.add_argument("--tags", default="", help="逗号分隔的标签")
324
+ args = parser.parse_args()
325
+
326
+ detail = json.loads(args.detail)
327
+ tags = [t.strip() for t in args.tags.split(",") if t.strip()]
328
+ log_op(args.session, args.op_type, args.summary, detail, tags)
@@ -0,0 +1,341 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ultra-memory MCP Server
4
+ * 将 ultra-memory 封装为标准 MCP 工具,供 Claude Code / OpenClaw 调用
5
+ *
6
+ * 提供 9 个工具:
7
+ * memory_init — 初始化会话
8
+ * memory_status — 查询当前会话状态
9
+ * memory_log — 记录操作(自动提取实体)
10
+ * memory_recall — 四层统一检索(ops/summary/semantic/entity)
11
+ * memory_summarize — 触发摘要压缩(含元压缩)
12
+ * memory_restore — 恢复上次会话
13
+ * memory_profile — 读写用户画像
14
+ * memory_entities — 查询结构化实体索引(新增)
15
+ * memory_extract_entities — 全量重提取实体(新增)
16
+ */
17
+
18
+ const { execSync, execFileSync } = require("child_process");
19
+ const path = require("path");
20
+ const fs = require("fs");
21
+ const os = require("os");
22
+ const readline = require("readline");
23
+
24
+ const SCRIPTS_DIR = path.join(__dirname);
25
+ const ULTRA_MEMORY_HOME =
26
+ process.env.ULTRA_MEMORY_HOME || path.join(os.homedir(), ".ultra-memory");
27
+
28
+ function runScript(script, args = []) {
29
+ const scriptPath = path.join(SCRIPTS_DIR, script);
30
+ try {
31
+ const result = execFileSync(
32
+ "python3",
33
+ [scriptPath, ...args],
34
+ { encoding: "utf-8", timeout: 15000 }
35
+ );
36
+ return { success: true, output: result.trim() };
37
+ } catch (e) {
38
+ return { success: false, output: e.stdout || e.message };
39
+ }
40
+ }
41
+
42
+ // MCP 工具定义(共 7 个)
43
+ const TOOLS = [
44
+ {
45
+ name: "memory_init",
46
+ description: "初始化 ultra-memory 会话。Claude Code 首次启动或新会话开始时调用,创建三层记忆结构并注入历史上下文",
47
+ inputSchema: {
48
+ type: "object",
49
+ properties: {
50
+ project: { type: "string", description: "项目名称", default: "default" },
51
+ resume: { type: "boolean", description: "尝试恢复最近会话", default: false }
52
+ },
53
+ required: []
54
+ }
55
+ },
56
+ {
57
+ name: "memory_status",
58
+ description: "查询当前会话状态:操作数、最后里程碑、context 压力级别(low/medium/high/critical)",
59
+ inputSchema: {
60
+ type: "object",
61
+ properties: {
62
+ session_id: { type: "string", description: "会话 ID" }
63
+ },
64
+ required: ["session_id"]
65
+ }
66
+ },
67
+ {
68
+ name: "memory_log",
69
+ description: "记录一条操作到当前会话的操作日志 (Layer 1)",
70
+ inputSchema: {
71
+ type: "object",
72
+ properties: {
73
+ session_id: { type: "string", description: "当前会话 ID" },
74
+ op_type: {
75
+ type: "string",
76
+ enum: ["tool_call", "file_write", "file_read", "bash_exec",
77
+ "reasoning", "user_instruction", "decision", "error", "milestone"],
78
+ description: "操作类型"
79
+ },
80
+ summary: { type: "string", description: "操作摘要(50字内)" },
81
+ detail: { type: "object", description: "详细信息(可选)" },
82
+ tags: { type: "array", items: { type: "string" }, description: "标签列表" }
83
+ },
84
+ required: ["session_id", "op_type", "summary"]
85
+ }
86
+ },
87
+ {
88
+ name: "memory_recall",
89
+ description: "从三层记忆中检索与查询相关的操作历史",
90
+ inputSchema: {
91
+ type: "object",
92
+ properties: {
93
+ session_id: { type: "string", description: "当前会话 ID" },
94
+ query: { type: "string", description: "检索关键词(中英文均可)" },
95
+ top_k: { type: "number", description: "返回结果数量,默认 5", default: 5 }
96
+ },
97
+ required: ["session_id", "query"]
98
+ }
99
+ },
100
+ {
101
+ name: "memory_summarize",
102
+ description: "触发当前会话的摘要压缩(将 ops 日志压缩为 summary.md)",
103
+ inputSchema: {
104
+ type: "object",
105
+ properties: {
106
+ session_id: { type: "string", description: "当前会话 ID" },
107
+ force: { type: "boolean", description: "强制压缩,即使条数不足", default: false }
108
+ },
109
+ required: ["session_id"]
110
+ }
111
+ },
112
+ {
113
+ name: "memory_restore",
114
+ description: "恢复指定项目的上次会话上下文,用于跨天继续任务",
115
+ inputSchema: {
116
+ type: "object",
117
+ properties: {
118
+ project: { type: "string", description: "项目名称", default: "default" },
119
+ verbose: { type: "boolean", description: "显示详细操作记录", default: false }
120
+ },
121
+ required: []
122
+ }
123
+ },
124
+ {
125
+ name: "memory_profile",
126
+ description: "读写用户画像(技术栈、偏好、项目列表)",
127
+ inputSchema: {
128
+ type: "object",
129
+ properties: {
130
+ action: { type: "string", enum: ["read", "update"], description: "操作类型" },
131
+ updates: {
132
+ type: "object",
133
+ description: "要更新的字段(action=update 时必填)"
134
+ }
135
+ },
136
+ required: ["action"]
137
+ }
138
+ },
139
+ {
140
+ name: "memory_entities",
141
+ description: "查询结构化实体索引(函数/文件/依赖/决策/错误),精确回答「用过哪些函数」「装了哪些包」「做了哪些决策」等结构化问题",
142
+ inputSchema: {
143
+ type: "object",
144
+ properties: {
145
+ entity_type: {
146
+ type: "string",
147
+ enum: ["function", "file", "dependency", "decision", "error", "class", "all"],
148
+ description: "实体类型过滤(all=不过滤)",
149
+ default: "all"
150
+ },
151
+ query: { type: "string", description: "搜索关键词(可选,空则返回全部该类型)", default: "" },
152
+ top_k: { type: "number", description: "返回数量", default: 10 }
153
+ },
154
+ required: []
155
+ }
156
+ },
157
+ {
158
+ name: "memory_extract_entities",
159
+ description: "对指定会话的 ops.jsonl 全量重新提取结构化实体(修复或初次建立实体索引时使用)",
160
+ inputSchema: {
161
+ type: "object",
162
+ properties: {
163
+ session_id: { type: "string", description: "会话 ID" }
164
+ },
165
+ required: ["session_id"]
166
+ }
167
+ }
168
+ ];
169
+
170
+ // 工具执行逻辑
171
+ function executeTool(name, input) {
172
+ switch (name) {
173
+ case "memory_init": {
174
+ const args = ["--project", input.project || "default"];
175
+ if (input.resume) args.push("--resume");
176
+ return runScript("init.py", args);
177
+ }
178
+ case "memory_status": {
179
+ // 读取 meta.json 并调用 check_context_pressure
180
+ const sessionId = input.session_id;
181
+ const metaPath = path.join(ULTRA_MEMORY_HOME, "sessions", sessionId, "meta.json");
182
+ let meta = {};
183
+ try {
184
+ meta = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
185
+ } catch {
186
+ return { success: false, output: `会话不存在: ${sessionId}` };
187
+ }
188
+ // 调用 init.py --check-pressure 获取压力级别
189
+ const pressureResult = runScript("init.py", ["--check-pressure", sessionId]);
190
+ const statusLines = [
191
+ `会话 ID: ${sessionId}`,
192
+ `项目: ${meta.project || "default"}`,
193
+ `操作数: ${meta.op_count || 0}`,
194
+ `最后里程碑: ${meta.last_milestone || "(无)"}`,
195
+ `上次压缩: ${meta.last_summary_at || "(未压缩)"}`,
196
+ pressureResult.output,
197
+ ];
198
+ return { success: true, output: statusLines.join("\n") };
199
+ }
200
+ case "memory_log": {
201
+ const args = [
202
+ "--session", input.session_id,
203
+ "--type", input.op_type,
204
+ "--summary", input.summary,
205
+ "--detail", JSON.stringify(input.detail || {}),
206
+ "--tags", (input.tags || []).join(",")
207
+ ];
208
+ return runScript("log_op.py", args);
209
+ }
210
+ case "memory_recall": {
211
+ const args = [
212
+ "--session", input.session_id,
213
+ "--query", input.query,
214
+ "--top-k", String(input.top_k || 5)
215
+ ];
216
+ return runScript("recall.py", args);
217
+ }
218
+ case "memory_summarize": {
219
+ const args = ["--session", input.session_id];
220
+ if (input.force) args.push("--force");
221
+ return runScript("summarize.py", args);
222
+ }
223
+ case "memory_restore": {
224
+ const args = ["--project", input.project || "default"];
225
+ if (input.verbose) args.push("--verbose");
226
+ return runScript("restore.py", args);
227
+ }
228
+ case "memory_profile": {
229
+ const profilePath = path.join(ULTRA_MEMORY_HOME, "semantic", "user_profile.json");
230
+ if (input.action === "read") {
231
+ try {
232
+ const content = fs.readFileSync(profilePath, "utf-8");
233
+ return { success: true, output: content };
234
+ } catch {
235
+ return { success: true, output: "{}" };
236
+ }
237
+ } else if (input.action === "update") {
238
+ let profile = {};
239
+ try { profile = JSON.parse(fs.readFileSync(profilePath, "utf-8")); } catch {}
240
+ Object.assign(profile, input.updates || {});
241
+ profile.last_updated = new Date().toISOString().slice(0, 10);
242
+ fs.mkdirSync(path.dirname(profilePath), { recursive: true });
243
+ fs.writeFileSync(profilePath, JSON.stringify(profile, null, 2));
244
+ return { success: true, output: "用户画像已更新" };
245
+ }
246
+ return { success: false, output: "未知操作" };
247
+ }
248
+ case "memory_entities": {
249
+ // 直接读取 entities.jsonl,按类型过滤后返回
250
+ const entitiesPath = path.join(ULTRA_MEMORY_HOME, "semantic", "entities.jsonl");
251
+ const targetType = (input.entity_type || "all").toLowerCase();
252
+ const query = (input.query || "").toLowerCase();
253
+ const topK = input.top_k || 10;
254
+ try {
255
+ const lines = fs.readFileSync(entitiesPath, "utf-8").split("\n").filter(Boolean);
256
+ const entities = lines.map(l => { try { return JSON.parse(l); } catch { return null; } })
257
+ .filter(Boolean);
258
+ let filtered = entities;
259
+ if (targetType !== "all") {
260
+ filtered = filtered.filter(e => e.entity_type === targetType);
261
+ }
262
+ if (query) {
263
+ filtered = filtered.filter(e =>
264
+ (e.name || "").toLowerCase().includes(query) ||
265
+ (e.context || "").toLowerCase().includes(query)
266
+ );
267
+ }
268
+ // 去重(同类型同名保留最新)
269
+ const seen = new Set();
270
+ const deduped = [];
271
+ for (const e of filtered.reverse()) {
272
+ const key = `${e.entity_type}:${e.name}`;
273
+ if (!seen.has(key)) { seen.add(key); deduped.push(e); }
274
+ }
275
+ const result = deduped.slice(0, topK);
276
+ const lines_out = result.map(e => {
277
+ let line = `[${e.entity_type}] ${e.name}`;
278
+ if (e.rationale) line += ` 依据: ${e.rationale}`;
279
+ if (e.manager) line += ` [${e.manager}]`;
280
+ if (e.context) line += `\n 来源: ${e.context}`;
281
+ return line;
282
+ });
283
+ const output = lines_out.length > 0
284
+ ? `找到 ${lines_out.length} 个实体:\n\n${lines_out.join("\n")}`
285
+ : "未找到匹配实体";
286
+ return { success: true, output };
287
+ } catch (e) {
288
+ return { success: true, output: "实体索引尚未建立,请先运行 memory_log 记录操作" };
289
+ }
290
+ }
291
+ case "memory_extract_entities": {
292
+ return runScript("extract_entities.py", ["--session", input.session_id, "--all"]);
293
+ }
294
+ default:
295
+ return { success: false, output: `未知工具: ${name}` };
296
+ }
297
+ }
298
+
299
+ // MCP stdio 协议处理
300
+ const rl = readline.createInterface({ input: process.stdin });
301
+
302
+ rl.on("line", (line) => {
303
+ let request;
304
+ try {
305
+ request = JSON.parse(line);
306
+ } catch {
307
+ return;
308
+ }
309
+
310
+ const { id, method, params } = request;
311
+
312
+ let response;
313
+ if (method === "initialize") {
314
+ response = {
315
+ id,
316
+ result: {
317
+ protocolVersion: "2024-11-05",
318
+ capabilities: { tools: {} },
319
+ serverInfo: { name: "ultra-memory", version: "3.0.0" }
320
+ }
321
+ };
322
+ } else if (method === "tools/list") {
323
+ response = { id, result: { tools: TOOLS } };
324
+ } else if (method === "tools/call") {
325
+ const { name, arguments: args } = params;
326
+ const result = executeTool(name, args || {});
327
+ response = {
328
+ id,
329
+ result: {
330
+ content: [{ type: "text", text: result.output }],
331
+ isError: !result.success
332
+ }
333
+ };
334
+ } else {
335
+ response = { id, error: { code: -32601, message: "Method not found" } };
336
+ }
337
+
338
+ process.stdout.write(JSON.stringify(response) + "\n");
339
+ });
340
+
341
+ process.stderr.write("[ultra-memory] MCP Server 已启动 (stdio 模式)\n");