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
@@ -1,176 +1,196 @@
1
- // src/tools/selfmod.js - Self-modification 工具
2
- // 参考 deepseek-harness 的 self-modification: agent 能检查/挂载/卸载自己的运行时能力
3
- // 这里落地为"能力级自修改": 枚举能力(工具+技能) / 启用 / 禁用 / 加载技能, 不破坏零依赖内核
4
- import { SkillLoader } from "../skills/loader.js";
5
- import fs from "node:fs";
6
- import path from "node:path";
7
-
8
- function capErr(name, msg) {
9
- return `[工具错误] ${name}: ${msg}`;
10
- }
11
-
12
- export function registerSelfmodTools(catalog, { skillsDir }) {
13
- const loader = new SkillLoader(skillsDir);
14
-
15
- // 1. 枚举全部能力: 工具 + 技能
16
- catalog.register({
17
- name: "list_capabilities",
18
- description: "枚举当前所有可用的工具能力(category/power/enabled) 和已安装技能。用于 agent 了解自己能干啥。",
19
- parameters: { type: "object", properties: { kind: { type: "string", enum: ["tool", "skill", "all"], description: "all=工具+技能(默认)" } }, required: [] },
20
- category: "selfmod",
21
- power: "agent",
22
- idempotent: true,
23
- execute: async (args) => {
24
- const kind = args && args.kind ? args.kind : "all";
25
- const lines = [];
26
- if (kind === "all" || kind === "tool") {
27
- lines.push("— 工具 —");
28
- for (const t of catalog.listDetailed()) {
29
- lines.push(`[${t.enabled ? "ON" : "OFF"}] ${t.name} (${t.category}/${t.power})${t.timeoutMs ? ` 超时${t.timeoutMs}ms` : ""}`);
30
- }
31
- }
32
- if (kind === "all" || kind === "skill") {
33
- lines.push("— 技能 —");
34
- for (const s of loader.list()) {
35
- lines.push(`${s.id}: ${s.name} — ${s.description}`);
36
- }
37
- }
38
- return lines.join("\n");
39
- },
40
- });
41
-
42
- // 2. 启用能力
43
- catalog.register({
44
- name: "enable_capability",
45
- description: "启用一个已注册但被禁用的工具能力。name 为工具名。",
46
- parameters: { type: "object", properties: { name: { type: "string" } }, required: ["name"] },
47
- category: "selfmod",
48
- power: "agent",
49
- execute: async (args) => {
50
- if (!catalog.enable(args.name)) return capErr("enable_capability", `未知工具: ${args.name}`);
51
- return `已启用: ${args.name}`;
52
- },
53
- });
54
-
55
- // 3. 禁用能力
56
- catalog.register({
57
- name: "disable_capability",
58
- description: "禁用工具能力(不卸载, 可随时启用)。name 为工具名。禁用后该工具不再出现在 LLM schema 且调用被拒。",
59
- parameters: { type: "object", properties: { name: { type: "string" } }, required: ["name"] },
60
- category: "selfmod",
61
- power: "agent",
62
- execute: async (args) => {
63
- if (!catalog.disable(args.name)) return capErr("disable_capability", `未知工具: ${args.name}`);
64
- return `已禁用: ${args.name}`;
65
- },
66
- });
67
-
68
- // 4. 加载技能
69
- catalog.register({
70
- name: "load_skill",
71
- description: "读取一个已安装技能的 SKILL.md 全文, 供 agent 按需加载使用。id 为技能名。",
72
- parameters: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
73
- category: "selfmod",
74
- power: "user",
75
- idempotent: true,
76
- execute: async (args) => {
77
- const content = loader.read(args.id);
78
- if (content === null) return capErr("load_skill", `未知技能: ${args.id}`);
79
-
80
- if (loader && typeof loader.trackUse === "function") { try { loader.trackUse(args.id); } catch {} }
81
- return `# ${args.id}\n\n${content}`;
82
- },
83
- });
84
-
85
- // 5. 创建新技能 (L5 auto-skill: 复杂任务后沉淀为可复用 Skill)
86
- catalog.register({
87
- name: "create_skill",
88
- description: "把一次成功的方法/流程沉淀为可复用的 Agent Skill。name=技能名(字母数字横线), description=一句话说明, content=SKILL.md 正文。正文推荐含三个段落(参考 addyosmani/agent-skills): ①「## 流程」逐步工作流+检查点 ②「## 反合理化」常见偷懒借口+反驳 ③「## 验证」完成后必须提供的证据。写进 skills/ 目录后自动被 loader 发现。",
89
- parameters: {
90
- type: "object",
91
- properties: {
92
- name: { type: "string", description: "技能名, 仅字母/数字/横线" },
93
- description: { type: "string", description: "技能一句话说明" },
94
- content: { type: "string", description: "SKILL.md 正文 (建议含: 流程/反合理化/验证 三段)" },
95
- },
96
- required: ["name", "description", "content"],
97
- },
98
- category: "selfmod",
99
- power: "agent",
100
- execute: async (args) => {
101
- const name = String(args.name || "").trim();
102
- if (!/^[a-zA-Z0-9-]+$/.test(name)) return "[工具错误] create_skill: 技能名仅允许字母/数字/横线: " + name;
103
- const desc = String(args.description || "").trim();
104
- const content = String(args.content || "").trim();
105
- if (!desc || !content) return "[工具错误] create_skill: 需 description + content";
106
- // v1.0.8: 长度上限, 防写超大文件/垃圾内容
107
- if (desc.length > 300) return "[工具错误] create_skill: description 超长 (最大 300 字符)";
108
- if (content.length > 50000) return "[工具错误] create_skill: content 超长 (最大 50000 字符)";
109
- const dir = path.join(skillsDir, name);
110
- fs.mkdirSync(dir, { recursive: true });
111
- const frontmatter = "---" + "\n" + "name: " + name + "\n" + "description: " + desc + "\n" + "---" + "\n" + "\n";
112
- fs.writeFileSync(path.join(dir, "SKILL.md"), frontmatter + content, "utf8");
113
- return "已创建技能: " + name + " (skills/" + name + "/SKILL.md)";
114
- },
115
- });
116
-
117
- // 6b. 自我进化: 从失败轨迹自动提炼经验 (refine 的上半场, 补齐「失败→经验」闭环)
118
- catalog.register({
119
- name: "refine",
120
- description: "从最近的失败工具调用轨迹自动提炼一条可复用经验教训 (自我进化闭环)。失败轨迹足够(≥2条)时, 用 LLM 提炼成一句话经验存进经验库, 后续任务自动注入上下文。",
121
- parameters: { type: "object", properties: { limit: { type: "number", description: "回看轨迹条数, 默认 20" } }, required: [] },
122
- category: "selfmod",
123
- power: "agent",
124
- execute: async (args, ctx) => {
125
- const agent = ctx && ctx.agent;
126
- if (!agent || typeof agent.refine !== "function") return capErr("refine", "无 agent 上下文");
127
- const r = await agent.refine({ limit: Number(args && args.limit) || 20 });
128
- return JSON.stringify(r);
129
- },
130
- });
131
-
132
- // 7. 自我进化: 从成功轨迹自动提炼可复用 Skill (refine 的下半场)
133
- catalog.register({
134
- name: "refine_skill",
135
- description: "从最近成功的工具调用轨迹自动提炼一个可复用 Skill (自我进化闭环)。成功轨迹足够且高频工具重复出现时, 用 LLM 提炼成 skills/<name>/SKILL.md。",
136
- parameters: { type: "object", properties: { limit: { type: "number", description: "回看轨迹条数, 默认 50" } }, required: [] },
137
- category: "selfmod",
138
- power: "agent",
139
- execute: async (args, ctx) => {
140
- const agent = ctx && ctx.agent;
141
- if (!agent || typeof agent.refineSkill !== "function") return capErr("refine_skill", "无 agent 上下文");
142
- const r = await agent.refineSkill({ limit: Number(args && args.limit) || 50 });
143
- return JSON.stringify(r);
144
- },
145
- });
146
-
147
- // 6. Session Replay: 从原始日志恢复会话历史 (跨天/崩溃续跑)
148
- catalog.register({
149
- name: "replay_session",
150
- description: "从原始对话日志恢复某会话的历史(跨天/崩溃后续跑)。sessionKey=会话名(默认default), days=回溯天数(默认7), limit=返回条数(默认40)。",
151
- parameters: {
152
- type: "object",
153
- properties: {
154
- sessionKey: { type: "string", description: "会话名, 默认 default" },
155
- days: { type: "number", description: "回溯天数, 默认 7" },
156
- limit: { type: "number", description: "返回条数, 默认 40" },
157
- },
158
- required: [],
159
- },
160
- category: "selfmod",
161
- power: "user",
162
- idempotent: true,
163
- execute: async (args, ctx) => {
164
- const agent = ctx && ctx.agent;
165
- if (!agent || !agent.l0) return capErr("replay_session", "无 l0 记录器");
166
- const msgs = agent.replaySession((args && args.sessionKey) || "default", {
167
- days: Number(args && args.days) || 7,
168
- limit: Number(args && args.limit) || 40,
169
- });
170
- if (!msgs.length) return "(该会话无历史记录)";
171
- return msgs.map(m => `${m.role}: ${m.content}`).join("\n");
172
- },
173
- });
174
-
175
- return catalog;
176
- }
1
+ // src/tools/selfmod.js - Self-modification 工具
2
+ // 参考 deepseek-harness 的 self-modification: agent 能检查/挂载/卸载自己的运行时能力
3
+ // 这里落地为"能力级自修改": 枚举能力(工具+技能) / 启用 / 禁用 / 加载技能, 不破坏零依赖内核
4
+ import { SkillLoader } from "../skills/loader.js";
5
+ import { scoreSkills } from "../skills/search.js";
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import { debug } from "../utils/logger.js";
9
+
10
+ function capErr(name, msg) {
11
+ return `[工具错误] ${name}: ${msg}`;
12
+ }
13
+
14
+ export function registerSelfmodTools(catalog, { skillsDir }) {
15
+ const loader = new SkillLoader(skillsDir);
16
+
17
+ // 1. 枚举全部能力: 工具 + 技能
18
+ catalog.register({
19
+ name: "list_capabilities",
20
+ description: "枚举当前所有可用的工具能力(category/power/enabled) 和已安装技能。用于 agent 了解自己能干啥。",
21
+ parameters: { type: "object", properties: { kind: { type: "string", enum: ["tool", "skill", "all"], description: "all=工具+技能(默认)" } }, required: [] },
22
+ category: "selfmod",
23
+ power: "agent",
24
+ idempotent: true,
25
+ execute: async (args) => {
26
+ const kind = args && args.kind ? args.kind : "all";
27
+ const lines = [];
28
+ if (kind === "all" || kind === "tool") {
29
+ lines.push("— 工具 —");
30
+ for (const t of catalog.listDetailed()) {
31
+ lines.push(`[${t.enabled ? "ON" : "OFF"}] ${t.name} (${t.category}/${t.power})${t.timeoutMs ? ` 超时${t.timeoutMs}ms` : ""}`);
32
+ }
33
+ }
34
+ if (kind === "all" || kind === "skill") {
35
+ lines.push("— 技能 —");
36
+ for (const s of loader.list()) {
37
+ lines.push(`${s.id}: ${s.name} — ${s.description}`);
38
+ }
39
+ }
40
+ return lines.join("\n");
41
+ },
42
+ });
43
+
44
+ // 2. 启用能力 (+ 按需披露: 让该工具的 schema 进入下一轮 LLM 请求)
45
+ catalog.register({
46
+ name: "enable_capability",
47
+ description: "启用并加载一个工具能力 (name 为工具名)。用于加载未默认携带的工具 —— 它们的功能已在【按需工具】清单里列出, 但完整参数说明需要先启用才能调用。",
48
+ parameters: { type: "object", properties: { name: { type: "string" } }, required: ["name"] },
49
+ category: "selfmod",
50
+ power: "agent",
51
+ execute: async (args) => {
52
+ if (!catalog.enable(args.name)) return capErr("enable_capability", `未知工具: ${args.name}`);
53
+ catalog.expose(args.name); // 披露给 LLM: 下一轮请求即可见其参数 schema
54
+ return `已启用: ${args.name}`;
55
+ },
56
+ });
57
+
58
+ // 3. 禁用能力
59
+ catalog.register({
60
+ name: "disable_capability",
61
+ description: "禁用工具能力(不卸载, 可随时启用)。name 为工具名。禁用后该工具不再出现在 LLM schema 且调用被拒。",
62
+ parameters: { type: "object", properties: { name: { type: "string" } }, required: ["name"] },
63
+ category: "selfmod",
64
+ power: "agent",
65
+ execute: async (args) => {
66
+ if (!catalog.disable(args.name)) return capErr("disable_capability", `未知工具: ${args.name}`);
67
+ return `已禁用: ${args.name}`;
68
+ },
69
+ });
70
+
71
+ // 4. 加载技能
72
+ catalog.register({
73
+ name: "load_skill",
74
+ description: "读取一个已安装技能的 SKILL.md 全文, 供 agent 按需加载使用。id 为技能名。",
75
+ parameters: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
76
+ category: "selfmod",
77
+ power: "user",
78
+ idempotent: true,
79
+ execute: async (args) => {
80
+ const content = loader.read(args.id);
81
+ if (content === null) return capErr("load_skill", `未知技能: ${args.id}`);
82
+
83
+ if (loader && typeof loader.trackUse === "function") { try { loader.trackUse(args.id); } catch (e) { debug(`[tools/selfmod] 已忽略异常: ${e && e.message ? e.message : e}`); } }
84
+ return `# ${args.id}\n\n${content}`;
85
+ },
86
+ });
87
+
88
+ // 4.5 技能检索 (蓝皮书 2026: 发现机制是技能生态的瓶颈 — 给 agent 一个检索入口)
89
+ catalog.register({
90
+ name: "skill_search",
91
+ description: "按关键词检索已安装技能, 对 name/description 打分排序返回。面对任务不确定用哪个技能时先用它发现, 再用 load_skill 读取全文。",
92
+ parameters: { type: "object", properties: { query: { type: "string", description: "关键词 (中英文均可)" } }, required: ["query"] },
93
+ category: "selfmod",
94
+ power: "user",
95
+ idempotent: true,
96
+ execute: async (args) => {
97
+ const q = String(args.query || "").trim();
98
+ if (!q) return capErr("skill_search", "需要 query 关键词");
99
+ if (!loader) return capErr("skill_search", "技能目录未装配");
100
+ const results = scoreSkills(loader, q).slice(0, 8);
101
+ return JSON.stringify({ query: q, count: results.length, results });
102
+ },
103
+ });
104
+
105
+ // 5. 创建新技能 (L5 auto-skill: 复杂任务后沉淀为可复用 Skill)
106
+ catalog.register({
107
+ name: "create_skill",
108
+ description: "把一次成功的方法/流程沉淀为可复用的 Agent Skill。name=技能名(字母数字横线), description=一句话说明, content=SKILL.md 正文。正文推荐含三个段落(参考 addyosmani/agent-skills): ①「## 流程」逐步工作流+检查点 ②「## 反合理化」常见偷懒借口+反驳 ③「## 验证」完成后必须提供的证据。写进 skills/ 目录后自动被 loader 发现。",
109
+ parameters: {
110
+ type: "object",
111
+ properties: {
112
+ name: { type: "string", description: "技能名, 仅字母/数字/横线" },
113
+ description: { type: "string", description: "技能一句话说明" },
114
+ content: { type: "string", description: "SKILL.md 正文 (建议含: 流程/反合理化/验证 三段)" },
115
+ },
116
+ required: ["name", "description", "content"],
117
+ },
118
+ category: "selfmod",
119
+ power: "agent",
120
+ execute: async (args) => {
121
+ const name = String(args.name || "").trim();
122
+ if (!/^[a-zA-Z0-9-]+$/.test(name)) return "[工具错误] create_skill: 技能名仅允许字母/数字/横线: " + name;
123
+ const desc = String(args.description || "").trim();
124
+ const content = String(args.content || "").trim();
125
+ if (!desc || !content) return "[工具错误] create_skill: 需 description + content";
126
+ // v1.0.8: 长度上限, 防写超大文件/垃圾内容
127
+ if (desc.length > 300) return "[工具错误] create_skill: description 超长 (最大 300 字符)";
128
+ if (content.length > 50000) return "[工具错误] create_skill: content 超长 (最大 50000 字符)";
129
+ const dir = path.join(skillsDir, name);
130
+ fs.mkdirSync(dir, { recursive: true });
131
+ const frontmatter = "---" + "\n" + "name: " + name + "\n" + "description: " + desc + "\n" + "---" + "\n" + "\n";
132
+ fs.writeFileSync(path.join(dir, "SKILL.md"), frontmatter + content, "utf8");
133
+ return "已创建技能: " + name + " (skills/" + name + "/SKILL.md)";
134
+ },
135
+ });
136
+
137
+ // 6b. 自我进化: 从失败轨迹自动提炼经验 (refine 的上半场, 补齐「失败→经验」闭环)
138
+ catalog.register({
139
+ name: "refine",
140
+ description: "从最近的失败工具调用轨迹自动提炼一条可复用经验教训 (自我进化闭环)。失败轨迹足够(≥2条)时, 用 LLM 提炼成一句话经验存进经验库, 后续任务自动注入上下文。",
141
+ parameters: { type: "object", properties: { limit: { type: "number", description: "回看轨迹条数, 默认 20" } }, required: [] },
142
+ category: "selfmod",
143
+ power: "agent",
144
+ execute: async (args, ctx) => {
145
+ const agent = ctx && ctx.agent;
146
+ if (!agent || typeof agent.refine !== "function") return capErr("refine", "无 agent 上下文");
147
+ const r = await agent.refine({ limit: Number(args && args.limit) || 20 });
148
+ return JSON.stringify(r);
149
+ },
150
+ });
151
+
152
+ // 7. 自我进化: 从成功轨迹自动提炼可复用 Skill (refine 的下半场)
153
+ catalog.register({
154
+ name: "refine_skill",
155
+ description: "从最近成功的工具调用轨迹自动提炼一个可复用 Skill (自我进化闭环)。成功轨迹足够且高频工具重复出现时, 用 LLM 提炼成 skills/<name>/SKILL.md。",
156
+ parameters: { type: "object", properties: { limit: { type: "number", description: "回看轨迹条数, 默认 50" } }, required: [] },
157
+ category: "selfmod",
158
+ power: "agent",
159
+ execute: async (args, ctx) => {
160
+ const agent = ctx && ctx.agent;
161
+ if (!agent || typeof agent.refineSkill !== "function") return capErr("refine_skill", "无 agent 上下文");
162
+ const r = await agent.refineSkill({ limit: Number(args && args.limit) || 50 });
163
+ return JSON.stringify(r);
164
+ },
165
+ });
166
+
167
+ // 6. Session Replay: 从原始日志恢复会话历史 (跨天/崩溃续跑)
168
+ catalog.register({
169
+ name: "replay_session",
170
+ description: "从原始对话日志恢复某会话的历史(跨天/崩溃后续跑)。sessionKey=会话名(默认default), days=回溯天数(默认7), limit=返回条数(默认40)。",
171
+ parameters: {
172
+ type: "object",
173
+ properties: {
174
+ sessionKey: { type: "string", description: "会话名, 默认 default" },
175
+ days: { type: "number", description: "回溯天数, 默认 7" },
176
+ limit: { type: "number", description: "返回条数, 默认 40" },
177
+ },
178
+ required: [],
179
+ },
180
+ category: "selfmod",
181
+ power: "user",
182
+ idempotent: true,
183
+ execute: async (args, ctx) => {
184
+ const agent = ctx && ctx.agent;
185
+ if (!agent || !agent.l0) return capErr("replay_session", "无 l0 记录器");
186
+ const msgs = agent.replaySession((args && args.sessionKey) || "default", {
187
+ days: Number(args && args.days) || 7,
188
+ limit: Number(args && args.limit) || 40,
189
+ });
190
+ if (!msgs.length) return "(该会话无历史记录)";
191
+ return msgs.map(m => `${m.role}: ${m.content}`).join("\n");
192
+ },
193
+ });
194
+
195
+ return catalog;
196
+ }
@@ -0,0 +1,225 @@
1
+ // src/tools/v3.js - v3.0 新工具注册 (codex 对齐)
2
+ // repo_map(aider) / apply_patch(aider SR 编辑块+快照) / review_code(OCR 分级) / goal_board(OMH)
3
+ // 零运行时依赖; 全部走 ToolCatalog 标准注册 (权限引擎/钩子链自动织入)。
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import { renderRepoMap } from "../repomap/index.js";
7
+ import { parseEditBlocks, applyAll, formatRetryFeedback } from "../edit/editblock.js";
8
+ import { Snapshot } from "../edit/snapshot.js";
9
+ import { runReview } from "../review/index.js";
10
+ import { safePath } from "./builtin.js";
11
+ import { generateWiki, checkStaleness } from "../wiki/index.js";
12
+ import { debug } from "../utils/logger.js";
13
+
14
+ export function registerV3Tools(catalog, { rootDir, agent = null }) {
15
+ // 0. repo_wiki — 代码库 Wiki (2026-10-02 吸收 ZCode repo-wiki): 架构文档 + file:line 绑定 + 敏感排除
16
+ catalog.register({
17
+ name: "repo_wiki",
18
+ // 陈旧时自动刷新写入 docs/WIKI.md → 有工作区副作用, 不能声明 readOnly (诚实声明, ZCode 教训)
19
+ capability: { riskLevel: "low", sideEffect: "workspace" },
20
+ description: "生成代码库 Wiki 架构文档: 按目录列出核心定义 (签名+file:line) + 模块依赖 mermaid 图; 敏感文件 (token/secret/credential/password 等) 自动排除。可选写入 docs/WIKI.md。",
21
+ parameters: {
22
+ type: "object",
23
+ properties: {
24
+ save: { type: "boolean", description: "是否写入 docs/WIKI.md (默认 false 只返回文本)" },
25
+ },
26
+ },
27
+ execute: async (args) => {
28
+ const out = path.join(rootDir, "docs", "WIKI.md");
29
+ // ZCode 语义: 源码变化后 wiki 陈旧, 自动重生成而非返回过期内容
30
+ const staleness = checkStaleness(out, rootDir);
31
+ const w = generateWiki(rootDir, {});
32
+ const summary = `文件 ${w.stats.files} · 定义 ${w.stats.defs} · 依赖边 ${w.stats.edges} · 敏感排除 ${w.sensitiveSkipped}`;
33
+ if (args.save || staleness.stale) {
34
+ fs.mkdirSync(path.dirname(out), { recursive: true });
35
+ fs.writeFileSync(out, w.text + "\n", "utf8");
36
+ return `Wiki 已刷新写入 docs/WIKI.md (此前状态: ${staleness.reason}; ${summary})\n\n${w.text}`;
37
+ }
38
+ return `Wiki 概况 (${summary}; 上次生成于 docs/WIKI.md, ${staleness.reason}):\n\n${w.text}`;
39
+ },
40
+ });
41
+ // 0b. usage_stats — 会话级使用统计 (2026-10-02 吸收 ZCode 使用统计: 模型消耗/调用次数)
42
+ catalog.register({
43
+ name: "usage_stats",
44
+ capability: { readOnly: true, riskLevel: "low", sideEffect: "none" },
45
+ description: "查看本会话 LLM 使用统计: 调用次数 / token 消耗 / 按模型分解。",
46
+ parameters: { type: "object", properties: {} },
47
+ execute: async () => {
48
+ const s = agent?.usageStats;
49
+ if (!s) return JSON.stringify({ calls: 0, tokens: 0, byModel: {} });
50
+ return JSON.stringify({ calls: s.calls, tokens: s.tokens, byModel: s.byModel });
51
+ },
52
+ });
53
+ // 0c. self_diagnose — 自诊断 (2026-10-03, "按症状下药"表自动化: 症状→根因→增强动作)
54
+ catalog.register({
55
+ name: "self_diagnose",
56
+ capability: { readOnly: true, riskLevel: "low", sideEffect: "none" },
57
+ description: "Agent 自诊断: 从审计链/失败案例库/使用统计/任务基线聚合信号, 按症状输出根因与增强动作 (零 LLM, 确定性)。",
58
+ parameters: { type: "object", properties: {} },
59
+ execute: async () => {
60
+ const { diagnoseAgent } = await import("../services/diagnose.js");
61
+ const d = diagnoseAgent({
62
+ dataDir: agent?.dataDir || path.join(rootDir, "data"),
63
+ rootDir,
64
+ usageStats: agent?.usageStats,
65
+ // 记忆管线健康度 (2026-10-03 接线): MemoryHealthMonitor 曾装配却零消费, 现作为诊断信号源
66
+ health: agent?.memoryHealth && typeof agent.memoryHealth.status === "function" ? agent.memoryHealth.status() : null,
67
+ });
68
+ return d.report;
69
+ },
70
+ });
71
+ // 1. repo_map — 仓库地图 (PageRank 标识符排序, token 预算内渲染)
72
+ catalog.register({
73
+ name: "repo_map",
74
+ description: "生成仓库地图: 按重要性(PageRank)列出代码标识符与结构骨架, 用于快速理解代码库。省 token, 优先于全量读目录。",
75
+ parameters: {
76
+ type: "object",
77
+ properties: {
78
+ root: { type: "string", description: "仓库根目录 (相对工作目录, 默认 .)" },
79
+ token_budget: { type: "number", description: "渲染 token 预算 (默认 1024)" },
80
+ },
81
+ required: [],
82
+ },
83
+ execute: async (args) => {
84
+ // 2026-09-18 修复 (P2): repo_map 原先对 args.root 无路径防护,
85
+ // 绝对路径/.. 穿越可枚举工作区外任意目录的结构与标识符 (信息泄露面)。
86
+ // 现与 write_file/apply_patch 同一安全不变量: safePath 前缀 + realpath 双校验。
87
+ try {
88
+ safePath(rootDir, args.root || ".");
89
+ } catch (e) {
90
+ return JSON.stringify({ error: `路径被拒绝: ${e.message}` });
91
+ }
92
+ const root = path.resolve(rootDir, args.root || ".");
93
+ if (!fs.existsSync(root)) return JSON.stringify({ error: `目录不存在: ${args.root}` });
94
+ try {
95
+ const map = renderRepoMap(root, { tokenBudget: Number(args.token_budget) || 1024 });
96
+ return JSON.stringify({ stats: map.stats, text: map.text });
97
+ } catch (e) {
98
+ return JSON.stringify({ error: e.message });
99
+ }
100
+ },
101
+ });
102
+
103
+ // 2. apply_patch — SR 编辑块应用 (aider 语义: SEARCH/REPLACE + 快照回滚)
104
+ catalog.register({
105
+ name: "apply_patch",
106
+ description: "按 SEARCH/REPLACE 编辑块修改文件。输入含 <<<<<<< SEARCH / ======= / >>>>>>> REPLACE 的编辑文本, 支持多文件多块。比整体重写更省 token、更精准。",
107
+ parameters: {
108
+ type: "object",
109
+ properties: {
110
+ content: { type: "string", description: "含一个或多个 SEARCH/REPLACE 块的编辑文本" },
111
+ },
112
+ required: ["content"],
113
+ },
114
+ execute: async (args) => {
115
+ const blocks = parseEditBlocks(String(args.content || ""));
116
+ if (!blocks.length) return JSON.stringify({ error: "未找到任何 SEARCH/REPLACE 块" });
117
+ // 路径防护 (2026-09-18 修复 P1): 原实现直接 path.resolve(rootDir, b.path) 后写盘,
118
+ // SEARCH/REPLACE 块携带绝对路径 (D:\...) 或 ..\..\ 穿越路径时可写工作区外任意文件,
119
+ // 且目标路径藏在 content 字符串里, permissions 的 findEscape 也看不见。
120
+ // 现统一走 safePath (前缀 + realpath 双重校验), 与 write_file 同一安全不变量。
121
+ try {
122
+ for (const b of blocks) safePath(rootDir, b.path);
123
+ } catch (e) {
124
+ return JSON.stringify({ error: `路径被拒绝: ${e.message}` });
125
+ }
126
+ // 快照所有目标文件 (回滚保险)
127
+ const snapPaths = [...new Set(blocks.map((b) => path.resolve(rootDir, b.path)))];
128
+ const snap = Snapshot.begin(snapPaths);
129
+ const byFile = new Map();
130
+ for (const b of blocks) {
131
+ const key = path.resolve(rootDir, b.path);
132
+ if (!byFile.has(key)) byFile.set(key, { file: b.path, blocks: [] });
133
+ byFile.get(key).blocks.push(b);
134
+ }
135
+ const results = [];
136
+ for (const { file, blocks: fblocks } of byFile.values()) {
137
+ const abs = path.resolve(rootDir, file);
138
+ if (!fs.existsSync(abs)) {
139
+ // 只支持新建: 全部块 search 为空
140
+ const allNew = fblocks.every((b) => !b.search.trim());
141
+ if (!allNew) { results.push({ file, ok: false, error: "not-found: 文件不存在且存在非新建块" }); continue; }
142
+ const content = fblocks.map((b) => b.replace).join("\n");
143
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
144
+ fs.writeFileSync(abs, content, "utf8");
145
+ results.push({ file, ok: true, created: true });
146
+ continue;
147
+ }
148
+ let content = fs.readFileSync(abs, "utf8");
149
+ const r = applyAll(content, fblocks, { fuzzy: true });
150
+ if (!r.ok) {
151
+ results.push({ file, ok: false, error: r.error, feedback: formatRetryFeedback(r.results) });
152
+ continue;
153
+ }
154
+ fs.writeFileSync(abs, r.content, "utf8");
155
+ results.push({ file, ok: true, blocks: fblocks.length });
156
+ }
157
+ const failed = results.filter((r) => !r.ok);
158
+ // 任一失败 → 整体回滚 (aider: 原子性优先), 回灌反馈交给 LLM 修复
159
+ if (failed.length) {
160
+ try { Snapshot.rollback(snap); } catch (e) { debug(`[tools/v3] 已忽略异常: ${e && e.message ? e.message : e}`); }
161
+ return JSON.stringify({ ok: false, rolled_back: true, results, retry_hint: "请根据 feedback 修正 SEARCH 块后重试" });
162
+ }
163
+ return JSON.stringify({ ok: true, files: results.length, blocks: blocks.length, results });
164
+ },
165
+ });
166
+
167
+ // 3. review_code — 分级代码审查 (OCR 五阶段: plan→group→review→relocate→filter)
168
+ catalog.register({
169
+ name: "review_code",
170
+ description: "对指定文件(或最近变更)执行分级审查, 输出 P0/P1/P2 问题清单 (密钥泄漏/调试残留/空 catch 等)。结果同步到 Web UI 审查面板。",
171
+ parameters: {
172
+ type: "object",
173
+ properties: {
174
+ files: { type: "array", items: { type: "string" }, description: "待审查文件路径列表 (相对工作目录)" },
175
+ diff: { type: "string", description: "可选: 直接传 diff 文本" },
176
+ },
177
+ required: [],
178
+ },
179
+ execute: async (args) => {
180
+ let { files = [], diff = null } = args || {};
181
+ if (!files.length && !diff) {
182
+ // 兜底: 用 traces 里最近的写操作文件
183
+ const recent = (agent?.traces?.read?.("write_file", 10) || []);
184
+ files = recent.map((t) => t.args?.path).filter(Boolean);
185
+ }
186
+ const out = runReview({ files, diff });
187
+ // 报告存到 agent, Web UI /api/review/latest 直接读
188
+ if (agent) agent._lastReview = { issues: out.issues, report: out.report, ts: Date.now() };
189
+ return JSON.stringify({ ok: true, total: out.issues.length, issues: out.issues, report: out.report });
190
+ },
191
+ });
192
+
193
+ // 4. goal_board — 目标看板 (OMH goal board: 增删改查 + 只读渲染)
194
+ catalog.register({
195
+ name: "goal_board",
196
+ description: "目标看板: 记录/更新长期目标的优先级与状态 (pending/in_progress/blocked/done)。用户在 Web UI 看板实时可见。",
197
+ parameters: {
198
+ type: "object",
199
+ properties: {
200
+ action: { type: "string", enum: ["add", "update", "list"], description: "add=新增 update=更新状态 list=列出" },
201
+ id: { type: "string", description: "目标 id (update 时必填)" },
202
+ title: { type: "string", description: "目标标题 (add 时必填)" },
203
+ priority: { type: "string", enum: ["p0", "p1", "p2"], description: "优先级 (add 可选)" },
204
+ status: { type: "string", enum: ["pending", "in_progress", "blocked", "done"], description: "新状态 (update 时必填)" },
205
+ },
206
+ required: ["action"],
207
+ },
208
+ execute: async (args) => {
209
+ const board = agent?.goalBoard;
210
+ if (!board) return JSON.stringify({ error: "目标看板未装配" });
211
+ const a = args.action;
212
+ if (a === "add") {
213
+ if (!args.title) return JSON.stringify({ error: "add 需要 title" });
214
+ const g = board.addGoal({ title: args.title, priority: args.priority || "p2" });
215
+ return JSON.stringify({ ok: true, goal: g });
216
+ }
217
+ if (a === "update") {
218
+ if (!args.id || !args.status) return JSON.stringify({ error: "update 需要 id + status" });
219
+ const ok = board.updateStatus(args.id, args.status);
220
+ return JSON.stringify({ ok });
221
+ }
222
+ return JSON.stringify({ ok: true, goals: board.list(), text: board.render() });
223
+ },
224
+ });
225
+ }