@bolloon/bolloon-agent 0.3.51 → 0.4.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 (218) hide show
  1. package/README.md +135 -135
  2. package/bin/bolloon-cli.cjs +183 -183
  3. package/bin/bolloon.cjs +0 -0
  4. package/bin/bolloon.js +157 -0
  5. package/dist/agents/constraint-layer.js +19 -19
  6. package/dist/agents/judgment-protocol.js +14 -14
  7. package/dist/agents/loop-review.js +21 -0
  8. package/dist/agents/pi-sdk-tools.js +86 -2
  9. package/dist/agents/pi-sdk-types.js +12 -12
  10. package/dist/agents/pi-sdk.js +90 -90
  11. package/dist/agents/shell-guard.js +22 -1
  12. package/dist/agents/skill-organizer.js +19 -19
  13. package/dist/agents/verify-recipe.js +84 -0
  14. package/dist/agents/workflow-pivot-loop.js +101 -13
  15. package/dist/bollharness/src/scripts/deploy-guard.js +8 -8
  16. package/dist/bollharness-integration/context-router-judgment.js +4 -4
  17. package/dist/bollharness-integration/context-router.js +292 -292
  18. package/dist/bollharness-integration/gate-state-machine.js +13 -13
  19. package/dist/bollharness-integration/llm-judgment-engine.js +134 -134
  20. package/dist/bollharness-integration/skill-adapter.js +21 -21
  21. package/dist/bootstrap/chat-archiver.js +14 -14
  22. package/dist/bootstrap/context-os.js +50 -40
  23. package/dist/bootstrap/memory-compressor.js +13 -13
  24. package/dist/cli-entry.js +36 -36
  25. package/dist/context-compaction/auto-compact.js +7 -7
  26. package/dist/electron/first-run.js +54 -54
  27. package/dist/electron-build/electron/first-run.js +54 -54
  28. package/dist/electron-preload.js.map +1 -1
  29. package/dist/electron.js.map +1 -1
  30. package/dist/external-engines/delegate-handle.js +70 -0
  31. package/dist/external-engines/delegate.js +24 -3
  32. package/dist/index.js +112 -112
  33. package/dist/llm/error-lessons.js +53 -0
  34. package/dist/llm/llm-judgment-client.js +102 -102
  35. package/dist/llm/pi-ai.js +61 -48
  36. package/dist/llm/system-prompt/layers/channel/human-async.md +41 -41
  37. package/dist/llm/system-prompt/layers/channel/local.md +14 -14
  38. package/dist/llm/system-prompt/layers/channel/p2p-agent.md +18 -18
  39. package/dist/llm/system-prompt/layers/channel/p2p-peer-sync.md +51 -51
  40. package/dist/llm/system-prompt/layers/channel/p2p-proactive.md +43 -43
  41. package/dist/llm/system-prompt/layers/channel/p2p-visitor.md +19 -19
  42. package/dist/llm/system-prompt/layers/channel/session-handoff.md +61 -61
  43. package/dist/llm/system-prompt/layers/core/artifacts_storage.md +89 -89
  44. package/dist/llm/system-prompt/layers/core/evenhandedness.md +21 -21
  45. package/dist/llm/system-prompt/layers/core/external-engagement.md +73 -73
  46. package/dist/llm/system-prompt/layers/core/hibs_reminders.md +15 -15
  47. package/dist/llm/system-prompt/layers/core/identity.md +48 -48
  48. package/dist/llm/system-prompt/layers/core/knowledge.md +17 -17
  49. package/dist/llm/system-prompt/layers/core/memory_system.md +12 -12
  50. package/dist/llm/system-prompt/layers/core/network_filesystem.md +28 -28
  51. package/dist/llm/system-prompt/layers/core/refusal.md +37 -37
  52. package/dist/llm/system-prompt/layers/core/tone.md +31 -31
  53. package/dist/llm/system-prompt/layers/core/tools.thin.md +13 -13
  54. package/dist/llm/system-prompt/layers/core/wellbeing.md +41 -41
  55. package/dist/llm/system-prompt/layers/role/architect.md +20 -20
  56. package/dist/llm/system-prompt/layers/role/expert.md +19 -19
  57. package/dist/llm/system-prompt/layers/role/implementer.md +15 -15
  58. package/dist/llm/system-prompt/layers/role/security.md +15 -15
  59. package/dist/llm/system-prompt/layers/tool/artifacts.md +72 -72
  60. package/dist/llm/system-prompt/layers/tool/bash.md +25 -25
  61. package/dist/llm/system-prompt/layers/tool/goal_handoff.md +77 -77
  62. package/dist/llm/system-prompt/layers/tool/hibs_api.md +171 -171
  63. package/dist/llm/system-prompt/layers/tool/image_search.md +70 -70
  64. package/dist/llm/system-prompt/layers/tool/manifest.md +89 -89
  65. package/dist/llm/system-prompt/layers/tool/mcp_apps.md +53 -53
  66. package/dist/llm/system-prompt/layers/tool/p2p_request.md +61 -61
  67. package/dist/llm/system-prompt/layers/tool/web_search.md +83 -83
  68. package/dist/llm/system-prompt/registry.js +9 -1
  69. package/dist/llm/tool-manifest/ask_user_input.js +1 -1
  70. package/dist/llm/tool-manifest/bash.js +3 -3
  71. package/dist/llm/tool-manifest/create_file.js +4 -4
  72. package/dist/llm/tool-manifest/fetch_sports_data.js +3 -3
  73. package/dist/llm/tool-manifest/image_search.js +2 -2
  74. package/dist/llm/tool-manifest/index.js +0 -59
  75. package/dist/llm/tool-manifest/mcp.js +2 -2
  76. package/dist/llm/tool-manifest/message_compose.js +3 -3
  77. package/dist/llm/tool-manifest/places.js +2 -2
  78. package/dist/llm/tool-manifest/present_files.js +1 -1
  79. package/dist/llm/tool-manifest/recipe.js +2 -2
  80. package/dist/llm/tool-manifest/str_replace.js +5 -5
  81. package/dist/llm/tool-manifest/view.js +3 -3
  82. package/dist/llm/tool-manifest/weather.js +4 -4
  83. package/dist/llm/tool-manifest/web.js +4 -4
  84. package/dist/locales/en.js +28 -0
  85. package/dist/locales/zh.js +26 -0
  86. package/dist/pi-ecosystem-colony/index.js +365 -0
  87. package/dist/pi-ecosystem-judgment/causal-judge.js +51 -51
  88. package/dist/pi-ecosystem-judgment/distill-prompt.js +42 -42
  89. package/dist/pi-ecosystem-judgment/distillation.js +14 -14
  90. package/dist/pi-ecosystem-judgment/monitor-gate.js +28 -28
  91. package/dist/pi-ecosystem-judgment/value-injection.js +102 -102
  92. package/dist/security/context-router-tool.js +15 -15
  93. package/dist/social/ant-colony/AdaptiveHeartbeat.js +101 -0
  94. package/dist/social/ant-colony/PheromoneEngine.js +227 -0
  95. package/dist/social/ant-colony/index.js +6 -0
  96. package/dist/social/ant-colony/types.js +24 -0
  97. package/dist/storage/trajectory.js +101 -0
  98. package/dist/test/ai-judgment-test.js +80 -0
  99. package/dist/test/bollharness-integration.test.js +318 -0
  100. package/dist/test/channel-agent-multi-dialogue.js +205 -0
  101. package/dist/test/channel-heartbeat-agent-test.js +201 -0
  102. package/dist/test/constraint-layer.test.js +164 -0
  103. package/dist/test/diap-identity-test.js +172 -0
  104. package/dist/test/diap-quick-test.js +62 -0
  105. package/dist/test/global-shared-context.test.js +315 -0
  106. package/dist/test/harness-judgment-injection.test.js +246 -0
  107. package/dist/test/harness-workflow-integrator-test.js +228 -0
  108. package/dist/test/human-value-store.test.js +243 -0
  109. package/dist/test/hybrid-integration-test.js +118 -0
  110. package/dist/test/hybrid-messenger-verify.js +55 -0
  111. package/dist/test/iroh-bistream-debug.js +38 -0
  112. package/dist/test/iroh-communication.test.js +66 -0
  113. package/dist/test/iroh-debug-test.js +57 -0
  114. package/dist/test/iroh-diap-test.js +71 -0
  115. package/dist/test/iroh-direct-connect.js +55 -0
  116. package/dist/test/iroh-e2e-fixed.js +89 -0
  117. package/dist/test/iroh-e2e-same-process.js +63 -0
  118. package/dist/test/iroh-e2e.js +66 -0
  119. package/dist/test/iroh-final-e2e.js +72 -0
  120. package/dist/test/iroh-relay-test.js +37 -0
  121. package/dist/test/iroh-simple-test.js +41 -0
  122. package/dist/test/iroh-transport-verify.js +54 -0
  123. package/dist/test/iroh-transport.test.js +37 -0
  124. package/dist/test/iroh-two-nodes.js +70 -0
  125. package/dist/test/iroh-verify.js +44 -0
  126. package/dist/test/judgment-decision.test.js +219 -0
  127. package/dist/test/llm-judgment-integration.test.js +220 -0
  128. package/dist/test/p2p-agent-complex-dialogue.js +385 -0
  129. package/dist/test/p2p-agent-dialogue.js +341 -0
  130. package/dist/test/p2p-agent-full-bidirectional.js +510 -0
  131. package/dist/test/p2p-agent-harness-flow.js +437 -0
  132. package/dist/test/p2p-agent-harness-single.js +143 -0
  133. package/dist/test/p2p-ai-dialogue-test.js +318 -0
  134. package/dist/test/p2p-cid-connect-test.js +195 -0
  135. package/dist/test/p2p-connect-receiver.js +69 -0
  136. package/dist/test/p2p-doc-transfer.js +110 -0
  137. package/dist/test/p2p-identity-page-test.js +77 -0
  138. package/dist/test/p2p-iroh-test.js +171 -0
  139. package/dist/test/p2p-minimal-test.js +241 -0
  140. package/dist/test/p2p-node-1.js +148 -0
  141. package/dist/test/p2p-node-2.js +148 -0
  142. package/dist/test/p2p-server.js +281 -0
  143. package/dist/test/p2p-two-nodes-test.js +438 -0
  144. package/dist/test/pi-sdk.test.js +44 -0
  145. package/dist/test/set-persona.js +40 -0
  146. package/dist/test/simple.test.js +9 -0
  147. package/dist/test/storage-integration.test.js +150 -0
  148. package/dist/test/subagent-manager.test.js +276 -0
  149. package/dist/test/test-gate-flow.test.js +81 -0
  150. package/dist/test/workflow-engine.test.js +87 -0
  151. package/dist/test/workflow-pivot-loop.test.js +246 -0
  152. package/dist/web/api-config.html +779 -779
  153. package/dist/web/client-hearth.js +10 -10
  154. package/dist/web/client.js +54 -0
  155. package/dist/web/components/wallet-viem.mjs +118 -118
  156. package/dist/web/delivery-ledger.js +103 -0
  157. package/dist/web/i18n.js +104 -0
  158. package/dist/web/index.html +472 -472
  159. package/dist/web/manifest.json +20 -20
  160. package/dist/web/routes-tasks.js +242 -24
  161. package/dist/web/server-storage.js +111 -0
  162. package/dist/web/server.js +55 -26
  163. package/dist/web/style.css +4946 -4946
  164. package/dist/web/task-breaker.js +18 -0
  165. package/dist/web/task-cancel.js +35 -0
  166. package/dist/web/task-deps.js +36 -0
  167. package/dist/web/task-review.js +27 -0
  168. package/dist/web/task-verify.js +60 -0
  169. package/package.json +196 -193
  170. package/scripts/build-cli.js +215 -215
  171. package/scripts/build-web.ts +130 -130
  172. package/scripts/postinstall.js +152 -152
  173. package/bin/bolloon-daemon.sh +0 -207
  174. package/dist/constraint-runtime/reference_data/archive_surface_snapshot.json +0 -63
  175. package/dist/constraint-runtime/reference_data/commands_snapshot.json +0 -1037
  176. package/dist/constraint-runtime/reference_data/subsystems/assistant.json +0 -8
  177. package/dist/constraint-runtime/reference_data/subsystems/bootstrap.json +0 -8
  178. package/dist/constraint-runtime/reference_data/subsystems/bridge.json +0 -32
  179. package/dist/constraint-runtime/reference_data/subsystems/buddy.json +0 -13
  180. package/dist/constraint-runtime/reference_data/subsystems/cli.json +0 -26
  181. package/dist/constraint-runtime/reference_data/subsystems/components.json +0 -32
  182. package/dist/constraint-runtime/reference_data/subsystems/constants.json +0 -28
  183. package/dist/constraint-runtime/reference_data/subsystems/coordinator.json +0 -8
  184. package/dist/constraint-runtime/reference_data/subsystems/entrypoints.json +0 -15
  185. package/dist/constraint-runtime/reference_data/subsystems/hooks.json +0 -32
  186. package/dist/constraint-runtime/reference_data/subsystems/keybindings.json +0 -21
  187. package/dist/constraint-runtime/reference_data/subsystems/memdir.json +0 -15
  188. package/dist/constraint-runtime/reference_data/subsystems/migrations.json +0 -18
  189. package/dist/constraint-runtime/reference_data/subsystems/moreright.json +0 -8
  190. package/dist/constraint-runtime/reference_data/subsystems/native_ts.json +0 -11
  191. package/dist/constraint-runtime/reference_data/subsystems/outputStyles.json +0 -8
  192. package/dist/constraint-runtime/reference_data/subsystems/plugins.json +0 -9
  193. package/dist/constraint-runtime/reference_data/subsystems/remote.json +0 -11
  194. package/dist/constraint-runtime/reference_data/subsystems/schemas.json +0 -8
  195. package/dist/constraint-runtime/reference_data/subsystems/screens.json +0 -10
  196. package/dist/constraint-runtime/reference_data/subsystems/server.json +0 -10
  197. package/dist/constraint-runtime/reference_data/subsystems/services.json +0 -32
  198. package/dist/constraint-runtime/reference_data/subsystems/skills.json +0 -27
  199. package/dist/constraint-runtime/reference_data/subsystems/state.json +0 -13
  200. package/dist/constraint-runtime/reference_data/subsystems/types.json +0 -18
  201. package/dist/constraint-runtime/reference_data/subsystems/upstreamproxy.json +0 -9
  202. package/dist/constraint-runtime/reference_data/subsystems/utils.json +0 -32
  203. package/dist/constraint-runtime/reference_data/subsystems/vim.json +0 -12
  204. package/dist/constraint-runtime/reference_data/subsystems/voice.json +0 -8
  205. package/dist/constraint-runtime/reference_data/tools_snapshot.json +0 -922
  206. package/dist/electron/config.js.map +0 -1
  207. package/dist/electron/dialogs.js.map +0 -1
  208. package/dist/electron/first-run.js.map +0 -1
  209. package/dist/electron/ipc.js.map +0 -1
  210. package/dist/electron/logger.js.map +0 -1
  211. package/dist/electron/main.js.map +0 -1
  212. package/dist/electron/menu.js.map +0 -1
  213. package/dist/electron/paths.js.map +0 -1
  214. package/dist/electron/server.js.map +0 -1
  215. package/dist/electron/tray.js.map +0 -1
  216. package/dist/electron/window.js.map +0 -1
  217. package/dist/utils/auto-update.js.map +0 -1
  218. package/dist/web/client.js.map +0 -7
@@ -44,6 +44,7 @@
44
44
  import * as fs from 'fs';
45
45
  import * as path from 'path';
46
46
  import * as os from 'os';
47
+ import { t as i18nT } from '../web/i18n.js';
47
48
  // ============================================================================
48
49
  // 硬编码兜底 (策略文件读不到时, 用这套)
49
50
  // 这是**最后一道防线** - AI 即便能改 ~/.bolloon/ 也没法删这个常量
@@ -357,6 +358,15 @@ const TERMINAL_DENY_PATTERNS = [
357
358
  /\bgit\s+push\b[^|]*\s+(-f|--force)/, // 强推
358
359
  /\bgit\s+reset\s+--hard\b/,
359
360
  /\bkill\s+-9\s+\d+\b/, // 杀进程 (保留给用户)
361
+ // 2026-08-11 (Hermes cron/lifecycle_guard 模式): 自生命周期命令 — agent 通过 terminal
362
+ // 重启/杀掉自己宿主服务 (bolloon) 会形成复活循环: 服务死 → supervisor 复活 → 自动 resume
363
+ // → 同一命令再自杀. 策略: 命令形状锚定 (只匹配真实命令标识符, 不匹配散文, 误报率低).
364
+ /\b(bolloon|bolloon-agent|bolloon-agent-service)\s+(restart|stop|kill|quit|shutdown)\b/i,
365
+ /\bpm2\s+(restart|stop|delete|kill)\s+[\w./-]*bolloon\b/i,
366
+ /\b(systemctl|service)\s+(restart|stop|kill)\s+[\w.-]*bolloon\b/i,
367
+ /\bpkill\s+(-[a-z]+\s+)*(-f\s+)?[\w./-]*bolloon\b/i,
368
+ /\btaskkill\s+[^|]*\/IM\s+[\w-]*bolloon\b/i,
369
+ /\btaskkill\s+[^|]*\/FI\s+"?[^"]*bolloon/i,
360
370
  ];
361
371
  /** 检查完整 shell 命令 (terminal 工具用). denylist-only: 命中高危模式即拒, 否则放行. */
362
372
  export function checkTerminalCommand(rawCmd) {
@@ -366,7 +376,18 @@ export function checkTerminalCommand(rawCmd) {
366
376
  for (const pattern of TERMINAL_DENY_PATTERNS) {
367
377
  if (pattern.test(cmd)) {
368
378
  auditShellCall('denied', cmd, [], `terminal 高危模式 ${pattern}`);
369
- return { allowed: false, reason: `命令命中高危护栏 ${pattern} (核心不碰: 提权/格式化/删根/.bolloon 数据). 换一条安全命令.`, matchedBy: 'arg-denylist' };
379
+ // 2026-08-11: 用户可见静态文案走 i18n 目录 (Hermes locales 模式); 目录不可用时中文直出
380
+ let reason;
381
+ try {
382
+ const isSelfLifecycle = /(bolloon|pm2|systemctl|service|pkill|taskkill)/.test(pattern.source);
383
+ reason = isSelfLifecycle
384
+ ? i18nT('guard.self_lifecycle_reason', undefined, { pattern: String(pattern) })
385
+ : i18nT('guard.deny_reason', undefined, { pattern: String(pattern) });
386
+ }
387
+ catch {
388
+ reason = `命令命中高危护栏 ${pattern} (核心不碰: 提权/格式化/删根/.bolloon 数据). 换一条安全命令.`;
389
+ }
390
+ return { allowed: false, reason, matchedBy: 'arg-denylist' };
370
391
  }
371
392
  }
372
393
  auditShellCall('allowed', cmd, []);
@@ -143,25 +143,25 @@ export async function scanLeftoverSkills(opts = {}) {
143
143
  // ───────────────────────── B. 经验进化 (LLM 完整总结) ─────────────────────────
144
144
  /** 把候选的工具调用记录扩写成完整经验 — LLM prompt (结构化 JSON 输出) */
145
145
  export function buildEvolvePrompt(c) {
146
- return `你是经验总结专家。下面是 Bolloon 智能体在一轮运行中连续成功调用的工具记录 (已运行 ${c.runs || 1} 次)。
147
-
148
- 工具记录:
149
- 名称: ${c.name}
150
- 描述: ${c.description}
151
- 原始记录:
152
- ${String(c.body || '').slice(0, 4000)}
153
-
154
- 请把这份"工具调用记录"扩写成一份完整的、可复用的经验文档, 要求:
155
- 1. name: 一个简短的小写英文名 (字母数字连字符, 概括这套操作)
156
- 2. description: 一句话描述 (什么场景用)
157
- 3. body: 完整经验正文, 按以下结构:
158
- ## 背景 — 这套操作在什么场景下用, 解决什么问题
159
- ## 触发条件 — 什么信号/需求出现时应该使用
160
- ## 流程 — 具体步骤 (保留原始工具名), 每步说明目的
161
- ## 注意事项 — 易错点 / 边界情况 / 已知坑
162
- ## 验证 — 怎么确认结果正确
163
-
164
- 只输出一个 JSON 对象, 不要任何其他文字:
146
+ return `你是经验总结专家。下面是 Bolloon 智能体在一轮运行中连续成功调用的工具记录 (已运行 ${c.runs || 1} 次)。
147
+
148
+ 工具记录:
149
+ 名称: ${c.name}
150
+ 描述: ${c.description}
151
+ 原始记录:
152
+ ${String(c.body || '').slice(0, 4000)}
153
+
154
+ 请把这份"工具调用记录"扩写成一份完整的、可复用的经验文档, 要求:
155
+ 1. name: 一个简短的小写英文名 (字母数字连字符, 概括这套操作)
156
+ 2. description: 一句话描述 (什么场景用)
157
+ 3. body: 完整经验正文, 按以下结构:
158
+ ## 背景 — 这套操作在什么场景下用, 解决什么问题
159
+ ## 触发条件 — 什么信号/需求出现时应该使用
160
+ ## 流程 — 具体步骤 (保留原始工具名), 每步说明目的
161
+ ## 注意事项 — 易错点 / 边界情况 / 已知坑
162
+ ## 验证 — 怎么确认结果正确
163
+
164
+ 只输出一个 JSON 对象, 不要任何其他文字:
165
165
  {"name": "...", "description": "...", "body": "..."}`;
166
166
  }
167
167
  /** 解析 LLM 返回的 JSON (容错: 剥 markdown 代码块, 取第一个 {...}) */
@@ -0,0 +1,84 @@
1
+ /**
2
+ * verify-recipe.ts — 项目验证配方 (借鉴 Hermes agent/verify/recipes.py:
3
+ * 从项目自己的 package.json scripts 提取验证命令, 依序执行 build→test)
4
+ *
5
+ * 用途: agent 完成修改后跑 verify_project 工具 → 完成契约 (B7) 要求的证据落地 —
6
+ * "宣布完成前展示具体证据 (命令输出/测试结果)" 有了工具支撑.
7
+ * 纯函数 + runner 分离, 可单测.
8
+ */
9
+ import { spawn } from 'child_process';
10
+ /**
11
+ * 从 package.json scripts 提取验证命令 (Hermes _detect_node_recipe 逻辑):
12
+ * build 阶段: build, typecheck (存在才加, 按序)
13
+ * test 阶段: test, check, lint (存在才加, 按序)
14
+ * runner: npm run <script> (pnpm/bun/yarn 时对应前缀)
15
+ */
16
+ export function extractVerifyCommands(scripts, packageManager) {
17
+ if (!scripts || typeof scripts !== 'object')
18
+ return [];
19
+ const runner = (entry) => {
20
+ if (packageManager === 'pnpm')
21
+ return `pnpm ${entry}`;
22
+ if (packageManager === 'bun')
23
+ return `bun run ${entry}`;
24
+ if (packageManager === 'yarn')
25
+ return `yarn ${entry}`;
26
+ return `npm run ${entry}`;
27
+ };
28
+ const out = [];
29
+ for (const name of ['build', 'typecheck', 'test', 'check', 'lint']) {
30
+ if (typeof scripts[name] === 'string') {
31
+ out.push({ name, command: runner(name) });
32
+ }
33
+ }
34
+ return out;
35
+ }
36
+ /** 从 lockfile 检测包管理器 (Hermes detect_package_manager) */
37
+ export function detectPackageManager(files) {
38
+ if (files.includes('pnpm-lock.yaml'))
39
+ return 'pnpm';
40
+ if (files.includes('bun.lockb') || files.includes('bun.lock'))
41
+ return 'bun';
42
+ if (files.includes('yarn.lock'))
43
+ return 'yarn';
44
+ if (files.includes('package-lock.json'))
45
+ return 'npm';
46
+ return undefined;
47
+ }
48
+ /** 依序执行验证命令; 任一步失败不中断 (收集全量结果), 全部 exit 0 才算通过 */
49
+ export async function runVerifyCommands(opts) {
50
+ const stepTimeoutMs = opts.stepTimeoutMs ?? 5 * 60 * 1000;
51
+ const steps = [];
52
+ for (const cmd of opts.commands) {
53
+ const t0 = Date.now();
54
+ const result = await new Promise((resolve) => {
55
+ const child = spawn(cmd.command, { cwd: opts.cwd, shell: true, env: process.env });
56
+ let output = '';
57
+ let timedOut = false;
58
+ const timer = setTimeout(() => {
59
+ timedOut = true;
60
+ child.kill('SIGTERM');
61
+ }, stepTimeoutMs);
62
+ child.stdout?.on('data', (d) => {
63
+ output += d.toString();
64
+ if (output.length > 20_000)
65
+ output = output.slice(-20_000);
66
+ });
67
+ child.stderr?.on('data', (d) => {
68
+ output += d.toString();
69
+ if (output.length > 20_000)
70
+ output = output.slice(-20_000);
71
+ });
72
+ child.on('close', (code) => {
73
+ clearTimeout(timer);
74
+ resolve({ exitCode: code, durationMs: Date.now() - t0, outputTail: output.slice(-2000), timedOut });
75
+ });
76
+ child.on('error', (err) => {
77
+ clearTimeout(timer);
78
+ resolve({ exitCode: null, durationMs: Date.now() - t0, outputTail: `spawn 失败: ${err.message}`, timedOut });
79
+ });
80
+ });
81
+ steps.push({ name: cmd.name, command: cmd.command, ...result });
82
+ }
83
+ return { steps, allPassed: steps.every((s) => s.exitCode === 0 && !s.timedOut) };
84
+ }
@@ -12,6 +12,55 @@
12
12
  * - Consecutive invalid iteration detection
13
13
  * - Token budget awareness
14
14
  */
15
+ import { detectRepeatingCalls, toolCallArgsHash } from './loop-review.js';
16
+ /**
17
+ * 2026-08-11 (借鉴 Hermes `_canonicalize_tool_call_arguments`): 工具参数规范化 —
18
+ * LLM 返回的 arguments 常带尾随散文/尾逗号/代码围栏, 直接 JSON.parse 失败会丢整个工具调用.
19
+ * 策略: 直接解析 → 截到最后一个闭合 } → 去代码围栏, 逐级降级; 结果必须是非数组对象.
20
+ */
21
+ export function canonicalizeToolCallArguments(argStr) {
22
+ if (typeof argStr !== 'string')
23
+ return {};
24
+ const s = argStr.trim();
25
+ if (!s)
26
+ return {};
27
+ const tryParse = (text) => {
28
+ try {
29
+ const parsed = JSON.parse(text);
30
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
31
+ return parsed;
32
+ }
33
+ catch { /* 下一级 */ }
34
+ return null;
35
+ };
36
+ const direct = tryParse(s);
37
+ if (direct)
38
+ return direct;
39
+ // LLM 在 JSON 后追加散文 — 截到最后一个闭合 } 再试
40
+ const lastClose = s.lastIndexOf('}');
41
+ if (lastClose > 0) {
42
+ const truncated = tryParse(s.slice(0, lastClose + 1));
43
+ if (truncated)
44
+ return truncated;
45
+ }
46
+ // 代码围栏包裹
47
+ const fenced = s.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, '').trim();
48
+ if (fenced && fenced !== s) {
49
+ const unfenced = tryParse(fenced);
50
+ if (unfenced)
51
+ return unfenced;
52
+ }
53
+ return {};
54
+ }
55
+ /** 2026-08-11 (Hermes build_worker_context 全限幅): 工具输出进 LLM 上下文的上限 */
56
+ export const TOOL_RESULT_MAX_CHARS = 12_000;
57
+ /** 截断工具输出 (纯函数, 可单测): 超限 → 保留前 max + 可见省略标记 */
58
+ export function truncateToolOutput(output, max = TOOL_RESULT_MAX_CHARS) {
59
+ const s = output ?? '';
60
+ if (s.length <= max)
61
+ return s;
62
+ return s.slice(0, max) + `\n… [truncated, ${s.length - max} chars omitted]`;
63
+ }
15
64
  /**
16
65
  * Analyze input to determine task complexity
17
66
  */
@@ -81,6 +130,10 @@ export class WorkflowPivotLoop {
81
130
  verbose = typeof process !== 'undefined' && process.env?.BOLLOON_VERBOSE === '1';
82
131
  vlog(msg) { if (this.verbose)
83
132
  console.log(msg); }
133
+ pushContinuationHint(hint) {
134
+ if (!this.continuationHints.includes(hint))
135
+ this.continuationHints.push(hint);
136
+ }
84
137
  onApproachingTokenBudget;
85
138
  compactedThisRun = false; // 防止 pivot 单次 execute 反复触发 compact
86
139
  // 2026-07-06: iter ≥ 2 时用简短 systemHeader 替代完整 systemPrompt
@@ -88,6 +141,11 @@ export class WorkflowPivotLoop {
88
141
  // (pi-ai.ts buildSystemPromptAsync 重复读 .md) — 真没必要. 第一轮装全, 后续用锚句占位.
89
142
  // compactor 触发后 (旧 systemHeader 代表性弱化) 再重新装一次全量.
90
143
  continuationSystemHeader = null;
144
+ // 2026-08-11 (Hermes `_get_continuation_prompt` 模式): 本轮工具执行的结构性问题
145
+ // (未知工具被跳过/输出过大) → 下轮注入显式续跑提示, LLM 不会静默忽略.
146
+ continuationHints = [];
147
+ // 2026-08-11 (循环工作流检测): 最近工具调用记录 (name+argsHash), 连续相同 = 有活动无进展
148
+ recentToolCalls = [];
91
149
  constructor(config) {
92
150
  this.tools = new Map();
93
151
  // Default configuration based on task complexity if not provided
@@ -167,16 +225,11 @@ export class WorkflowPivotLoop {
167
225
  if (!fn || !fn.name)
168
226
  continue;
169
227
  const name = fn.name;
170
- if (!this.tools.has(name))
228
+ if (!this.tools.has(name)) {
229
+ this.pushContinuationHint(`工具 ${name} 未注册/不存在, 调用已跳过 — 请换用已提供的工具`);
171
230
  continue;
172
- let args = {};
173
- try {
174
- const parsed = JSON.parse(fn.arguments || '{}');
175
- if (parsed && typeof parsed === 'object')
176
- args = parsed;
177
231
  }
178
- catch { /* args 保持空 */ }
179
- out.push({ name, args: this.normalizeArgs(args), description: '', parameters: {} });
232
+ out.push({ name, args: this.normalizeArgs(canonicalizeToolCallArguments(fn.arguments)), description: '', parameters: {} });
180
233
  }
181
234
  return out;
182
235
  }
@@ -238,7 +291,13 @@ export class WorkflowPivotLoop {
238
291
  tool: 'loop'
239
292
  });
240
293
  // Build context for LLM
241
- const context = this.buildContext();
294
+ let context = this.buildContext();
295
+ // 2026-08-11: 注入工具续跑提示 (Hermes continuation prompt 模式) — 结构性问题显式告知,
296
+ // 用完即清 (只对下一轮 LLM 可见一次)
297
+ if (this.continuationHints.length > 0) {
298
+ context += `\n\n【工具续跑提示】\n${this.continuationHints.map((h) => `- ${h}`).join('\n')}\n请基于以上信息继续推进任务, 不要重复已失败的调用。`;
299
+ this.continuationHints = [];
300
+ }
242
301
  const fullPrompt = `${systemPrompt}\n\n${context}`;
243
302
  // 2026-07-06: iter ≥ 2 改用 continuationHeader — messageHistory 已经载过全 persona/tools,
244
303
  // 重装 11K + 25 layer 是浪费, 同时让 pi-ai.chat 走 < 2000 分支 (不重新装 system prompt).
@@ -415,7 +474,29 @@ export class WorkflowPivotLoop {
415
474
  args: toolCall.args || {},
416
475
  });
417
476
  try {
477
+ // 2026-08-11 (Hermes wedged-loop 检测): 连续 3 次相同工具+相同参数 = 疑似循环工作流 —
478
+ // 计入 no-progress (达到上限由 determineExitReason 中断), 注入续跑提示让 LLM 换策略
479
+ const argsHash = toolCallArgsHash(toolCall.args);
480
+ this.recentToolCalls.push({ name: toolCall.name, argsHash });
481
+ if (this.recentToolCalls.length > 8)
482
+ this.recentToolCalls.shift();
483
+ const rep = detectRepeatingCalls(this.recentToolCalls);
484
+ if (rep.repeating) {
485
+ this.state.consecutiveNoProgress++;
486
+ this.pushContinuationHint(`检测到重复工具调用 ${rep.name} (连续 ${this.recentToolCalls.slice(-3).length} 次相同参数) — 疑似循环, 请换一种策略或缩小范围, 不要重复同一调用`);
487
+ this.emit({
488
+ type: 'error',
489
+ content: `⚠ 检测到重复工具调用 ${rep.name} (连续 3 次相同参数) — 疑似循环工作流, 已计入无进展`,
490
+ tool: 'loop-guard',
491
+ });
492
+ }
418
493
  const result = await tool.execute(toolCall.args ?? {});
494
+ // 2026-08-11: 输出过大 → 续跑提示 (Hermes dropped-tools continuation 模式),
495
+ // 上下文后续可能被压缩, 提醒 LLM 需要精确数据时缩小范围重查
496
+ const outLen = (result.output || '').length;
497
+ if (outLen > 12_000) {
498
+ this.pushContinuationHint(`工具 ${toolCall.name} 输出过长 (${outLen} 字符), 上下文后续可能被压缩 — 如需精确数据请缩小范围重查`);
499
+ }
419
500
  // 2026-06-15: step-timeline — 关闭节点 (success / error)
420
501
  this.emit({
421
502
  type: result.success ? 'step_done' : 'step_error',
@@ -437,7 +518,11 @@ export class WorkflowPivotLoop {
437
518
  role: 'assistant',
438
519
  content: reply,
439
520
  toolCall,
440
- toolResult: result
521
+ // 2026-08-11 (Hermes 全限幅): 存进 LLM 上下文的工具输出截断到 12K,
522
+ // 前端 step_done emit 仍用完整 output (不影响 UI)
523
+ toolResult: outLen > TOOL_RESULT_MAX_CHARS
524
+ ? { ...result, output: truncateToolOutput(result.output) }
525
+ : result
441
526
  });
442
527
  // Record quality from tool result
443
528
  const toolQuality = this.evaluateToolResult(result);
@@ -519,9 +604,12 @@ export class WorkflowPivotLoop {
519
604
  try {
520
605
  const obj = JSON.parse(match[1]);
521
606
  if (obj && obj.name && this.tools.has(obj.name)) {
522
- const args = this.normalizeArgs(obj.arguments || {});
607
+ const args = this.normalizeArgs(canonicalizeToolCallArguments(obj.arguments));
523
608
  pending.push({ name: obj.name, args, description: '', parameters: {} });
524
609
  }
610
+ else if (obj && obj.name && !this.tools.has(obj.name)) {
611
+ this.pushContinuationHint(`工具 ${obj.name} 未注册/不存在, 调用已跳过 — 请换用已提供的工具`);
612
+ }
525
613
  }
526
614
  catch (e) {
527
615
  // JSON 解析失败, 继续下一 match
@@ -778,8 +866,8 @@ export class WorkflowPivotLoop {
778
866
  // 完整重装只在 iter=1 和 compact 之后两次
779
867
  const lines = fullSystemPrompt.split('\n');
780
868
  const identityLine = lines.find((l) => l.includes('你是') && !l.includes('(续)')) ?? '';
781
- return `[continuation] 你的 persona + tools + judgments 已在首轮 messageHistory 中.
782
- ${identityLine ? identityLine + '\n' : ''}当前 step 别再读 persona 全文, 继续推进任务即可.
869
+ return `[continuation] 你的 persona + tools + judgments 已在首轮 messageHistory 中.
870
+ ${identityLine ? identityLine + '\n' : ''}当前 step 别再读 persona 全文, 继续推进任务即可.
783
871
  > <final gen> 只在真完成时输出.`;
784
872
  }
785
873
  /**
@@ -104,17 +104,17 @@ function checkRsync(cmd) {
104
104
  function block(reason, host = null) {
105
105
  let guidance;
106
106
  if (host === PROD_IP) {
107
- guidance = `请使用标准部署流程:
108
- 后端: bash scripts/deploy.sh --yes
109
- Demo 正式: bash scripts/deploy-demo.sh <name> --channel prod --yes
110
- Demo 内测: bash scripts/deploy-demo.sh <name> --channel preview --yes
111
- Edge/Nginx: bash scripts/deploy-edge.sh --yes
107
+ guidance = `请使用标准部署流程:
108
+ 后端: bash scripts/deploy.sh --yes
109
+ Demo 正式: bash scripts/deploy-demo.sh <name> --channel prod --yes
110
+ Demo 内测: bash scripts/deploy-demo.sh <name> --channel preview --yes
111
+ Edge/Nginx: bash scripts/deploy-edge.sh --yes
112
112
  详见 Bolloon.md Development Commands。`;
113
113
  }
114
114
  else if (BRIDGE_VPS_HOSTS.includes(host ?? "")) {
115
- guidance = `Bridge VPS 必须走 git pull 更新路径:
116
- ssh root@${host} 'sudo -u boll git -C /opt/boll pull --ff-only'
117
- scp/rsync 直传会重新制造 orphan worktree。
115
+ guidance = `Bridge VPS 必须走 git pull 更新路径:
116
+ ssh root@${host} 'sudo -u boll git -C /opt/boll pull --ff-only'
117
+ scp/rsync 直传会重新制造 orphan worktree。
118
118
  详见 docs/issues/guard-20260408-0445-bridge-vps-orphan-worktree.md。`;
119
119
  }
120
120
  else {
@@ -253,9 +253,9 @@ export async function generateJudgmentInjection(filePath, gate, options = {}) {
253
253
  const header = gate === 3
254
254
  ? '# Plan Freeze - Decision Principles'
255
255
  : `# Gate ${gate} - Active Judgments`;
256
- return `${header}
257
- # Path: ${filePath}
258
- # Confidence: ${(result.confidence * 100).toFixed(0)}%
259
-
256
+ return `${header}
257
+ # Path: ${filePath}
258
+ # Confidence: ${(result.confidence * 100).toFixed(0)}%
259
+
260
260
  ${result.contextYaml}`;
261
261
  }