ppxans-harness 2.6.0 → 3.2.1

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 (189) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +195 -195
  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 +6 -124
  11. package/config/ppx.json.example +143 -0
  12. package/package.json +16 -6
  13. package/skills/.usage.json +6 -0
  14. package/skills/agent-professional-training/SKILL.md +94 -0
  15. package/skills/agent-professional-training/trajectories/2026-09-11-dpo-pairs.jsonl +2 -0
  16. package/skills/agent-professional-training/trajectories/2026-09-11-round2-trajectory.json +15 -0
  17. package/skills/agent-professional-training/trajectories/2026-09-13-first-run-trajectory.json +17 -0
  18. package/skills/brainstorm/SKILL.md +24 -0
  19. package/skills/cupid-lover-comms/SKILL.md +37 -0
  20. package/skills/debug/SKILL.md +26 -0
  21. package/skills/plan/SKILL.md +25 -0
  22. package/skills/ponytail/SKILL.md +25 -0
  23. package/skills/ppx-memory/SKILL.md +91 -0
  24. package/skills/ppx-memory/scripts/cli.js +192 -0
  25. package/skills/ppx-memory/scripts/experience.js +133 -0
  26. package/skills/ppx-memory/scripts/fact-store.js +842 -0
  27. package/skills/ppx-memory/scripts/l0.js +52 -0
  28. package/skills/ppx-memory/scripts/l2.js +146 -0
  29. package/skills/ppx-memory/scripts/l3.js +112 -0
  30. package/skills/ppx-memory/scripts/memory-ticker.js +238 -0
  31. package/skills/ppx-memory/scripts/pii.js +42 -0
  32. package/skills/ppx-memory/scripts/schema.js +80 -0
  33. package/skills/ppx-memory/scripts/session.js +398 -0
  34. package/skills/ppx-memory/scripts/similarity.js +43 -0
  35. package/skills/ppx-memory/scripts/store.js +116 -0
  36. package/skills/ppx-memory/scripts/wal.js +38 -0
  37. package/skills/ppx-selfheal/SKILL.md +24 -0
  38. package/skills/ppx-selfheal/scripts/cli.js +80 -0
  39. package/skills/ppx-selfheal/scripts/healer.js +184 -0
  40. package/skills/ppx-selfheal/scripts/logger.js +17 -0
  41. package/skills/ppx-selfheal/scripts/store.js +116 -0
  42. package/skills/prompt-depth-kit/SKILL.md +28 -0
  43. package/skills/session-naming/SKILL.md +36 -0
  44. package/skills/verify/SKILL.md +25 -0
  45. package/src/agent/index.js +1352 -717
  46. package/src/agent/prompts.js +46 -2
  47. package/src/aml-server.js +197 -151
  48. package/src/ans/eviction.js +123 -143
  49. package/src/ans/guard.js +159 -120
  50. package/src/ans/lifecycle.js +96 -93
  51. package/src/ans/proactive.js +112 -129
  52. package/src/ans/reward.js +95 -111
  53. package/src/ans/values.js +15 -15
  54. package/src/audit/audit-chain.js +43 -7
  55. package/src/audit/verifier.js +157 -120
  56. package/src/bus/circuit-breaker.js +9 -1
  57. package/src/bus/runtime-bus.js +107 -93
  58. package/src/channels/base.js +57 -34
  59. package/src/channels/feishu.js +118 -126
  60. package/src/channels/http.js +1090 -651
  61. package/src/channels/index.js +111 -110
  62. package/src/channels/log.js +29 -29
  63. package/src/channels/wechat-crypto.js +73 -74
  64. package/src/channels/wechat.js +191 -197
  65. package/src/channels/workspace.js +94 -0
  66. package/src/channels-cli.js +126 -124
  67. package/src/cli.js +129 -120
  68. package/src/commands/index.js +142 -0
  69. package/src/config/channels.js +137 -170
  70. package/src/config/index.js +277 -224
  71. package/src/config/placeholder.js +31 -0
  72. package/src/config/providers.js +148 -182
  73. package/src/config/settings.js +156 -182
  74. package/src/core/policy.js +69 -18
  75. package/src/core/trace.js +8 -6
  76. package/src/edit/editblock.js +266 -0
  77. package/src/edit/snapshot.js +67 -0
  78. package/src/evidence/index.js +153 -0
  79. package/src/evolve/playbook.js +9 -10
  80. package/src/hooks/index.js +113 -0
  81. package/src/llm/client.js +4 -3
  82. package/src/llm/dsml.js +74 -74
  83. package/src/llm/embedder.js +41 -35
  84. package/src/llm/fence.js +52 -52
  85. package/src/llm/index.js +4 -4
  86. package/src/llm/local-embedder.js +94 -0
  87. package/src/llm/presets.js +113 -0
  88. package/src/llm/pricing.js +93 -0
  89. package/src/llm/retry.js +73 -73
  90. package/src/llm/router.js +92 -89
  91. package/src/mcp/admin.js +44 -10
  92. package/src/mcp/client.js +487 -477
  93. package/src/mcp/http.js +23 -37
  94. package/src/mcp/index.js +116 -116
  95. package/src/mcp/server.js +23 -18
  96. package/src/mcp/tasks.js +9 -14
  97. package/src/memory/asset-hub.js +6 -11
  98. package/src/memory/canvas.js +2 -5
  99. package/src/memory/compaction.js +28 -28
  100. package/src/memory/experience.js +133 -122
  101. package/src/memory/fact-store.js +914 -698
  102. package/src/memory/failure-episode.js +20 -11
  103. package/src/memory/fork.js +17 -8
  104. package/src/memory/index.js +8 -6
  105. package/src/memory/l0.js +53 -52
  106. package/src/memory/l2.js +145 -130
  107. package/src/memory/l3.js +111 -111
  108. package/src/memory/legion-board.js +71 -0
  109. package/src/memory/memory-ticker.js +239 -240
  110. package/src/memory/session.js +398 -397
  111. package/src/memory/sqlite-store.js +581 -0
  112. package/src/mode/blackboard.js +49 -49
  113. package/src/mode/graph.js +42 -41
  114. package/src/mode/index.js +64 -64
  115. package/src/mode/legion.js +54 -51
  116. package/src/mode/plan-exec.js +50 -50
  117. package/src/mode/router.js +28 -40
  118. package/src/orchestrator/agent-worker.js +69 -69
  119. package/src/orchestrator/dag.js +90 -83
  120. package/src/orchestrator/experts.js +76 -0
  121. package/src/orchestrator/index.js +1 -1
  122. package/src/orchestrator/legion.js +179 -187
  123. package/src/orchestrator/supervisor.js +6 -8
  124. package/src/permissions/index.js +378 -0
  125. package/src/persona/index.js +28 -29
  126. package/src/plugin/builtin.js +313 -215
  127. package/src/plugin/context.js +80 -79
  128. package/src/plugin/index.js +62 -62
  129. package/src/plugin/v3.js +73 -0
  130. package/src/protocol/index.js +148 -0
  131. package/src/repomap/index.js +309 -0
  132. package/src/review/index.js +393 -0
  133. package/src/seam/registry.js +3 -0
  134. package/src/seam/shell.js +55 -55
  135. package/src/security/injection.js +79 -0
  136. package/src/selfheal/evolve.js +67 -67
  137. package/src/selfheal/healer.js +184 -167
  138. package/src/selfheal/run.js +9 -9
  139. package/src/server.js +63 -60
  140. package/src/services/diagnose.js +180 -0
  141. package/src/services/learning-service.js +9 -0
  142. package/src/services/memory-health.js +34 -6
  143. package/src/services/memory-service.js +42 -9
  144. package/src/services/triage.js +138 -0
  145. package/src/session/parts.js +76 -0
  146. package/src/session/projection.js +73 -0
  147. package/src/session/rollout.js +54 -0
  148. package/src/session/turn.js +137 -0
  149. package/src/skills/lint.js +72 -0
  150. package/src/skills/loader.js +231 -150
  151. package/src/skills/search.js +58 -0
  152. package/src/skills/verify.js +95 -100
  153. package/src/tools/advanced.js +388 -352
  154. package/src/tools/builtin.js +384 -297
  155. package/src/tools/catalog.js +283 -159
  156. package/src/tools/command-guard.js +112 -112
  157. package/src/tools/custom.js +47 -47
  158. package/src/tools/delegate.js +383 -297
  159. package/src/tools/document.js +254 -253
  160. package/src/tools/git.js +151 -0
  161. package/src/tools/governance.js +47 -20
  162. package/src/tools/index.js +16 -11
  163. package/src/tools/methods.js +178 -178
  164. package/src/tools/ocr.js +59 -59
  165. package/src/tools/sandbox-worker.js +40 -0
  166. package/src/tools/sandbox.js +92 -0
  167. package/src/tools/seam.js +162 -125
  168. package/src/tools/selfmod.js +196 -176
  169. package/src/tools/v3.js +225 -0
  170. package/src/tools/vad.js +176 -0
  171. package/src/tools/voice.js +238 -0
  172. package/src/utils/async.js +14 -0
  173. package/src/utils/config-file.js +53 -0
  174. package/src/utils/crashguard.js +88 -0
  175. package/src/utils/http.js +53 -0
  176. package/src/utils/id.js +8 -0
  177. package/src/utils/json-state.js +33 -0
  178. package/src/utils/logger.js +17 -17
  179. package/src/utils/ndjson.js +25 -0
  180. package/src/utils/pii.js +42 -42
  181. package/src/utils/rate-limit.js +50 -0
  182. package/src/utils/schema.js +80 -0
  183. package/src/utils/similarity.js +43 -0
  184. package/src/utils/store.js +170 -108
  185. package/src/utils/text.js +15 -15
  186. package/src/utils/trace.js +153 -153
  187. package/src/utils/wal.js +39 -0
  188. package/src/utils/winutf8.js +16 -15
  189. 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
+ }