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.
- MindForge.py +20 -0
- mindforge/__init__.py +111 -0
- mindforge/adapters/__init__.py +18 -0
- mindforge/adapters/claude_adapter.py +84 -0
- mindforge/adapters/crewai_memory.py +76 -0
- mindforge/adapters/generic_api.py +183 -0
- mindforge/adapters/langgraph_store.py +463 -0
- mindforge/adapters/openclaw_adapter.py +91 -0
- mindforge/api/__init__.py +1 -0
- mindforge/api/server.py +1567 -0
- mindforge/cli/__init__.py +1 -0
- mindforge/cli/main.py +13607 -0
- mindforge/core/__init__.py +37 -0
- mindforge/core/embedding.py +847 -0
- mindforge/core/encryption.py +597 -0
- mindforge/core/indexer.py +363 -0
- mindforge/core/mindforge.py +6298 -0
- mindforge/core/paths.py +43 -0
- mindforge/core/query.py +501 -0
- mindforge/core/storage.py +18656 -0
- mindforge/core/types.py +314 -0
- mindforge/core/version.py +56 -0
- mindforge/daemon/__init__.py +0 -0
- mindforge/daemon/server.py +277 -0
- mindforge/mcp/__init__.py +4 -0
- mindforge/mcp/server.py +2559 -0
- mindforge/modules/__init__.py +138 -0
- mindforge/modules/categorizer.py +151 -0
- mindforge/modules/conflict_detector.py +356 -0
- mindforge/modules/connectors.py +331 -0
- mindforge/modules/context_window.py +337 -0
- mindforge/modules/event_bus.py +572 -0
- mindforge/modules/evolution.py +210 -0
- mindforge/modules/experience.py +402 -0
- mindforge/modules/federated.py +722 -0
- mindforge/modules/federated_acl.py +393 -0
- mindforge/modules/follow_up.py +315 -0
- mindforge/modules/hybrid_search.py +565 -0
- mindforge/modules/importer.py +374 -0
- mindforge/modules/integrator.py +196 -0
- mindforge/modules/intent_router.py +359 -0
- mindforge/modules/knowledge_graph.py +395 -0
- mindforge/modules/memory_compressor.py +592 -0
- mindforge/modules/memory_decay.py +474 -0
- mindforge/modules/memory_passport.py +250 -0
- mindforge/modules/multi_agent.py +534 -0
- mindforge/modules/multimodal.py +304 -0
- mindforge/modules/outcome_learning.py +186 -0
- mindforge/modules/personality.py +275 -0
- mindforge/modules/principled_forgetting.py +258 -0
- mindforge/modules/privacy.py +736 -0
- mindforge/modules/recall.py +263 -0
- mindforge/modules/session_focus.py +305 -0
- mindforge/modules/share_conflict.py +521 -0
- mindforge/modules/skill_extractor.py +340 -0
- mindforge/modules/skill_pack.py +205 -0
- mindforge/modules/timeline_viz.py +206 -0
- mindforge/modules/v6.py +931 -0
- mindforge_memory-6.4.1.dist-info/METADATA +281 -0
- mindforge_memory-6.4.1.dist-info/RECORD +64 -0
- mindforge_memory-6.4.1.dist-info/WHEEL +5 -0
- mindforge_memory-6.4.1.dist-info/entry_points.txt +3 -0
- mindforge_memory-6.4.1.dist-info/licenses/LICENSE +21 -0
- mindforge_memory-6.4.1.dist-info/top_level.txt +2 -0
mindforge/mcp/server.py
ADDED
|
@@ -0,0 +1,2559 @@
|
|
|
1
|
+
"""MindForge MCP Server — Model Context Protocol (stdio transport).
|
|
2
|
+
|
|
3
|
+
Implements MCP 2024-11-05 over stdio JSON-RPC.
|
|
4
|
+
|
|
5
|
+
Launch options:
|
|
6
|
+
python -m MindForge.mcp.server --db-path ./memory.db
|
|
7
|
+
MindForge-mcp --db-path ./memory.db
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
import time as _time
|
|
16
|
+
import traceback
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Dict, List, Optional
|
|
19
|
+
|
|
20
|
+
# Allow running without editable install: add parent of mcp/ to sys.path
|
|
21
|
+
_PKG_ROOT = Path(__file__).resolve().parent.parent
|
|
22
|
+
if str(_PKG_ROOT) not in sys.path:
|
|
23
|
+
sys.path.insert(0, str(_PKG_ROOT))
|
|
24
|
+
|
|
25
|
+
# v5.6.2 收敛:版本号唯一真值在 mindforge/core/version.py,此处仅做再导出,
|
|
26
|
+
# 避免发版时出现版本漂移(v5.8.13 起统一 mindforge.* 命名空间,不再需要回退)。
|
|
27
|
+
from mindforge.core.version import __version__
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# MCP transport — Content-Length framed JSON-RPC over stdio
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
def _log(msg: str) -> None:
|
|
35
|
+
"""Debug output goes to stderr; never write to stdout."""
|
|
36
|
+
sys.stderr.write(f"[mcp-mindforge] {msg}\n")
|
|
37
|
+
sys.stderr.flush()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# P3-07:错误详情里可能夹带绝对路径,回给客户端前做脱敏。
|
|
41
|
+
_PATH_RE = re.compile(r"(?:[A-Za-z]:\\[^\s\"']+|/(?:[^\s/\"']+/)+[^\s\"']*)")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _safe_error_msg(exc: Exception) -> str:
|
|
45
|
+
"""把异常转成可安全返回给 MCP 客户端的短消息:抹掉路径、截断超长文本。"""
|
|
46
|
+
msg = _PATH_RE.sub("<path>", str(exc))
|
|
47
|
+
return msg[:200]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _read_message() -> Dict[str, Any]:
|
|
51
|
+
headers: Dict[str, str] = {}
|
|
52
|
+
# v5.6.2 安全修复:消息体大小上限(与 REST API 一致,10MB)
|
|
53
|
+
MAX_CONTENT_LENGTH = 10 * 1024 * 1024
|
|
54
|
+
while True:
|
|
55
|
+
line = sys.stdin.buffer.readline()
|
|
56
|
+
if not line:
|
|
57
|
+
# P2 #17 补强:stdin 关闭(EOF)直接退出,而不是落入
|
|
58
|
+
# "无 Content-Length → ValueError → 重试" 的 100% CPU 空转。
|
|
59
|
+
# 此前把 EOF 与"空头部行"同样当作 break,随后 length<=0 抛
|
|
60
|
+
# ValueError,serve_forever 会无限重读同一 EOF。
|
|
61
|
+
raise EOFError("stdin closed")
|
|
62
|
+
if line in (b"\r\n", b"\n"):
|
|
63
|
+
break
|
|
64
|
+
try:
|
|
65
|
+
decoded = line.decode("ascii", errors="replace").strip()
|
|
66
|
+
except Exception:
|
|
67
|
+
continue
|
|
68
|
+
if ":" in decoded:
|
|
69
|
+
k, v = decoded.split(":", 1)
|
|
70
|
+
headers[k.strip().lower()] = v.strip()
|
|
71
|
+
try:
|
|
72
|
+
length = int(headers.get("content-length", "0"))
|
|
73
|
+
except ValueError:
|
|
74
|
+
length = 0
|
|
75
|
+
# v5.6.2 安全修复:拒绝超大消息体,防止内存耗尽 DoS
|
|
76
|
+
if length > MAX_CONTENT_LENGTH:
|
|
77
|
+
raise ValueError(f"Content-Length {length} exceeds maximum {MAX_CONTENT_LENGTH}")
|
|
78
|
+
# P2 #17 修复:Content-Length 缺失或 ≤ 0 时直接拒绝,不再用 readline() 试探
|
|
79
|
+
# (MCP 协议必须有 Content-Length 头,readline 可能读到无限长数据)
|
|
80
|
+
if length <= 0:
|
|
81
|
+
raise ValueError("Missing or invalid Content-Length header")
|
|
82
|
+
raw = sys.stdin.buffer.read(length)
|
|
83
|
+
if not raw:
|
|
84
|
+
raise EOFError("stdin closed")
|
|
85
|
+
# P2 #23 修复:外部输入用安全 JSON 解析(深度限制)
|
|
86
|
+
try:
|
|
87
|
+
from mindforge.core.storage import _safe_json_loads
|
|
88
|
+
return _safe_json_loads(raw.decode("utf-8"))
|
|
89
|
+
except ImportError:
|
|
90
|
+
return json.loads(raw.decode("utf-8"))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _write_message(payload: Dict[str, Any]) -> None:
|
|
94
|
+
body = json.dumps(payload, ensure_ascii=False, default=str).encode("utf-8")
|
|
95
|
+
sys.stdout.buffer.write(f"Content-Length: {len(body)}\r\n\r\n".encode("ascii"))
|
|
96
|
+
sys.stdout.buffer.write(body)
|
|
97
|
+
sys.stdout.buffer.flush()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _respond(request: Dict[str, Any], result: Any) -> None:
|
|
101
|
+
_write_message({"jsonrpc": "2.0", "id": request.get("id"), "result": result})
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _respond_error(request: Dict[str, Any], code: int, message: str, data: Any = None) -> None:
|
|
105
|
+
body: Dict[str, Any] = {"jsonrpc": "2.0", "id": request.get("id"),
|
|
106
|
+
"error": {"code": code, "message": message}}
|
|
107
|
+
if data is not None:
|
|
108
|
+
body["error"]["data"] = data
|
|
109
|
+
_write_message(body)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _notify(method: str, params: Optional[Dict[str, Any]] = None) -> None:
|
|
113
|
+
msg: Dict[str, Any] = {"jsonrpc": "2.0", "method": method}
|
|
114
|
+
if params is not None:
|
|
115
|
+
msg["params"] = params
|
|
116
|
+
_write_message(msg)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
# Tool schemas — matches MCP tools/list
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
TOOL_SCHEMAS: List[Dict[str, Any]] = [
|
|
124
|
+
{
|
|
125
|
+
"name": "memory_add",
|
|
126
|
+
"description": "Add a new memory entry to MindForge lifelong memory system.",
|
|
127
|
+
"inputSchema": {
|
|
128
|
+
"type": "object",
|
|
129
|
+
"required": ["content"],
|
|
130
|
+
"properties": {
|
|
131
|
+
"content": {"type": "string", "description": "记忆正文内容"},
|
|
132
|
+
"category": {"type": "string", "description": "分类,默认 general"},
|
|
133
|
+
"tags": {"type": "array", "items": {"type": "string"}, "description": "标签列表"},
|
|
134
|
+
"importance": {"type": "string", "enum": ["critical", "high", "medium", "low"],
|
|
135
|
+
"description": "重要性级别,默认 medium"},
|
|
136
|
+
"layer": {"type": "string", "enum": ["sensory", "short_term", "long_term", "permanent"],
|
|
137
|
+
"description": "记忆层级,默认 short_term"},
|
|
138
|
+
"agent_id": {"type": "string", "description": "关联 Agent ID(可选)"},
|
|
139
|
+
"valid_from": {"type": "number", "description": "事实有效起始时间(Unix 秒,v5.7.6;0=无边界)"},
|
|
140
|
+
"valid_to": {"type": "number", "description": "事实有效截止时间(Unix 秒,v5.7.6;0=仍有效)"},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "memory_supersede",
|
|
146
|
+
"description": "Bi-temporal 事实取代(v5.7.6):关闭旧事实有效窗口并创建新事实,旧版本保留在历史中。",
|
|
147
|
+
"inputSchema": {
|
|
148
|
+
"type": "object",
|
|
149
|
+
"required": ["id", "content"],
|
|
150
|
+
"properties": {
|
|
151
|
+
"id": {"type": "string", "description": "要取代的旧记忆 ID"},
|
|
152
|
+
"content": {"type": "string", "description": "新事实内容"},
|
|
153
|
+
"category": {"type": "string", "description": "新分类(默认继承旧事实)"},
|
|
154
|
+
"tags": {"type": "array", "items": {"type": "string"}, "description": "新标签(默认继承旧事实)"},
|
|
155
|
+
"importance": {"type": "string", "enum": ["critical", "high", "medium", "low"],
|
|
156
|
+
"description": "新重要性(默认继承旧事实)"},
|
|
157
|
+
"layer": {"type": "string", "enum": ["sensory", "short_term", "long_term", "permanent"],
|
|
158
|
+
"description": "新层级(默认继承旧事实)"},
|
|
159
|
+
"valid_from": {"type": "number", "description": "新事实有效起始时间(Unix 秒,默认=当前时间)"},
|
|
160
|
+
"agent_id": {"type": "string", "description": "调用者 Agent ID(写入审计)"},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "memory_valid_at",
|
|
166
|
+
"description": "按事实有效时间查询(Bi-temporal as-of,v5.7.6):返回 timestamp 时处于有效窗口的记忆。",
|
|
167
|
+
"inputSchema": {
|
|
168
|
+
"type": "object",
|
|
169
|
+
"properties": {
|
|
170
|
+
"ts": {"type": "number", "description": "Unix 时间戳(默认=当前时间)"},
|
|
171
|
+
"category": {"type": "string", "description": "按分类过滤"},
|
|
172
|
+
"limit": {"type": "integer", "minimum": 1, "maximum": 500, "default": 50},
|
|
173
|
+
"actor": {"type": "string", "description": "调用者身份(Agent ID),启用隐私过滤"},
|
|
174
|
+
"session_id": {"type": "string", "description": "调用者会话 ID"},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"name": "memory_agent_pin",
|
|
180
|
+
"description": "Agent 自主标记关键记忆(v5.7.7):置顶冻结衰减 + 重置遗忘分,可提升重要度。",
|
|
181
|
+
"inputSchema": {
|
|
182
|
+
"type": "object",
|
|
183
|
+
"required": ["id"],
|
|
184
|
+
"properties": {
|
|
185
|
+
"id": {"type": "string", "description": "记忆 ID"},
|
|
186
|
+
"importance": {"type": "string", "enum": ["low", "medium", "high", "critical"],
|
|
187
|
+
"description": "提升重要度(可选)"},
|
|
188
|
+
"agent_id": {"type": "string", "description": "调用者 Agent ID"},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "memory_agent_forget",
|
|
194
|
+
"description": "Agent 自主遗忘(v5.7.7):软删除移入回收站(可 restore 恢复),记录原因。",
|
|
195
|
+
"inputSchema": {
|
|
196
|
+
"type": "object",
|
|
197
|
+
"required": ["id"],
|
|
198
|
+
"properties": {
|
|
199
|
+
"id": {"type": "string", "description": "记忆 ID"},
|
|
200
|
+
"reason": {"type": "string", "description": "遗忘原因(写入审计)"},
|
|
201
|
+
"agent_id": {"type": "string", "description": "调用者 Agent ID"},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"name": "memory_agent_decay_boost",
|
|
207
|
+
"description": "Agent 标记「这条不再重要」(v5.7.7):提升 forgetting_score 加速遗忘(封顶 10)。",
|
|
208
|
+
"inputSchema": {
|
|
209
|
+
"type": "object",
|
|
210
|
+
"required": ["id"],
|
|
211
|
+
"properties": {
|
|
212
|
+
"id": {"type": "string", "description": "记忆 ID"},
|
|
213
|
+
"amount": {"type": "number", "minimum": 0.1, "maximum": 10.0,
|
|
214
|
+
"description": "遗忘分增量(默认 1.0)"},
|
|
215
|
+
"agent_id": {"type": "string", "description": "调用者 Agent ID"},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"name": "memory_connector_ingest",
|
|
221
|
+
"description": "通过连接器导入数据(v5.7.7):json / csv / markdown / file / url。",
|
|
222
|
+
"inputSchema": {
|
|
223
|
+
"type": "object",
|
|
224
|
+
"required": ["connector", "source"],
|
|
225
|
+
"properties": {
|
|
226
|
+
"connector": {"type": "string",
|
|
227
|
+
"description": "连接器名称(json/csv/markdown/file/url)"},
|
|
228
|
+
"source": {"type": "string", "description": "数据源(文件路径或 URL)"},
|
|
229
|
+
"category": {"type": "string", "description": "导入分类"},
|
|
230
|
+
"source_agent": {"type": "string", "description": "来源 Agent 标记"},
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"name": "conflict_reconcile",
|
|
236
|
+
"description": "冲突自动调和(Bi-temporal,v5.7.7):keep_newer / keep_higher_importance 自动失效旧事实,其余待人工。",
|
|
237
|
+
"inputSchema": {
|
|
238
|
+
"type": "object",
|
|
239
|
+
"properties": {
|
|
240
|
+
"auto": {"type": "boolean", "default": True,
|
|
241
|
+
"description": "false 时仅报告不执行"},
|
|
242
|
+
"memory_ids": {"type": "array", "items": {"type": "string"},
|
|
243
|
+
"description": "限定记忆 ID 列表(可选)"},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"name": "memory_search",
|
|
249
|
+
"description": "Hybrid search: vector recall + TF-IDF + FTS5 + fuzzy (v5.4.5). Set use_embedding=false to disable vector search. Set explain=true for a per-result score breakdown (v6.3.2).",
|
|
250
|
+
"inputSchema": {
|
|
251
|
+
"type": "object",
|
|
252
|
+
"required": ["query"],
|
|
253
|
+
"properties": {
|
|
254
|
+
"query": {"type": "string", "description": "搜索查询"},
|
|
255
|
+
"max_results": {"type": "integer", "minimum": 1, "maximum": 50, "default": 5},
|
|
256
|
+
"min_relevance": {"type": "number", "minimum": 0.0, "maximum": 1.0, "default": 0.0},
|
|
257
|
+
"category": {"type": "string", "description": "按分类过滤(可选)"},
|
|
258
|
+
"agent_id": {"type": "string", "description": "按 Agent ID 过滤(可选)"},
|
|
259
|
+
"use_embedding": {"type": "boolean", "default": True, "description": "启用向量召回(v5.4.5,默认 true)"},
|
|
260
|
+
"explain": {"type": "boolean", "default": False, "description": "返回每条结果的得分分解:四路召回各自原始分、胜出路径、文本+向量融合明细(v6.3.2,默认 false 零开销)"},
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"name": "memory_list",
|
|
266
|
+
"description": "分页列出记忆条目,支持按分类/层级/重要性过滤和排序。"
|
|
267
|
+
"传入 actor 后会按隐私引擎过滤(仅返回该调用者有权访问的记忆),"
|
|
268
|
+
"不传则返回全部(兼容既有客户端)。",
|
|
269
|
+
"inputSchema": {
|
|
270
|
+
"type": "object",
|
|
271
|
+
"properties": {
|
|
272
|
+
"category": {"type": "string"},
|
|
273
|
+
"layer": {"type": "string", "enum": ["sensory", "short_term", "long_term", "permanent"]},
|
|
274
|
+
"limit": {"type": "integer", "minimum": 1, "maximum": 500, "default": 20},
|
|
275
|
+
"offset": {"type": "integer", "minimum": 0, "default": 0},
|
|
276
|
+
"sort_by": {"type": "string", "enum": ["created_at", "updated_at", "importance"],
|
|
277
|
+
"default": "created_at"},
|
|
278
|
+
"actor": {"type": "string",
|
|
279
|
+
"description": "调用者身份(Agent ID)。传入后启用隐私过滤,见 F1 方案。"},
|
|
280
|
+
"session_id": {"type": "string", "description": "调用者会话 ID(配合 actor 使用)"},
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"name": "memory_stats",
|
|
286
|
+
"description": "记忆库统计:总数、分类、层级、重要性、标签数量等。",
|
|
287
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"name": "memory_importance",
|
|
291
|
+
"description": "Agent 记忆重要度分析:分布、漂移、低估/高估与重评估建议。",
|
|
292
|
+
"inputSchema": {
|
|
293
|
+
"type": "object",
|
|
294
|
+
"required": ["agent_id"],
|
|
295
|
+
"properties": {"agent_id": {"type": "string"}},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"name": "memory_context",
|
|
300
|
+
"description": "为 LLM Prompt 格式化上下文注入(Token 预算感知)。",
|
|
301
|
+
"inputSchema": {
|
|
302
|
+
"type": "object",
|
|
303
|
+
"required": ["agent_id", "query"],
|
|
304
|
+
"properties": {
|
|
305
|
+
"agent_id": {"type": "string"},
|
|
306
|
+
"query": {"type": "string"},
|
|
307
|
+
"token_budget": {"type": "integer", "minimum": 256, "maximum": 128000, "default": 4000},
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
"name": "agent_emotion",
|
|
313
|
+
"description": "Agent 情感追踪:每日情感、转换序列、波动性评分。",
|
|
314
|
+
"inputSchema": {
|
|
315
|
+
"type": "object",
|
|
316
|
+
"required": ["agent_id"],
|
|
317
|
+
"properties": {
|
|
318
|
+
"agent_id": {"type": "string"},
|
|
319
|
+
"days": {"type": "integer", "minimum": 1, "maximum": 365, "default": 30},
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
"name": "char_relationship",
|
|
325
|
+
"description": "短剧角色关系分析:六型分类(ally/rival/romance/family/mentor/stranger)+ 情感弧线 + 强度。需同时提供 char1_id 和 char2_id。",
|
|
326
|
+
"inputSchema": {
|
|
327
|
+
"type": "object",
|
|
328
|
+
"required": ["drama_id"],
|
|
329
|
+
"properties": {
|
|
330
|
+
"drama_id": {"type": "string"},
|
|
331
|
+
"char1_id": {"type": "string", "description": "角色 1 ID(可选,用于特定角色关系分析)"},
|
|
332
|
+
"char2_id": {"type": "string", "description": "角色 2 ID(可选,用于特定角色关系分析)"},
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"name": "drama_genre_trend",
|
|
338
|
+
"description": "短剧类型趋势:rising / declining / stable + 平均评分。",
|
|
339
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"name": "drama_binge_score",
|
|
343
|
+
"description": "短剧追剧粘性评分(节奏/张力/互动/经典/完成率)。",
|
|
344
|
+
"inputSchema": {
|
|
345
|
+
"type": "object",
|
|
346
|
+
"required": ["drama_id"],
|
|
347
|
+
"properties": {"drama_id": {"type": "string"}},
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
# ===== v5.4.1 新增六大能力 =====
|
|
351
|
+
{
|
|
352
|
+
"name": "memory_reflection",
|
|
353
|
+
"description": "记忆反思(元认知):主题分布、情感基调、关键经验、焦点漂移与结构化反思报告。",
|
|
354
|
+
"inputSchema": {
|
|
355
|
+
"type": "object",
|
|
356
|
+
"required": ["agent_id"],
|
|
357
|
+
"properties": {
|
|
358
|
+
"agent_id": {"type": "string"},
|
|
359
|
+
"days": {"type": "integer", "minimum": 1, "maximum": 365, "default": 30},
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"name": "memory_lineage",
|
|
365
|
+
"description": "记忆血缘溯源:单条记忆的版本历史、关联链接、审计事件与生命周期时间线。",
|
|
366
|
+
"inputSchema": {
|
|
367
|
+
"type": "object",
|
|
368
|
+
"required": ["memory_id"],
|
|
369
|
+
"properties": {"memory_id": {"type": "string"}},
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
"name": "memory_reinforce",
|
|
374
|
+
"description": "记忆强化候选:识别高价值但正在衰减的记忆,给出强化排序与推荐动作。",
|
|
375
|
+
"inputSchema": {
|
|
376
|
+
"type": "object",
|
|
377
|
+
"required": ["agent_id"],
|
|
378
|
+
"properties": {
|
|
379
|
+
"agent_id": {"type": "string"},
|
|
380
|
+
"days": {"type": "integer", "minimum": 1, "maximum": 365, "default": 90},
|
|
381
|
+
"limit": {"type": "integer", "minimum": 1, "maximum": 50, "default": 10},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"name": "drama_plot_thread",
|
|
387
|
+
"description": "剧情伏笔线索追踪:识别伏笔埋设与回收,输出线索列表、未回收线索与回收率。",
|
|
388
|
+
"inputSchema": {
|
|
389
|
+
"type": "object",
|
|
390
|
+
"required": ["drama_id"],
|
|
391
|
+
"properties": {"drama_id": {"type": "string"}},
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
"name": "drama_episode_curve",
|
|
396
|
+
"description": "分集张力曲线:按集聚合张力指标,输出全剧曲线、高潮集、波动率与形态分类。",
|
|
397
|
+
"inputSchema": {
|
|
398
|
+
"type": "object",
|
|
399
|
+
"required": ["drama_id"],
|
|
400
|
+
"properties": {"drama_id": {"type": "string"}},
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
"name": "drama_screen_time",
|
|
405
|
+
"description": "角色戏份平衡:角色台词量/字数/出场占比 + 基尼系数 + 独角戏/双核/群像结构判定。",
|
|
406
|
+
"inputSchema": {
|
|
407
|
+
"type": "object",
|
|
408
|
+
"required": ["drama_id"],
|
|
409
|
+
"properties": {"drama_id": {"type": "string"}},
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
# ===== v5.4.2 新增两大能力 =====
|
|
413
|
+
{
|
|
414
|
+
"name": "fed_acl_add",
|
|
415
|
+
"description": "联邦记忆细粒度 ACL:添加规则(主体×资源×操作×allow/deny,支持优先级/信任阈值/过期)。",
|
|
416
|
+
"inputSchema": {
|
|
417
|
+
"type": "object",
|
|
418
|
+
"required": ["principal", "resource"],
|
|
419
|
+
"properties": {
|
|
420
|
+
"principal": {"type": "string", "description": "peer ID 或 * 表示任意节点"},
|
|
421
|
+
"resource": {"type": "string", "description": "all / memory:<id> / category:<名> / tag:<名>"},
|
|
422
|
+
"operations": {"type": "string", "default": "read", "description": "read/write/reshare/*,逗号分隔"},
|
|
423
|
+
"effect": {"type": "string", "enum": ["allow", "deny"], "default": "allow"},
|
|
424
|
+
"priority": {"type": "integer", "minimum": 0, "maximum": 10000, "default": 100},
|
|
425
|
+
"trust_min": {"type": "number", "minimum": 0, "maximum": 1, "default": 0},
|
|
426
|
+
"expires_hours": {"type": "number", "description": "规则有效时长(小时),缺省永久"},
|
|
427
|
+
"note": {"type": "string"},
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
"name": "fed_acl_remove",
|
|
433
|
+
"description": "联邦记忆细粒度 ACL:删除规则。",
|
|
434
|
+
"inputSchema": {
|
|
435
|
+
"type": "object",
|
|
436
|
+
"required": ["rule_id"],
|
|
437
|
+
"properties": {"rule_id": {"type": "string"}},
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
"name": "fed_acl_list",
|
|
442
|
+
"description": "联邦记忆细粒度 ACL:规则列表(可按主体/效果过滤)。",
|
|
443
|
+
"inputSchema": {
|
|
444
|
+
"type": "object",
|
|
445
|
+
"properties": {
|
|
446
|
+
"principal": {"type": "string"},
|
|
447
|
+
"effect": {"type": "string", "enum": ["allow", "deny"]},
|
|
448
|
+
"limit": {"type": "integer", "minimum": 1, "maximum": 1000, "default": 200},
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
"name": "fed_acl_check",
|
|
454
|
+
"description": "联邦记忆细粒度 ACL:访问评估(默认拒绝;返回 allowed/effect/reason/matched_rule)。",
|
|
455
|
+
"inputSchema": {
|
|
456
|
+
"type": "object",
|
|
457
|
+
"required": ["peer_id", "memory_id"],
|
|
458
|
+
"properties": {
|
|
459
|
+
"peer_id": {"type": "string"},
|
|
460
|
+
"memory_id": {"type": "string"},
|
|
461
|
+
"operation": {"type": "string", "enum": ["read", "write", "reshare"], "default": "read"},
|
|
462
|
+
"peer_trust": {"type": "number", "minimum": 0, "maximum": 1},
|
|
463
|
+
"memory_category": {"type": "string"},
|
|
464
|
+
"memory_tags": {"type": "array", "items": {"type": "string"}},
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
"name": "fed_acl_stats",
|
|
470
|
+
"description": "联邦记忆细粒度 ACL:规则分布与拒绝审计统计。",
|
|
471
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
"name": "share_conflict_list",
|
|
475
|
+
"description": "共享记忆冲突:冲突记录列表(可按状态过滤 open/resolved/dismissed)。",
|
|
476
|
+
"inputSchema": {
|
|
477
|
+
"type": "object",
|
|
478
|
+
"properties": {
|
|
479
|
+
"status": {"type": "string", "enum": ["open", "resolved", "dismissed"]},
|
|
480
|
+
"limit": {"type": "integer", "minimum": 1, "maximum": 500, "default": 50},
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
"name": "share_conflict_resolve",
|
|
486
|
+
"description": "共享记忆冲突:按策略解决 open 冲突(lww=版本+时间戳决胜覆盖;keep_both=另存分支并关联)。",
|
|
487
|
+
"inputSchema": {
|
|
488
|
+
"type": "object",
|
|
489
|
+
"required": ["conflict_id", "strategy"],
|
|
490
|
+
"properties": {
|
|
491
|
+
"conflict_id": {"type": "string"},
|
|
492
|
+
"strategy": {"type": "string", "enum": ["lww", "keep_both"]},
|
|
493
|
+
"actor": {"type": "string"},
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
"name": "share_conflict_dismiss",
|
|
499
|
+
"description": "共享记忆冲突:人工关闭 open 冲突(判定无需处理)。",
|
|
500
|
+
"inputSchema": {
|
|
501
|
+
"type": "object",
|
|
502
|
+
"required": ["conflict_id"],
|
|
503
|
+
"properties": {
|
|
504
|
+
"conflict_id": {"type": "string"},
|
|
505
|
+
"actor": {"type": "string"},
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
"name": "share_conflict_stats",
|
|
511
|
+
"description": "共享记忆冲突:冲突态势统计(按状态/类型/解决方式)。",
|
|
512
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
513
|
+
},
|
|
514
|
+
# ===== v5.3.9 新增五大能力 =====
|
|
515
|
+
{
|
|
516
|
+
"name": "intent_router",
|
|
517
|
+
"description": "意图分类路由(规则正则→关键词加权→LLM 兜底)。返回 intent / label / confidence / routing_target。",
|
|
518
|
+
"inputSchema": {
|
|
519
|
+
"type": "object",
|
|
520
|
+
"required": ["text"],
|
|
521
|
+
"properties": {
|
|
522
|
+
"text": {"type": "string", "description": "要分类的文本"},
|
|
523
|
+
"force": {"type": "string", "description": "强制指定意图 ID(debug 用)"},
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
"name": "conflict_scan",
|
|
529
|
+
"description": "记忆矛盾扫描:反义词对 / 属性值不一致 / 时间线冲突,并可自动衰减(降重要性 + 打标签)。",
|
|
530
|
+
"inputSchema": {
|
|
531
|
+
"type": "object",
|
|
532
|
+
"properties": {
|
|
533
|
+
"category": {"type": "string", "description": "只扫描指定分类"},
|
|
534
|
+
"limit": {"type": "integer", "minimum": 1, "maximum": 5000, "default": 500},
|
|
535
|
+
"apply_decay": {"type": "boolean", "default": False,
|
|
536
|
+
"description": "若 true,直接写入衰减动作"},
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
"name": "skill_extract",
|
|
542
|
+
"description": "从记忆中抽取可复用的技能模板(聚类→槽位→步骤→触发词)。",
|
|
543
|
+
"inputSchema": {
|
|
544
|
+
"type": "object",
|
|
545
|
+
"properties": {
|
|
546
|
+
"category": {"type": "string"},
|
|
547
|
+
"limit": {"type": "integer", "minimum": 1, "maximum": 10000, "default": 2000},
|
|
548
|
+
"min_cluster_size": {"type": "integer", "minimum": 1, "default": 2},
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
"name": "rerank_search",
|
|
554
|
+
"description": "混合检索增强:同义词/上位词查询扩展 + 三路召回 + Cross-Encoder 重排。",
|
|
555
|
+
"inputSchema": {
|
|
556
|
+
"type": "object",
|
|
557
|
+
"required": ["query"],
|
|
558
|
+
"properties": {
|
|
559
|
+
"query": {"type": "string"},
|
|
560
|
+
"max_results": {"type": "integer", "minimum": 1, "maximum": 50, "default": 10},
|
|
561
|
+
"expand": {"type": "boolean", "default": True},
|
|
562
|
+
"rerank": {"type": "boolean", "default": True},
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
"name": "session_focus",
|
|
568
|
+
"description": "会话焦点分析:主题聚类、焦点词、漂移检测、可生成增强查询。",
|
|
569
|
+
"inputSchema": {
|
|
570
|
+
"type": "object",
|
|
571
|
+
"required": ["messages"],
|
|
572
|
+
"properties": {
|
|
573
|
+
"messages": {
|
|
574
|
+
"type": "array",
|
|
575
|
+
"description": '每条为 {"id","role","content","timestamp?"} 或 {"role":"user","content":"..."}',
|
|
576
|
+
"items": {"type": "object"},
|
|
577
|
+
},
|
|
578
|
+
"window_size": {"type": "integer", "minimum": 5, "maximum": 400, "default": 40},
|
|
579
|
+
"augment_query": {"type": "string", "description": "若提供,生成带焦点的增强查询"},
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
# v5.4.5 新增向量检索工具
|
|
584
|
+
{
|
|
585
|
+
"name": "rebuild_embeddings",
|
|
586
|
+
"description": "重建所有记忆的嵌入向量(需安装 sentence-transformers)。",
|
|
587
|
+
"inputSchema": {
|
|
588
|
+
"type": "object",
|
|
589
|
+
"properties": {
|
|
590
|
+
"batch_size": {"type": "integer", "minimum": 1, "maximum": 1000, "default": 100},
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
"name": "embedding_status",
|
|
596
|
+
"description": "查看嵌入向量引擎状态:是否可用、模型名、维度、向量数量。",
|
|
597
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
"name": "memory_diff",
|
|
601
|
+
"description": "对比两个记忆版本的差异(v5.5.8 新增)。返回内容、分类、标签和重要度的结构化差异报告。",
|
|
602
|
+
"inputSchema": {
|
|
603
|
+
"type": "object",
|
|
604
|
+
"required": ["version_a", "version_b"],
|
|
605
|
+
"properties": {
|
|
606
|
+
"version_a": {"type": "string", "description": "版本 A 的 ID"},
|
|
607
|
+
"version_b": {"type": "string", "description": "版本 B 的 ID"},
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
"name": "memory_graph_stats",
|
|
613
|
+
"description": "知识图谱统计(v5.7.9):实体/关系总数与类型分布。",
|
|
614
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
"name": "memory_graph_related",
|
|
618
|
+
"description": "查询与指定实体相关的实体(BFS,v5.7.9)。",
|
|
619
|
+
"inputSchema": {
|
|
620
|
+
"type": "object",
|
|
621
|
+
"required": ["entity"],
|
|
622
|
+
"properties": {
|
|
623
|
+
"entity": {"type": "string", "description": "实体名称"},
|
|
624
|
+
"depth": {"type": "integer", "minimum": 1, "maximum": 5, "default": 2, "description": "BFS 深度"},
|
|
625
|
+
"max_results": {"type": "integer", "minimum": 1, "maximum": 100, "default": 20, "description": "返回上限"},
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
"name": "memory_graph_extract",
|
|
631
|
+
"description": "触发知识图谱抽取(v5.7.9):指定记忆 ID 或全库增量(同进程防抖,STRICT 隐私跳过)。",
|
|
632
|
+
"inputSchema": {
|
|
633
|
+
"type": "object",
|
|
634
|
+
"properties": {
|
|
635
|
+
"memory_id": {"type": "string", "description": "指定记忆 ID(缺省=全库增量)"},
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
"name": "memory_experience_record",
|
|
641
|
+
"description": "记录一次任务经验(v5.8.0):Agent 把成功任务存为 Case,供后续蒸馏为可复用技能。",
|
|
642
|
+
"inputSchema": {
|
|
643
|
+
"type": "object",
|
|
644
|
+
"required": ["task"],
|
|
645
|
+
"properties": {
|
|
646
|
+
"task": {"type": "string", "description": "任务描述"},
|
|
647
|
+
"content": {"type": "string", "description": "过程/步骤内容"},
|
|
648
|
+
"result": {"type": "string", "description": "结果"},
|
|
649
|
+
"outcome": {"type": "string", "enum": ["success", "partial", "failure"], "default": "success"},
|
|
650
|
+
"duration_seconds": {"type": "number", "description": "耗时秒"},
|
|
651
|
+
"tags": {"type": "array", "items": {"type": "string"}, "description": "标签"},
|
|
652
|
+
"source_memory_ids": {"type": "array", "items": {"type": "string"}, "description": "来源记忆 ID"},
|
|
653
|
+
"agent_id": {"type": "string", "description": "Agent ID(写入审计)"},
|
|
654
|
+
},
|
|
655
|
+
},
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
"name": "memory_skill_distill",
|
|
659
|
+
"description": "从成功案例蒸馏技能模板并持久化(v5.8.0)。",
|
|
660
|
+
"inputSchema": {
|
|
661
|
+
"type": "object",
|
|
662
|
+
"properties": {
|
|
663
|
+
"min_cluster_size": {"type": "integer", "minimum": 1, "maximum": 20, "default": 2},
|
|
664
|
+
"max_skills": {"type": "integer", "minimum": 1, "maximum": 500, "default": 50},
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
"name": "memory_skill_match",
|
|
670
|
+
"description": "按新任务匹配已存技能(v5.8.0,触发词/标签/bigram 三重评分)。",
|
|
671
|
+
"inputSchema": {
|
|
672
|
+
"type": "object",
|
|
673
|
+
"required": ["query"],
|
|
674
|
+
"properties": {
|
|
675
|
+
"query": {"type": "string", "description": "任务描述/查询"},
|
|
676
|
+
"limit": {"type": "integer", "minimum": 1, "maximum": 50, "default": 5},
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
"name": "memory_skill_render",
|
|
682
|
+
"description": "用参数渲染技能为可执行流程(v5.8.0)。",
|
|
683
|
+
"inputSchema": {
|
|
684
|
+
"type": "object",
|
|
685
|
+
"required": ["name"],
|
|
686
|
+
"properties": {
|
|
687
|
+
"name": {"type": "string", "description": "技能名"},
|
|
688
|
+
"params": {"type": "object", "description": "槽位参数 key=value 映射"},
|
|
689
|
+
},
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
"name": "memory_skill_stats",
|
|
694
|
+
"description": "经验统计(v5.8.0):案例数/技能数/结果分布。",
|
|
695
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
"name": "fed_peer_register",
|
|
699
|
+
"description": "注册联邦对等节点(v5.8.6)。Ed25519 签名+防重放。",
|
|
700
|
+
"inputSchema": {
|
|
701
|
+
"type": "object",
|
|
702
|
+
"required": ["peer_id", "name"],
|
|
703
|
+
"properties": {
|
|
704
|
+
"peer_id": {"type": "string"},
|
|
705
|
+
"name": {"type": "string"},
|
|
706
|
+
"trust_level": {"type": "number"},
|
|
707
|
+
"shared_categories": {"type": "array", "items": {"type": "string"}}
|
|
708
|
+
}
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
"name": "fed_peer_remove",
|
|
713
|
+
"description": "移除联邦对等节点(v5.8.6)。",
|
|
714
|
+
"inputSchema": {
|
|
715
|
+
"type": "object",
|
|
716
|
+
"required": ["peer_id"],
|
|
717
|
+
"properties": {
|
|
718
|
+
"peer_id": {"type": "string"}
|
|
719
|
+
}
|
|
720
|
+
},
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
"name": "fed_peer_list",
|
|
724
|
+
"description": "列出联邦对等节点及状态(v5.8.6)。",
|
|
725
|
+
"inputSchema": {
|
|
726
|
+
"type": "object",
|
|
727
|
+
"required": [],
|
|
728
|
+
"properties": {}
|
|
729
|
+
},
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
"name": "fed_memory_share",
|
|
733
|
+
"description": "把记忆共享给一组对等节点(v5.8.6)。",
|
|
734
|
+
"inputSchema": {
|
|
735
|
+
"type": "object",
|
|
736
|
+
"required": ["memory_id", "peer_ids"],
|
|
737
|
+
"properties": {
|
|
738
|
+
"memory_id": {"type": "string"},
|
|
739
|
+
"peer_ids": {"type": "array", "items": {"type": "string"}},
|
|
740
|
+
"access_policy": {"type": "string"},
|
|
741
|
+
"expires_hours": {"type": "number"}
|
|
742
|
+
}
|
|
743
|
+
},
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
"name": "fed_memory_revoke",
|
|
747
|
+
"description": "撤销记忆的联邦共享(v5.8.6)。",
|
|
748
|
+
"inputSchema": {
|
|
749
|
+
"type": "object",
|
|
750
|
+
"required": ["memory_id"],
|
|
751
|
+
"properties": {
|
|
752
|
+
"memory_id": {"type": "string"},
|
|
753
|
+
"peer_ids": {"type": "array", "items": {"type": "string"}}
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
"name": "fed_memory_list_shared",
|
|
759
|
+
"description": "列出已共享的记忆(v5.8.6)。",
|
|
760
|
+
"inputSchema": {
|
|
761
|
+
"type": "object",
|
|
762
|
+
"required": [],
|
|
763
|
+
"properties": {
|
|
764
|
+
"peer_id": {"type": "string"}
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
"name": "fed_search",
|
|
770
|
+
"description": "跨联邦节点检索记忆(v5.8.6)。",
|
|
771
|
+
"inputSchema": {
|
|
772
|
+
"type": "object",
|
|
773
|
+
"required": ["query"],
|
|
774
|
+
"properties": {
|
|
775
|
+
"query": {"type": "string"},
|
|
776
|
+
"peer_ids": {"type": "array", "items": {"type": "string"}},
|
|
777
|
+
"max_per_peer": {"type": "integer"}
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
"name": "fed_stats",
|
|
783
|
+
"description": "联邦共享统计(v5.8.6)。",
|
|
784
|
+
"inputSchema": {
|
|
785
|
+
"type": "object",
|
|
786
|
+
"required": [],
|
|
787
|
+
"properties": {}
|
|
788
|
+
},
|
|
789
|
+
},
|
|
790
|
+
# ---- v6.0.0 功能 1-8 ----
|
|
791
|
+
{
|
|
792
|
+
"name": "chat_import",
|
|
793
|
+
"description": "功能 1:一键导入记忆(ChatGPT/Claude 对话导出,v6.0.0)。",
|
|
794
|
+
"inputSchema": {
|
|
795
|
+
"type": "object",
|
|
796
|
+
"required": ["data"],
|
|
797
|
+
"properties": {
|
|
798
|
+
"data": {"type": "object", "description": "ChatGPT/Claude 导出 JSON"},
|
|
799
|
+
"dry_run": {"type": "boolean"}
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
"name": "timeline_viz",
|
|
805
|
+
"description": "功能 2:记忆时间线可视化(聚合统计,不含明文,v6.0.0)。",
|
|
806
|
+
"inputSchema": {
|
|
807
|
+
"type": "object",
|
|
808
|
+
"required": [],
|
|
809
|
+
"properties": {
|
|
810
|
+
"category": {"type": "string"},
|
|
811
|
+
"days": {"type": "integer"}
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
"name": "skill_pack_export",
|
|
817
|
+
"description": "功能 3:导出可分享技能包(v6.0.0)。",
|
|
818
|
+
"inputSchema": {"type": "object", "required": [], "properties": {}},
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
"name": "skill_pack_import",
|
|
822
|
+
"description": "功能 3:导入技能包(v6.0.0)。",
|
|
823
|
+
"inputSchema": {
|
|
824
|
+
"type": "object",
|
|
825
|
+
"required": ["pack"],
|
|
826
|
+
"properties": {
|
|
827
|
+
"pack": {"type": "object"},
|
|
828
|
+
"mode": {"type": "string", "enum": ["merge", "replace"]}
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
"name": "memory_evolve",
|
|
834
|
+
"description": "功能 4:记忆演变(保留旧版本 + 记录原因,v6.0.0)。",
|
|
835
|
+
"inputSchema": {
|
|
836
|
+
"type": "object",
|
|
837
|
+
"required": ["memory_id"],
|
|
838
|
+
"properties": {
|
|
839
|
+
"memory_id": {"type": "string"},
|
|
840
|
+
"content": {"type": "string"},
|
|
841
|
+
"category": {"type": "string"},
|
|
842
|
+
"reason": {"type": "string"}
|
|
843
|
+
}
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
"name": "memory_evolution_history",
|
|
848
|
+
"description": "功能 4:记忆演变历史(v6.0.0)。",
|
|
849
|
+
"inputSchema": {
|
|
850
|
+
"type": "object",
|
|
851
|
+
"required": ["memory_id"],
|
|
852
|
+
"properties": {"memory_id": {"type": "string"}, "limit": {"type": "integer"}}
|
|
853
|
+
},
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
"name": "memory_learn_outcome",
|
|
857
|
+
"description": "功能 5:按结果学习的记忆(v6.0.0)。",
|
|
858
|
+
"inputSchema": {
|
|
859
|
+
"type": "object",
|
|
860
|
+
"required": ["memory_id"],
|
|
861
|
+
"properties": {
|
|
862
|
+
"memory_id": {"type": "string"},
|
|
863
|
+
"outcome": {"type": "string", "enum": ["success", "partial", "failure"]},
|
|
864
|
+
"note": {"type": "string"}
|
|
865
|
+
}
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
"name": "passport_export",
|
|
870
|
+
"description": "功能 6:导出记忆护照(跨模型迁移,v6.0.0)。",
|
|
871
|
+
"inputSchema": {"type": "object", "required": [], "properties": {}},
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
"name": "passport_import",
|
|
875
|
+
"description": "功能 6:导入记忆护照(v6.0.0)。",
|
|
876
|
+
"inputSchema": {
|
|
877
|
+
"type": "object",
|
|
878
|
+
"required": ["passport"],
|
|
879
|
+
"properties": {
|
|
880
|
+
"passport": {"type": "object"},
|
|
881
|
+
"mode": {"type": "string", "enum": ["merge", "replace"]}
|
|
882
|
+
}
|
|
883
|
+
},
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
"name": "passport_revoke",
|
|
887
|
+
"description": "功能 6:撤销记忆护照导入(可逆,v6.0.0)。",
|
|
888
|
+
"inputSchema": {
|
|
889
|
+
"type": "object",
|
|
890
|
+
"required": ["passport_id"],
|
|
891
|
+
"properties": {"passport_id": {"type": "string"}}
|
|
892
|
+
},
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
"name": "forget_explain",
|
|
896
|
+
"description": "功能 7:解释记忆的遗忘原因(v6.0.0)。",
|
|
897
|
+
"inputSchema": {
|
|
898
|
+
"type": "object",
|
|
899
|
+
"required": ["memory_id"],
|
|
900
|
+
"properties": {"memory_id": {"type": "string"}}
|
|
901
|
+
},
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
"name": "forget_run",
|
|
905
|
+
"description": ("功能 7:执行有原则遗忘 GC(v6.0.0)。"
|
|
906
|
+
"v6.1.2:days 为冷却窗口,创建不足 days 天的记忆不归档,"
|
|
907
|
+
"0 表示关闭冷却。"),
|
|
908
|
+
"inputSchema": {
|
|
909
|
+
"type": "object",
|
|
910
|
+
"required": [],
|
|
911
|
+
"properties": {
|
|
912
|
+
"dry_run": {"type": "boolean"},
|
|
913
|
+
"days": {"type": "integer",
|
|
914
|
+
"description": "冷却天数(默认 30,0 = 不过滤新记忆)"}
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
"name": "follow_up_create",
|
|
920
|
+
"description": "功能 8:登记跟进事项(主动收尾,v6.0.0)。",
|
|
921
|
+
"inputSchema": {
|
|
922
|
+
"type": "object",
|
|
923
|
+
"required": ["memory_id"],
|
|
924
|
+
"properties": {
|
|
925
|
+
"memory_id": {"type": "string"},
|
|
926
|
+
"note": {"type": "string"},
|
|
927
|
+
"due_at": {"type": "number"}
|
|
928
|
+
}
|
|
929
|
+
},
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
"name": "follow_up_due",
|
|
933
|
+
"description": "功能 8:查询到期未办结跟进(v6.0.0)。",
|
|
934
|
+
"inputSchema": {
|
|
935
|
+
"type": "object",
|
|
936
|
+
"required": [],
|
|
937
|
+
"properties": {"limit": {"type": "integer"}}
|
|
938
|
+
},
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
"name": "follow_up_resolve",
|
|
942
|
+
"description": "功能 8:办结跟进事项(v6.0.0)。",
|
|
943
|
+
"inputSchema": {
|
|
944
|
+
"type": "object",
|
|
945
|
+
"required": ["follow_up_id"],
|
|
946
|
+
"properties": {"follow_up_id": {"type": "string"}}
|
|
947
|
+
},
|
|
948
|
+
},
|
|
949
|
+
{
|
|
950
|
+
"name": "follow_up_nag",
|
|
951
|
+
"description": "功能 8:主动催办到期未办结跟进并生成回执(v6.1.1)。",
|
|
952
|
+
"inputSchema": {
|
|
953
|
+
"type": "object",
|
|
954
|
+
"required": [],
|
|
955
|
+
"properties": {
|
|
956
|
+
"interval_hours": {"type": "number"},
|
|
957
|
+
"max_nags": {"type": "integer"}
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
},
|
|
961
|
+
# ---- v6.0.0 正式版:v6 端口总览 ----
|
|
962
|
+
{
|
|
963
|
+
"name": "v6_status",
|
|
964
|
+
"description": "v6.0.0 正式版:查看 v6 端口总览(多 Agent 协作 + 6G AI 记忆预览)。",
|
|
965
|
+
"inputSchema": {
|
|
966
|
+
"type": "object",
|
|
967
|
+
"required": [],
|
|
968
|
+
"properties": {}
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
# ---- v6.0.0 正式版:多 Agent 记忆空间 ----
|
|
972
|
+
{
|
|
973
|
+
"name": "space_create",
|
|
974
|
+
"description": "v6.0.0 正式版:创建多 Agent 共享记忆空间(owner 自动加入)。",
|
|
975
|
+
"inputSchema": {
|
|
976
|
+
"type": "object",
|
|
977
|
+
"required": ["name", "owner_agent"],
|
|
978
|
+
"properties": {
|
|
979
|
+
"name": {"type": "string"},
|
|
980
|
+
"owner_agent": {"type": "string"},
|
|
981
|
+
"description": {"type": "string"},
|
|
982
|
+
"policy": {"type": "string", "enum": ["shared", "restricted"], "default": "shared"}
|
|
983
|
+
}
|
|
984
|
+
},
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
"name": "space_list",
|
|
988
|
+
"description": "v6.0.0 正式版:列出多 Agent 记忆空间(可只看我加入的)。",
|
|
989
|
+
"inputSchema": {
|
|
990
|
+
"type": "object",
|
|
991
|
+
"required": [],
|
|
992
|
+
"properties": {
|
|
993
|
+
"agent_id": {"type": "string"},
|
|
994
|
+
"mine": {"type": "boolean", "default": False}
|
|
995
|
+
}
|
|
996
|
+
},
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
"name": "space_join",
|
|
1000
|
+
"description": "v6.0.0 正式版:以 reader 身份加入多 Agent 记忆空间。",
|
|
1001
|
+
"inputSchema": {
|
|
1002
|
+
"type": "object",
|
|
1003
|
+
"required": ["space", "agent_id"],
|
|
1004
|
+
"properties": {
|
|
1005
|
+
"space": {"type": "string"},
|
|
1006
|
+
"agent_id": {"type": "string"}
|
|
1007
|
+
}
|
|
1008
|
+
},
|
|
1009
|
+
},
|
|
1010
|
+
{
|
|
1011
|
+
"name": "space_add_member",
|
|
1012
|
+
"description": "v6.0.0 正式版:向空间添加成员并分配角色(仅 owner,editor/reader)。",
|
|
1013
|
+
"inputSchema": {
|
|
1014
|
+
"type": "object",
|
|
1015
|
+
"required": ["space", "member_agent", "actor"],
|
|
1016
|
+
"properties": {
|
|
1017
|
+
"space": {"type": "string"},
|
|
1018
|
+
"member_agent": {"type": "string"},
|
|
1019
|
+
"role": {"type": "string", "enum": ["editor", "reader"], "default": "reader"},
|
|
1020
|
+
"actor": {"type": "string"}
|
|
1021
|
+
}
|
|
1022
|
+
},
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
"name": "space_share",
|
|
1026
|
+
"description": "v6.0.0 正式版:把记忆共享进多 Agent 空间(PRIVATE/STRICT 禁止)。",
|
|
1027
|
+
"inputSchema": {
|
|
1028
|
+
"type": "object",
|
|
1029
|
+
"required": ["space", "memory_id", "actor"],
|
|
1030
|
+
"properties": {
|
|
1031
|
+
"space": {"type": "string"},
|
|
1032
|
+
"memory_id": {"type": "string"},
|
|
1033
|
+
"actor": {"type": "string"}
|
|
1034
|
+
}
|
|
1035
|
+
},
|
|
1036
|
+
},
|
|
1037
|
+
{
|
|
1038
|
+
"name": "space_memories",
|
|
1039
|
+
"description": "v6.0.0 正式版:列出空间中的共享记忆。",
|
|
1040
|
+
"inputSchema": {
|
|
1041
|
+
"type": "object",
|
|
1042
|
+
"required": ["space", "actor"],
|
|
1043
|
+
"properties": {
|
|
1044
|
+
"space": {"type": "string"},
|
|
1045
|
+
"actor": {"type": "string"},
|
|
1046
|
+
"limit": {"type": "integer", "default": 50}
|
|
1047
|
+
}
|
|
1048
|
+
},
|
|
1049
|
+
},
|
|
1050
|
+
{
|
|
1051
|
+
"name": "space_stats",
|
|
1052
|
+
"description": "v6.0.0 正式版:多 Agent 记忆空间统计(单空间或全局)。",
|
|
1053
|
+
"inputSchema": {
|
|
1054
|
+
"type": "object",
|
|
1055
|
+
"required": [],
|
|
1056
|
+
"properties": {"space": {"type": "string"}}
|
|
1057
|
+
},
|
|
1058
|
+
},
|
|
1059
|
+
# ---- v6.0.0 正式版:6G AI 记忆预览 ----
|
|
1060
|
+
{
|
|
1061
|
+
"name": "preview_open",
|
|
1062
|
+
"description": "6G:开启预览通道(与稳定版并行、互不污染,独立 SQLite 预览库)。",
|
|
1063
|
+
"inputSchema": {
|
|
1064
|
+
"type": "object",
|
|
1065
|
+
"required": [],
|
|
1066
|
+
"properties": {"config": {"type": "object"}}
|
|
1067
|
+
},
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
"name": "preview_close",
|
|
1071
|
+
"description": ("6G:关闭并清理预览通道。"
|
|
1072
|
+
"v6.1.2:purge=false 时只标记关闭、保留独立预览库文件。"),
|
|
1073
|
+
"inputSchema": {
|
|
1074
|
+
"type": "object",
|
|
1075
|
+
"required": ["channel_id"],
|
|
1076
|
+
"properties": {
|
|
1077
|
+
"channel_id": {"type": "string"},
|
|
1078
|
+
"purge": {"type": "boolean",
|
|
1079
|
+
"description": "是否删除独立库文件(默认 true)"}
|
|
1080
|
+
}
|
|
1081
|
+
},
|
|
1082
|
+
},
|
|
1083
|
+
{
|
|
1084
|
+
"name": "preview_channels",
|
|
1085
|
+
"description": "6G:列出预览通道。",
|
|
1086
|
+
"inputSchema": {
|
|
1087
|
+
"type": "object",
|
|
1088
|
+
"required": [],
|
|
1089
|
+
"properties": {"include_closed": {"type": "boolean", "default": False}}
|
|
1090
|
+
},
|
|
1091
|
+
},
|
|
1092
|
+
{
|
|
1093
|
+
"name": "preview_snapshot",
|
|
1094
|
+
"description": "6G:把主库记忆快照进预览通道(不修改主库)。",
|
|
1095
|
+
"inputSchema": {
|
|
1096
|
+
"type": "object",
|
|
1097
|
+
"required": ["channel_id", "memory_id"],
|
|
1098
|
+
"properties": {
|
|
1099
|
+
"channel_id": {"type": "string"},
|
|
1100
|
+
"memory_id": {"type": "string"}
|
|
1101
|
+
}
|
|
1102
|
+
},
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
"name": "preview_distill",
|
|
1106
|
+
"description": "6G:上下文蒸馏(分层摘要 HIGH 保留全文 + Token 压缩)。",
|
|
1107
|
+
"inputSchema": {
|
|
1108
|
+
"type": "object",
|
|
1109
|
+
"required": ["memory_ids"],
|
|
1110
|
+
"properties": {
|
|
1111
|
+
"memory_ids": {"type": "array", "items": {"type": "string"}},
|
|
1112
|
+
"max_tokens": {"type": "integer", "default": 2000}
|
|
1113
|
+
}
|
|
1114
|
+
},
|
|
1115
|
+
},
|
|
1116
|
+
{
|
|
1117
|
+
"name": "preview_index",
|
|
1118
|
+
"description": "6G:增量索引统计(写入即索引的近实时召回指标)。",
|
|
1119
|
+
"inputSchema": {
|
|
1120
|
+
"type": "object",
|
|
1121
|
+
"required": [],
|
|
1122
|
+
"properties": {}
|
|
1123
|
+
},
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
"name": "preview_timeline",
|
|
1127
|
+
"description": "6G:单记忆时间线(版本历史 + 演变记录 + 审计足迹)。",
|
|
1128
|
+
"inputSchema": {
|
|
1129
|
+
"type": "object",
|
|
1130
|
+
"required": ["memory_id"],
|
|
1131
|
+
"properties": {"memory_id": {"type": "string"}}
|
|
1132
|
+
},
|
|
1133
|
+
},
|
|
1134
|
+
]
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
# ---------------------------------------------------------------------------
|
|
1138
|
+
# Enum helpers
|
|
1139
|
+
# ---------------------------------------------------------------------------
|
|
1140
|
+
|
|
1141
|
+
def _to_importance(s: Optional[str], default):
|
|
1142
|
+
from mindforge import Importance
|
|
1143
|
+
if not s:
|
|
1144
|
+
return default
|
|
1145
|
+
return Importance.from_string(s)
|
|
1146
|
+
|
|
1147
|
+
|
|
1148
|
+
def _to_layer(s: Optional[str], default):
|
|
1149
|
+
from mindforge import MemoryLayer
|
|
1150
|
+
if not s:
|
|
1151
|
+
return default
|
|
1152
|
+
return MemoryLayer.from_string(s)
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
def _to_list_str(v) -> List[str]:
|
|
1156
|
+
if v is None:
|
|
1157
|
+
return []
|
|
1158
|
+
if isinstance(v, str):
|
|
1159
|
+
return [t.strip() for t in v.split(",") if t.strip()]
|
|
1160
|
+
return [str(x) for x in v]
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
# v5.5.8: 必需参数校验装饰器
|
|
1164
|
+
def _require_args(args: Dict[str, Any], *required: str) -> Optional[Dict[str, Any]]:
|
|
1165
|
+
"""检查必需参数是否存在,返回 error dict 或 None(校验通过)"""
|
|
1166
|
+
missing = [k for k in required if k not in args or args[k] is None or args[k] == ""]
|
|
1167
|
+
if missing:
|
|
1168
|
+
return {"ok": False, "error": f"Missing required parameter(s): {', '.join(missing)}"}
|
|
1169
|
+
return None
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
# ---------------------------------------------------------------------------
|
|
1173
|
+
# Serialization helpers
|
|
1174
|
+
# ---------------------------------------------------------------------------
|
|
1175
|
+
|
|
1176
|
+
def _safe_int(v: Any, default: int = 0, lo: Optional[int] = None, hi: Optional[int] = None) -> int:
|
|
1177
|
+
"""Safely coerce arbitrary input to int, with optional range clamping.
|
|
1178
|
+
|
|
1179
|
+
Returns *default* for None / empty / non-numeric strings instead of raising.
|
|
1180
|
+
"""
|
|
1181
|
+
if v is None or v == "":
|
|
1182
|
+
return default
|
|
1183
|
+
try:
|
|
1184
|
+
iv = int(v)
|
|
1185
|
+
except (ValueError, TypeError):
|
|
1186
|
+
return default
|
|
1187
|
+
if lo is not None and iv < lo:
|
|
1188
|
+
iv = lo
|
|
1189
|
+
if hi is not None and iv > hi:
|
|
1190
|
+
iv = hi
|
|
1191
|
+
return iv
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
def _safe_float(v: Any, default: float = 0.0, lo: Optional[float] = None, hi: Optional[float] = None) -> float:
|
|
1195
|
+
"""Safely coerce arbitrary input to float, with optional range clamping.
|
|
1196
|
+
|
|
1197
|
+
v5.6.2 安全修复:拒绝 inf/nan 等非法值,防止异常输入直达底层。
|
|
1198
|
+
"""
|
|
1199
|
+
if v is None or v == "":
|
|
1200
|
+
return default
|
|
1201
|
+
try:
|
|
1202
|
+
fv = float(v)
|
|
1203
|
+
except (ValueError, TypeError):
|
|
1204
|
+
return default
|
|
1205
|
+
import math
|
|
1206
|
+
if math.isnan(fv) or math.isinf(fv):
|
|
1207
|
+
return default
|
|
1208
|
+
if lo is not None and fv < lo:
|
|
1209
|
+
fv = lo
|
|
1210
|
+
if hi is not None and fv > hi:
|
|
1211
|
+
fv = hi
|
|
1212
|
+
return fv
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
def _clean(v: Any) -> Any:
|
|
1216
|
+
if isinstance(v, dict):
|
|
1217
|
+
return {str(k): _clean(x) for k, x in v.items()}
|
|
1218
|
+
if isinstance(v, (list, tuple, set)):
|
|
1219
|
+
return [_clean(x) for x in v]
|
|
1220
|
+
if hasattr(v, "isoformat"):
|
|
1221
|
+
try:
|
|
1222
|
+
return v.isoformat()
|
|
1223
|
+
except Exception:
|
|
1224
|
+
return str(v)
|
|
1225
|
+
if hasattr(v, "value"):
|
|
1226
|
+
return str(v.value)
|
|
1227
|
+
return v
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
def _entry_to_dict(m) -> Dict[str, Any]:
|
|
1231
|
+
d: Dict[str, Any] = {
|
|
1232
|
+
"id": str(m.id),
|
|
1233
|
+
"content": str(m.content),
|
|
1234
|
+
"category": str(m.category),
|
|
1235
|
+
"tags": list(m.tags) if m.tags else [],
|
|
1236
|
+
"importance": _clean(m.importance),
|
|
1237
|
+
"layer": _clean(m.layer),
|
|
1238
|
+
"agent_id": str(getattr(m, "source_agent", "") or ""),
|
|
1239
|
+
"created_at": None,
|
|
1240
|
+
"updated_at": None,
|
|
1241
|
+
"starred": bool(getattr(m, "starred", False)),
|
|
1242
|
+
"pinned": bool(getattr(m, "pinned", False)),
|
|
1243
|
+
}
|
|
1244
|
+
ca = getattr(m, "created_at", None)
|
|
1245
|
+
ua = getattr(m, "updated_at", None)
|
|
1246
|
+
if ca is not None:
|
|
1247
|
+
try:
|
|
1248
|
+
d["created_at"] = ca.isoformat()
|
|
1249
|
+
except Exception:
|
|
1250
|
+
d["created_at"] = str(ca)
|
|
1251
|
+
if ua is not None:
|
|
1252
|
+
try:
|
|
1253
|
+
d["updated_at"] = ua.isoformat()
|
|
1254
|
+
except Exception:
|
|
1255
|
+
d["updated_at"] = str(ua)
|
|
1256
|
+
return d
|
|
1257
|
+
|
|
1258
|
+
|
|
1259
|
+
# ---------------------------------------------------------------------------
|
|
1260
|
+
# Tool handlers
|
|
1261
|
+
# ---------------------------------------------------------------------------
|
|
1262
|
+
|
|
1263
|
+
def h_memory_add(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1264
|
+
from mindforge import Importance, MemoryLayer, PrivacyLevel
|
|
1265
|
+
err = _require_args(args, "content")
|
|
1266
|
+
if err:
|
|
1267
|
+
return err
|
|
1268
|
+
try:
|
|
1269
|
+
entry = mf.add(
|
|
1270
|
+
content=str(args["content"]),
|
|
1271
|
+
category=args.get("category") or "general",
|
|
1272
|
+
tags=_to_list_str(args.get("tags")),
|
|
1273
|
+
importance=_to_importance(args.get("importance"), Importance.MEDIUM),
|
|
1274
|
+
layer=_to_layer(args.get("layer"), MemoryLayer.SHORT_TERM),
|
|
1275
|
+
privacy=PrivacyLevel.INTERNAL,
|
|
1276
|
+
source_agent=args.get("agent_id") or "",
|
|
1277
|
+
valid_from=_safe_float(args.get("valid_from"), 0.0, 0.0),
|
|
1278
|
+
valid_to=_safe_float(args.get("valid_to"), 0.0, 0.0),
|
|
1279
|
+
)
|
|
1280
|
+
return {"ok": True, "memory": _entry_to_dict(entry)}
|
|
1281
|
+
except ValueError as e:
|
|
1282
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1283
|
+
|
|
1284
|
+
|
|
1285
|
+
def h_memory_search(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1286
|
+
err = _require_args(args, "query")
|
|
1287
|
+
if err:
|
|
1288
|
+
return err
|
|
1289
|
+
categories = [args["category"]] if args.get("category") else None
|
|
1290
|
+
use_emb = args.get("use_embedding", True)
|
|
1291
|
+
if not isinstance(use_emb, bool):
|
|
1292
|
+
use_emb = True
|
|
1293
|
+
# v6.3.2: explain 透传(默认 False 零开销;True 时每条结果携带得分分解)
|
|
1294
|
+
explain = args.get("explain", False)
|
|
1295
|
+
if not isinstance(explain, bool):
|
|
1296
|
+
explain = False
|
|
1297
|
+
res = mf.search(
|
|
1298
|
+
query=str(args["query"]),
|
|
1299
|
+
max_results=_safe_int(args.get("max_results", 5), 5, 1, 100),
|
|
1300
|
+
min_relevance=_safe_float(args.get("min_relevance", 0.0), 0.0, 0.0, 1.0),
|
|
1301
|
+
categories=categories,
|
|
1302
|
+
agent_id=args.get("agent_id") or "",
|
|
1303
|
+
use_embedding=use_emb,
|
|
1304
|
+
explain=explain,
|
|
1305
|
+
)
|
|
1306
|
+
items = []
|
|
1307
|
+
for c in res.chunks:
|
|
1308
|
+
item = {
|
|
1309
|
+
"relevance_score": float(c.relevance_score),
|
|
1310
|
+
"memory_id": str(c.memory_id),
|
|
1311
|
+
"content": str(c.content),
|
|
1312
|
+
"category": str(c.category),
|
|
1313
|
+
"tags": list(getattr(c, "tags", None) or []),
|
|
1314
|
+
"layer": _clean(getattr(c, "layer", None)),
|
|
1315
|
+
}
|
|
1316
|
+
# v6.3.2: 仅 explain=true 时携带(不请求则响应形状与旧版完全一致)
|
|
1317
|
+
if explain:
|
|
1318
|
+
item["score_breakdown"] = getattr(c, "score_breakdown", None)
|
|
1319
|
+
items.append(item)
|
|
1320
|
+
return {"count": len(items), "results": items}
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
def h_memory_list(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1324
|
+
entries = mf.list(
|
|
1325
|
+
category=args.get("category") or None,
|
|
1326
|
+
layer=_to_layer(args.get("layer"), None),
|
|
1327
|
+
limit=_safe_int(args.get("limit", 20), 20, 1, 10000),
|
|
1328
|
+
offset=_safe_int(args.get("offset", 0), 0, 0),
|
|
1329
|
+
sort_by=str(args.get("sort_by", "created_at")),
|
|
1330
|
+
# F1 修复:MCP 列表入口接入隐私过滤。传了 actor 才过滤,
|
|
1331
|
+
# 未传时保持原有"返回全部"语义(兼容既有客户端)。
|
|
1332
|
+
actor=str(args.get("actor") or ""),
|
|
1333
|
+
session_id=str(args.get("session_id") or ""),
|
|
1334
|
+
)
|
|
1335
|
+
return {"count": len(entries), "results": [_entry_to_dict(e) for e in entries]}
|
|
1336
|
+
|
|
1337
|
+
|
|
1338
|
+
def h_memory_stats(mf, _args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1339
|
+
s = mf.stats()
|
|
1340
|
+
return _clean(s)
|
|
1341
|
+
|
|
1342
|
+
|
|
1343
|
+
def h_memory_importance(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1344
|
+
err = _require_args(args, "agent_id")
|
|
1345
|
+
if err:
|
|
1346
|
+
return err
|
|
1347
|
+
agent_id = str(args["agent_id"])
|
|
1348
|
+
return {"agent_id": agent_id, "result": _clean(mf.storage.memory_importance(agent_id))}
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
def h_memory_context(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1352
|
+
err = _require_args(args, "agent_id", "query")
|
|
1353
|
+
if err:
|
|
1354
|
+
return err
|
|
1355
|
+
agent_id = str(args["agent_id"])
|
|
1356
|
+
query = str(args["query"])
|
|
1357
|
+
token_budget = _safe_int(args.get("token_budget", 4000), 4000, 256, 128000)
|
|
1358
|
+
return {
|
|
1359
|
+
"agent_id": agent_id,
|
|
1360
|
+
"query": query,
|
|
1361
|
+
"token_budget": token_budget,
|
|
1362
|
+
"result": _clean(mf.storage.memory_context(agent_id, query, max_tokens=token_budget)),
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
|
|
1366
|
+
def h_agent_emotion(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1367
|
+
err = _require_args(args, "agent_id")
|
|
1368
|
+
if err:
|
|
1369
|
+
return err
|
|
1370
|
+
agent_id = str(args["agent_id"])
|
|
1371
|
+
days = _safe_int(args.get("days", 30), 30, 1, 3650)
|
|
1372
|
+
return {"agent_id": agent_id, "days": days,
|
|
1373
|
+
"result": _clean(mf.storage.agent_emotion(agent_id, days=days))}
|
|
1374
|
+
|
|
1375
|
+
|
|
1376
|
+
def h_char_relationship(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1377
|
+
err = _require_args(args, "drama_id")
|
|
1378
|
+
if err:
|
|
1379
|
+
return err
|
|
1380
|
+
drama_id = str(args["drama_id"])
|
|
1381
|
+
char1_id = str(args.get("char1_id") or "")
|
|
1382
|
+
char2_id = str(args.get("char2_id") or "")
|
|
1383
|
+
if not char1_id or not char2_id:
|
|
1384
|
+
return {
|
|
1385
|
+
"drama_id": drama_id,
|
|
1386
|
+
"error": "char_relationship 需要同时提供 char1_id 和 char2_id",
|
|
1387
|
+
"hint": "传 char1_id + char2_id 可分析特定角色关系;仅传 drama_id 不足以完成分析",
|
|
1388
|
+
}
|
|
1389
|
+
result = mf.storage.char_relationship(drama_id, char1_id, char2_id)
|
|
1390
|
+
return {"drama_id": drama_id, "char1_id": char1_id, "char2_id": char2_id,
|
|
1391
|
+
"result": _clean(result)}
|
|
1392
|
+
|
|
1393
|
+
|
|
1394
|
+
def h_drama_genre_trend(mf, _args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1395
|
+
return {"result": _clean(mf.storage.drama_genre_trend())}
|
|
1396
|
+
|
|
1397
|
+
|
|
1398
|
+
def h_drama_binge_score(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1399
|
+
err = _require_args(args, "drama_id")
|
|
1400
|
+
if err:
|
|
1401
|
+
return err
|
|
1402
|
+
drama_id = str(args["drama_id"])
|
|
1403
|
+
return {"drama_id": drama_id, "result": _clean(mf.storage.drama_binge_score(drama_id))}
|
|
1404
|
+
|
|
1405
|
+
|
|
1406
|
+
# ===== v5.4.1 六大能力 MCP handlers =====
|
|
1407
|
+
|
|
1408
|
+
def h_memory_reflection(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1409
|
+
err = _require_args(args, "agent_id")
|
|
1410
|
+
if err:
|
|
1411
|
+
return err
|
|
1412
|
+
agent_id = str(args["agent_id"])
|
|
1413
|
+
days = _safe_int(args.get("days", 30), 30, 1, 365)
|
|
1414
|
+
return {"agent_id": agent_id, "days": days,
|
|
1415
|
+
"result": _clean(mf.storage.memory_reflection(agent_id, days))}
|
|
1416
|
+
|
|
1417
|
+
|
|
1418
|
+
def h_memory_lineage(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1419
|
+
err = _require_args(args, "memory_id")
|
|
1420
|
+
if err:
|
|
1421
|
+
return err
|
|
1422
|
+
memory_id = str(args["memory_id"])
|
|
1423
|
+
return {"memory_id": memory_id,
|
|
1424
|
+
"result": _clean(mf.storage.memory_lineage(memory_id))}
|
|
1425
|
+
|
|
1426
|
+
|
|
1427
|
+
def h_memory_reinforce(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1428
|
+
err = _require_args(args, "agent_id")
|
|
1429
|
+
if err:
|
|
1430
|
+
return err
|
|
1431
|
+
agent_id = str(args["agent_id"])
|
|
1432
|
+
days = _safe_int(args.get("days", 90), 90, 1, 365)
|
|
1433
|
+
limit = _safe_int(args.get("limit", 10), 10, 1, 50)
|
|
1434
|
+
return {"agent_id": agent_id, "days": days, "limit": limit,
|
|
1435
|
+
"result": _clean(mf.storage.memory_reinforce(agent_id, days, limit))}
|
|
1436
|
+
|
|
1437
|
+
|
|
1438
|
+
def h_drama_plot_thread(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1439
|
+
err = _require_args(args, "drama_id")
|
|
1440
|
+
if err:
|
|
1441
|
+
return err
|
|
1442
|
+
drama_id = str(args["drama_id"])
|
|
1443
|
+
return {"drama_id": drama_id, "result": _clean(mf.storage.drama_plot_thread(drama_id))}
|
|
1444
|
+
|
|
1445
|
+
|
|
1446
|
+
def h_drama_episode_curve(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1447
|
+
err = _require_args(args, "drama_id")
|
|
1448
|
+
if err:
|
|
1449
|
+
return err
|
|
1450
|
+
drama_id = str(args["drama_id"])
|
|
1451
|
+
return {"drama_id": drama_id, "result": _clean(mf.storage.drama_episode_curve(drama_id))}
|
|
1452
|
+
|
|
1453
|
+
|
|
1454
|
+
def h_drama_screen_time(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1455
|
+
err = _require_args(args, "drama_id")
|
|
1456
|
+
if err:
|
|
1457
|
+
return err
|
|
1458
|
+
drama_id = str(args["drama_id"])
|
|
1459
|
+
return {"drama_id": drama_id, "result": _clean(mf.storage.drama_screen_time(drama_id))}
|
|
1460
|
+
|
|
1461
|
+
|
|
1462
|
+
# ===== v5.4.2 两大能力 MCP handlers =====
|
|
1463
|
+
|
|
1464
|
+
def h_fed_acl_add(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1465
|
+
err = _require_args(args, "principal", "resource")
|
|
1466
|
+
if err:
|
|
1467
|
+
return err
|
|
1468
|
+
expires_hours = args.get("expires_hours")
|
|
1469
|
+
try:
|
|
1470
|
+
expires_hours = float(expires_hours) if expires_hours is not None else None
|
|
1471
|
+
except (TypeError, ValueError):
|
|
1472
|
+
expires_hours = None
|
|
1473
|
+
result = mf.federated_acl.add_rule(
|
|
1474
|
+
principal=str(args["principal"]),
|
|
1475
|
+
resource=str(args["resource"]),
|
|
1476
|
+
operations=str(args.get("operations", "read")),
|
|
1477
|
+
effect=str(args.get("effect", "allow")),
|
|
1478
|
+
priority=_safe_int(args.get("priority", 100), 100, 0, 10000),
|
|
1479
|
+
trust_min=float(args.get("trust_min", 0.0) or 0.0),
|
|
1480
|
+
expires_hours=expires_hours,
|
|
1481
|
+
note=str(args.get("note", "")))
|
|
1482
|
+
return _clean(result)
|
|
1483
|
+
|
|
1484
|
+
|
|
1485
|
+
def h_fed_acl_remove(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1486
|
+
err = _require_args(args, "rule_id")
|
|
1487
|
+
if err:
|
|
1488
|
+
return err
|
|
1489
|
+
return _clean(mf.federated_acl.remove_rule(str(args["rule_id"])))
|
|
1490
|
+
|
|
1491
|
+
|
|
1492
|
+
def h_fed_acl_list(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1493
|
+
rules = mf.federated_acl.list_rules(
|
|
1494
|
+
principal=args.get("principal"),
|
|
1495
|
+
effect=args.get("effect"),
|
|
1496
|
+
limit=_safe_int(args.get("limit", 200), 200, 1, 1000))
|
|
1497
|
+
return {"count": len(rules), "rules": _clean(rules)}
|
|
1498
|
+
|
|
1499
|
+
|
|
1500
|
+
def h_fed_acl_check(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1501
|
+
err = _require_args(args, "peer_id", "memory_id")
|
|
1502
|
+
if err:
|
|
1503
|
+
return err
|
|
1504
|
+
peer_trust = args.get("peer_trust")
|
|
1505
|
+
try:
|
|
1506
|
+
peer_trust = float(peer_trust) if peer_trust is not None else None
|
|
1507
|
+
except (TypeError, ValueError):
|
|
1508
|
+
peer_trust = None
|
|
1509
|
+
tags = args.get("memory_tags") or []
|
|
1510
|
+
if isinstance(tags, str):
|
|
1511
|
+
tags = [tags]
|
|
1512
|
+
result = mf.federated_acl.check_access(
|
|
1513
|
+
peer_id=str(args["peer_id"]),
|
|
1514
|
+
memory_id=str(args["memory_id"]),
|
|
1515
|
+
operation=str(args.get("operation", "read")),
|
|
1516
|
+
peer_trust=peer_trust,
|
|
1517
|
+
memory_category=args.get("memory_category"),
|
|
1518
|
+
memory_tags=[str(t) for t in tags])
|
|
1519
|
+
return _clean(result)
|
|
1520
|
+
|
|
1521
|
+
|
|
1522
|
+
def h_fed_acl_stats(mf, _args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1523
|
+
return _clean(mf.federated_acl.acl_stats())
|
|
1524
|
+
|
|
1525
|
+
|
|
1526
|
+
def h_share_conflict_list(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1527
|
+
conflicts = mf.share_conflict.list_conflicts(
|
|
1528
|
+
status=args.get("status"),
|
|
1529
|
+
limit=_safe_int(args.get("limit", 50), 50, 1, 500))
|
|
1530
|
+
return {"count": len(conflicts), "conflicts": _clean(conflicts)}
|
|
1531
|
+
|
|
1532
|
+
|
|
1533
|
+
def h_share_conflict_resolve(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1534
|
+
err = _require_args(args, "conflict_id", "strategy")
|
|
1535
|
+
if err:
|
|
1536
|
+
return err
|
|
1537
|
+
return _clean(mf.share_conflict.resolve(
|
|
1538
|
+
str(args["conflict_id"]), str(args["strategy"]),
|
|
1539
|
+
actor=str(args.get("actor", ""))))
|
|
1540
|
+
|
|
1541
|
+
|
|
1542
|
+
def h_share_conflict_dismiss(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1543
|
+
err = _require_args(args, "conflict_id")
|
|
1544
|
+
if err:
|
|
1545
|
+
return err
|
|
1546
|
+
return _clean(mf.share_conflict.dismiss(
|
|
1547
|
+
str(args["conflict_id"]), actor=str(args.get("actor", ""))))
|
|
1548
|
+
|
|
1549
|
+
|
|
1550
|
+
def h_share_conflict_stats(mf, _args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1551
|
+
return _clean(mf.share_conflict.stats())
|
|
1552
|
+
|
|
1553
|
+
|
|
1554
|
+
# ===== v5.3.9 五大能力 MCP handlers =====
|
|
1555
|
+
|
|
1556
|
+
def h_intent_router(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1557
|
+
err = _require_args(args, "text")
|
|
1558
|
+
if err:
|
|
1559
|
+
return err
|
|
1560
|
+
result = mf.classify_intent(str(args["text"]), force=args.get("force"))
|
|
1561
|
+
return _clean(result)
|
|
1562
|
+
|
|
1563
|
+
|
|
1564
|
+
def h_conflict_scan(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1565
|
+
return _clean(mf.scan_conflicts(
|
|
1566
|
+
category=args.get("category"),
|
|
1567
|
+
limit=_safe_int(args.get("limit", 500), 500, 1, 5000),
|
|
1568
|
+
apply_decay=bool(args.get("apply_decay", False)),
|
|
1569
|
+
))
|
|
1570
|
+
|
|
1571
|
+
|
|
1572
|
+
def h_skill_extract(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1573
|
+
return _clean(mf.extract_skills(
|
|
1574
|
+
category=args.get("category"),
|
|
1575
|
+
limit=_safe_int(args.get("limit", 2000), 2000, 1, 50000),
|
|
1576
|
+
min_cluster_size=max(1, _safe_int(args.get("min_cluster_size", 2), 2, 1, 100)),
|
|
1577
|
+
))
|
|
1578
|
+
|
|
1579
|
+
|
|
1580
|
+
def h_rerank_search(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1581
|
+
err = _require_args(args, "query")
|
|
1582
|
+
if err:
|
|
1583
|
+
return err
|
|
1584
|
+
return _clean(mf.search_enhanced(
|
|
1585
|
+
query=str(args["query"]),
|
|
1586
|
+
max_results=min(50, max(1, _safe_int(args.get("max_results", 10), 10, 1, 50))),
|
|
1587
|
+
expand=bool(args.get("expand", True)),
|
|
1588
|
+
rerank=bool(args.get("rerank", True)),
|
|
1589
|
+
))
|
|
1590
|
+
|
|
1591
|
+
|
|
1592
|
+
def h_session_focus(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1593
|
+
raw_msgs = args.get("messages") or []
|
|
1594
|
+
msgs: List[Dict[str, Any]] = []
|
|
1595
|
+
for i, m in enumerate(raw_msgs, 1):
|
|
1596
|
+
if not isinstance(m, dict):
|
|
1597
|
+
continue
|
|
1598
|
+
msgs.append({
|
|
1599
|
+
"id": str(m.get("id") or f"m{i}"),
|
|
1600
|
+
"role": str(m.get("role") or "user").lower(),
|
|
1601
|
+
"content": str(m.get("content") or ""),
|
|
1602
|
+
"timestamp": float(m.get("timestamp") or i),
|
|
1603
|
+
})
|
|
1604
|
+
return _clean(mf.session_focus(
|
|
1605
|
+
msgs,
|
|
1606
|
+
window_size=max(5, _safe_int(args.get("window_size", 40), 40, 5, 500)),
|
|
1607
|
+
augment_query=args.get("augment_query"),
|
|
1608
|
+
))
|
|
1609
|
+
|
|
1610
|
+
|
|
1611
|
+
def h_rebuild_embeddings(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1612
|
+
batch_size = _safe_int(args.get("batch_size", 100), 100, 1, 1000)
|
|
1613
|
+
return _clean(mf.rebuild_embeddings(batch_size=batch_size))
|
|
1614
|
+
|
|
1615
|
+
|
|
1616
|
+
def h_embedding_status(mf, _args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1617
|
+
return _clean(mf.get_embedding_status())
|
|
1618
|
+
|
|
1619
|
+
|
|
1620
|
+
def h_memory_diff(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1621
|
+
err = _require_args(args, "version_a", "version_b")
|
|
1622
|
+
if err:
|
|
1623
|
+
return err
|
|
1624
|
+
return _clean(mf.memory_diff(
|
|
1625
|
+
str(args["version_a"]),
|
|
1626
|
+
str(args["version_b"]),
|
|
1627
|
+
))
|
|
1628
|
+
|
|
1629
|
+
|
|
1630
|
+
def h_memory_supersede(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1631
|
+
err = _require_args(args, "id", "content")
|
|
1632
|
+
if err:
|
|
1633
|
+
return err
|
|
1634
|
+
try:
|
|
1635
|
+
new_id = mf.supersede(
|
|
1636
|
+
entry_id=str(args["id"]),
|
|
1637
|
+
content=str(args["content"]),
|
|
1638
|
+
category=args.get("category"),
|
|
1639
|
+
tags=_to_list_str(args["tags"]) if args.get("tags") else None,
|
|
1640
|
+
importance=_to_importance(args["importance"], None) if args.get("importance") else None,
|
|
1641
|
+
layer=_to_layer(args["layer"], None) if args.get("layer") else None,
|
|
1642
|
+
actor=args.get("agent_id") or "",
|
|
1643
|
+
valid_from=_safe_float(args.get("valid_from"), 0.0, 0.0) or None,
|
|
1644
|
+
)
|
|
1645
|
+
except ValueError as e:
|
|
1646
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1647
|
+
if not new_id:
|
|
1648
|
+
return {"ok": False, "error": "memory not found or not accessible"}
|
|
1649
|
+
return {"ok": True, "new_id": new_id}
|
|
1650
|
+
|
|
1651
|
+
|
|
1652
|
+
def h_memory_valid_at(mf, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
1653
|
+
ts_raw = args.get("ts")
|
|
1654
|
+
ts_v = None if ts_raw is None else _safe_float(ts_raw, 0.0, 0.0)
|
|
1655
|
+
entries = mf.valid_at(
|
|
1656
|
+
timestamp=ts_v,
|
|
1657
|
+
category=args.get("category"),
|
|
1658
|
+
limit=_safe_int(args.get("limit", 50), 50, 1, 500),
|
|
1659
|
+
actor=args.get("actor") or "",
|
|
1660
|
+
session_id=args.get("session_id") or "",
|
|
1661
|
+
)
|
|
1662
|
+
return {
|
|
1663
|
+
"as_of": ts_v,
|
|
1664
|
+
"total": len(entries),
|
|
1665
|
+
"memories": [_entry_to_dict(e) for e in entries],
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
|
|
1669
|
+
def h_memory_agent_pin(mf, args):
|
|
1670
|
+
err = _require_args(args, "id")
|
|
1671
|
+
if err:
|
|
1672
|
+
return err
|
|
1673
|
+
try:
|
|
1674
|
+
ok = mf.agent_pin(str(args["id"]), importance=args.get("importance"),
|
|
1675
|
+
actor=args.get("agent_id") or "")
|
|
1676
|
+
except ValueError as e:
|
|
1677
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1678
|
+
return {"ok": ok}
|
|
1679
|
+
|
|
1680
|
+
|
|
1681
|
+
def h_memory_agent_forget(mf, args):
|
|
1682
|
+
err = _require_args(args, "id")
|
|
1683
|
+
if err:
|
|
1684
|
+
return err
|
|
1685
|
+
try:
|
|
1686
|
+
ok = mf.agent_forget(str(args["id"]), reason=args.get("reason") or "",
|
|
1687
|
+
actor=args.get("agent_id") or "")
|
|
1688
|
+
except ValueError as e:
|
|
1689
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1690
|
+
return {"ok": ok}
|
|
1691
|
+
|
|
1692
|
+
|
|
1693
|
+
def h_memory_agent_decay_boost(mf, args):
|
|
1694
|
+
err = _require_args(args, "id")
|
|
1695
|
+
if err:
|
|
1696
|
+
return err
|
|
1697
|
+
try:
|
|
1698
|
+
ok = mf.agent_decay_boost(
|
|
1699
|
+
str(args["id"]),
|
|
1700
|
+
amount=_safe_float(args.get("amount", 1.0), 1.0, 0.1, 10.0),
|
|
1701
|
+
actor=args.get("agent_id") or "")
|
|
1702
|
+
except ValueError as e:
|
|
1703
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1704
|
+
return {"ok": ok}
|
|
1705
|
+
|
|
1706
|
+
|
|
1707
|
+
def h_memory_connector_ingest(mf, args):
|
|
1708
|
+
err = _require_args(args, "connector", "source")
|
|
1709
|
+
if err:
|
|
1710
|
+
return err
|
|
1711
|
+
try:
|
|
1712
|
+
stats = mf.connector_ingest(
|
|
1713
|
+
str(args["connector"]), str(args["source"]),
|
|
1714
|
+
category=args.get("category"),
|
|
1715
|
+
source_agent=args.get("source_agent") or "connector")
|
|
1716
|
+
except ValueError as e:
|
|
1717
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1718
|
+
return {"ok": True, "stats": stats}
|
|
1719
|
+
|
|
1720
|
+
|
|
1721
|
+
def h_experience_record(mf, args):
|
|
1722
|
+
err = _require_args(args, "task")
|
|
1723
|
+
if err:
|
|
1724
|
+
return err
|
|
1725
|
+
try:
|
|
1726
|
+
case = mf.record_experience(
|
|
1727
|
+
task=str(args["task"]), content=args.get("content", ""),
|
|
1728
|
+
result=args.get("result", ""),
|
|
1729
|
+
outcome=str(args.get("outcome", "success") or "success"),
|
|
1730
|
+
duration_seconds=float(args.get("duration_seconds", 0.0) or 0.0),
|
|
1731
|
+
tags=args.get("tags"), source_memory_ids=args.get("source_memory_ids"),
|
|
1732
|
+
agent_id=str(args.get("agent_id", "") or ""))
|
|
1733
|
+
return {"ok": True, "case": case}
|
|
1734
|
+
except (ValueError, TypeError) as e:
|
|
1735
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1736
|
+
|
|
1737
|
+
|
|
1738
|
+
def h_chat_import(mf, args):
|
|
1739
|
+
data = args.get("data")
|
|
1740
|
+
if not isinstance(data, dict):
|
|
1741
|
+
return {"ok": False, "error": "Field 'data' must be an object"}
|
|
1742
|
+
try:
|
|
1743
|
+
return {"ok": True, **mf.import_chat_history(
|
|
1744
|
+
data, dry_run=bool(args.get("dry_run", False)))}
|
|
1745
|
+
except (ValueError, TypeError) as e:
|
|
1746
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1747
|
+
|
|
1748
|
+
|
|
1749
|
+
def h_timeline_viz(mf, args):
|
|
1750
|
+
try:
|
|
1751
|
+
return {"ok": True, **mf.timeline_visual(
|
|
1752
|
+
category=args.get("category") or None,
|
|
1753
|
+
days=int(args.get("days", 365) or 365))}
|
|
1754
|
+
except (ValueError, TypeError) as e:
|
|
1755
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1756
|
+
|
|
1757
|
+
|
|
1758
|
+
def h_skill_pack_export(mf, _args):
|
|
1759
|
+
try:
|
|
1760
|
+
return {"ok": True, **mf.skill_pack_export()}
|
|
1761
|
+
except Exception as e:
|
|
1762
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1763
|
+
|
|
1764
|
+
|
|
1765
|
+
def h_skill_pack_import(mf, args):
|
|
1766
|
+
pack = args.get("pack")
|
|
1767
|
+
if not isinstance(pack, dict):
|
|
1768
|
+
return {"ok": False, "error": "Field 'pack' must be an object"}
|
|
1769
|
+
try:
|
|
1770
|
+
v = mf.skill_pack_validate(pack)
|
|
1771
|
+
if not v.get("valid"):
|
|
1772
|
+
return {"ok": False, "error": "Invalid skill pack",
|
|
1773
|
+
"details": v.get("errors")}
|
|
1774
|
+
return {"ok": True, **mf.skill_pack_import(
|
|
1775
|
+
pack, mode=args.get("mode", "merge"))}
|
|
1776
|
+
except (ValueError, TypeError) as e:
|
|
1777
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1778
|
+
|
|
1779
|
+
|
|
1780
|
+
def h_memory_evolve(mf, args):
|
|
1781
|
+
err = _require_args(args, "memory_id")
|
|
1782
|
+
if err:
|
|
1783
|
+
return err
|
|
1784
|
+
try:
|
|
1785
|
+
return {"ok": True, **mf.evolve_memory(
|
|
1786
|
+
args["memory_id"], content=args.get("content"),
|
|
1787
|
+
category=args.get("category"), reason=args.get("reason", ""))}
|
|
1788
|
+
except (ValueError, TypeError) as e:
|
|
1789
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1790
|
+
|
|
1791
|
+
|
|
1792
|
+
def h_memory_evolution_history(mf, args):
|
|
1793
|
+
err = _require_args(args, "memory_id")
|
|
1794
|
+
if err:
|
|
1795
|
+
return err
|
|
1796
|
+
try:
|
|
1797
|
+
return {"ok": True, "memory_id": args["memory_id"],
|
|
1798
|
+
"evolutions": mf.list_evolutions(
|
|
1799
|
+
args["memory_id"], limit=int(args.get("limit", 50) or 50))}
|
|
1800
|
+
except (ValueError, TypeError) as e:
|
|
1801
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1802
|
+
|
|
1803
|
+
|
|
1804
|
+
def h_memory_learn_outcome(mf, args):
|
|
1805
|
+
err = _require_args(args, "memory_id")
|
|
1806
|
+
if err:
|
|
1807
|
+
return err
|
|
1808
|
+
try:
|
|
1809
|
+
return {"ok": True, **mf.learn_from_outcome(
|
|
1810
|
+
args["memory_id"], outcome=args.get("outcome", "success"),
|
|
1811
|
+
note=args.get("note", ""))}
|
|
1812
|
+
except (ValueError, TypeError) as e:
|
|
1813
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1814
|
+
|
|
1815
|
+
|
|
1816
|
+
def h_passport_export(mf, _args):
|
|
1817
|
+
try:
|
|
1818
|
+
return {"ok": True, **mf.passport_export()}
|
|
1819
|
+
except Exception as e:
|
|
1820
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1821
|
+
|
|
1822
|
+
|
|
1823
|
+
def h_passport_import(mf, args):
|
|
1824
|
+
pp = args.get("passport")
|
|
1825
|
+
if not isinstance(pp, dict):
|
|
1826
|
+
return {"ok": False, "error": "Field 'passport' must be an object"}
|
|
1827
|
+
try:
|
|
1828
|
+
return {"ok": True, **mf.passport_import(
|
|
1829
|
+
pp, mode=args.get("mode", "merge"))}
|
|
1830
|
+
except (ValueError, TypeError) as e:
|
|
1831
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1832
|
+
|
|
1833
|
+
|
|
1834
|
+
def h_passport_revoke(mf, args):
|
|
1835
|
+
err = _require_args(args, "passport_id")
|
|
1836
|
+
if err:
|
|
1837
|
+
return err
|
|
1838
|
+
try:
|
|
1839
|
+
return {"ok": True, **mf.passport_revoke(args["passport_id"])}
|
|
1840
|
+
except (ValueError, TypeError) as e:
|
|
1841
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1842
|
+
|
|
1843
|
+
|
|
1844
|
+
def h_forget_explain(mf, args):
|
|
1845
|
+
err = _require_args(args, "memory_id")
|
|
1846
|
+
if err:
|
|
1847
|
+
return err
|
|
1848
|
+
try:
|
|
1849
|
+
return {"ok": True, **mf.forget_explain(args["memory_id"])}
|
|
1850
|
+
except (ValueError, TypeError) as e:
|
|
1851
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1852
|
+
|
|
1853
|
+
|
|
1854
|
+
def h_forget_run(mf, args):
|
|
1855
|
+
try:
|
|
1856
|
+
# v6.1.2:接通 days 冷却窗口(此前 core 有参数但三层都传不进去)
|
|
1857
|
+
return {"ok": True, **mf.forget_run(
|
|
1858
|
+
dry_run=bool(args.get("dry_run", False)),
|
|
1859
|
+
days=_safe_int(args.get("days", 30), default=30, lo=0, hi=3650))}
|
|
1860
|
+
except Exception as e:
|
|
1861
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1862
|
+
|
|
1863
|
+
|
|
1864
|
+
def h_follow_up_create(mf, args):
|
|
1865
|
+
err = _require_args(args, "memory_id")
|
|
1866
|
+
if err:
|
|
1867
|
+
return err
|
|
1868
|
+
try:
|
|
1869
|
+
return {"ok": True, **mf.follow_up_create(
|
|
1870
|
+
args["memory_id"], note=args.get("note", ""),
|
|
1871
|
+
due_at=float(args.get("due_at", 0) or 0))}
|
|
1872
|
+
except (ValueError, TypeError) as e:
|
|
1873
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1874
|
+
|
|
1875
|
+
|
|
1876
|
+
def h_follow_up_due(mf, args):
|
|
1877
|
+
try:
|
|
1878
|
+
return {"ok": True, "due": mf.follow_up_due(
|
|
1879
|
+
limit=int(args.get("limit", 20) or 20))}
|
|
1880
|
+
except (ValueError, TypeError) as e:
|
|
1881
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1882
|
+
|
|
1883
|
+
|
|
1884
|
+
def h_follow_up_resolve(mf, args):
|
|
1885
|
+
err = _require_args(args, "follow_up_id")
|
|
1886
|
+
if err:
|
|
1887
|
+
return err
|
|
1888
|
+
try:
|
|
1889
|
+
return {"ok": True, **mf.follow_up_resolve(args["follow_up_id"])}
|
|
1890
|
+
except (ValueError, TypeError) as e:
|
|
1891
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1892
|
+
|
|
1893
|
+
|
|
1894
|
+
def h_follow_up_nag(mf, args):
|
|
1895
|
+
try:
|
|
1896
|
+
return {"ok": True, **mf.follow_up_nag(
|
|
1897
|
+
interval_hours=float(args.get("interval_hours", 24.0) or 24.0),
|
|
1898
|
+
max_nags=int(args.get("max_nags", 3) or 3))}
|
|
1899
|
+
except (ValueError, TypeError) as e:
|
|
1900
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1901
|
+
|
|
1902
|
+
|
|
1903
|
+
# ---------------------------------------------------------------------------
|
|
1904
|
+
# v6.0.0 正式版:v6 端口总览 + 多 Agent 空间 + 6G AI 记忆预览 handlers
|
|
1905
|
+
# ---------------------------------------------------------------------------
|
|
1906
|
+
|
|
1907
|
+
def h_v6_status(mf, _args):
|
|
1908
|
+
try:
|
|
1909
|
+
return {"ok": True, **mf.v6_status()}
|
|
1910
|
+
except (ValueError, TypeError) as e:
|
|
1911
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1912
|
+
|
|
1913
|
+
|
|
1914
|
+
def h_space_create(mf, args):
|
|
1915
|
+
err = _require_args(args, "name", "owner_agent")
|
|
1916
|
+
if err:
|
|
1917
|
+
return err
|
|
1918
|
+
try:
|
|
1919
|
+
return {"ok": True, **mf.multi_agent.create_space(
|
|
1920
|
+
name=args["name"], owner_agent=args["owner_agent"],
|
|
1921
|
+
description=args.get("description", ""),
|
|
1922
|
+
policy=args.get("policy", "shared"))}
|
|
1923
|
+
except (ValueError, TypeError) as e:
|
|
1924
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1925
|
+
|
|
1926
|
+
|
|
1927
|
+
def h_space_list(mf, args):
|
|
1928
|
+
try:
|
|
1929
|
+
agent_id = args.get("agent_id", "")
|
|
1930
|
+
if args.get("mine") and not agent_id:
|
|
1931
|
+
agent_id = "mcp"
|
|
1932
|
+
return {"ok": True, "spaces": mf.multi_agent.list_spaces(
|
|
1933
|
+
agent_id=agent_id)}
|
|
1934
|
+
except (ValueError, TypeError) as e:
|
|
1935
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1936
|
+
|
|
1937
|
+
|
|
1938
|
+
def h_space_join(mf, args):
|
|
1939
|
+
err = _require_args(args, "space", "agent_id")
|
|
1940
|
+
if err:
|
|
1941
|
+
return err
|
|
1942
|
+
try:
|
|
1943
|
+
return {"ok": True, **mf.multi_agent.add_member(
|
|
1944
|
+
space_ref=args["space"], agent_id=args["agent_id"],
|
|
1945
|
+
role="reader", actor=args["agent_id"])}
|
|
1946
|
+
except (ValueError, TypeError) as e:
|
|
1947
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1948
|
+
|
|
1949
|
+
|
|
1950
|
+
def h_space_add_member(mf, args):
|
|
1951
|
+
err = _require_args(args, "space", "member_agent", "actor")
|
|
1952
|
+
if err:
|
|
1953
|
+
return err
|
|
1954
|
+
try:
|
|
1955
|
+
return {"ok": True, **mf.multi_agent.add_member(
|
|
1956
|
+
space_ref=args["space"], agent_id=args["member_agent"],
|
|
1957
|
+
role=args.get("role", "reader"), actor=args["actor"])}
|
|
1958
|
+
except (ValueError, TypeError) as e:
|
|
1959
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1960
|
+
|
|
1961
|
+
|
|
1962
|
+
def h_space_share(mf, args):
|
|
1963
|
+
err = _require_args(args, "space", "memory_id", "actor")
|
|
1964
|
+
if err:
|
|
1965
|
+
return err
|
|
1966
|
+
try:
|
|
1967
|
+
return {"ok": True, **mf.multi_agent.share_memory(
|
|
1968
|
+
space_ref=args["space"], memory_id=args["memory_id"],
|
|
1969
|
+
actor=args["actor"])}
|
|
1970
|
+
except (ValueError, TypeError) as e:
|
|
1971
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1972
|
+
|
|
1973
|
+
|
|
1974
|
+
def h_space_memories(mf, args):
|
|
1975
|
+
err = _require_args(args, "space", "actor")
|
|
1976
|
+
if err:
|
|
1977
|
+
return err
|
|
1978
|
+
try:
|
|
1979
|
+
return {"ok": True, **mf.multi_agent.list_space_memories(
|
|
1980
|
+
space_ref=args["space"], actor=args["actor"],
|
|
1981
|
+
limit=int(args.get("limit", 50) or 50))}
|
|
1982
|
+
except (ValueError, TypeError) as e:
|
|
1983
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1984
|
+
|
|
1985
|
+
|
|
1986
|
+
def h_space_stats(mf, args):
|
|
1987
|
+
try:
|
|
1988
|
+
return {"ok": True, **mf.multi_agent.space_stats(
|
|
1989
|
+
space_ref=args.get("space", ""))}
|
|
1990
|
+
except (ValueError, TypeError) as e:
|
|
1991
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
1992
|
+
|
|
1993
|
+
|
|
1994
|
+
def h_preview_open(mf, args):
|
|
1995
|
+
try:
|
|
1996
|
+
cfg = args.get("config")
|
|
1997
|
+
if cfg is not None and not isinstance(cfg, dict):
|
|
1998
|
+
return {"ok": False, "error": "Field 'config' must be an object"}
|
|
1999
|
+
return {"ok": True, **mf.memory_preview.open_preview_channel(cfg)}
|
|
2000
|
+
except (ValueError, TypeError) as e:
|
|
2001
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
2002
|
+
|
|
2003
|
+
|
|
2004
|
+
def h_preview_close(mf, args):
|
|
2005
|
+
err = _require_args(args, "channel_id")
|
|
2006
|
+
if err:
|
|
2007
|
+
return err
|
|
2008
|
+
try:
|
|
2009
|
+
return {"ok": True, **mf.memory_preview.close_preview_channel(
|
|
2010
|
+
args["channel_id"], purge=bool(args.get("purge", True)))}
|
|
2011
|
+
except (ValueError, TypeError) as e:
|
|
2012
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
2013
|
+
|
|
2014
|
+
|
|
2015
|
+
def h_preview_channels(mf, args):
|
|
2016
|
+
try:
|
|
2017
|
+
return {"ok": True, "channels": mf.memory_preview.list_preview_channels(
|
|
2018
|
+
include_closed=bool(args.get("include_closed", False)))}
|
|
2019
|
+
except (ValueError, TypeError) as e:
|
|
2020
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
2021
|
+
|
|
2022
|
+
|
|
2023
|
+
def h_preview_snapshot(mf, args):
|
|
2024
|
+
err = _require_args(args, "channel_id", "memory_id")
|
|
2025
|
+
if err:
|
|
2026
|
+
return err
|
|
2027
|
+
try:
|
|
2028
|
+
return {"ok": True, **mf.memory_preview.snapshot_memory(
|
|
2029
|
+
args["channel_id"], args["memory_id"])}
|
|
2030
|
+
except (ValueError, TypeError) as e:
|
|
2031
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
2032
|
+
|
|
2033
|
+
|
|
2034
|
+
def h_preview_distill(mf, args):
|
|
2035
|
+
mids = args.get("memory_ids")
|
|
2036
|
+
if not isinstance(mids, list) or not mids:
|
|
2037
|
+
return {"ok": False, "error": "Missing 'memory_ids' array field"}
|
|
2038
|
+
try:
|
|
2039
|
+
return {"ok": True, **mf.memory_preview.distill_context(
|
|
2040
|
+
mids, max_tokens=int(args.get("max_tokens", 2000) or 2000))}
|
|
2041
|
+
except (ValueError, TypeError) as e:
|
|
2042
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
2043
|
+
|
|
2044
|
+
|
|
2045
|
+
def h_preview_index(mf, _args):
|
|
2046
|
+
try:
|
|
2047
|
+
return {"ok": True, **mf.memory_preview.incremental_index_stats()}
|
|
2048
|
+
except (ValueError, TypeError) as e:
|
|
2049
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
2050
|
+
|
|
2051
|
+
|
|
2052
|
+
def h_preview_timeline(mf, args):
|
|
2053
|
+
err = _require_args(args, "memory_id")
|
|
2054
|
+
if err:
|
|
2055
|
+
return err
|
|
2056
|
+
try:
|
|
2057
|
+
return {"ok": True, **mf.memory_preview.timeline(args["memory_id"])}
|
|
2058
|
+
except (ValueError, TypeError) as e:
|
|
2059
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
2060
|
+
|
|
2061
|
+
|
|
2062
|
+
def h_skill_distill(mf, args):
|
|
2063
|
+
try:
|
|
2064
|
+
stats = mf.distill_skills(
|
|
2065
|
+
min_cluster_size=int(args.get("min_cluster_size", 2) or 2),
|
|
2066
|
+
max_skills=int(args.get("max_skills", 50) or 50))
|
|
2067
|
+
return {"ok": True, **stats}
|
|
2068
|
+
except (ValueError, TypeError) as e:
|
|
2069
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
2070
|
+
|
|
2071
|
+
|
|
2072
|
+
def h_skill_match(mf, args):
|
|
2073
|
+
err = _require_args(args, "query")
|
|
2074
|
+
if err:
|
|
2075
|
+
return err
|
|
2076
|
+
try:
|
|
2077
|
+
return {"ok": True, "query": args["query"],
|
|
2078
|
+
"skills": mf.skill_match(str(args["query"]),
|
|
2079
|
+
limit=int(args.get("limit", 5) or 5))}
|
|
2080
|
+
except (ValueError, TypeError) as e:
|
|
2081
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
2082
|
+
|
|
2083
|
+
|
|
2084
|
+
def h_skill_render(mf, args):
|
|
2085
|
+
err = _require_args(args, "name")
|
|
2086
|
+
if err:
|
|
2087
|
+
return err
|
|
2088
|
+
try:
|
|
2089
|
+
params = args.get("params") or {}
|
|
2090
|
+
out = mf.skill_render(str(args["name"]), **params)
|
|
2091
|
+
if out is None:
|
|
2092
|
+
return {"ok": False, "error": f"skill not found: {args['name']}"}
|
|
2093
|
+
return {"ok": True, "rendered": out}
|
|
2094
|
+
except (ValueError, TypeError) as e:
|
|
2095
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
2096
|
+
|
|
2097
|
+
|
|
2098
|
+
def h_skill_stats(mf, args):
|
|
2099
|
+
try:
|
|
2100
|
+
return {"ok": True, "stats": mf.skill_stats()}
|
|
2101
|
+
except Exception as e:
|
|
2102
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
2103
|
+
|
|
2104
|
+
|
|
2105
|
+
def h_graph_stats(mf, args):
|
|
2106
|
+
try:
|
|
2107
|
+
return {"ok": True, "stats": mf.graph_stats()}
|
|
2108
|
+
except Exception as e:
|
|
2109
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
2110
|
+
|
|
2111
|
+
|
|
2112
|
+
def h_graph_related(mf, args):
|
|
2113
|
+
err = _require_args(args, "entity")
|
|
2114
|
+
if err:
|
|
2115
|
+
return err
|
|
2116
|
+
try:
|
|
2117
|
+
return {"ok": True, "entity": args["entity"],
|
|
2118
|
+
"related": mf.graph_related(str(args["entity"]), depth=int(args.get("depth", 2)))}
|
|
2119
|
+
except Exception as e:
|
|
2120
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
2121
|
+
|
|
2122
|
+
|
|
2123
|
+
def h_graph_extract(mf, args):
|
|
2124
|
+
try:
|
|
2125
|
+
mid = args.get("memory_id", "") or ""
|
|
2126
|
+
stats = mf.extract_graph(memory_id=mid) if mid else mf.extract_graph()
|
|
2127
|
+
return {"ok": True, **stats}
|
|
2128
|
+
except ValueError as e:
|
|
2129
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
2130
|
+
|
|
2131
|
+
|
|
2132
|
+
def h_conflict_reconcile(mf, args):
|
|
2133
|
+
try:
|
|
2134
|
+
auto = args.get("auto", True)
|
|
2135
|
+
if not isinstance(auto, bool):
|
|
2136
|
+
raise ValueError("Field 'auto' must be a boolean")
|
|
2137
|
+
result = mf.reconcile_conflicts(
|
|
2138
|
+
memory_ids=args.get("memory_ids"),
|
|
2139
|
+
auto=auto)
|
|
2140
|
+
except ValueError as e:
|
|
2141
|
+
return {"ok": False, "error": _safe_error_msg(e)}
|
|
2142
|
+
return {"ok": True, **result}
|
|
2143
|
+
|
|
2144
|
+
|
|
2145
|
+
|
|
2146
|
+
# ---------------------------------------------------------------------------
|
|
2147
|
+
# v5.8.6 新增:联邦共享 MCP 工具(对等节点管理 + 跨节点共享/检索)
|
|
2148
|
+
# ---------------------------------------------------------------------------
|
|
2149
|
+
def h_fed_peer_register(mf, args):
|
|
2150
|
+
err = _require_args(args, "peer_id", "name")
|
|
2151
|
+
if err:
|
|
2152
|
+
return err
|
|
2153
|
+
return _clean(mf.federated.register_peer(
|
|
2154
|
+
str(args["peer_id"]), str(args["name"]),
|
|
2155
|
+
trust_level=float(args.get('trust_level', 0.5)),
|
|
2156
|
+
shared_categories=args.get('shared_categories')))
|
|
2157
|
+
|
|
2158
|
+
|
|
2159
|
+
def h_fed_peer_remove(mf, args):
|
|
2160
|
+
err = _require_args(args, "peer_id")
|
|
2161
|
+
if err:
|
|
2162
|
+
return err
|
|
2163
|
+
return _clean(mf.federated.remove_peer(str(args["peer_id"])))
|
|
2164
|
+
|
|
2165
|
+
|
|
2166
|
+
def h_fed_peer_list(mf, _args):
|
|
2167
|
+
return _clean(mf.federated.get_peers())
|
|
2168
|
+
|
|
2169
|
+
|
|
2170
|
+
def h_fed_memory_share(mf, args):
|
|
2171
|
+
err = _require_args(args, "memory_id", "peer_ids")
|
|
2172
|
+
if err:
|
|
2173
|
+
return err
|
|
2174
|
+
return _clean(mf.federated.share_memory(
|
|
2175
|
+
str(args["memory_id"]), list(args["peer_ids"]),
|
|
2176
|
+
access_policy=str(args.get('access_policy', 'read_only')),
|
|
2177
|
+
expires_hours=args.get('expires_hours')))
|
|
2178
|
+
|
|
2179
|
+
|
|
2180
|
+
def h_fed_memory_revoke(mf, args):
|
|
2181
|
+
err = _require_args(args, "memory_id")
|
|
2182
|
+
if err:
|
|
2183
|
+
return err
|
|
2184
|
+
return _clean(mf.federated.revoke_share(
|
|
2185
|
+
str(args["memory_id"]), args.get("peer_ids")))
|
|
2186
|
+
|
|
2187
|
+
|
|
2188
|
+
def h_fed_memory_list_shared(mf, args):
|
|
2189
|
+
return _clean(mf.federated.get_shared_memories(
|
|
2190
|
+
peer_id=args.get('peer_id')))
|
|
2191
|
+
|
|
2192
|
+
|
|
2193
|
+
def h_fed_search(mf, args):
|
|
2194
|
+
err = _require_args(args, "query")
|
|
2195
|
+
if err:
|
|
2196
|
+
return err
|
|
2197
|
+
return _clean(mf.federated.federated_search(
|
|
2198
|
+
str(args["query"]),
|
|
2199
|
+
peer_ids=args.get('peer_ids'),
|
|
2200
|
+
max_per_peer=_safe_int(args.get('max_per_peer', 5), 5, 1, 50)))
|
|
2201
|
+
|
|
2202
|
+
|
|
2203
|
+
def h_fed_stats(mf, _args):
|
|
2204
|
+
return _clean(mf.federated.compute_federated_stats())
|
|
2205
|
+
|
|
2206
|
+
HANDLERS: Dict[str, Any] = {
|
|
2207
|
+
"memory_add": h_memory_add,
|
|
2208
|
+
"memory_search": h_memory_search,
|
|
2209
|
+
"memory_list": h_memory_list,
|
|
2210
|
+
"memory_stats": h_memory_stats,
|
|
2211
|
+
"memory_importance": h_memory_importance,
|
|
2212
|
+
"memory_context": h_memory_context,
|
|
2213
|
+
"agent_emotion": h_agent_emotion,
|
|
2214
|
+
"char_relationship": h_char_relationship,
|
|
2215
|
+
"drama_genre_trend": h_drama_genre_trend,
|
|
2216
|
+
"drama_binge_score": h_drama_binge_score,
|
|
2217
|
+
# v5.4.1 新增
|
|
2218
|
+
"memory_reflection": h_memory_reflection,
|
|
2219
|
+
"memory_lineage": h_memory_lineage,
|
|
2220
|
+
"memory_reinforce": h_memory_reinforce,
|
|
2221
|
+
"drama_plot_thread": h_drama_plot_thread,
|
|
2222
|
+
"drama_episode_curve": h_drama_episode_curve,
|
|
2223
|
+
"drama_screen_time": h_drama_screen_time,
|
|
2224
|
+
# v5.4.2 新增
|
|
2225
|
+
"fed_acl_add": h_fed_acl_add,
|
|
2226
|
+
"fed_acl_remove": h_fed_acl_remove,
|
|
2227
|
+
"fed_acl_list": h_fed_acl_list,
|
|
2228
|
+
"fed_acl_check": h_fed_acl_check,
|
|
2229
|
+
"fed_acl_stats": h_fed_acl_stats,
|
|
2230
|
+
"share_conflict_list": h_share_conflict_list,
|
|
2231
|
+
"share_conflict_resolve": h_share_conflict_resolve,
|
|
2232
|
+
"share_conflict_dismiss": h_share_conflict_dismiss,
|
|
2233
|
+
"share_conflict_stats": h_share_conflict_stats,
|
|
2234
|
+
# v5.3.9 新增
|
|
2235
|
+
"intent_router": h_intent_router,
|
|
2236
|
+
"conflict_scan": h_conflict_scan,
|
|
2237
|
+
"skill_extract": h_skill_extract,
|
|
2238
|
+
"rerank_search": h_rerank_search,
|
|
2239
|
+
"session_focus": h_session_focus,
|
|
2240
|
+
# v5.4.5 新增向量检索
|
|
2241
|
+
"rebuild_embeddings": h_rebuild_embeddings,
|
|
2242
|
+
"embedding_status": h_embedding_status,
|
|
2243
|
+
# v5.5.8 新增
|
|
2244
|
+
"memory_diff": h_memory_diff,
|
|
2245
|
+
# v5.7.6 新增:Bi-temporal 事实时序
|
|
2246
|
+
"memory_supersede": h_memory_supersede,
|
|
2247
|
+
"memory_valid_at": h_memory_valid_at,
|
|
2248
|
+
# v5.7.7 新增:Agent 记忆治理 + 连接器 + 冲突调和
|
|
2249
|
+
"memory_agent_pin": h_memory_agent_pin,
|
|
2250
|
+
"memory_agent_forget": h_memory_agent_forget,
|
|
2251
|
+
"memory_agent_decay_boost": h_memory_agent_decay_boost,
|
|
2252
|
+
"memory_connector_ingest": h_memory_connector_ingest,
|
|
2253
|
+
"conflict_reconcile": h_conflict_reconcile,
|
|
2254
|
+
# v5.7.9 新增:知识图谱
|
|
2255
|
+
"memory_graph_stats": h_graph_stats,
|
|
2256
|
+
"memory_graph_related": h_graph_related,
|
|
2257
|
+
"memory_graph_extract": h_graph_extract,
|
|
2258
|
+
# v5.8.0 新增:程序性记忆 / 经验蒸馏
|
|
2259
|
+
"memory_experience_record": h_experience_record,
|
|
2260
|
+
"memory_skill_distill": h_skill_distill,
|
|
2261
|
+
"memory_skill_match": h_skill_match,
|
|
2262
|
+
"memory_skill_render": h_skill_render,
|
|
2263
|
+
"memory_skill_stats": h_skill_stats,
|
|
2264
|
+
# v5.8.6 新增:联邦共享 MCP
|
|
2265
|
+
"fed_peer_register": h_fed_peer_register,
|
|
2266
|
+
"fed_peer_remove": h_fed_peer_remove,
|
|
2267
|
+
"fed_peer_list": h_fed_peer_list,
|
|
2268
|
+
"fed_memory_share": h_fed_memory_share,
|
|
2269
|
+
"fed_memory_revoke": h_fed_memory_revoke,
|
|
2270
|
+
"fed_memory_list_shared": h_fed_memory_list_shared,
|
|
2271
|
+
"fed_search": h_fed_search,
|
|
2272
|
+
"fed_stats": h_fed_stats,
|
|
2273
|
+
# v6.0.0 功能 1-8
|
|
2274
|
+
"chat_import": h_chat_import,
|
|
2275
|
+
"timeline_viz": h_timeline_viz,
|
|
2276
|
+
"skill_pack_export": h_skill_pack_export,
|
|
2277
|
+
"skill_pack_import": h_skill_pack_import,
|
|
2278
|
+
"memory_evolve": h_memory_evolve,
|
|
2279
|
+
"memory_evolution_history": h_memory_evolution_history,
|
|
2280
|
+
"memory_learn_outcome": h_memory_learn_outcome,
|
|
2281
|
+
"passport_export": h_passport_export,
|
|
2282
|
+
"passport_import": h_passport_import,
|
|
2283
|
+
"passport_revoke": h_passport_revoke,
|
|
2284
|
+
"forget_explain": h_forget_explain,
|
|
2285
|
+
"forget_run": h_forget_run,
|
|
2286
|
+
"follow_up_create": h_follow_up_create,
|
|
2287
|
+
"follow_up_due": h_follow_up_due,
|
|
2288
|
+
"follow_up_resolve": h_follow_up_resolve,
|
|
2289
|
+
"follow_up_nag": h_follow_up_nag,
|
|
2290
|
+
# v6.0.0 正式版:v6 端口总览 + 多 Agent 空间 + 6G AI 记忆预览
|
|
2291
|
+
"v6_status": h_v6_status,
|
|
2292
|
+
"space_create": h_space_create,
|
|
2293
|
+
"space_list": h_space_list,
|
|
2294
|
+
"space_join": h_space_join,
|
|
2295
|
+
"space_add_member": h_space_add_member,
|
|
2296
|
+
"space_share": h_space_share,
|
|
2297
|
+
"space_memories": h_space_memories,
|
|
2298
|
+
"space_stats": h_space_stats,
|
|
2299
|
+
"preview_open": h_preview_open,
|
|
2300
|
+
"preview_close": h_preview_close,
|
|
2301
|
+
"preview_channels": h_preview_channels,
|
|
2302
|
+
"preview_snapshot": h_preview_snapshot,
|
|
2303
|
+
"preview_distill": h_preview_distill,
|
|
2304
|
+
"preview_index": h_preview_index,
|
|
2305
|
+
"preview_timeline": h_preview_timeline,
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
|
|
2309
|
+
# ---------------------------------------------------------------------------
|
|
2310
|
+
# MCP request dispatcher
|
|
2311
|
+
# ---------------------------------------------------------------------------
|
|
2312
|
+
|
|
2313
|
+
def _handle_initialize(request: Dict[str, Any]) -> Dict[str, Any]:
|
|
2314
|
+
_notify("notifications/initialized")
|
|
2315
|
+
return {
|
|
2316
|
+
"protocolVersion": "2024-11-05",
|
|
2317
|
+
"capabilities": {"tools": {}, "logging": {}},
|
|
2318
|
+
"serverInfo": {"name": "mindforge", "version": __version__},
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
|
|
2322
|
+
def _handle_tools_list(_request: Dict[str, Any]) -> Dict[str, Any]:
|
|
2323
|
+
return {"tools": list(TOOL_SCHEMAS)}
|
|
2324
|
+
|
|
2325
|
+
|
|
2326
|
+
def _is_tool_error(result: Any) -> bool:
|
|
2327
|
+
"""判断 handler 返回值是否表示「工具执行失败」。
|
|
2328
|
+
|
|
2329
|
+
v6.1.2 修复:本项目的 handler 有两种失败写法——`{"ok": False, "error": ...}`
|
|
2330
|
+
(参数校验、_require_args)与 `{"success": False, "error": ...}`(core 门面
|
|
2331
|
+
直接透传)。此前 `_handle_tools_call` 无论成败都返回同一个成功信封,MCP 客户端
|
|
2332
|
+
只能解析文本才知道失败,把报错当结果用(例如自动化流程会以为写入成功)。
|
|
2333
|
+
|
|
2334
|
+
只认显式的 False:`ok`/`success` 缺失或为真值都按成功处理,避免把
|
|
2335
|
+
「返回体恰好带 success 字段」的正常结果误判为失败。
|
|
2336
|
+
"""
|
|
2337
|
+
if not isinstance(result, dict):
|
|
2338
|
+
return False
|
|
2339
|
+
return result.get("ok") is False or result.get("success") is False
|
|
2340
|
+
|
|
2341
|
+
|
|
2342
|
+
def _handle_tools_call(mf, request: Dict[str, Any]) -> Dict[str, Any]:
|
|
2343
|
+
params = request.get("params") or {}
|
|
2344
|
+
if not isinstance(params, dict):
|
|
2345
|
+
raise ValueError("Invalid params: expected object")
|
|
2346
|
+
name = params.get("name")
|
|
2347
|
+
arguments = params.get("arguments") or {}
|
|
2348
|
+
if not isinstance(arguments, dict):
|
|
2349
|
+
arguments = {}
|
|
2350
|
+
if name not in HANDLERS:
|
|
2351
|
+
raise ValueError(f"Unknown tool: {name}")
|
|
2352
|
+
result = HANDLERS[name](mf, arguments)
|
|
2353
|
+
text = json.dumps(result, ensure_ascii=False, indent=2, default=str)
|
|
2354
|
+
envelope: Dict[str, Any] = {"content": [{"type": "text", "text": text}]}
|
|
2355
|
+
# MCP spec:工具层失败用 isError 标记(JSON-RPC 本身仍是成功响应,
|
|
2356
|
+
# 失败信息随 content 返回,客户端据此区分「查询无结果」与「调用出错」)。
|
|
2357
|
+
if _is_tool_error(result):
|
|
2358
|
+
envelope["isError"] = True
|
|
2359
|
+
return envelope
|
|
2360
|
+
|
|
2361
|
+
|
|
2362
|
+
def _parse_args(argv: List[str]) -> Dict[str, Any]:
|
|
2363
|
+
cfg: Dict[str, Any] = {"db_path": None, "key_file": None, "auth_secret": None}
|
|
2364
|
+
i = 0
|
|
2365
|
+
while i < len(argv):
|
|
2366
|
+
a = argv[i]
|
|
2367
|
+
if a == "--db-path" and i + 1 < len(argv):
|
|
2368
|
+
cfg["db_path"] = argv[i + 1]; i += 2
|
|
2369
|
+
elif a.startswith("--db-path="):
|
|
2370
|
+
cfg["db_path"] = a.split("=", 1)[1]; i += 1
|
|
2371
|
+
elif a == "--key-file" and i + 1 < len(argv):
|
|
2372
|
+
cfg["key_file"] = argv[i + 1]; i += 2
|
|
2373
|
+
elif a.startswith("--key-file="):
|
|
2374
|
+
cfg["key_file"] = a.split("=", 1)[1]; i += 1
|
|
2375
|
+
elif a == "--auth-secret" and i + 1 < len(argv):
|
|
2376
|
+
cfg["auth_secret"] = argv[i + 1]; i += 2
|
|
2377
|
+
elif a.startswith("--auth-secret="):
|
|
2378
|
+
cfg["auth_secret"] = a.split("=", 1)[1]; i += 1
|
|
2379
|
+
else:
|
|
2380
|
+
i += 1
|
|
2381
|
+
return cfg
|
|
2382
|
+
|
|
2383
|
+
|
|
2384
|
+
def _check_auth(msg: Dict[str, Any], expected_secret: str) -> bool:
|
|
2385
|
+
"""认证检查:initialize 请求须携带 _meta.authSecret,与 shared-secret 比对"""
|
|
2386
|
+
if not expected_secret:
|
|
2387
|
+
return True
|
|
2388
|
+
params = msg.get("params") or {}
|
|
2389
|
+
meta = params.get("_meta") or {}
|
|
2390
|
+
client_secret = meta.get("authSecret", "")
|
|
2391
|
+
import hmac as _hmac
|
|
2392
|
+
return _hmac.compare_digest(str(client_secret), expected_secret)
|
|
2393
|
+
|
|
2394
|
+
|
|
2395
|
+
# v5.6.5 P2 #17:认证态改为下方加锁的 _AuthState 持有者(不再用裸全局 bool)
|
|
2396
|
+
_AUTH_IDLE_TIMEOUT = 30 * 60 # 30 分钟无活动自动登出(P2 #16 修复)
|
|
2397
|
+
|
|
2398
|
+
|
|
2399
|
+
class _AuthState:
|
|
2400
|
+
"""线程安全的认证状态(v5.6.5 P2 #17)。
|
|
2401
|
+
|
|
2402
|
+
此前认证态是裸全局 bool,多线程下读改写无原子保护;空闲时间戳改用
|
|
2403
|
+
time.monotonic(P3 #23),不受系统墙上时钟回拨影响。
|
|
2404
|
+
"""
|
|
2405
|
+
|
|
2406
|
+
def __init__(self):
|
|
2407
|
+
import threading
|
|
2408
|
+
self._lock = threading.Lock()
|
|
2409
|
+
self.authed = False
|
|
2410
|
+
self.last_activity = 0.0
|
|
2411
|
+
|
|
2412
|
+
def reset(self) -> None:
|
|
2413
|
+
with self._lock:
|
|
2414
|
+
self.authed = False
|
|
2415
|
+
self.last_activity = 0.0
|
|
2416
|
+
|
|
2417
|
+
def login(self, now: float) -> None:
|
|
2418
|
+
with self._lock:
|
|
2419
|
+
self.authed = True
|
|
2420
|
+
self.last_activity = now
|
|
2421
|
+
|
|
2422
|
+
def is_authed(self) -> bool:
|
|
2423
|
+
with self._lock:
|
|
2424
|
+
return self.authed
|
|
2425
|
+
|
|
2426
|
+
def authorize(self, now: float, timeout: float):
|
|
2427
|
+
"""已认证则刷新活跃时间返回 True;过期登出返回 'expired';未认证 False。"""
|
|
2428
|
+
with self._lock:
|
|
2429
|
+
if not self.authed:
|
|
2430
|
+
return False
|
|
2431
|
+
if now - self.last_activity > timeout:
|
|
2432
|
+
self.authed = False
|
|
2433
|
+
self.last_activity = 0.0
|
|
2434
|
+
return "expired"
|
|
2435
|
+
self.last_activity = now
|
|
2436
|
+
return True
|
|
2437
|
+
|
|
2438
|
+
|
|
2439
|
+
def serve_forever(db_path: Optional[str] = None, key_file: Optional[str] = None,
|
|
2440
|
+
auth_secret: Optional[str] = None) -> int:
|
|
2441
|
+
from mindforge import MindForge
|
|
2442
|
+
if not db_path:
|
|
2443
|
+
# v5.6.5 P2 #18:与 CLI 统一默认数据库路径(core.paths 单点真值)
|
|
2444
|
+
from mindforge.core.paths import get_default_db_path
|
|
2445
|
+
db_path = get_default_db_path()
|
|
2446
|
+
_log(f"db: {db_path}")
|
|
2447
|
+
kwargs: Dict[str, Any] = {"db_path": db_path, "encrypted": False}
|
|
2448
|
+
if key_file:
|
|
2449
|
+
kwargs["key_file"] = key_file
|
|
2450
|
+
kwargs["encrypted"] = True
|
|
2451
|
+
mf = MindForge(**kwargs)
|
|
2452
|
+
|
|
2453
|
+
# v5.6.5 P2 #17:认证状态收敛到加锁的 _AuthState 持有者
|
|
2454
|
+
_auth = _AuthState()
|
|
2455
|
+
expected_secret = auth_secret or os.environ.get("MINDFORGE_MCP_SECRET", "")
|
|
2456
|
+
|
|
2457
|
+
# v5.6.3 安全增强:未设置认证密钥时,MCP 服务对所有能连上的客户端开放。
|
|
2458
|
+
# 信任模型:MCP 走 stdin/stdout,传输层本身不认证对端——能拉起本进程或向
|
|
2459
|
+
# stdin 写数据的即被视为受信本地宿主。若把它桥接到网络/多用户环境,则属于
|
|
2460
|
+
# 暴露面,需要 shared-secret 认证(客户端在 initialize 携带 _meta.authSecret)。
|
|
2461
|
+
if not expected_secret:
|
|
2462
|
+
# P2-09 加固:受管/CI 环境可设 MINDFORGE_MCP_REQUIRE_AUTH 强制 fail-closed,
|
|
2463
|
+
# 避免误以无认证方式启动。
|
|
2464
|
+
require_auth = os.environ.get("MINDFORGE_MCP_REQUIRE_AUTH", "").strip().lower()
|
|
2465
|
+
if require_auth in ("1", "true", "yes", "on"):
|
|
2466
|
+
_log(
|
|
2467
|
+
"ERROR: MINDFORGE_MCP_REQUIRE_AUTH is set but MINDFORGE_MCP_SECRET "
|
|
2468
|
+
"(or --auth-secret) is empty. Refusing to start an unauthenticated "
|
|
2469
|
+
"MCP server."
|
|
2470
|
+
)
|
|
2471
|
+
return 2
|
|
2472
|
+
_log(
|
|
2473
|
+
"WARNING: MINDFORGE_MCP_SECRET not set — MCP server accepts all clients. "
|
|
2474
|
+
"This is only safe for a local stdio host you trust. If this server is "
|
|
2475
|
+
"reachable over a network, set MINDFORGE_MCP_SECRET to enable shared-secret "
|
|
2476
|
+
"authentication (client sends _meta.authSecret on initialize), or set "
|
|
2477
|
+
"MINDFORGE_MCP_REQUIRE_AUTH=1 to refuse startup without a secret."
|
|
2478
|
+
)
|
|
2479
|
+
|
|
2480
|
+
while True:
|
|
2481
|
+
try:
|
|
2482
|
+
msg = _read_message()
|
|
2483
|
+
except EOFError:
|
|
2484
|
+
_log("stdin closed")
|
|
2485
|
+
return 0
|
|
2486
|
+
except Exception as e:
|
|
2487
|
+
_log(f"read error: {e}")
|
|
2488
|
+
continue
|
|
2489
|
+
|
|
2490
|
+
method = msg.get("method", "")
|
|
2491
|
+
rid = msg.get("id")
|
|
2492
|
+
try:
|
|
2493
|
+
if method == "initialize":
|
|
2494
|
+
if _check_auth(msg, expected_secret):
|
|
2495
|
+
_auth.login(_time.monotonic())
|
|
2496
|
+
_respond(msg, _handle_initialize(msg))
|
|
2497
|
+
else:
|
|
2498
|
+
_auth.reset()
|
|
2499
|
+
if rid is not None:
|
|
2500
|
+
_respond_error(msg, code=-32001, message="Authentication failed")
|
|
2501
|
+
elif method == "notifications/initialized":
|
|
2502
|
+
pass
|
|
2503
|
+
elif expected_secret and not _auth.is_authed():
|
|
2504
|
+
if rid is not None:
|
|
2505
|
+
_respond_error(msg, code=-32001, message="Not authenticated")
|
|
2506
|
+
elif expected_secret and _auth.is_authed():
|
|
2507
|
+
# P2 #16 修复:认证过期检查(30 分钟无活动自动登出)。
|
|
2508
|
+
# v5.6.5:状态读改写在 _AuthState 锁内原子完成,计时用 monotonic。
|
|
2509
|
+
_authz = _auth.authorize(_time.monotonic(), _AUTH_IDLE_TIMEOUT)
|
|
2510
|
+
if _authz == "expired":
|
|
2511
|
+
if rid is not None:
|
|
2512
|
+
_respond_error(msg, code=-32001, message="Session expired")
|
|
2513
|
+
continue
|
|
2514
|
+
if method == "tools/list":
|
|
2515
|
+
_respond(msg, _handle_tools_list(msg))
|
|
2516
|
+
elif method == "tools/call":
|
|
2517
|
+
_respond(msg, _handle_tools_call(mf, msg))
|
|
2518
|
+
elif method == "ping":
|
|
2519
|
+
_respond(msg, {})
|
|
2520
|
+
elif method in ("shutdown", "exit"):
|
|
2521
|
+
if rid is not None:
|
|
2522
|
+
_respond(msg, {})
|
|
2523
|
+
return 0
|
|
2524
|
+
else:
|
|
2525
|
+
if rid is None:
|
|
2526
|
+
_log(f"ignored notification: {method}")
|
|
2527
|
+
else:
|
|
2528
|
+
_respond_error(msg, code=-32601, message=f"Method not found: {method}")
|
|
2529
|
+
elif method == "tools/list":
|
|
2530
|
+
_respond(msg, _handle_tools_list(msg))
|
|
2531
|
+
elif method == "tools/call":
|
|
2532
|
+
_respond(msg, _handle_tools_call(mf, msg))
|
|
2533
|
+
elif method == "ping":
|
|
2534
|
+
_respond(msg, {})
|
|
2535
|
+
elif method in ("shutdown", "exit"):
|
|
2536
|
+
if rid is not None:
|
|
2537
|
+
_respond(msg, {})
|
|
2538
|
+
return 0
|
|
2539
|
+
else:
|
|
2540
|
+
if rid is None:
|
|
2541
|
+
_log(f"ignored notification: {method}")
|
|
2542
|
+
else:
|
|
2543
|
+
_respond_error(msg, code=-32601, message=f"Method not found: {method}")
|
|
2544
|
+
except Exception as e:
|
|
2545
|
+
tb = traceback.format_exc(limit=6)
|
|
2546
|
+
_log(f"handler error {method}: {e}\n{tb}")
|
|
2547
|
+
if rid is not None:
|
|
2548
|
+
_respond_error(msg, code=-32000, message="Internal server error")
|
|
2549
|
+
|
|
2550
|
+
|
|
2551
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
2552
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
2553
|
+
cfg = _parse_args(argv)
|
|
2554
|
+
return serve_forever(db_path=cfg.get("db_path"), key_file=cfg.get("key_file"),
|
|
2555
|
+
auth_secret=cfg.get("auth_secret"))
|
|
2556
|
+
|
|
2557
|
+
|
|
2558
|
+
if __name__ == "__main__":
|
|
2559
|
+
sys.exit(main())
|