ppxans-harness 2.4.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +218 -265
  3. package/bin/ppx-channels.js +2 -2
  4. package/bin/ppx-serve.js +5 -5
  5. package/bin/ppx-setup.js +124 -0
  6. package/bin/ppx-web.js +140 -0
  7. package/bin/ppx.js +2 -2
  8. package/config/identity.md +6 -6
  9. package/config/ishiki.md +16 -16
  10. package/config/ppx.json +15 -151
  11. package/config/ppx.json.example +143 -0
  12. package/package.json +17 -10
  13. package/skills/.usage.json +6 -0
  14. package/skills/agent-professional-training/SKILL.md +94 -0
  15. package/skills/brainstorm/SKILL.md +24 -0
  16. package/skills/cupid-lover-comms/SKILL.md +37 -0
  17. package/skills/debug/SKILL.md +26 -0
  18. package/skills/plan/SKILL.md +25 -0
  19. package/skills/ponytail/SKILL.md +25 -0
  20. package/skills/ppx-memory/SKILL.md +91 -0
  21. package/skills/ppx-memory/scripts/cli.js +192 -0
  22. package/skills/ppx-memory/scripts/experience.js +133 -0
  23. package/skills/ppx-memory/scripts/fact-store.js +842 -0
  24. package/skills/ppx-memory/scripts/l0.js +52 -0
  25. package/skills/ppx-memory/scripts/l2.js +146 -0
  26. package/skills/ppx-memory/scripts/l3.js +112 -0
  27. package/skills/ppx-memory/scripts/memory-ticker.js +238 -0
  28. package/skills/ppx-memory/scripts/pii.js +42 -0
  29. package/skills/ppx-memory/scripts/schema.js +80 -0
  30. package/skills/ppx-memory/scripts/session.js +398 -0
  31. package/skills/ppx-memory/scripts/similarity.js +43 -0
  32. package/skills/ppx-memory/scripts/store.js +116 -0
  33. package/skills/ppx-memory/scripts/wal.js +38 -0
  34. package/skills/ppx-selfheal/SKILL.md +24 -0
  35. package/skills/ppx-selfheal/scripts/cli.js +80 -0
  36. package/skills/ppx-selfheal/scripts/healer.js +184 -0
  37. package/skills/ppx-selfheal/scripts/logger.js +17 -0
  38. package/skills/ppx-selfheal/scripts/store.js +116 -0
  39. package/skills/prompt-depth-kit/SKILL.md +28 -0
  40. package/skills/session-naming/SKILL.md +36 -0
  41. package/skills/verify/SKILL.md +25 -0
  42. package/src/agent/index.js +1340 -717
  43. package/src/agent/prompts.js +47 -3
  44. package/src/aml-server.js +197 -151
  45. package/src/ans/eviction.js +123 -143
  46. package/src/ans/guard.js +159 -120
  47. package/src/ans/lifecycle.js +96 -93
  48. package/src/ans/proactive.js +112 -129
  49. package/src/ans/reward.js +95 -111
  50. package/src/ans/values.js +15 -15
  51. package/src/audit/audit-chain.js +43 -7
  52. package/src/audit/verifier.js +157 -120
  53. package/src/bus/circuit-breaker.js +9 -1
  54. package/src/bus/runtime-bus.js +107 -93
  55. package/src/channels/base.js +57 -34
  56. package/src/channels/feishu.js +118 -126
  57. package/src/channels/http.js +1090 -592
  58. package/src/channels/index.js +111 -110
  59. package/src/channels/log.js +29 -29
  60. package/src/channels/wechat-crypto.js +73 -74
  61. package/src/channels/wechat.js +191 -197
  62. package/src/channels/workspace.js +94 -0
  63. package/src/channels-cli.js +126 -124
  64. package/src/cli.js +129 -120
  65. package/src/commands/index.js +142 -0
  66. package/src/config/channels.js +137 -170
  67. package/src/config/index.js +277 -224
  68. package/src/config/placeholder.js +31 -0
  69. package/src/config/providers.js +148 -188
  70. package/src/config/settings.js +156 -182
  71. package/src/core/policy.js +69 -18
  72. package/src/core/trace.js +8 -6
  73. package/src/edit/editblock.js +266 -0
  74. package/src/edit/snapshot.js +67 -0
  75. package/src/evidence/index.js +153 -0
  76. package/src/evolve/playbook.js +9 -10
  77. package/src/hooks/index.js +113 -0
  78. package/src/llm/client.js +188 -446
  79. package/src/llm/dsml.js +74 -74
  80. package/src/llm/embedder.js +41 -35
  81. package/src/llm/fence.js +52 -105
  82. package/src/llm/index.js +4 -4
  83. package/src/llm/local-embedder.js +94 -0
  84. package/src/llm/presets.js +113 -0
  85. package/src/llm/pricing.js +93 -0
  86. package/src/llm/retry.js +73 -73
  87. package/src/llm/router.js +92 -97
  88. package/src/mcp/admin.js +326 -0
  89. package/src/mcp/client.js +487 -375
  90. package/src/mcp/http.js +203 -0
  91. package/src/mcp/index.js +116 -116
  92. package/src/mcp/server.js +392 -0
  93. package/src/mcp/tasks.js +133 -0
  94. package/src/memory/asset-hub.js +6 -11
  95. package/src/memory/canvas.js +2 -5
  96. package/src/memory/compaction.js +28 -28
  97. package/src/memory/experience.js +133 -122
  98. package/src/memory/fact-store.js +914 -698
  99. package/src/memory/failure-episode.js +20 -11
  100. package/src/memory/fork.js +17 -8
  101. package/src/memory/index.js +8 -6
  102. package/src/memory/l0.js +53 -52
  103. package/src/memory/l2.js +145 -130
  104. package/src/memory/l3.js +111 -111
  105. package/src/memory/legion-board.js +71 -0
  106. package/src/memory/memory-ticker.js +239 -240
  107. package/src/memory/session.js +398 -397
  108. package/src/memory/sqlite-store.js +581 -0
  109. package/src/mode/blackboard.js +49 -49
  110. package/src/mode/graph.js +42 -41
  111. package/src/mode/index.js +64 -64
  112. package/src/mode/legion.js +54 -51
  113. package/src/mode/plan-exec.js +50 -50
  114. package/src/mode/router.js +28 -40
  115. package/src/orchestrator/agent-worker.js +69 -69
  116. package/src/orchestrator/dag.js +90 -83
  117. package/src/orchestrator/experts.js +76 -0
  118. package/src/orchestrator/index.js +1 -1
  119. package/src/orchestrator/legion.js +179 -187
  120. package/src/orchestrator/supervisor.js +6 -8
  121. package/src/permissions/index.js +378 -0
  122. package/src/persona/index.js +28 -29
  123. package/src/plugin/builtin.js +313 -212
  124. package/src/plugin/context.js +80 -79
  125. package/src/plugin/index.js +62 -62
  126. package/src/plugin/v3.js +73 -0
  127. package/src/protocol/index.js +148 -0
  128. package/src/repomap/index.js +309 -0
  129. package/src/review/index.js +393 -0
  130. package/src/seam/registry.js +3 -0
  131. package/src/seam/shell.js +55 -55
  132. package/src/security/injection.js +79 -0
  133. package/src/selfheal/evolve.js +67 -67
  134. package/src/selfheal/healer.js +184 -167
  135. package/src/selfheal/run.js +9 -9
  136. package/src/server.js +63 -60
  137. package/src/services/diagnose.js +180 -0
  138. package/src/services/learning-service.js +9 -0
  139. package/src/services/memory-health.js +34 -6
  140. package/src/services/memory-service.js +42 -9
  141. package/src/services/triage.js +138 -0
  142. package/src/session/parts.js +76 -0
  143. package/src/session/projection.js +73 -0
  144. package/src/session/rollout.js +54 -0
  145. package/src/session/turn.js +137 -0
  146. package/src/skills/lint.js +72 -0
  147. package/src/skills/loader.js +231 -150
  148. package/src/skills/search.js +58 -0
  149. package/src/skills/verify.js +95 -100
  150. package/src/tools/advanced.js +388 -352
  151. package/src/tools/builtin.js +384 -297
  152. package/src/tools/catalog.js +283 -159
  153. package/src/tools/command-guard.js +112 -112
  154. package/src/tools/custom.js +47 -47
  155. package/src/tools/delegate.js +383 -297
  156. package/src/tools/document.js +254 -253
  157. package/src/tools/git.js +151 -0
  158. package/src/tools/governance.js +47 -20
  159. package/src/tools/index.js +16 -11
  160. package/src/tools/methods.js +178 -178
  161. package/src/tools/ocr.js +59 -59
  162. package/src/tools/sandbox-worker.js +40 -0
  163. package/src/tools/sandbox.js +92 -0
  164. package/src/tools/seam.js +162 -125
  165. package/src/tools/selfmod.js +196 -176
  166. package/src/tools/v3.js +225 -0
  167. package/src/tools/vad.js +176 -0
  168. package/src/tools/voice.js +238 -0
  169. package/src/utils/async.js +14 -0
  170. package/src/utils/config-file.js +53 -0
  171. package/src/utils/crashguard.js +88 -0
  172. package/src/utils/http.js +53 -0
  173. package/src/utils/id.js +8 -0
  174. package/src/utils/json-state.js +33 -0
  175. package/src/utils/logger.js +17 -17
  176. package/src/utils/ndjson.js +25 -0
  177. package/src/utils/pii.js +42 -42
  178. package/src/utils/rate-limit.js +50 -0
  179. package/src/utils/schema.js +80 -0
  180. package/src/utils/similarity.js +43 -0
  181. package/src/utils/store.js +170 -108
  182. package/src/utils/text.js +15 -15
  183. package/src/utils/trace.js +153 -153
  184. package/src/utils/wal.js +39 -0
  185. package/src/utils/winutf8.js +16 -15
  186. package/src/wiki/index.js +170 -0
package/src/tools/seam.js CHANGED
@@ -1,125 +1,162 @@
1
- // src/tools/seam.js - 能力缝(Capability Seam)辅助层
2
- // 参考 deepseek-harness 的 Capability Seam 三分法:
3
- // Service Definition(声明/元数据) / Service Provider(execute 实现) / Consumer(runWithPolicy 统一策略入口)
4
- // 零依赖, 纯 Node 原生。保留皮皮虾原有错误语义, 追加超时门禁/禁用门禁/追踪回调。
5
-
6
- export const TOOL_ERROR_PREFIX = "[工具错误]";
7
-
8
- // power 权限级: user < agent < super
9
- export const POWER_LEVEL = { user: 0, agent: 1, super: 2 };
10
-
11
- // ---- Definition 层: 元数据归一化 + 校验 ----
12
- export function normalizeMeta(def = {}) {
13
- if (!def || typeof def.name !== "string" || !def.name) {
14
- throw new Error("能力缝 Definition 失败: 需 name");
15
- }
16
- return {
17
- name: def.name,
18
- description: def.description || "",
19
- parameters: def.parameters || { type: "object", properties: {}, required: [] },
20
- // 能力缝新增元数据
21
- category: def.category || "misc", // 能力域: file/net/system/memory/selfmod/...
22
- power: def.power || "user", // 权限级: user/agent(0栓塞)
23
- timeoutMs: Number(def.timeoutMs) || 0, // 0 = 不限时
24
- idempotent: !!def.idempotent, // 是否可安全重试
25
- enabled: def.enabled !== false, // 默认启用
26
- execute: def.execute,
27
- // 工具钩子链 (吸收 OpenClaw before/after 钩子):
28
- // before(args, ctx) -> undefined 继续 | 字符串短路 | throw 拒绝
29
- // after(args, result, ctx) -> 后处理(结果审计/清理), 错误不阻塞
30
- before: typeof def.before === "function" ? def.before : null,
31
- after: typeof def.after === "function" ? def.after : null,
32
- };
33
- }
34
-
35
- // ---- Consumer 层: 统一策略执行 ----
36
- // 统一处理: 禁用门禁 / 实现缺失 / 超时门禁 / 标准错误语义 / 追踪回调
37
- export async function runWithPolicy(meta, args, ctx = {}) {
38
- if (meta.enabled === false) {
39
- return `${TOOL_ERROR_PREFIX} ${meta.name}: 能力已禁用`;
40
- }
41
- // power 权限门禁: 仅当 ctx.power 明确提供时生效(向后兼容, 无 ctx.power 默认放行)
42
- if (ctx && ctx.power) {
43
- const need = POWER_LEVEL[meta.power] ?? 0;
44
- const have = POWER_LEVEL[ctx.power] ?? 0;
45
- if (have < need) {
46
- return `${TOOL_ERROR_PREFIX} ${meta.name}: 权限不足(需要 ${meta.power}, 当前 ${ctx.power})`;
47
- }
48
- }
49
- const fn = meta.execute;
50
- if (typeof fn !== "function") {
51
- return `${TOOL_ERROR_PREFIX} ${meta.name}: 无实现(Provider 缺失)`;
52
- }
53
- // before 钩子: 返回非 undefined 则短路(不执行), throw 则拒绝
54
- if (meta.before) {
55
- try {
56
- const shortCircuit = await meta.before(args, ctx);
57
- if (shortCircuit !== undefined && shortCircuit !== null) {
58
- return typeof shortCircuit === "string" ? shortCircuit : JSON.stringify(shortCircuit);
59
- }
60
- } catch (e) {
61
- return `${TOOL_ERROR_PREFIX} ${meta.name}: before 钩子拒绝: ${e.message}`;
62
- }
63
- }
64
- let timer = null;
65
- let timedOut = false;
66
- const ctrl = new AbortController();
67
- // 超时预算: 工具级声明优先 (meta.timeoutMs > 0), 否则全局默认 (ctx.timeoutMs, agent 从 config.agent.tool_timeout_ms 传入)
68
- // v1.6.0 (第四刀): 无声明工具不再永不超时 — 有保守全局默认兑底, 避免单个慢工具卡死整个对话
69
- const effectiveTimeout = meta.timeoutMs > 0 ? meta.timeoutMs : (Number(ctx.timeoutMs) || 0);
70
- if (effectiveTimeout > 0) {
71
- timer = setTimeout(() => { timedOut = true; ctrl.abort(); }, effectiveTimeout);
72
- }
73
- try {
74
- // v1.6.0 (第四刀): 双层超时兜底 —
75
- // 1) signal 传给 execute: 配合的工具提前终止释放资源 (资源超时)
76
- // 2) Promise.race 强制超时返回: 不响应 signal 的工具也不至于永远挂住对话 (语义超时兜底)
77
- // 这是文档指出的灰色地带: 光靠 abort 信号, 不配合的工具会无限期挂着。
78
- const run = () => (fn.length >= 2 ? fn(args, { ...ctx, signal: ctrl.signal }) : fn(args));
79
- let result;
80
- if (effectiveTimeout > 0) {
81
- let raceTimer = null;
82
- const timeoutGuard = new Promise((_, reject) => {
83
- raceTimer = setTimeout(() => reject(Object.assign(new Error("timeout"), { timedOut: true })), effectiveTimeout);
84
- });
85
- const pRun = Promise.resolve().then(run);
86
- // 哨兵: 超时赢时工具方后到的 reject 忽略, 防 unhandledRejection 崩进程
87
- pRun.catch(() => {});
88
- try {
89
- result = await Promise.race([pRun, timeoutGuard]);
90
- } catch (e) {
91
- if (e && e.timedOut) timedOut = true;
92
- throw e;
93
- } finally {
94
- if (raceTimer) clearTimeout(raceTimer);
95
- }
96
- } else {
97
- result = await run();
98
- }
99
- if (timedOut) return `${TOOL_ERROR_PREFIX} ${meta.name}: 超时`;
100
- if (meta.after) {
101
- try { await meta.after(args, result, ctx); } catch { /* after 钩子错误不阻塞 */ }
102
- }
103
- if (typeof ctx.onResult === "function") ctx.onResult(meta.name, "ok", null);
104
- return typeof result === "string" ? result : JSON.stringify(result);
105
- } catch (e) {
106
- if (typeof ctx.onResult === "function") ctx.onResult(meta.name, "error", e.message);
107
- return `${TOOL_ERROR_PREFIX} ${meta.name}: ${timedOut ? "超时" : e.message}`;
108
- } finally {
109
- if (timer) clearTimeout(timer);
110
- }
111
- }
112
-
113
- // 供 selfmod 工具 / 追踪用的纯净元数据(不含 execute 实现)
114
- export function toDescriptor(meta) {
115
- return {
116
- name: meta.name,
117
- description: meta.description,
118
- parameters: meta.parameters,
119
- category: meta.category,
120
- power: meta.power,
121
- timeoutMs: meta.timeoutMs,
122
- idempotent: meta.idempotent,
123
- enabled: meta.enabled,
124
- };
125
- }
1
+ // src/tools/seam.js - 能力缝(Capability Seam)辅助层
2
+ // 参考 deepseek-harness 的 Capability Seam 三分法:
3
+ // Service Definition(声明/元数据) / Service Provider(execute 实现) / Consumer(runWithPolicy 统一策略入口)
4
+ // 零依赖, 纯 Node 原生。保留皮皮虾原有错误语义, 追加超时门禁/禁用门禁/追踪回调。
5
+
6
+ export const TOOL_ERROR_PREFIX = "[工具错误]";
7
+
8
+ // ---- B1: 工具结果标准化 (吸收 codex format_exec_output_for_model) ----
9
+ // 命令类工具返回统一元数据头, 模型不靠猜判断成败:
10
+ // [exit=0 time=0.42s out=3行] <内容>
11
+ // [exit=1 time=1.02s out=0行][stderr] <错误>
12
+ // [exit=timeout time=30000ms] command timed out after 30000ms
13
+ // 纯函数, 可独立测试 (B1 验收点)。
14
+ // opts = { ms, lineCount, timedOut, exitCode, timedOutMs }
15
+ export function formatToolResultHeader({
16
+ ms = 0,
17
+ lineCount = 0,
18
+ timedOut = false,
19
+ exitCode = null,
20
+ timedOutMs = 0,
21
+ } = {}) {
22
+ if (timedOut) {
23
+ return `[exit=timeout time=${ms}ms] command timed out after ${timedOutMs || ms}ms`;
24
+ }
25
+ const codeStr = exitCode === null ? "?" : String(exitCode);
26
+ return `[exit=${codeStr} time=${(ms / 1000).toFixed(2)}s out=${lineCount}行]`;
27
+ }
28
+
29
+ // 计算文本行数 (B1 辅助): 去尾随空行, 纯空白算 0
30
+ // 修正: 尾部换行不应多计一行 (echo 输出 "abc\r\n" 应为 1 行)
31
+ export function countLines(text) {
32
+ const s = String(text ?? "");
33
+ const t = s.replace(/\r\n/g, "\n").replace(/\n\s*$/, "").trim();
34
+ if (!t) return 0;
35
+ return t.split("\n").length;
36
+ }
37
+
38
+ // power 权限级: user < agent < super
39
+ export const POWER_LEVEL = { user: 0, agent: 1, super: 2 };
40
+
41
+ // ---- Definition 层: 元数据归一化 + 校验 ----
42
+ export function normalizeMeta(def = {}) {
43
+ if (!def || typeof def.name !== "string" || !def.name) {
44
+ throw new Error("能力缝 Definition 失败: 需 name");
45
+ }
46
+ return {
47
+ name: def.name,
48
+ description: def.description || "",
49
+ parameters: def.parameters || { type: "object", properties: {}, required: [] },
50
+ // 能力缝新增元数据
51
+ category: def.category || "misc", // 能力域: file/net/system/memory/selfmod/...
52
+ power: def.power || "user", // 权限级: user/agent(0栓塞)
53
+ timeoutMs: Number(def.timeoutMs) || 0, // 0 = 不限时
54
+ idempotent: !!def.idempotent, // 是否可安全重试
55
+ enabled: def.enabled !== false, // 默认启用
56
+ // 工具能力声明 (2026-10-02 吸收 ZCode PermissionToolCapability):
57
+ // readOnly 只读不产生副作用
58
+ // destructive 破坏性操作 (不可逆/删数据)
59
+ // riskLevel low | medium | high | critical
60
+ // alwaysAsk 无论何种模式都需人工确认 (压过一切放行分支)
61
+ // sideEffect 副作用域: "none" | "workspace" | "network" | "system"
62
+ capability: def.capability || null,
63
+ execute: def.execute,
64
+ // 工具钩子链 (吸收 OpenClaw before/after 钩子):
65
+ // before(args, ctx) -> undefined 继续 | 字符串短路 | throw 拒绝
66
+ // after(args, result, ctx) -> 后处理(结果审计/清理), 错误不阻塞
67
+ before: typeof def.before === "function" ? def.before : null,
68
+ after: typeof def.after === "function" ? def.after : null,
69
+ };
70
+ }
71
+
72
+ // ---- Consumer 层: 统一策略执行 ----
73
+ // 统一处理: 禁用门禁 / 实现缺失 / 超时门禁 / 标准错误语义 / 追踪回调
74
+ export async function runWithPolicy(meta, args, ctx = {}) {
75
+ if (meta.enabled === false) {
76
+ return `${TOOL_ERROR_PREFIX} ${meta.name}: 能力已禁用`;
77
+ }
78
+ // power 权限门禁: 仅当 ctx.power 明确提供时生效(向后兼容, 无 ctx.power 默认放行)
79
+ if (ctx && ctx.power) {
80
+ const need = POWER_LEVEL[meta.power] ?? 0;
81
+ const have = POWER_LEVEL[ctx.power] ?? 0;
82
+ if (have < need) {
83
+ return `${TOOL_ERROR_PREFIX} ${meta.name}: 权限不足(需要 ${meta.power}, 当前 ${ctx.power})`;
84
+ }
85
+ }
86
+ const fn = meta.execute;
87
+ if (typeof fn !== "function") {
88
+ return `${TOOL_ERROR_PREFIX} ${meta.name}: 无实现(Provider 缺失)`;
89
+ }
90
+ // before 钩子: 返回非 undefined 则短路(不执行), throw 则拒绝
91
+ if (meta.before) {
92
+ try {
93
+ const shortCircuit = await meta.before(args, ctx);
94
+ if (shortCircuit !== undefined && shortCircuit !== null) {
95
+ return typeof shortCircuit === "string" ? shortCircuit : JSON.stringify(shortCircuit);
96
+ }
97
+ } catch (e) {
98
+ return `${TOOL_ERROR_PREFIX} ${meta.name}: before 钩子拒绝: ${e.message}`;
99
+ }
100
+ }
101
+ let timer = null;
102
+ let timedOut = false;
103
+ const ctrl = new AbortController();
104
+ // 超时预算: 工具级声明优先 (meta.timeoutMs > 0), 否则全局默认 (ctx.timeoutMs, agent 从 config.agent.tool_timeout_ms 传入)
105
+ // v1.6.0 (第四刀): 无声明工具不再永不超时 — 有保守全局默认兑底, 避免单个慢工具卡死整个对话
106
+ const effectiveTimeout = meta.timeoutMs > 0 ? meta.timeoutMs : (Number(ctx.timeoutMs) || 0);
107
+ if (effectiveTimeout > 0) {
108
+ timer = setTimeout(() => { timedOut = true; ctrl.abort(); }, effectiveTimeout);
109
+ }
110
+ try {
111
+ // v1.6.0 (第四刀): 双层超时兜底 —
112
+ // 1) signal 传给 execute: 配合的工具提前终止释放资源 (资源超时)
113
+ // 2) Promise.race 强制超时返回: 不响应 signal 的工具也不至于永远挂住对话 (语义超时兜底)
114
+ // 这是文档指出的灰色地带: 光靠 abort 信号, 不配合的工具会无限期挂着。
115
+ const run = () => (fn.length >= 2 ? fn(args, { ...ctx, signal: ctrl.signal }) : fn(args));
116
+ let result;
117
+ if (effectiveTimeout > 0) {
118
+ let raceTimer = null;
119
+ const timeoutGuard = new Promise((_, reject) => {
120
+ raceTimer = setTimeout(() => reject(Object.assign(new Error("timeout"), { timedOut: true })), effectiveTimeout);
121
+ });
122
+ const pRun = Promise.resolve().then(run);
123
+ // 哨兵: 超时赢时工具方后到的 reject 忽略, 防 unhandledRejection 崩进程
124
+ pRun.catch(() => {});
125
+ try {
126
+ result = await Promise.race([pRun, timeoutGuard]);
127
+ } catch (e) {
128
+ if (e && e.timedOut) timedOut = true;
129
+ throw e;
130
+ } finally {
131
+ if (raceTimer) clearTimeout(raceTimer);
132
+ }
133
+ } else {
134
+ result = await run();
135
+ }
136
+ if (timedOut) return `${TOOL_ERROR_PREFIX} ${meta.name}: 超时`;
137
+ if (meta.after) {
138
+ try { await meta.after(args, result, ctx); } catch { /* after 钩子错误不阻塞 */ }
139
+ }
140
+ if (typeof ctx.onResult === "function") ctx.onResult(meta.name, "ok", null);
141
+ return typeof result === "string" ? result : JSON.stringify(result);
142
+ } catch (e) {
143
+ if (typeof ctx.onResult === "function") ctx.onResult(meta.name, "error", e.message);
144
+ return `${TOOL_ERROR_PREFIX} ${meta.name}: ${timedOut ? "超时" : e.message}`;
145
+ } finally {
146
+ if (timer) clearTimeout(timer);
147
+ }
148
+ }
149
+
150
+ // 供 selfmod 工具 / 追踪用的纯净元数据(不含 execute 实现)
151
+ export function toDescriptor(meta) {
152
+ return {
153
+ name: meta.name,
154
+ description: meta.description,
155
+ parameters: meta.parameters,
156
+ category: meta.category,
157
+ power: meta.power,
158
+ timeoutMs: meta.timeoutMs,
159
+ idempotent: meta.idempotent,
160
+ enabled: meta.enabled,
161
+ };
162
+ }