mindforge-memory 6.4.1__py3-none-any.whl

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.
Files changed (64) hide show
  1. MindForge.py +20 -0
  2. mindforge/__init__.py +111 -0
  3. mindforge/adapters/__init__.py +18 -0
  4. mindforge/adapters/claude_adapter.py +84 -0
  5. mindforge/adapters/crewai_memory.py +76 -0
  6. mindforge/adapters/generic_api.py +183 -0
  7. mindforge/adapters/langgraph_store.py +463 -0
  8. mindforge/adapters/openclaw_adapter.py +91 -0
  9. mindforge/api/__init__.py +1 -0
  10. mindforge/api/server.py +1567 -0
  11. mindforge/cli/__init__.py +1 -0
  12. mindforge/cli/main.py +13607 -0
  13. mindforge/core/__init__.py +37 -0
  14. mindforge/core/embedding.py +847 -0
  15. mindforge/core/encryption.py +597 -0
  16. mindforge/core/indexer.py +363 -0
  17. mindforge/core/mindforge.py +6298 -0
  18. mindforge/core/paths.py +43 -0
  19. mindforge/core/query.py +501 -0
  20. mindforge/core/storage.py +18656 -0
  21. mindforge/core/types.py +314 -0
  22. mindforge/core/version.py +56 -0
  23. mindforge/daemon/__init__.py +0 -0
  24. mindforge/daemon/server.py +277 -0
  25. mindforge/mcp/__init__.py +4 -0
  26. mindforge/mcp/server.py +2559 -0
  27. mindforge/modules/__init__.py +138 -0
  28. mindforge/modules/categorizer.py +151 -0
  29. mindforge/modules/conflict_detector.py +356 -0
  30. mindforge/modules/connectors.py +331 -0
  31. mindforge/modules/context_window.py +337 -0
  32. mindforge/modules/event_bus.py +572 -0
  33. mindforge/modules/evolution.py +210 -0
  34. mindforge/modules/experience.py +402 -0
  35. mindforge/modules/federated.py +722 -0
  36. mindforge/modules/federated_acl.py +393 -0
  37. mindforge/modules/follow_up.py +315 -0
  38. mindforge/modules/hybrid_search.py +565 -0
  39. mindforge/modules/importer.py +374 -0
  40. mindforge/modules/integrator.py +196 -0
  41. mindforge/modules/intent_router.py +359 -0
  42. mindforge/modules/knowledge_graph.py +395 -0
  43. mindforge/modules/memory_compressor.py +592 -0
  44. mindforge/modules/memory_decay.py +474 -0
  45. mindforge/modules/memory_passport.py +250 -0
  46. mindforge/modules/multi_agent.py +534 -0
  47. mindforge/modules/multimodal.py +304 -0
  48. mindforge/modules/outcome_learning.py +186 -0
  49. mindforge/modules/personality.py +275 -0
  50. mindforge/modules/principled_forgetting.py +258 -0
  51. mindforge/modules/privacy.py +736 -0
  52. mindforge/modules/recall.py +263 -0
  53. mindforge/modules/session_focus.py +305 -0
  54. mindforge/modules/share_conflict.py +521 -0
  55. mindforge/modules/skill_extractor.py +340 -0
  56. mindforge/modules/skill_pack.py +205 -0
  57. mindforge/modules/timeline_viz.py +206 -0
  58. mindforge/modules/v6.py +931 -0
  59. mindforge_memory-6.4.1.dist-info/METADATA +281 -0
  60. mindforge_memory-6.4.1.dist-info/RECORD +64 -0
  61. mindforge_memory-6.4.1.dist-info/WHEEL +5 -0
  62. mindforge_memory-6.4.1.dist-info/entry_points.txt +3 -0
  63. mindforge_memory-6.4.1.dist-info/licenses/LICENSE +21 -0
  64. mindforge_memory-6.4.1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,314 @@
1
+ """
2
+ MindForge v5.0 类型定义
3
+ """
4
+
5
+ from enum import Enum
6
+ from dataclasses import dataclass, field
7
+ from typing import List, Optional, Dict, Any
8
+
9
+
10
+ class PrivacyLevel(Enum):
11
+ """隐私级别"""
12
+ PUBLIC = "PUBLIC"
13
+ INTERNAL = "INTERNAL"
14
+ PRIVATE = "PRIVATE"
15
+ STRICT = "STRICT"
16
+
17
+ @classmethod
18
+ def from_string(cls, value: str) -> "PrivacyLevel":
19
+ try:
20
+ return cls(value.upper())
21
+ except ValueError:
22
+ return cls.INTERNAL
23
+
24
+ def to_int(self) -> int:
25
+ mapping = {"PUBLIC": 0, "INTERNAL": 1, "PRIVATE": 2, "STRICT": 3}
26
+ return mapping[self.value]
27
+
28
+
29
+ class Importance(Enum):
30
+ """重要性级别"""
31
+ LOW = "LOW"
32
+ MEDIUM = "MEDIUM"
33
+ HIGH = "HIGH"
34
+ CRITICAL = "CRITICAL"
35
+
36
+ @classmethod
37
+ def from_string(cls, value: str) -> "Importance":
38
+ try:
39
+ return cls(value.upper())
40
+ except ValueError:
41
+ return cls.MEDIUM
42
+
43
+ def to_int(self) -> int:
44
+ mapping = {"LOW": 0, "MEDIUM": 1, "HIGH": 2, "CRITICAL": 3}
45
+ return mapping[self.value]
46
+
47
+
48
+ class MemoryType(Enum):
49
+ """记忆类型(多模态)"""
50
+ TEXT = "text"
51
+ IMAGE = "image"
52
+ AUDIO = "audio"
53
+ CODE = "code"
54
+ STRUCTURED = "structured"
55
+ MULTIMODAL = "multimodal"
56
+
57
+ @classmethod
58
+ def from_string(cls, value: str) -> "MemoryType":
59
+ try:
60
+ return cls(value.lower())
61
+ except ValueError:
62
+ return cls.TEXT
63
+
64
+
65
+ class MemoryLayer(Enum):
66
+ """记忆层级(四层架构)"""
67
+ SENSORY = "sensory"
68
+ SHORT_TERM = "short_term"
69
+ LONG_TERM = "long_term"
70
+ PERMANENT = "permanent"
71
+
72
+ @classmethod
73
+ def from_string(cls, value: str) -> "MemoryLayer":
74
+ try:
75
+ return cls(value.lower())
76
+ except ValueError:
77
+ return cls.SHORT_TERM
78
+
79
+ def to_int(self) -> int:
80
+ mapping = {"sensory": 0, "short_term": 1, "long_term": 2, "permanent": 3}
81
+ return mapping[self.value]
82
+
83
+
84
+ @dataclass
85
+ class MemoryConfig:
86
+ """记忆系统配置"""
87
+ db_path: str = "./data/memory.db"
88
+ key_file: str = "./data/.key"
89
+ encrypted: bool = True
90
+ default_privacy: PrivacyLevel = PrivacyLevel.INTERNAL
91
+ default_importance: Importance = Importance.MEDIUM
92
+ default_layer: MemoryLayer = MemoryLayer.SHORT_TERM
93
+ auto_consolidate: bool = True
94
+ # v5.7.9:add/update 后自动抽取实体/关系入知识图谱(默认关闭,显式开启)
95
+ auto_extract_graph: bool = False
96
+ consolidate_interval_hours: int = 24
97
+ forget_curve_enabled: bool = True
98
+ vector_dim: int = 384
99
+ max_short_term_items: int = 100
100
+ sensory_buffer_size: int = 50
101
+ sensory_retention_seconds: int = 30
102
+ # v5.4.9 新增:Webhook / 事件通知配置
103
+ webhooks: Optional[List[Dict[str, Any]]] = None
104
+ webhook_secret: str = ""
105
+ event_bus_enabled: bool = True
106
+
107
+
108
+ class DramaGenre(Enum):
109
+ """短剧类型"""
110
+ ROMANCE = "romance"
111
+ SUSPENSE = "suspense"
112
+ COMEDY = "comedy"
113
+ ACTION = "action"
114
+ HORROR = "horror"
115
+ SCIFI = "scifi"
116
+ FANTASY = "fantasy"
117
+ DRAMA = "drama"
118
+ OTHER = "other"
119
+
120
+ @classmethod
121
+ def from_string(cls, value: str) -> "DramaGenre":
122
+ try:
123
+ return cls(value.lower())
124
+ except ValueError:
125
+ return cls.OTHER
126
+
127
+
128
+ class DramaStatus(Enum):
129
+ """短剧状态"""
130
+ WATCHING = "watching"
131
+ COMPLETED = "completed"
132
+ PLANNED = "planned"
133
+ DROPPED = "dropped"
134
+
135
+ @classmethod
136
+ def from_string(cls, value: str) -> "DramaStatus":
137
+ try:
138
+ return cls(value.lower())
139
+ except ValueError:
140
+ return cls.PLANNED
141
+
142
+
143
+ @dataclass
144
+ class DramaSeries:
145
+ """短剧系列"""
146
+ id: str
147
+ title: str
148
+ genre: DramaGenre = DramaGenre.OTHER
149
+ total_episodes: int = 0
150
+ current_episode: int = 0
151
+ status: DramaStatus = DramaStatus.PLANNED
152
+ platform: str = ""
153
+ rating: float = 0.0
154
+ description: str = ""
155
+ tags: List[str] = field(default_factory=list)
156
+ cover_url: str = ""
157
+ metadata: Dict[str, Any] = field(default_factory=dict)
158
+ created_at: float = 0.0
159
+ updated_at: float = 0.0
160
+ last_watched_at: float = 0.0
161
+
162
+ def to_dict(self) -> dict:
163
+ return {
164
+ "id": self.id,
165
+ "title": self.title,
166
+ "genre": self.genre.value,
167
+ "total_episodes": self.total_episodes,
168
+ "current_episode": self.current_episode,
169
+ "status": self.status.value,
170
+ "platform": self.platform,
171
+ "rating": self.rating,
172
+ "description": self.description,
173
+ "tags": self.tags,
174
+ "cover_url": self.cover_url,
175
+ "metadata": self.metadata,
176
+ "created_at": self.created_at,
177
+ "updated_at": self.updated_at,
178
+ "last_watched_at": self.last_watched_at,
179
+ }
180
+
181
+
182
+ @dataclass
183
+ class DramaScene:
184
+ """短剧场次"""
185
+ id: str
186
+ drama_id: str
187
+ episode: int
188
+ scene_number: int
189
+ title: str
190
+ content: str = ""
191
+ location: str = ""
192
+ time_of_day: str = ""
193
+ tags: List[str] = field(default_factory=list)
194
+ metadata: Dict[str, Any] = field(default_factory=dict)
195
+ created_at: float = 0.0
196
+
197
+ def to_dict(self) -> dict:
198
+ return {
199
+ "id": self.id,
200
+ "drama_id": self.drama_id,
201
+ "episode": self.episode,
202
+ "scene_number": self.scene_number,
203
+ "title": self.title,
204
+ "content": self.content,
205
+ "location": self.location,
206
+ "time_of_day": self.time_of_day,
207
+ "tags": self.tags,
208
+ "metadata": self.metadata,
209
+ "created_at": self.created_at,
210
+ }
211
+
212
+
213
+ @dataclass
214
+ class DramaCharacter:
215
+ """短剧角色"""
216
+ id: str
217
+ drama_id: str
218
+ name: str
219
+ role: str = "supporting"
220
+ actor: str = ""
221
+ description: str = ""
222
+ personality: str = ""
223
+ avatar_url: str = ""
224
+ tags: List[str] = field(default_factory=list)
225
+ metadata: Dict[str, Any] = field(default_factory=dict)
226
+ created_at: float = 0.0
227
+
228
+ def to_dict(self) -> dict:
229
+ return {
230
+ "id": self.id,
231
+ "drama_id": self.drama_id,
232
+ "name": self.name,
233
+ "role": self.role,
234
+ "actor": self.actor,
235
+ "description": self.description,
236
+ "personality": self.personality,
237
+ "avatar_url": self.avatar_url,
238
+ "tags": self.tags,
239
+ "metadata": self.metadata,
240
+ "created_at": self.created_at,
241
+ }
242
+
243
+
244
+ @dataclass
245
+ class DramaLine:
246
+ """短剧台词(可关联为记忆)"""
247
+ id: str
248
+ drama_id: str
249
+ line_text: str
250
+ scene_id: str = ""
251
+ character_id: str = ""
252
+ character_name: str = ""
253
+ context: str = ""
254
+ episode: int = 0
255
+ timestamp: str = ""
256
+ is_classic: bool = False
257
+ memory_id: str = ""
258
+ tags: List[str] = field(default_factory=list)
259
+ metadata: Dict[str, Any] = field(default_factory=dict)
260
+ created_at: float = 0.0
261
+
262
+ def to_dict(self) -> dict:
263
+ return {
264
+ "id": self.id,
265
+ "drama_id": self.drama_id,
266
+ "scene_id": self.scene_id,
267
+ "character_id": self.character_id,
268
+ "character_name": self.character_name,
269
+ "line_text": self.line_text,
270
+ "context": self.context,
271
+ "episode": self.episode,
272
+ "timestamp": self.timestamp,
273
+ "is_classic": self.is_classic,
274
+ "memory_id": self.memory_id,
275
+ "tags": self.tags,
276
+ "metadata": self.metadata,
277
+ "created_at": self.created_at,
278
+ }
279
+
280
+
281
+ # ---------------------------------------------------------------------------
282
+ # 入参归一助手(v6.1.2)
283
+ # ---------------------------------------------------------------------------
284
+
285
+ def coerce_memory_id(value: Any) -> Optional[str]:
286
+ """把 memory_id 入参归一为字符串,识别失败返回 None。
287
+
288
+ 为什么需要它:`mf.add()` 返回的是 `MemoryEntry` 对象,用户很自然地把它原样
289
+ 传回其它接口(如 `list_evolutions(m.add(...))`)。历史上各处写的是
290
+ `if not memory_id or len(memory_id) > 128`,对对象求 `len()` 直接抛
291
+ `TypeError: object of type 'MemoryEntry' has no len()`——对外表现为崩溃。
292
+ v6.1.1 已在三个入口(learn_from_outcome / follow_up_create / forget_explain)
293
+ 修过这类问题,但每个模块各留了一份私有副本;v6.1.2 把规则收敛到此处的唯一
294
+ 实现,供 storage 的演变/版本接口共用。
295
+
296
+ 接受:
297
+ - `str`:原样返回;
298
+ - 带 `id` 字符串属性的对象(`MemoryEntry`、`ExperienceCase` 等):返回其 `id`。
299
+ 其余(int / list / dict / None / 空串 / 超长)一律返回 None,由调用方按各自的
300
+ 错误约定处理(返回 error dict 或空列表),不再抛 TypeError。
301
+ """
302
+ if isinstance(value, str):
303
+ candidate = value
304
+ else:
305
+ try:
306
+ inner = getattr(value, "id", None)
307
+ except Exception: # noqa: BLE001 病态对象(属性访问抛异常)与非法类型同等对待
308
+ return None
309
+ if not isinstance(inner, str):
310
+ return None
311
+ candidate = inner
312
+ if not candidate or len(candidate) > 128:
313
+ return None
314
+ return candidate
@@ -0,0 +1,56 @@
1
+ """
2
+ MindForge 版本号单一来源
3
+ ========================
4
+
5
+ 历史问题:`__version__` 的真值散落在 5 处(顶层包、core/storage.py、
6
+ core/mindforge.py、cli/main.py、mcp/server.py),每处都带一份硬编码兜底。
7
+ 发版时要改 5 次,漏改就会出现「顶层 5.6.1 / storage 5.6.0」这类漂移。
8
+
9
+ 本模块把版本号收敛为唯一真值:
10
+
11
+ - 运行时代码一律从这里 import,不再自带硬编码兜底;
12
+ - `pyproject.toml` 通过 `[tool.setuptools.dynamic] version = {attr =
13
+ "mindforge.core.version.__version__"}` 读取同一份值,打包元数据与运行时
14
+ 完全一致。
15
+
16
+ 发版清单:只改本文件的 `__version__`(与 `VERSION_INFO`),其余位置
17
+ (pyproject 动态版本、CLI/MCP/API、README/官网徽章)全部自动跟随。
18
+ """
19
+
20
+ from typing import Tuple
21
+
22
+ #: 当前发布版本(唯一真值)
23
+ __version__: str = "6.4.1"
24
+
25
+ #: 结构化版本元组,便于比较(如 `VERSION_INFO >= (6, 1, 0)`)
26
+ VERSION_INFO: Tuple[int, int, int] = (6, 4, 1)
27
+
28
+ #: 下一个大版本的目标版本号(供 v6 端口预留引用)
29
+ NEXT_MAJOR_TARGET: str = "7.0.0"
30
+
31
+
32
+ def get_version() -> str:
33
+ """返回当前版本号字符串(等价于 `__version__`)。
34
+
35
+ Returns:
36
+ 形如 "6.1.2" 的 semver 字符串。
37
+ """
38
+ return __version__
39
+
40
+
41
+ def get_version_info() -> Tuple[int, int, int]:
42
+ """返回结构化版本元组。
43
+
44
+ Returns:
45
+ 形如 (6, 1, 2) 的三元整数元组。
46
+ """
47
+ return VERSION_INFO
48
+
49
+
50
+ __all__ = [
51
+ "__version__",
52
+ "VERSION_INFO",
53
+ "NEXT_MAJOR_TARGET",
54
+ "get_version",
55
+ "get_version_info",
56
+ ]
File without changes
@@ -0,0 +1,277 @@
1
+ """MindForge 常驻守护进程(daemon)——毫秒级调用路径。
2
+
3
+ 背景(2026-11-08 延迟实测):引擎直连 2-3ms,但 CLI 子进程路径 698ms——
4
+ 差距全部来自 Python 解释器启动与导入开销。daemon 把引擎实例常驻在
5
+ 一个长生命周期进程里,宿主(dsh 插件等)通过 stdin/stdout 行式 JSON
6
+ 协议调用,消除每次调用的进程启动税。
7
+
8
+ 协议(v6.4.0):
9
+ 请求(stdin,每行一个 JSON):
10
+ {"id": <int|str>, "cmd": "<command>", "args": {<kwargs>}}
11
+ 响应(stdout,每行一个 JSON,立即可 flush):
12
+ {"id": ..., "ok": true, "data": {...}} 成功
13
+ {"id": ..., "ok": false, "error": "<msg>"} 失败(进程不退出)
14
+ 生命周期:
15
+ "ping" → {"pong": true, "pid": ..., "uptime_ms": ...}
16
+ "shutdown" → 优雅退出(退出码 0)
17
+ EOF → 退出
18
+ stderr: 启动横幅与日志(stdout 保持纯 JSON 行,严禁污染)
19
+
20
+ 命令集(v1,输出契约对齐 CLI --json):
21
+ add / search / memory-recall / memory-context / stats /
22
+ compress / compress-list / decompress / adaptive-search
23
+
24
+ 设计原则:
25
+ - 单线程顺序处理:v1 目标是正确与低延迟,agent 场景以顺序调用为主
26
+ - 每请求独立 try/except:单条请求异常不杀进程
27
+ - 引擎实例只建一次、永不 close(daemon 生命周期 = 引擎生命周期)
28
+ """
29
+ from __future__ import annotations
30
+
31
+ import json
32
+ import os
33
+ import sys
34
+ import time
35
+
36
+ from mindforge.core.types import (
37
+ Importance,
38
+ MemoryLayer,
39
+ MemoryType,
40
+ PrivacyLevel,
41
+ )
42
+
43
+ __all__ = ["run_daemon"]
44
+
45
+
46
+ def _make_serializer():
47
+ """引擎对象 → CLI --json 兼容 dict 的转换集合(对齐 cli/main.py 契约)。"""
48
+
49
+ def add(entry):
50
+ return {
51
+ "status": "ok",
52
+ "id": entry.id,
53
+ "content": entry.content,
54
+ "category": entry.category,
55
+ "tags": entry.tags,
56
+ "layer": entry.layer.value,
57
+ "importance": entry.importance.value
58
+ if getattr(entry.importance, "value", None)
59
+ else str(entry.importance),
60
+ }
61
+
62
+ def search(result):
63
+ return {
64
+ "chunks": [
65
+ {
66
+ "memory_id": c.memory_id,
67
+ "content": c.content,
68
+ "category": c.category,
69
+ "layer": c.layer.value,
70
+ "relevance_score": c.relevance_score,
71
+ "tags": c.tags,
72
+ "score_breakdown": c.score_breakdown,
73
+ }
74
+ for c in result.chunks
75
+ ],
76
+ "total_found": result.total_found,
77
+ "query_time_ms": result.query_time_ms,
78
+ "strategy_used": result.strategy_used,
79
+ "token_estimate": result.token_estimate,
80
+ "layers_used": result.layers_used,
81
+ "approximate": getattr(result, "approximate", False),
82
+ }
83
+
84
+ return add, search
85
+
86
+
87
+ def run_daemon(db_path: str, key_file: str | None = None, encrypted: bool = False) -> int:
88
+ """启动常驻守护进程主循环。返回退出码。"""
89
+ from mindforge.core.types import MemoryConfig
90
+ from mindforge.core.mindforge import MindForge
91
+
92
+ t_start = time.time()
93
+ cm = MindForge(
94
+ config=MemoryConfig(db_path=db_path, key_file=key_file, encrypted=encrypted)
95
+ )
96
+ add_ser, search_ser = _make_serializer()
97
+ taxonomy = None # lazy:add 缺省分类时才建
98
+
99
+ print(
100
+ json.dumps(
101
+ {
102
+ "event": "ready",
103
+ "pid": os.getpid(),
104
+ "db_path": db_path,
105
+ "startup_ms": int((time.time() - t_start) * 1000),
106
+ },
107
+ ensure_ascii=False,
108
+ ),
109
+ file=sys.stderr,
110
+ flush=True,
111
+ )
112
+
113
+ def cmd_add(args):
114
+ nonlocal taxonomy
115
+ content = args["content"]
116
+ category = args.get("category")
117
+ tags = args.get("tags")
118
+ if not category or not tags:
119
+ if taxonomy is None:
120
+ from mindforge.modules.categorizer import TaxonomyManager
121
+
122
+ taxonomy = TaxonomyManager()
123
+ if not category:
124
+ category = taxonomy.suggest_category(content)
125
+ if not tags:
126
+ tags = taxonomy.suggest_tags(content)
127
+ entry = cm.add(
128
+ content=content,
129
+ category=category,
130
+ tags=tags,
131
+ privacy=PrivacyLevel.from_string(args.get("privacy", "INTERNAL")),
132
+ importance=Importance.from_string(args.get("importance", "MEDIUM")),
133
+ memory_type=MemoryType.from_string(args.get("type", "text")),
134
+ layer=MemoryLayer.from_string(args.get("layer", "short_term")),
135
+ source_session=args.get("session", ""),
136
+ source_agent=args.get("agent", ""),
137
+ )
138
+ return add_ser(entry)
139
+
140
+ def cmd_search(args):
141
+ result = cm.search(
142
+ query=args["query"],
143
+ max_results=args.get("limit", 10),
144
+ min_relevance=0.2,
145
+ agent_id=args.get("agent", ""),
146
+ session_id=args.get("session", ""),
147
+ use_embedding=args.get("use_embedding", True),
148
+ explain=args.get("explain", False),
149
+ )
150
+ return search_ser(result)
151
+
152
+ def cmd_memory_recall(args):
153
+ return cm.memory_recall(
154
+ args["agent"], args["query"],
155
+ args.get("top_k", 10), args.get("days", 30),
156
+ )
157
+
158
+ def cmd_memory_context(args):
159
+ return cm.memory_context(
160
+ args["agent"], args["query"], args.get("max_tokens", 4000)
161
+ )
162
+
163
+ def cmd_stats(args):
164
+ return cm.detailed_stats()
165
+
166
+ def cmd_compress(args):
167
+ return cm.compress_memories(
168
+ threshold=args.get("threshold", 0.5),
169
+ dry_run=args.get("dry_run", False),
170
+ categories=[args["category"]] if args.get("category") else None,
171
+ min_cluster_size=args.get("min_cluster_size", 2),
172
+ )
173
+
174
+ def cmd_compress_list(args):
175
+ items = cm.list_compressions()
176
+ return {"total": len(items), "clusters": items}
177
+
178
+ def cmd_decompress(args):
179
+ return cm.decompress_cluster(args["cluster_id"])
180
+
181
+ def cmd_adaptive_search(args):
182
+ return cm.adaptive_search(
183
+ query=args["query"],
184
+ session_id=args.get("session", "cli"),
185
+ max_results=args.get("limit", 10),
186
+ )
187
+
188
+ handlers = {
189
+ "add": cmd_add,
190
+ "search": cmd_search,
191
+ "memory-recall": cmd_memory_recall,
192
+ "memory-context": cmd_memory_context,
193
+ "stats": cmd_stats,
194
+ "compress": cmd_compress,
195
+ "compress-list": cmd_compress_list,
196
+ "decompress": cmd_decompress,
197
+ "adaptive-search": cmd_adaptive_search,
198
+ }
199
+
200
+ for line in sys.stdin:
201
+ line = line.strip()
202
+ if not line:
203
+ continue
204
+ try:
205
+ req = json.loads(line)
206
+ rid = req.get("id")
207
+ cmd = req.get("cmd", "")
208
+ args = req.get("args", {}) or {}
209
+ except json.JSONDecodeError as e:
210
+ sys.stdout.write(
211
+ json.dumps({"id": None, "ok": False, "error": f"bad json: {e}"}) + "\n"
212
+ )
213
+ sys.stdout.flush()
214
+ continue
215
+
216
+ if cmd == "ping":
217
+ sys.stdout.write(
218
+ json.dumps(
219
+ {
220
+ "id": rid,
221
+ "ok": True,
222
+ "data": {
223
+ "pong": True,
224
+ "pid": os.getpid(),
225
+ "uptime_ms": int((time.time() - t_start) * 1000),
226
+ },
227
+ },
228
+ ensure_ascii=False,
229
+ )
230
+ + "\n"
231
+ )
232
+ sys.stdout.flush()
233
+ continue
234
+ if cmd == "shutdown":
235
+ sys.stdout.write(
236
+ json.dumps({"id": rid, "ok": True, "data": {"bye": True}}) + "\n"
237
+ )
238
+ sys.stdout.flush()
239
+ break
240
+
241
+ handler = handlers.get(cmd)
242
+ if handler is None:
243
+ sys.stdout.write(
244
+ json.dumps(
245
+ {
246
+ "id": rid,
247
+ "ok": False,
248
+ "error": f"unknown cmd: {cmd}(支持: {sorted(handlers) + ['ping', 'shutdown']})",
249
+ },
250
+ ensure_ascii=False,
251
+ )
252
+ + "\n"
253
+ )
254
+ sys.stdout.flush()
255
+ continue
256
+
257
+ try:
258
+ data = handler(args)
259
+ sys.stdout.write(
260
+ json.dumps({"id": rid, "ok": True, "data": data}, ensure_ascii=False)
261
+ + "\n"
262
+ )
263
+ except Exception as e: # 单请求失败不杀进程
264
+ sys.stdout.write(
265
+ json.dumps(
266
+ {"id": rid, "ok": False, "error": f"{type(e).__name__}: {e}"},
267
+ ensure_ascii=False,
268
+ )
269
+ + "\n"
270
+ )
271
+ sys.stdout.flush()
272
+
273
+ try:
274
+ cm.close()
275
+ except Exception:
276
+ pass
277
+ return 0
@@ -0,0 +1,4 @@
1
+ """MindForge MCP package — Model Context Protocol server."""
2
+ from .server import main, serve_forever
3
+
4
+ __all__ = ["main", "serve_forever"]