@bolloon/bolloon-agent 0.1.34 → 0.1.36

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 (226) hide show
  1. package/.auto-evolve-calls +1 -0
  2. package/.last-auto-evolve-baseline +1 -0
  3. package/.playwright-mcp/page-2026-06-16T07-51-45-706Z.yml +130 -0
  4. package/.playwright-mcp/page-2026-06-16T07-56-44-116Z.yml +131 -0
  5. package/Bolloon.md +144 -0
  6. package/Dive-into/CITATION.cff +17 -0
  7. package/Dive-into/LICENSE +25 -0
  8. package/Dive-into/README.md +598 -0
  9. package/Dive-into/README_zh.md +599 -0
  10. package/Dive-into/assets/context.png +0 -0
  11. package/Dive-into/assets/extensibility.png +0 -0
  12. package/Dive-into/assets/iteration.png +0 -0
  13. package/Dive-into/assets/layered_architecture.png +0 -0
  14. package/Dive-into/assets/main_structure.png +0 -0
  15. package/Dive-into/assets/permission.png +0 -0
  16. package/Dive-into/assets/session_compact.png +0 -0
  17. package/Dive-into/assets/subagent.png +0 -0
  18. package/Dive-into/paper/Dive_into_Claude_Code.pdf +0 -0
  19. package/README.md +1 -1
  20. package/dist/agents/p2p-chat-tools.js +6 -6
  21. package/dist/agents/permission-mode.js +115 -0
  22. package/dist/agents/pi-sdk.js +687 -25
  23. package/dist/agents/pre-tool-validator.js +194 -0
  24. package/dist/agents/workflow-pivot-loop.js +113 -12
  25. package/dist/bollharness/src/scripts/checks/check_doc_freshness.js +1 -1
  26. package/dist/bollharness/src/scripts/checks/check_doc_links.js +1 -1
  27. package/dist/bollharness/src/scripts/context_router.js +1 -1
  28. package/dist/bollharness/src/scripts/deploy-guard.js +1 -1
  29. package/dist/bollharness/src/scripts/guard_router.js +1 -1
  30. package/dist/bollharness/src/scripts/hooks/_hook_output.js +1 -1
  31. package/dist/bollharness/src/scripts/hooks/risk-tracker.js +1 -1
  32. package/dist/bollharness-integration/context-router.js +2 -2
  33. package/dist/bollharness-integration/guard-checker.js +1 -1
  34. package/dist/bootstrap/bootstrap.js +117 -0
  35. package/dist/bootstrap/context-collector.js +312 -0
  36. package/dist/bootstrap/context-hierarchy.js +218 -0
  37. package/dist/bootstrap/lifecycle-hooks.js +171 -0
  38. package/dist/bootstrap/project-context.js +158 -0
  39. package/dist/context-compaction/auto-compact.js +144 -0
  40. package/dist/context-compaction/budget-gate.js +28 -0
  41. package/dist/context-compaction/budget-reduce.js +35 -0
  42. package/dist/context-compaction/context-collapse.js +66 -0
  43. package/dist/context-compaction/index.js +21 -0
  44. package/dist/context-compaction/microcompact.js +51 -0
  45. package/dist/context-compaction/pipeline.js +123 -0
  46. package/dist/context-compaction/snip.js +45 -0
  47. package/dist/context-compaction/token-estimator.js +35 -0
  48. package/dist/context-compaction/types.js +19 -0
  49. package/dist/heartbeat/HealthMonitor.js +3 -2
  50. package/dist/index.js +49 -2
  51. package/dist/llm/llm-judgment-client.js +32 -30
  52. package/dist/llm/pi-ai.js +125 -28
  53. package/dist/llm/system-prompt/health.js +129 -0
  54. package/dist/llm/system-prompt/registry.js +246 -0
  55. package/dist/llm/system-prompt/strip-hibsml.js +51 -0
  56. package/dist/llm/tool-manifest/ask_user_input.js +35 -0
  57. package/dist/llm/tool-manifest/bash.js +23 -0
  58. package/dist/llm/tool-manifest/create_file.js +24 -0
  59. package/dist/llm/tool-manifest/fetch_sports_data.js +26 -0
  60. package/dist/llm/tool-manifest/image_search.js +24 -0
  61. package/dist/llm/tool-manifest/index.js +69 -0
  62. package/dist/llm/tool-manifest/mcp.js +43 -0
  63. package/dist/llm/tool-manifest/message_compose.js +31 -0
  64. package/dist/llm/tool-manifest/places.js +83 -0
  65. package/dist/llm/tool-manifest/present_files.js +21 -0
  66. package/dist/llm/tool-manifest/recipe.js +40 -0
  67. package/dist/llm/tool-manifest/recommend_apps.js +23 -0
  68. package/dist/llm/tool-manifest/str_replace.js +27 -0
  69. package/dist/llm/tool-manifest/types.js +7 -0
  70. package/dist/llm/tool-manifest/view.js +24 -0
  71. package/dist/llm/tool-manifest/weather.js +27 -0
  72. package/dist/llm/tool-manifest/web.js +51 -0
  73. package/dist/network/p2p-direct.js +23 -0
  74. package/dist/network/source-intent-broadcaster.js +203 -0
  75. package/dist/network/source-intent.js +100 -0
  76. package/dist/pi-ecosystem-judgment/adaptive-scan.js +279 -0
  77. package/dist/pi-ecosystem-judgment/causal-judge.js +449 -0
  78. package/dist/pi-ecosystem-judgment/detect-hook.js +168 -0
  79. package/dist/pi-ecosystem-judgment/distill-prompt.js +226 -0
  80. package/dist/pi-ecosystem-judgment/evolve-judgment.js +170 -0
  81. package/dist/pi-ecosystem-judgment/human-value-pipeline.js +21 -0
  82. package/dist/pi-ecosystem-judgment/human-value-store.js +283 -22
  83. package/dist/pi-ecosystem-judgment/injection-gate.js +208 -0
  84. package/dist/pi-ecosystem-judgment/monitor-gate.js +188 -0
  85. package/dist/pi-ecosystem-judgment/value-injection.js +8 -2
  86. package/dist/security/builtin-guards.js +124 -0
  87. package/dist/security/context-router-tool.js +106 -0
  88. package/dist/security/input-scanner.js +223 -0
  89. package/dist/security/react-harness.js +143 -0
  90. package/dist/security/tool-gate.js +235 -0
  91. package/dist/utils/auto-evolve-policy.js +117 -0
  92. package/dist/utils/clamp.js +7 -0
  93. package/dist/utils/double.js +6 -0
  94. package/dist/web/client.js +3810 -3830
  95. package/dist/web/components/p2p/P2PModal.js +188 -0
  96. package/dist/web/components/p2p/index.js +264 -226
  97. package/dist/web/components/p2p/p2p-modal.js +657 -0
  98. package/dist/web/components/p2p/p2p-tools.js +248 -0
  99. package/dist/web/index.html +60 -49
  100. package/dist/web/server.js +827 -124
  101. package/dist/web/style.css +531 -249
  102. package/dist/web/ui/message-renderer.js +463 -0
  103. package/dist/web/ui/step-timeline.js +375 -0
  104. package/lefthook.yml +33 -0
  105. package/package.json +3 -2
  106. package/scripts/auto-evolve-loop.ts +481 -0
  107. package/scripts/auto-evolve-oneshot.sh +155 -0
  108. package/scripts/auto-evolve-snapshot.sh +136 -0
  109. package/scripts/build-web.ts +35 -1
  110. package/scripts/detect-schema-changes.sh +48 -0
  111. package/scripts/diff-reviewer.ts +159 -0
  112. package/scripts/validate-system-prompt.ts +142 -0
  113. package/scripts/weekly-report.ts +364 -0
  114. package/src/agents/p2p-chat-tools.ts +6 -6
  115. package/src/agents/permission-mode.ts +127 -0
  116. package/src/agents/pi-sdk.ts +741 -30
  117. package/src/agents/pre-tool-validator.ts +213 -0
  118. package/src/agents/workflow-pivot-loop.ts +110 -19
  119. package/src/bollharness/CLAUDE.md +1 -1
  120. package/src/bollharness/README.md +2 -2
  121. package/src/bollharness/README.zh-CN.md +2 -2
  122. package/src/bollharness/reference/boll-reference/scripts/hooks/find-boll-root.sh +2 -2
  123. package/src/bollharness/scripts/context-fragments/truth-source-hierarchy.md +2 -2
  124. package/src/bollharness/scripts/context-fragments/version-sources.md +1 -1
  125. package/src/bollharness/scripts/hooks/find-project-root.sh +4 -4
  126. package/src/bollharness/src/scripts/checks/check_doc_freshness.ts +1 -1
  127. package/src/bollharness/src/scripts/checks/check_doc_links.ts +1 -1
  128. package/src/bollharness/src/scripts/context_router.ts +1 -1
  129. package/src/bollharness/src/scripts/deploy-guard.ts +1 -1
  130. package/src/bollharness/src/scripts/guard_router.ts +1 -1
  131. package/src/bollharness/src/scripts/hooks/_hook_output.ts +1 -1
  132. package/src/bollharness/src/scripts/hooks/risk-tracker.ts +1 -1
  133. package/src/bollharness/templates/scaffold/.gitignore.append +1 -1
  134. package/src/bollharness/templates/scaffold/CLAUDE.md +2 -2
  135. package/src/bollharness-integration/context-router.ts +2 -2
  136. package/src/bollharness-integration/guard-checker.ts +1 -1
  137. package/src/bootstrap/bootstrap.ts +135 -0
  138. package/src/bootstrap/context-collector.ts +371 -0
  139. package/src/bootstrap/context-hierarchy.ts +283 -0
  140. package/src/bootstrap/lifecycle-hooks.ts +289 -0
  141. package/src/bootstrap/project-context.ts +171 -0
  142. package/src/context-compaction/auto-compact.ts +153 -0
  143. package/src/context-compaction/budget-gate.ts +32 -0
  144. package/src/context-compaction/budget-reduce.ts +37 -0
  145. package/src/context-compaction/context-collapse.ts +72 -0
  146. package/src/context-compaction/index.ts +24 -0
  147. package/src/context-compaction/microcompact.ts +54 -0
  148. package/src/context-compaction/pipeline.ts +133 -0
  149. package/src/context-compaction/snip.ts +51 -0
  150. package/src/context-compaction/token-estimator.ts +36 -0
  151. package/src/context-compaction/types.ts +99 -0
  152. package/src/heartbeat/HealthMonitor.ts +3 -2
  153. package/src/index.ts +47 -2
  154. package/src/llm/llm-judgment-client.ts +36 -35
  155. package/src/llm/pi-ai.ts +135 -29
  156. package/src/llm/system-prompt/health.ts +159 -0
  157. package/src/llm/system-prompt/layers/channel/local.md +14 -0
  158. package/src/llm/system-prompt/layers/channel/p2p-agent.md +18 -0
  159. package/src/llm/system-prompt/layers/channel/p2p-visitor.md +19 -0
  160. package/src/llm/system-prompt/layers/core/artifacts_storage.md +89 -0
  161. package/src/llm/system-prompt/layers/core/evenhandedness.md +21 -0
  162. package/src/llm/system-prompt/layers/core/hibs_reminders.md +15 -0
  163. package/src/llm/system-prompt/layers/core/identity.md +37 -0
  164. package/src/llm/system-prompt/layers/core/knowledge.md +17 -0
  165. package/src/llm/system-prompt/layers/core/memory_system.md +12 -0
  166. package/src/llm/system-prompt/layers/core/network_filesystem.md +28 -0
  167. package/src/llm/system-prompt/layers/core/refusal.md +37 -0
  168. package/src/llm/system-prompt/layers/core/tone.md +31 -0
  169. package/src/llm/system-prompt/layers/core/tools.thin.md +13 -0
  170. package/src/llm/system-prompt/layers/core/wellbeing.md +41 -0
  171. package/src/llm/system-prompt/layers/role/architect.md +20 -0
  172. package/src/llm/system-prompt/layers/role/expert.md +19 -0
  173. package/src/llm/system-prompt/layers/role/implementer.md +15 -0
  174. package/src/llm/system-prompt/layers/role/security.md +15 -0
  175. package/src/llm/system-prompt/layers/tool/artifacts.md +72 -0
  176. package/src/llm/system-prompt/layers/tool/bash.md +25 -0
  177. package/src/llm/system-prompt/layers/tool/hibs_api.md +171 -0
  178. package/src/llm/system-prompt/layers/tool/image_search.md +70 -0
  179. package/src/llm/system-prompt/layers/tool/manifest.md +89 -0
  180. package/src/llm/system-prompt/layers/tool/mcp_apps.md +53 -0
  181. package/src/llm/system-prompt/layers/tool/web_search.md +83 -0
  182. package/src/llm/system-prompt/registry.ts +325 -0
  183. package/src/llm/system-prompt/strip-hibsml.ts +52 -0
  184. package/src/llm/tool-manifest/ask_user_input.ts +37 -0
  185. package/src/llm/tool-manifest/bash.ts +25 -0
  186. package/src/llm/tool-manifest/create_file.ts +26 -0
  187. package/src/llm/tool-manifest/fetch_sports_data.ts +28 -0
  188. package/src/llm/tool-manifest/image_search.ts +26 -0
  189. package/src/llm/tool-manifest/index.ts +88 -0
  190. package/src/llm/tool-manifest/mcp.ts +46 -0
  191. package/src/llm/tool-manifest/message_compose.ts +33 -0
  192. package/src/llm/tool-manifest/places.ts +86 -0
  193. package/src/llm/tool-manifest/present_files.ts +23 -0
  194. package/src/llm/tool-manifest/recipe.ts +42 -0
  195. package/src/llm/tool-manifest/recommend_apps.ts +25 -0
  196. package/src/llm/tool-manifest/str_replace.ts +29 -0
  197. package/src/llm/tool-manifest/types.ts +52 -0
  198. package/src/llm/tool-manifest/view.ts +26 -0
  199. package/src/llm/tool-manifest/weather.ts +29 -0
  200. package/src/llm/tool-manifest/web.ts +54 -0
  201. package/src/network/p2p-direct.ts +22 -0
  202. package/src/network/source-intent-broadcaster.ts +242 -0
  203. package/src/network/source-intent.ts +167 -0
  204. package/src/security/builtin-guards.ts +162 -0
  205. package/src/security/context-router-tool.ts +122 -0
  206. package/src/security/input-scanner.ts +287 -0
  207. package/src/security/react-harness.ts +177 -0
  208. package/src/security/tool-gate.ts +294 -0
  209. package/src/utils/auto-evolve-policy.ts +138 -0
  210. package/src/utils/clamp.ts +5 -0
  211. package/src/web/client.js +105 -2187
  212. package/src/web/client.ts +4326 -0
  213. package/src/web/index.html +60 -49
  214. package/src/web/server.ts +894 -148
  215. package/src/web/style.css +531 -249
  216. package/src/web/ui/message-renderer.ts +540 -0
  217. package/src/web/ui/step-timeline.ts +394 -0
  218. package/staging/auto-evolve/clean-001/.review-verdict +9 -0
  219. package/staging/auto-evolve/clean-001/clean-001.patch +14 -0
  220. package/staging/auto-evolve/e2e-001/.patch-id +1 -0
  221. package/staging/auto-evolve/e2e-001/.review-verdict +12 -0
  222. package/staging/auto-evolve/e2e-001/e2e-001.patch +11 -0
  223. package/staging/auto-evolve/test-bad/.review-verdict +12 -0
  224. package/staging/auto-evolve/test-bad/test-bad.patch +11 -0
  225. package/test-results/.last-run.json +6 -0
  226. package/test-results/src-test-web-loop-status-b-5734c-atus-bar-/346/230/276/347/244/272-spinner-/344/270/215/346/230/276/347/244/272/345/276/252/347/216/257/350/256/241/346/225/260/error-context.md +285 -0
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Pre-Tool Validator — 4 步链式校验
3
+ *
4
+ * 严格对齐 Claude Code 论文 PreToolUse 4 步:
5
+ * 1. modeGate(tool, mode) — bypassPermissions → 直接放行(除 shell)
6
+ * 2. blacklistGate(tool, args) — 6 模式黑名单 (recursive rm, force push, dd 等)
7
+ * 3. shellGuardGate(tool, args) — **完整** shell-guard (checkCommand + checkWritePath), 永远生效
8
+ * 4. schemaGate(tool, args) — 第一版 stub: always allow (后续接入 tool-gate.ts)
9
+ *
10
+ * 每步独立 try/catch, 失败 = 跳过该步, 不阻塞 (与现有 lifecycle-hooks 风格一致)
11
+ *
12
+ * 失败静默: 任何 step 异常 → console.warn + 继续下一 step, 不抛错
13
+ */
14
+
15
+ import { shouldRunBlacklist, isShellTool, type PermissionMode } from './permission-mode.js';
16
+ import { checkCommand, checkWritePath } from './shell-guard.js';
17
+
18
+ export interface PreToolResult {
19
+ allowed: boolean;
20
+ reason?: string;
21
+ /** 哪一步拒绝的 (供 audit log) */
22
+ rejectedBy?: 'mode' | 'blacklist' | 'shell-guard' | 'schema';
23
+ mode: PermissionMode;
24
+ /** bypassPermissions 时记录, 区分 "完全放行" vs "shell-guard 仍生效" */
25
+ shellGuardRetained?: boolean;
26
+ }
27
+
28
+ const DANGEROUS_PATTERNS: Array<{ re: RegExp; reason: string }> = [
29
+ { re: /\brm\s+(-[a-z]*f[a-z]*\s+)?-[a-z]*r[a-z]*\s+\//, reason: '禁止递归删除根目录' },
30
+ { re: /\bgit\s+push\s+.*--force\b/, reason: '禁止 force push' },
31
+ { re: /\brm\s+-rf\s+~\//, reason: '禁止递归删除 home' },
32
+ { re: /\bdd\s+if=.*\s+of=\/dev\//, reason: '禁止 dd 覆盖块设备' },
33
+ { re: /\bcurl\s+.*\|\s*(ba)?sh\b/, reason: '禁止 curl|sh 直执行' },
34
+ { re: />\s*\/dev\/sd[a-z]/, reason: '禁止写裸设备' },
35
+ ];
36
+
37
+ /**
38
+ * Step 1: modeGate
39
+ * - bypassPermissions + 非 shell → 直接放行 (但保留 shellGuardRetained 标记)
40
+ * - 其他 → 继续
41
+ */
42
+ function modeGate(tool: string, mode: PermissionMode): PreToolResult | null {
43
+ try {
44
+ if (mode === 'bypassPermissions' && !isShellTool(tool)) {
45
+ return { allowed: true, mode, rejectedBy: undefined, shellGuardRetained: false };
46
+ }
47
+ return null; // 不在这一步决定
48
+ } catch (err) {
49
+ console.warn('[pre-tool] modeGate failed (silent):', err);
50
+ return null;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Step 2: blacklistGate
56
+ * 6 模式黑名单 (现有 lifecycle-hooks.ts 行为)
57
+ */
58
+ function blacklistGate(tool: string, args: Record<string, unknown>, mode: PermissionMode): PreToolResult | null {
59
+ try {
60
+ if (!isShellTool(tool)) return null;
61
+ const cmd = String(args.command || args.cmd || '');
62
+ if (!cmd) return null;
63
+ for (const { re, reason } of DANGEROUS_PATTERNS) {
64
+ if (re.test(cmd)) {
65
+ return { allowed: false, reason, rejectedBy: 'blacklist', mode };
66
+ }
67
+ }
68
+ return null;
69
+ } catch (err) {
70
+ console.warn('[pre-tool] blacklistGate failed (silent):', err);
71
+ return null;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Step 3: shellGuardGate
77
+ * 永远跑 (绕过 mode), 是 bolloon 自身的安全底线
78
+ *
79
+ * 接完整 shell-guard.ts (checkCommand + checkWritePath):
80
+ * - checkCommand 走命令白名单 + 参数黑名单 (允许列表默认 git/node/npm/npx/tsx/tsc/vitest 等)
81
+ * - checkWritePath 走路径黑名单 (硬编码: pi-sdk.ts, shell-guard.ts, .env, .git/, .bolloon/, package.json, dist/ 等)
82
+ *
83
+ * 失败静默: 任何异常 → 跳过这步, 不阻塞
84
+ */
85
+ function shellGuardGate(tool: string, args: Record<string, unknown>, mode: PermissionMode): PreToolResult | null {
86
+ try {
87
+ if (!isShellTool(tool)) return null;
88
+
89
+ // 1. 命令检查 (含白名单 + arg 黑名单)
90
+ const cmd = String(args.command || args.cmd || '');
91
+ let bin = '';
92
+ if (cmd) {
93
+ // shell-guard 期望 (cmd, args[]) 形式
94
+ // 简单 split: 第一个 token 是 cmd, 之后是 args
95
+ const parts = cmd.split(/\s+/).filter(Boolean);
96
+ bin = parts[0] || '';
97
+ const rest = parts.slice(1);
98
+ if (bin) {
99
+ const cmdResult = checkCommand(bin, rest);
100
+ if (!cmdResult.allowed) {
101
+ return {
102
+ allowed: false,
103
+ reason: cmdResult.reason,
104
+ rejectedBy: 'shell-guard',
105
+ mode,
106
+ shellGuardRetained: true,
107
+ };
108
+ }
109
+ }
110
+ }
111
+
112
+ // 2. 写路径检查 — 从 2 个来源抽:
113
+ // a) 结构化字段 (args.path / args.target / args.file)
114
+ // b) 命令字符串里的 token (老测试传 args.command='cat pi-sdk.ts' 时也能拦)
115
+ const pathCandidates: string[] = [];
116
+ const structured = String(
117
+ args.path || args.target || args.file || args.targetPath || args.destination || ''
118
+ );
119
+ if (structured) pathCandidates.push(structured);
120
+ if (cmd) {
121
+ // 简单 tokenize: 按空白切, 取不含 shell 元字符的 token
122
+ const tokens = cmd.split(/\s+/).filter((t) => t && !/^[|>;&`$()<]/.test(t));
123
+ for (const t of tokens) {
124
+ if (t !== bin) pathCandidates.push(t); // 跳过命令本身
125
+ }
126
+ }
127
+ // 纯文件名 denylist (兜底 — checkWritePath 的 regex 要求 /src/xxx/ 锚定, 纯文件名拦不到)
128
+ // 只对**写/删/改**类命令生效 (rm, cp, mv, sed -i, echo >); 读类命令 (cat, head, tail, ls) 跳过
129
+ const READ_BINS = new Set(['cat', 'head', 'tail', 'wc', 'ls', 'echo', 'pwd', 'date', 'mkdir', 'touch', 'find', 'grep', 'git', 'node', 'npm', 'npx', 'tsx', 'tsc', 'vitest']);
130
+ const FILENAME_DENYLIST = [
131
+ 'pi-sdk.ts', 'shell-guard.ts', 'shell-tool.ts',
132
+ '.env', 'package.json', 'tsconfig.json',
133
+ ];
134
+ const isReadOperation = READ_BINS.has(bin);
135
+ for (const candidate of pathCandidates) {
136
+ // 2a. checkWritePath 只对写操作调用 (读类命令绕开, 避免 cat pi-sdk.ts 误拦)
137
+ if (!isReadOperation) {
138
+ const pathResult = checkWritePath(candidate);
139
+ if (!pathResult.allowed) {
140
+ return {
141
+ allowed: false,
142
+ reason: pathResult.reason,
143
+ rejectedBy: 'shell-guard',
144
+ mode,
145
+ shellGuardRetained: true,
146
+ };
147
+ }
148
+ }
149
+ // 2b. 纯文件名兜底 — 只对写操作生效
150
+ if (!isReadOperation) {
151
+ const baseName = candidate.split('/').pop() || '';
152
+ if (FILENAME_DENYLIST.includes(baseName)) {
153
+ return {
154
+ allowed: false,
155
+ reason: `shell-guard: 禁止 ${bin} 操作 ${candidate} (命中文件名硬编码黑名单)`,
156
+ rejectedBy: 'shell-guard',
157
+ mode,
158
+ shellGuardRetained: true,
159
+ };
160
+ }
161
+ }
162
+ }
163
+
164
+ return null; // 通过
165
+ } catch (err) {
166
+ console.warn('[pre-tool] shellGuardGate failed (silent):', err);
167
+ return null;
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Step 4: schemaGate
173
+ * 第一版 stub: always allow
174
+ */
175
+ function schemaGate(_tool: string, _args: Record<string, unknown>, mode: PermissionMode): PreToolResult | null {
176
+ return null; // 不在这一步决定
177
+ }
178
+
179
+ /**
180
+ * 主入口: 4 步链式, 任何一步拒绝 → 返回, 否则最终放行
181
+ */
182
+ export function validatePreToolUse(
183
+ tool: string,
184
+ args: Record<string, unknown>,
185
+ mode: PermissionMode
186
+ ): PreToolResult {
187
+ const steps = [
188
+ () => modeGate(tool, mode),
189
+ () => blacklistGate(tool, args, mode),
190
+ () => shellGuardGate(tool, args, mode),
191
+ () => schemaGate(tool, args, mode),
192
+ ];
193
+ for (const step of steps) {
194
+ const result = step();
195
+ if (result && !result.allowed) return result;
196
+ }
197
+ // 全部通过 → 放行
198
+ // bypassPermissions + shell 走到这里 = shellGuardGate 已经放过, 但要标记 shellGuardRetained
199
+ return {
200
+ allowed: true,
201
+ mode,
202
+ rejectedBy: undefined,
203
+ shellGuardRetained: isShellTool(tool),
204
+ };
205
+ }
206
+
207
+ // ============================================================
208
+ // 测试钩子
209
+ // ============================================================
210
+
211
+ export function _resetValidatorForTest(): void {
212
+ // validator 是 pure, 保留 API 一致
213
+ }
@@ -298,33 +298,64 @@ export class WorkflowPivotLoop {
298
298
 
299
299
  for (const toolCall of pendingTools) {
300
300
  this.state.toolCallsCount++;
301
-
301
+
302
302
  const tool = this.tools.get(toolCall.name);
303
303
  if (!tool) {
304
304
  this.emit({
305
305
  type: 'error',
306
306
  content: `❌ 未知工具: ${toolCall.name}`
307
307
  });
308
+ // 2026-06-15: step-timeline — 未知工具也开/关一个 step 节点
309
+ this.emit({
310
+ type: 'step_start',
311
+ content: `未知工具 ${toolCall.name}`,
312
+ tool: toolCall.name,
313
+ args: toolCall.args || {},
314
+ });
315
+ this.emit({
316
+ type: 'step_error',
317
+ content: `未知工具 ${toolCall.name}`,
318
+ tool: toolCall.name,
319
+ error: 'Unknown tool',
320
+ });
308
321
  this.messageHistory.push({
309
322
  role: 'tool',
310
323
  content: JSON.stringify({ success: false, error: `Unknown tool: ${toolCall.name}` })
311
324
  });
312
325
  continue;
313
326
  }
314
-
327
+
315
328
  this.emit({
316
329
  type: 'tool',
317
330
  content: `🔧 执行: ${toolCall.name}`,
318
331
  tool: toolCall.name
319
332
  });
320
-
333
+ // 2026-06-15: step-timeline — 开节点
334
+ this.emit({
335
+ type: 'step_start',
336
+ content: `调用 ${toolCall.name}`,
337
+ tool: toolCall.name,
338
+ args: toolCall.args || {},
339
+ });
340
+
321
341
  try {
322
342
  const result = await tool.execute(toolCall.args ?? {});
323
-
343
+
344
+ // 2026-06-15: step-timeline — 关闭节点 (success / error)
345
+ this.emit({
346
+ type: result.success ? 'step_done' : 'step_error',
347
+ content: result.success
348
+ ? `${toolCall.name} 成功`
349
+ : `${toolCall.name} 失败: ${result.error}`,
350
+ tool: toolCall.name,
351
+ success: result.success,
352
+ output: result.output,
353
+ error: result.error,
354
+ });
324
355
  this.emit({
325
356
  type: result.success ? 'status' : 'error',
326
- content: result.success
327
- ? `✅ ${toolCall.name} 成功`
357
+ content: result.success
358
+ ? `✅ ${toolCall.name} 成功`
328
359
  : `❌ ${toolCall.name} 失败: ${result.error}`
329
360
  });
330
361
 
@@ -420,9 +451,49 @@ export class WorkflowPivotLoop {
420
451
  private extractPendingToolUses(content: string): ToolDefinition[] {
421
452
  const pending: ToolDefinition[] = [];
422
453
 
454
+ // Pattern 0: <tool_use>{...JSON...}</tool_use> (Anthropic 风格 + minimax 也用)
455
+ // 这次 LLM 输出: <tool_use>\n{"name": "read_document", "arguments": {"path": "/Users/.../README.md"}}\n</tool_use>
456
+ const toolUseRe = /<tool_use>\s*(\{[\s\S]*?\})\s*<\/tool_use>/g;
457
+ let match;
458
+ while ((match = toolUseRe.exec(content)) !== null) {
459
+ try {
460
+ const obj = JSON.parse(match[1]);
461
+ if (obj && obj.name && this.tools.has(obj.name)) {
462
+ const args = this.normalizeArgs(obj.arguments || {});
463
+ pending.push({ name: obj.name, args, description: '', parameters: {} });
464
+ }
465
+ } catch (e) {
466
+ // JSON 解析失败, 继续下一 match
467
+ }
468
+ }
469
+
470
+ // Pattern 0b: <function_calls><invoke name="X"><parameter name="k">v</parameter>...</invoke></function_calls>
471
+ // 这次 minimax LLM 用这种 Anthropic 风格 XML
472
+ const fnCallsRe = /<function_calls>([\s\S]*?)<\/function_calls>/g;
473
+ while ((match = fnCallsRe.exec(content)) !== null) {
474
+ const block = match[1];
475
+ // 抓 <invoke name="X">...</invoke>
476
+ const invokeRe = /<invoke\s+name="(\w+)"\s*>([\s\S]*?)<\/invoke>/g;
477
+ let im;
478
+ while ((im = invokeRe.exec(block)) !== null) {
479
+ const name = im[1];
480
+ if (!this.tools.has(name)) continue;
481
+ // 抓 <parameter name="k">v</parameter> 列表
482
+ const args: Record<string, string> = {};
483
+ const paramRe = /<parameter\s+name="(\w+)"\s*>([\s\S]*?)<\/parameter>/g;
484
+ let pm;
485
+ while ((pm = paramRe.exec(im[2])) !== null) {
486
+ args[pm[1]] = pm[2].trim().replace(/^["']|['"]$/g, '');
487
+ }
488
+ // 避免重复添加
489
+ if (!pending.some(p => p.name === name)) {
490
+ pending.push({ name, args, description: '', parameters: {} });
491
+ }
492
+ }
493
+ }
494
+
423
495
  // Pattern 1: Chinese format "调用工具: tool_name(args)"
424
496
  const pattern1 = /调用工具[::]\s*(\w+)\s*\(([^)]*)\)/g;
425
- let match;
426
497
  while ((match = pattern1.exec(content)) !== null) {
427
498
  const name = match[1];
428
499
  const argsStr = match[2];
@@ -471,25 +542,45 @@ export class WorkflowPivotLoop {
471
542
  private parseArgs(argsStr: string): Record<string, string> {
472
543
  const args: Record<string, string> = {};
473
544
  if (!argsStr || !argsStr.trim()) return args;
474
-
545
+
475
546
  const pairs = argsStr.split(',').map(s => s.trim()).filter(Boolean);
476
547
  for (const pair of pairs) {
477
- const colonIdx = pair.indexOf(':');
478
- if (colonIdx > 0) {
479
- const key = pair.substring(0, colonIdx).trim();
480
- const value = pair.substring(colonIdx + 1).trim().replace(/^['"]|['"]$/g, '');
481
- args[key] = value;
482
- } else {
483
- // No colon, try to parse as positional
484
- const parts = pair.split(/\s+/);
485
- if (parts.length >= 2) {
486
- args[parts[0]] = parts.slice(1).join(' ');
487
- }
548
+ // 2026-06-15: LLM 实际输出 3 种格式 — 全支持
549
+ // 1) JSON 风格: {"key":"value"} (服务器日志显示, LLM 不会真输出完整 JSON)
550
+ // 2) key="value" 含双引号 (本次 read_document(path="/Users/..."))
551
+ // 3) key='value' 含单引号
552
+ // 4) key:value (老 Chinese 格式)
553
+ // 5) key value (positional 兜底)
554
+ let m = pair.match(/^["']?([\w-]+)["']?\s*=\s*["']([^"']*)["']$/);
555
+ if (m) { args[m[1]] = m[2]; continue; }
556
+ m = pair.match(/^["']?([\w-]+)["']?\s*[:=]\s*([^,]+)$/);
557
+ if (m) { args[m[1]] = m[2].trim().replace(/^['"]|['"]$/g, ''); continue; }
558
+ m = pair.match(/^["']?([\w-]+)["']?\s*[:]\s*["']?([^"']*)["']?$/);
559
+ if (m) { args[m[1]] = m[2].trim(); continue; }
560
+ // positional 兜底
561
+ const parts = pair.split(/\s+/);
562
+ if (parts.length >= 2) {
563
+ args[parts[0]] = parts.slice(1).join(' ');
488
564
  }
489
565
  }
490
566
  return args;
491
567
  }
492
568
 
569
+ /**
570
+ * 把 tool_use JSON 里的 arguments (已经是对象) 转成 Record<string, string>
571
+ * JSON parser 直接给对象, 但 tool.execute 期望 Record<string, string>
572
+ * 非字符串值 JSON.stringify 一下
573
+ */
574
+ private normalizeArgs(args: Record<string, unknown>): Record<string, string> {
575
+ const out: Record<string, string> = {};
576
+ for (const [k, v] of Object.entries(args || {})) {
577
+ if (v == null) continue;
578
+ if (typeof v === 'string') out[k] = v;
579
+ else out[k] = JSON.stringify(v);
580
+ }
581
+ return out;
582
+ }
583
+
493
584
  /**
494
585
  * Build context from message history
495
586
  */
@@ -1,4 +1,4 @@
1
- # CLAUDE.md — bollharness
1
+ # Bolloon.md — bollharness
2
2
 
3
3
  bollharness 是一个 **AI Agent Session 治理框架**——通过 Claude Code hooks、guards、上下文路由和安装器,给任意项目加装结构化的 agent 行为约束。
4
4
 
@@ -22,7 +22,7 @@ You end up supervising more than you saved in development time. The 80% it does
22
22
  ## The Insight
23
23
 
24
24
  ```
25
- CLAUDE.md instruction compliance: ~20%
25
+ Bolloon.md instruction compliance: ~20%
26
26
  PreToolUse hook enforcement: 100%
27
27
  ```
28
28
 
@@ -103,7 +103,7 @@ your-project/
103
103
  ├── scripts/
104
104
  │ ├── hooks/ # 16 lifecycle hooks
105
105
  │ └── checks/ # 15 automated validators
106
- └── CLAUDE.md # Governance guide (generated, yours to edit)
106
+ └── Bolloon.md # Governance guide (generated, yours to edit)
107
107
  ```
108
108
 
109
109
  The installer is idempotent — run it twice, get the same result.
@@ -22,7 +22,7 @@ Claude Code 能力很强。但不加约束时,它有结构性偏见:
22
22
  ## 核心洞察
23
23
 
24
24
  ```
25
- CLAUDE.md 指令遵从率: ~20%
25
+ Bolloon.md 指令遵从率: ~20%
26
26
  PreToolUse hook 执行率: 100%
27
27
  ```
28
28
 
@@ -101,7 +101,7 @@ your-project/
101
101
  ├── scripts/
102
102
  │ ├── hooks/ # 16 个生命周期 hook
103
103
  │ └── checks/ # 15 个自动化验证器
104
- └── CLAUDE.md # 治理指南(自动生成,你可以自由编辑)
104
+ └── Bolloon.md # 治理指南(自动生成,你可以自由编辑)
105
105
  ```
106
106
 
107
107
  安装器是幂等的——跑两次,结果一样。
@@ -1,6 +1,6 @@
1
1
  #!/bin/bash
2
2
  # Walks up from $PWD to find the boll repo root.
3
- # Identifies it by presence of BOTH CLAUDE.md and scripts/guard-feedback.py.
3
+ # Identifies it by presence of BOTH Bolloon.md and scripts/guard-feedback.py.
4
4
  # This handles nested git repos (e.g. boll-progress/) that would confuse
5
5
  # `git rev-parse --show-toplevel`.
6
6
  #
@@ -8,7 +8,7 @@
8
8
  set -e
9
9
  d="${PWD}"
10
10
  while [ "$d" != "/" ] && [ -n "$d" ]; do
11
- if [ -f "$d/CLAUDE.md" ] && [ -f "$d/scripts/guard-feedback.py" ]; then
11
+ if [ -f "$d/Bolloon.md" ] && [ -f "$d/scripts/guard-feedback.py" ]; then
12
12
  echo "$d"
13
13
  exit 0
14
14
  fi
@@ -7,9 +7,9 @@
7
7
  | 1 | 运行中的代码 | 实际行为是最终真相 |
8
8
  | 2 | 数据库 schema + 测试 | 可执行的约束 |
9
9
  | 3 | ADR / TECH / PLAN 文档 | 设计决策记录 |
10
- | 4 | CLAUDE.md | 开发指导(派生自上层) |
10
+ | 4 | Bolloon.md | 开发指导(派生自上层) |
11
11
  | 5 | MEMORY.md | 会话间记忆(最易过期) |
12
12
 
13
13
  **行为约束**: 当不同层级的信息冲突时,必须以高优先级来源为准并修正低优先级来源。
14
- CLAUDE.md 中的信息必须可以从代码或 ADR 中验证——不得包含无法溯源的断言。
14
+ Bolloon.md 中的信息必须可以从代码或 ADR 中验证——不得包含无法溯源的断言。
15
15
  修改真相源文件时,必须检查下游是否需要同步更新。
@@ -4,7 +4,7 @@
4
4
 
5
5
  | 版本号 | 权威来源 | 必须同步的位置 |
6
6
  |--------|----------|----------------|
7
- | MCP Python | `mcp-server/pyproject.toml` | CLAUDE.md 引用 |
7
+ | MCP Python | `mcp-server/pyproject.toml` | Bolloon.md 引用 |
8
8
  | MCP Node | `mcp-server-node/package.json` | 必须与 Python 版本一致 |
9
9
  | 后端 API | `backend/product/protocol/service.py` | /protocol 元数据返回 |
10
10
  | 前端 | `website/package.json` | 部署产物 BUILD_ID |
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # Anchor priority:
5
5
  # 1. .bollharness/MANIFEST.yaml (primary — declared by installer)
6
- # 2. CLAUDE.md (fallback — older projects or pre-install)
6
+ # 2. Bolloon.md (fallback — older projects or pre-install)
7
7
  #
8
8
  # Why not scripts/guard-feedback.ts like the old boll-specific script?
9
9
  # Because that created a circular dependency: guard-feedback.ts was both
@@ -23,10 +23,10 @@ while [ "$d" != "/" ] && [ -n "$d" ]; do
23
23
  d="$(dirname "$d")"
24
24
  done
25
25
 
26
- # Fallback 1: walk up again looking for CLAUDE.md alone
26
+ # Fallback 1: walk up again looking for Bolloon.md alone
27
27
  d="${PWD}"
28
28
  while [ "$d" != "/" ] && [ -n "$d" ]; do
29
- if [ -f "$d/CLAUDE.md" ]; then
29
+ if [ -f "$d/Bolloon.md" ]; then
30
30
  echo "$d"
31
31
  exit 0
32
32
  fi
@@ -35,7 +35,7 @@ done
35
35
 
36
36
  # Fallback 2: CLAUDE_PROJECT_DIR env var set by Claude Code
37
37
  if [ -n "${CLAUDE_PROJECT_DIR:-}" ]; then
38
- if [ -f "${CLAUDE_PROJECT_DIR}/.bollharness/MANIFEST.yaml" ] || [ -f "${CLAUDE_PROJECT_DIR}/CLAUDE.md" ]; then
38
+ if [ -f "${CLAUDE_PROJECT_DIR}/.bollharness/MANIFEST.yaml" ] || [ -f "${CLAUDE_PROJECT_DIR}/Bolloon.md" ]; then
39
39
  echo "${CLAUDE_PROJECT_DIR}"
40
40
  exit 0
41
41
  fi
@@ -75,7 +75,7 @@ export function run(repoRoot: string, mode: string = "full"): Finding[] {
75
75
 
76
76
  const actualRoutes = countRouteDecorators(repoRoot);
77
77
  const routesDoc = path.join(repoRoot, ".boll", "rules", "backend-routes.md");
78
- const claudeMd = path.join(repoRoot, "CLAUDE.md");
78
+ const claudeMd = path.join(repoRoot, "Bolloon.md");
79
79
 
80
80
  if (fs.existsSync(routesDoc)) {
81
81
  const routesContent = fs.readFileSync(routesDoc, "utf-8");
@@ -5,7 +5,7 @@ import { Finding } from "./finding";
5
5
  export function run(repoRoot: string, mode: string = "full"): Finding[] {
6
6
  const findings: Finding[] = [];
7
7
 
8
- const docFiles = ["CLAUDE.md", "docs/ROADMAP.md", "README.md"];
8
+ const docFiles = ["Bolloon.md", "docs/ROADMAP.md", "README.md"];
9
9
  const maxAgeDays = 7;
10
10
 
11
11
  for (const docFile of docFiles) {
@@ -20,7 +20,7 @@ export const CONTEXT_MAP: Record<string, string[]> = {
20
20
  "scenes/": ["scene-fidelity", "two-language"],
21
21
  "website/app/[scene]/": ["scene-fidelity", "two-language"],
22
22
  "website/components/scene/": ["scene-fidelity", "two-language"],
23
- "CLAUDE.md": ["truth-source-hierarchy"],
23
+ "Bolloon.md": ["truth-source-hierarchy"],
24
24
  "MEMORY.md": ["truth-source-hierarchy"],
25
25
  "docs/INDEX.md": ["truth-source-hierarchy"],
26
26
  "mcp-server/pyproject.toml": ["version-sources"],
@@ -134,7 +134,7 @@ function block(reason: string, host: string | null = null): void {
134
134
  Demo 正式: bash scripts/deploy-demo.sh <name> --channel prod --yes
135
135
  Demo 内测: bash scripts/deploy-demo.sh <name> --channel preview --yes
136
136
  Edge/Nginx: bash scripts/deploy-edge.sh --yes
137
- 详见 CLAUDE.md Development Commands。`;
137
+ 详见 Bolloon.md Development Commands。`;
138
138
  } else if (BRIDGE_VPS_HOSTS.includes(host ?? "")) {
139
139
  guidance = `Bridge VPS 必须走 git pull 更新路径:
140
140
  ssh root@${host} 'sudo -u boll git -C /opt/boll pull --ff-only'
@@ -10,7 +10,7 @@ export const GUARD_MAP: Record<string, string[]> = {
10
10
  "docs/issues/": ["check_issue_closure"],
11
11
  "backend/product/routes/": ["check_doc_freshness"],
12
12
  "docs/ROADMAP.md": ["check_doc_freshness"],
13
- "CLAUDE.md": ["check_doc_freshness"],
13
+ "Bolloon.md": ["check_doc_freshness"],
14
14
  "docs/magic/": ["check_doc_freshness"],
15
15
  ".boll/rules/backend-routes.md": ["check_doc_freshness"],
16
16
  ".boll/settings.json": ["check_hook_installed"],
@@ -5,7 +5,7 @@ import * as fs from "fs";
5
5
  process.stdout.write(
6
6
  `\n\n## Bollharness Context\n\n` +
7
7
  `bollharness is an AI Agent Session Governance Framework.\n\n` +
8
- `For more information, see CLAUDE.md in the project root.\n`
8
+ `For more information, see Bolloon.md in the project root.\n`
9
9
  );
10
10
 
11
11
  process.exit(0);
@@ -9,7 +9,7 @@ const RISK_ORDER: Record<string, number> = { R0: 0, R1: 1, R2: 2, R3: 3, R4: 4 }
9
9
  const RISK_ELEVATORS: [string, string][] = [
10
10
  ["scripts/deploy", "R4"],
11
11
  ["backend/product/db/migration", "R4"],
12
- ["CLAUDE.md", "R3"],
12
+ ["Bolloon.md", "R3"],
13
13
  [".boll/settings.json", "R3"],
14
14
  [".boll/skills/", "R3"],
15
15
  [".boll/rules/", "R3"],
@@ -13,4 +13,4 @@
13
13
  .claude/
14
14
  !.claude/settings.json
15
15
  !.claude/settings.json.template
16
- !.claude/CLAUDE.md.template
16
+ !.claude/Bolloon.md.template
@@ -1,9 +1,9 @@
1
- # CLAUDE.md
1
+ # Bolloon.md
2
2
 
3
3
  <!--
4
4
  Auto-generated by bollharness installer.
5
5
  This file is the primary instruction source for Claude Code sessions.
6
- Edit freely — the installer will NOT overwrite an existing CLAUDE.md.
6
+ Edit freely — the installer will NOT overwrite an existing Bolloon.md.
7
7
  -->
8
8
 
9
9
  ## Project Overview
@@ -28,7 +28,7 @@ export const CONTEXT_MAP: Record<string, string[]> = {
28
28
  'src/bollharness-integration/': ['truth-source-hierarchy', 'artifact-linkage'],
29
29
  'docs/': ['truth-source-hierarchy', 'two-language'],
30
30
  'docs/decisions/': ['truth-source-hierarchy', 'artifact-linkage'],
31
- 'CLAUDE.md': ['truth-source-hierarchy', 'bridge-constitution'],
31
+ 'Bolloon.md': ['truth-source-hierarchy', 'bridge-constitution'],
32
32
  'README.md': ['truth-source-hierarchy', 'general-dev-principles'],
33
33
  };
34
34
 
@@ -357,7 +357,7 @@ export const CONTEXT_FRAGMENTS = {
357
357
  'project-governance': `# Project Governance
358
358
 
359
359
  ## Truth Sources
360
- - CLAUDE.md: Project overview and norms
360
+ - Bolloon.md: Project overview and norms
361
361
  - docs/: Architecture and decisions
362
362
  - code/: Implementation
363
363
  - issues/: Tracking and discussions
@@ -90,7 +90,7 @@ export const GUARD_MAP: Record<string, string[]> = {
90
90
  'src/constraints/': ['check_api_types', 'check_versions'],
91
91
  'src/social/': ['check_api_types', 'check_versions'],
92
92
  'docs/': ['check_doc_freshness', 'check_doc_links'],
93
- 'CLAUDE.md': ['check_doc_freshness', 'check_artifact_link'],
93
+ 'Bolloon.md': ['check_doc_freshness', 'check_artifact_link'],
94
94
  'README.md': ['check_doc_freshness'],
95
95
  'docs/decisions/': ['check_artifact_link', 'check_versions'],
96
96
  '.boll/': ['check_hook_installed'],