ppxans-harness 2.6.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 -262
  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/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 +46 -2
  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 -651
  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 -182
  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 +4 -3
  79. package/src/llm/dsml.js +74 -74
  80. package/src/llm/embedder.js +41 -35
  81. package/src/llm/fence.js +52 -52
  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 -89
  88. package/src/mcp/admin.js +44 -10
  89. package/src/mcp/client.js +487 -477
  90. package/src/mcp/http.js +23 -37
  91. package/src/mcp/index.js +116 -116
  92. package/src/mcp/server.js +23 -18
  93. package/src/mcp/tasks.js +9 -14
  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 -215
  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,112 +1,112 @@
1
- // src/tools/command-guard.js - 命令守卫 (吸收 Hermes Agent approval 机制)
2
- // 三层防线:
3
- // 1. 用户 deny 规则 (config.security.deny, glob 风格) — 最高优先级, 即使 allow_all 也拦
4
- // 2. 硬黑名单 HARD_BLOCK (rm -rf /、fork bomb、写裸设备、管道到 shell 等) — allow_all 也拦
5
- // 3. 常规高危黑名单 DEFAULT_DENY (继承皮皮虾 P0) — allow_all 放行
6
- // 4. 白名单前缀 (allow_all=false 时) — 只放行已知安全命令前缀
7
- // 反混淆: normalizeCommand 先去引号再规范化空白, 防 `rm ""-rf` / `bash <(curl)` 引号技巧绕过
8
-
9
- // ---- 反混淆规范化: 仅用于检测, 不用于实际执行 ----
10
- export function normalizeCommand(cmd) {
11
- let s = String(cmd || "");
12
- s = s.replace(/["'`]/g, ""); // 去引号防绕过
13
- s = s.replace(/\s+/g, " ").trim(); // 合并空白
14
- return s;
15
- }
16
-
17
- // ---- 硬黑名单: allow_all 也无法放行 (破坏宿主 / 不可逆 / 远程代码落地执行) ----
18
- // v1.0.9: rm 类正则去掉行首/符号前缀限制 — `env rm --no-preserve-root /` 等前缀变体 (sudo/env/&&) 曾绕过
19
- export const HARD_BLOCK = [
20
- { pattern: /rm\s+(-[a-z]*r[a-z]*\s+)*--?no-preserve-root(\s|$)/i, reason: "rm --no-preserve-root 破坏根目录" },
21
- { pattern: /rm\s+(-[a-z]*r[a-z]*\s+)*\/\s*$/i, reason: "rm -rf / 类删除根目录" },
22
- { pattern: /:\s*\(\s*\)\s*\{\s*:\s*\|/, reason: "fork bomb 进程炸弹" },
23
- { pattern: /mkfs[.\s][^\n;]*\/dev\/(sd[a-z]|nvme[0-9])/i, reason: "格式化磁盘设备" },
24
- { pattern: /dd\s+[^\n;]*of=\/dev\/(sd[a-z]|nvme[0-9])/i, reason: "dd 写裸磁盘设备" },
25
- { pattern: /(curl|wget)\s+[^\s|;&]+\s*\|[\s]*(ba|z|k)?sh/i, reason: "管道下载内容到 shell 执行 (不可信代码)" },
26
- { pattern: /(ba|z|k)?sh\s*<\s*\(\s*(curl|wget)/i, reason: "进程替换执行远程内容" },
27
- ];
28
-
29
- // ---- 常规高危黑名单 (继承皮皮虾 P0: 删除/格式化/关机/强杀/强制推送等) ----
30
- export const DEFAULT_DENY = [
31
- /delete|erase|rmdir|rd \/s|deltree/i,
32
- /format\s/i, /mkfs/i, /fdisk/i, /diskpart/i, /shutdown/i,
33
- /restart/i, /reboot/i, /halt/i, /poweroff/i,
34
- /reg\s+delete/i, /taskkill/i, /pkill/i, /kill\s+-9/i,
35
- /rm\s+-rf/i, /rm\s+-fr/i,
36
- /curl|wget|Invoke-WebRequest|iwr/i,
37
- /git\s+push.*--force/i, /git\s+reset.*--hard/i,
38
- ];
39
-
40
- export const DEFAULT_ALLOW_PREFIX = [
41
- "git", "npm", "npx", "yarn", "pnpm", "node", "python", "python3",
42
- "ls", "dir", "pwd", "cat", "type", "echo", "head", "tail", "grep",
43
- "find", "wc", "cp", "copy", "mv", "move", "mkdir", "touch", "tree",
44
- "cd", "help", "ipconfig", "netstat", "tasklist", "whoami", "date", "time", "tsc",
45
- ];
46
-
47
- // 命中拦截后附加的指引: 明确告知不要重试/改写绕过 (Hermes approval 同款约束)
48
- export const DENY_HINT = " 命中后不要重试或改写命令绕过 — 确需执行请让用户调整 security 配置。";
49
-
50
- // glob 风格规则 ('git push --force*') -> 正则
51
- export function globToRegExp(glob) {
52
- let s = String(glob || "").trim();
53
- if (!s) return null;
54
- s = s.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
55
- try { return new RegExp("^" + s + "$", "i"); } catch { return null; }
56
- }
57
-
58
- // 统一检查入口:
59
- // opts = { allowAll|allow_all, allowPrefix, denyList, deny, hardBlock }
60
- // v1.0.9: 兼容 snake 配置键 allow_all (config.security.allow_all 是 snake, 原只认 camel 导致 allow_all=true 永不生效)
61
- // 返回 { ok: true, normalized } | { ok: false, reason, hard }
62
- export function checkCommand(cmd, opts = {}) {
63
- const normalized = normalizeCommand(cmd);
64
- if (!normalized) return { ok: false, hard: false, reason: "空命令" };
65
-
66
- // 1. 用户 deny 规则 (最高优先级, allow_all 也拦)
67
- const userDeny = (opts && opts.deny) || [];
68
- for (const d of userDeny) {
69
- const re = d instanceof RegExp ? d : globToRegExp(d);
70
- if (re && re.test(normalized)) {
71
- return { ok: false, hard: true, reason: "用户 deny 规则拦截: " + String(d).slice(0, 60) };
72
- }
73
- }
74
-
75
- // 2. 硬黑名单 (allow_all 也拦)
76
- const hardBlock = (opts && opts.hardBlock) || HARD_BLOCK;
77
- for (const { pattern, reason } of hardBlock) {
78
- if (pattern.test(normalized)) return { ok: false, hard: true, reason: "硬黑名单拦截: " + reason };
79
- }
80
-
81
- // 3. 常规高危黑名单 (allow_all 放行)
82
- const deny = (opts && opts.denyList) || DEFAULT_DENY;
83
- for (const re of deny) {
84
- if (re.test(normalized)) return { ok: false, hard: false, reason: "命令被拒绝: 命中高危黑名单 (delete/format/shutdown/curl等)" };
85
- }
86
-
87
- // 4. 白名单前缀 (allow_all=false 时)
88
- const allowAll = !!(opts && (opts.allowAll || opts.allow_all));
89
- if (!allowAll) {
90
- const allowPrefix = (opts && opts.allowPrefix) || DEFAULT_ALLOW_PREFIX;
91
- const first = normalized.split(/[\s|&;>]+/)[0];
92
- const hit = allowPrefix.some((a) => first.toLowerCase().replace(/\.exe$/i, "") === a.toLowerCase());
93
- if (!hit) {
94
- return { ok: false, hard: false, reason: `命令不在白名单: ${first}. 允许: git/npm/node/python/cat/cp/mkdir 等, 或设置 security.allow_all.` };
95
- }
96
- }
97
-
98
- return { ok: true, normalized };
99
- }
100
-
101
- // 兼容导出 (旧 isDeniedCommand 语义: 只查常规高危 + 硬黑名单 + 用户 deny, 不看白名单)
102
- export function isDeniedCommand(cmd, options) {
103
- return !checkCommand(cmd, { ...(options || {}), allowAll: true }).ok;
104
- }
105
-
106
- // 兼容导出 (旧 isAllowedCommand 语义: allow_all 直接放行, 否则查前缀白名单; 不查 deny)
107
- export function isAllowedCommand(cmd, options) {
108
- if (options && (options.allowAll || options.allow_all)) return true;
109
- const allowPrefix = (options && options.allowPrefix) || DEFAULT_ALLOW_PREFIX;
110
- const first = String(cmd || "").trim().split(/[\s|&;>]+/)[0];
111
- return allowPrefix.some((a) => first.toLowerCase().replace(/\.exe$/i, "") === a.toLowerCase());
112
- }
1
+ // src/tools/command-guard.js - 命令守卫 (吸收 Hermes Agent approval 机制)
2
+ // 三层防线:
3
+ // 1. 用户 deny 规则 (config.security.deny, glob 风格) — 最高优先级, 即使 allow_all 也拦
4
+ // 2. 硬黑名单 HARD_BLOCK (rm -rf /、fork bomb、写裸设备、管道到 shell 等) — allow_all 也拦
5
+ // 3. 常规高危黑名单 DEFAULT_DENY (继承皮皮虾 P0) — allow_all 放行
6
+ // 4. 白名单前缀 (allow_all=false 时) — 只放行已知安全命令前缀
7
+ // 反混淆: normalizeCommand 先去引号再规范化空白, 防 `rm ""-rf` / `bash <(curl)` 引号技巧绕过
8
+
9
+ // ---- 反混淆规范化: 仅用于检测, 不用于实际执行 ----
10
+ export function normalizeCommand(cmd) {
11
+ let s = String(cmd || "");
12
+ s = s.replace(/["'`]/g, ""); // 去引号防绕过
13
+ s = s.replace(/\s+/g, " ").trim(); // 合并空白
14
+ return s;
15
+ }
16
+
17
+ // ---- 硬黑名单: allow_all 也无法放行 (破坏宿主 / 不可逆 / 远程代码落地执行) ----
18
+ // v1.0.9: rm 类正则去掉行首/符号前缀限制 — `env rm --no-preserve-root /` 等前缀变体 (sudo/env/&&) 曾绕过
19
+ export const HARD_BLOCK = [
20
+ { pattern: /rm\s+(-[a-z]*r[a-z]*\s+)*--?no-preserve-root(\s|$)/i, reason: "rm --no-preserve-root 破坏根目录" },
21
+ { pattern: /rm\s+(-[a-z]*r[a-z]*\s+)*\/\s*$/i, reason: "rm -rf / 类删除根目录" },
22
+ { pattern: /:\s*\(\s*\)\s*\{\s*:\s*\|/, reason: "fork bomb 进程炸弹" },
23
+ { pattern: /mkfs[.\s][^\n;]*\/dev\/(sd[a-z]|nvme[0-9])/i, reason: "格式化磁盘设备" },
24
+ { pattern: /dd\s+[^\n;]*of=\/dev\/(sd[a-z]|nvme[0-9])/i, reason: "dd 写裸磁盘设备" },
25
+ { pattern: /(curl|wget)\s+[^\s|;&]+\s*\|[\s]*(ba|z|k)?sh/i, reason: "管道下载内容到 shell 执行 (不可信代码)" },
26
+ { pattern: /(ba|z|k)?sh\s*<\s*\(\s*(curl|wget)/i, reason: "进程替换执行远程内容" },
27
+ ];
28
+
29
+ // ---- 常规高危黑名单 (继承皮皮虾 P0: 删除/格式化/关机/强杀/强制推送等) ----
30
+ export const DEFAULT_DENY = [
31
+ /delete|erase|rmdir|rd \/s|deltree/i,
32
+ /format\s/i, /mkfs/i, /fdisk/i, /diskpart/i, /shutdown/i,
33
+ /restart/i, /reboot/i, /halt/i, /poweroff/i,
34
+ /reg\s+delete/i, /taskkill/i, /pkill/i, /kill\s+-9/i,
35
+ /rm\s+-rf/i, /rm\s+-fr/i,
36
+ /curl|wget|Invoke-WebRequest|iwr/i,
37
+ /git\s+push.*--force/i, /git\s+reset.*--hard/i,
38
+ ];
39
+
40
+ export const DEFAULT_ALLOW_PREFIX = [
41
+ "git", "npm", "npx", "yarn", "pnpm", "node", "python", "python3",
42
+ "ls", "dir", "pwd", "cat", "type", "echo", "head", "tail", "grep",
43
+ "find", "wc", "cp", "copy", "mv", "move", "mkdir", "touch", "tree",
44
+ "cd", "help", "ipconfig", "netstat", "tasklist", "whoami", "date", "time", "tsc",
45
+ ];
46
+
47
+ // 命中拦截后附加的指引: 明确告知不要重试/改写绕过 (Hermes approval 同款约束)
48
+ export const DENY_HINT = " 命中后不要重试或改写命令绕过 — 确需执行请让用户调整 security 配置。";
49
+
50
+ // glob 风格规则 ('git push --force*') -> 正则
51
+ export function globToRegExp(glob) {
52
+ let s = String(glob || "").trim();
53
+ if (!s) return null;
54
+ s = s.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
55
+ try { return new RegExp("^" + s + "$", "i"); } catch { return null; }
56
+ }
57
+
58
+ // 统一检查入口:
59
+ // opts = { allowAll|allow_all, allowPrefix, denyList, deny, hardBlock }
60
+ // v1.0.9: 兼容 snake 配置键 allow_all (config.security.allow_all 是 snake, 原只认 camel 导致 allow_all=true 永不生效)
61
+ // 返回 { ok: true, normalized } | { ok: false, reason, hard }
62
+ export function checkCommand(cmd, opts = {}) {
63
+ const normalized = normalizeCommand(cmd);
64
+ if (!normalized) return { ok: false, hard: false, reason: "空命令" };
65
+
66
+ // 1. 用户 deny 规则 (最高优先级, allow_all 也拦)
67
+ const userDeny = (opts && opts.deny) || [];
68
+ for (const d of userDeny) {
69
+ const re = d instanceof RegExp ? d : globToRegExp(d);
70
+ if (re && re.test(normalized)) {
71
+ return { ok: false, hard: true, reason: "用户 deny 规则拦截: " + String(d).slice(0, 60) };
72
+ }
73
+ }
74
+
75
+ // 2. 硬黑名单 (allow_all 也拦)
76
+ const hardBlock = (opts && opts.hardBlock) || HARD_BLOCK;
77
+ for (const { pattern, reason } of hardBlock) {
78
+ if (pattern.test(normalized)) return { ok: false, hard: true, reason: "硬黑名单拦截: " + reason };
79
+ }
80
+
81
+ // 3. 常规高危黑名单 (allow_all 放行)
82
+ const deny = (opts && opts.denyList) || DEFAULT_DENY;
83
+ for (const re of deny) {
84
+ if (re.test(normalized)) return { ok: false, hard: false, reason: "命令被拒绝: 命中高危黑名单 (delete/format/shutdown/curl等)" };
85
+ }
86
+
87
+ // 4. 白名单前缀 (allow_all=false 时)
88
+ const allowAll = !!(opts && (opts.allowAll || opts.allow_all));
89
+ if (!allowAll) {
90
+ const allowPrefix = (opts && opts.allowPrefix) || DEFAULT_ALLOW_PREFIX;
91
+ const first = normalized.split(/[\s|&;>]+/)[0];
92
+ const hit = allowPrefix.some((a) => first.toLowerCase().replace(/\.exe$/i, "") === a.toLowerCase());
93
+ if (!hit) {
94
+ return { ok: false, hard: false, reason: `命令不在白名单: ${first}. 允许: git/npm/node/python/cat/cp/mkdir 等, 或设置 security.allow_all.` };
95
+ }
96
+ }
97
+
98
+ return { ok: true, normalized };
99
+ }
100
+
101
+ // 兼容导出 (旧 isDeniedCommand 语义: 只查常规高危 + 硬黑名单 + 用户 deny, 不看白名单)
102
+ export function isDeniedCommand(cmd, options) {
103
+ return !checkCommand(cmd, { ...(options || {}), allowAll: true }).ok;
104
+ }
105
+
106
+ // 兼容导出 (旧 isAllowedCommand 语义: allow_all 直接放行, 否则查前缀白名单; 不查 deny)
107
+ export function isAllowedCommand(cmd, options) {
108
+ if (options && (options.allowAll || options.allow_all)) return true;
109
+ const allowPrefix = (options && options.allowPrefix) || DEFAULT_ALLOW_PREFIX;
110
+ const first = String(cmd || "").trim().split(/[\s|&;>]+/)[0];
111
+ return allowPrefix.some((a) => first.toLowerCase().replace(/\.exe$/i, "") === a.toLowerCase());
112
+ }
@@ -1,47 +1,47 @@
1
- // src/tools/custom.js - 用户自定义工具注册
2
- // 目标: 用户不改源码即可扩展 agent 能力。
3
- // 扫描 <customDir>/*.cjs (或 .js), 每个文件导出工具定义 { name, description, parameters, execute },
4
- // 用 ToolCatalog.register 注册, 与内置工具同权 (可被 LLM 调用、可 enable/disable)。
5
- // 示例 (custom-tools/hello.cjs):
6
- // module.exports = {
7
- // name: "hello",
8
- // description: "返回问候语。",
9
- // parameters: { type: "object", properties: { name: { type: "string" } }, required: [] },
10
- // execute: async (args) => `你好, ${args.name || "世界"}!`,
11
- // };
12
- import fs from "node:fs";
13
- import path from "node:path";
14
- import { createRequire } from "node:module";
15
- import { info, warn } from "../utils/logger.js";
16
-
17
- function isToolDef(v) {
18
- return v && typeof v === "object" && typeof v.name === "string" && v.name && typeof v.execute === "function";
19
- }
20
-
21
- // 同步注册 (构造函数内可用): 用 createRequire 加载 CommonJS 工具文件
22
- export function registerCustomTools(catalog, customDir) {
23
- if (!customDir || !fs.existsSync(customDir)) return 0;
24
- const require = createRequire(import.meta.url);
25
- let files = [];
26
- try { files = fs.readdirSync(customDir); } catch { return 0; }
27
- const candidates = files.filter((f) => f.endsWith(".cjs") || f.endsWith(".js")).sort();
28
-
29
- let count = 0;
30
- for (const f of candidates) {
31
- const full = path.join(customDir, f);
32
- try {
33
- const mod = require(full);
34
- const def = mod && mod.default ? mod.default : mod;
35
- if (!isToolDef(def)) {
36
- warn(`[custom-tools] ${f} 需导出 { name, description, parameters, execute }, 跳过`);
37
- continue;
38
- }
39
- catalog.register(def);
40
- count += 1;
41
- } catch (e) {
42
- warn(`[custom-tools] 加载 ${f} 失败: ${e.message}`);
43
- }
44
- }
45
- if (count) info(`[custom-tools] 已注册 ${count} 个自定义工具`);
46
- return count;
47
- }
1
+ // src/tools/custom.js - 用户自定义工具注册
2
+ // 目标: 用户不改源码即可扩展 agent 能力。
3
+ // 扫描 <customDir>/*.cjs (或 .js), 每个文件导出工具定义 { name, description, parameters, execute },
4
+ // 用 ToolCatalog.register 注册, 与内置工具同权 (可被 LLM 调用、可 enable/disable)。
5
+ // 示例 (custom-tools/hello.cjs):
6
+ // module.exports = {
7
+ // name: "hello",
8
+ // description: "返回问候语。",
9
+ // parameters: { type: "object", properties: { name: { type: "string" } }, required: [] },
10
+ // execute: async (args) => `你好, ${args.name || "世界"}!`,
11
+ // };
12
+ import fs from "node:fs";
13
+ import path from "node:path";
14
+ import { createRequire } from "node:module";
15
+ import { info, warn } from "../utils/logger.js";
16
+
17
+ function isToolDef(v) {
18
+ return v && typeof v === "object" && typeof v.name === "string" && v.name && typeof v.execute === "function";
19
+ }
20
+
21
+ // 同步注册 (构造函数内可用): 用 createRequire 加载 CommonJS 工具文件
22
+ export function registerCustomTools(catalog, customDir) {
23
+ if (!customDir || !fs.existsSync(customDir)) return 0;
24
+ const require = createRequire(import.meta.url);
25
+ let files = [];
26
+ try { files = fs.readdirSync(customDir); } catch { return 0; }
27
+ const candidates = files.filter((f) => f.endsWith(".cjs") || f.endsWith(".js")).sort();
28
+
29
+ let count = 0;
30
+ for (const f of candidates) {
31
+ const full = path.join(customDir, f);
32
+ try {
33
+ const mod = require(full);
34
+ const def = mod && mod.default ? mod.default : mod;
35
+ if (!isToolDef(def)) {
36
+ warn(`[custom-tools] ${f} 需导出 { name, description, parameters, execute }, 跳过`);
37
+ continue;
38
+ }
39
+ catalog.register(def);
40
+ count += 1;
41
+ } catch (e) {
42
+ warn(`[custom-tools] 加载 ${f} 失败: ${e.message}`);
43
+ }
44
+ }
45
+ if (count) info(`[custom-tools] 已注册 ${count} 个自定义工具`);
46
+ return count;
47
+ }