@phuetz/code-buddy 2.0.0 → 2.1.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 (403) hide show
  1. package/README.fr.md +56 -0
  2. package/README.md +73 -16
  3. package/codebuddy-runtime.json +5 -5
  4. package/dist/agent/codebuddy-agent.d.ts +18 -0
  5. package/dist/agent/codebuddy-agent.js +45 -8
  6. package/dist/agent/execution/agent-executor.d.ts +0 -9
  7. package/dist/agent/execution/agent-executor.js +165 -12
  8. package/dist/agent/execution/context-pipeline.d.ts +9 -0
  9. package/dist/agent/execution/context-pipeline.js +64 -2
  10. package/dist/agent/execution/tool-loop-guard.d.ts +76 -0
  11. package/dist/agent/execution/tool-loop-guard.js +165 -0
  12. package/dist/agent/execution/tool-selection-strategy.js +19 -2
  13. package/dist/agent/facades/session-facade.d.ts +0 -6
  14. package/dist/agent/facades/session-facade.js +0 -45
  15. package/dist/agent/hermes-claw-migrate.d.ts +0 -21
  16. package/dist/agent/hermes-claw-migrate.js +9 -4
  17. package/dist/agent/hermes-protocol-gateways.js +8 -1
  18. package/dist/agent/repo-profiler.js +17 -4
  19. package/dist/agent/self-improvement/authored-tool-runtime.js +10 -2
  20. package/dist/agent/self-improvement/evolution/ast-novelty.d.ts +5 -0
  21. package/dist/agent/self-improvement/evolution/ast-novelty.js +58 -23
  22. package/dist/agent/self-improvement/evolution/code-variant-store.d.ts +3 -0
  23. package/dist/agent/self-improvement/evolution/evolution-engine.js +2 -0
  24. package/dist/agent/self-improvement/evolution/protected-paths.js +6 -0
  25. package/dist/agent/self-improvement/evolution/variant-fitness.d.ts +13 -2
  26. package/dist/agent/self-improvement/evolution/variant-fitness.js +184 -58
  27. package/dist/agent/self-improvement/evolution/worktree-scorer.d.ts +2 -0
  28. package/dist/agent/self-improvement/evolution/worktree-scorer.js +15 -0
  29. package/dist/agent/self-improvement/evolutionary-archive.d.ts +2 -1
  30. package/dist/agent/self-improvement/evolutionary-archive.js +34 -5
  31. package/dist/agent/self-improvement/proposal-store.d.ts +1 -0
  32. package/dist/agent/self-improvement/proposal-store.js +79 -17
  33. package/dist/agent/self-improvement/skill-apply-journal.d.ts +44 -0
  34. package/dist/agent/self-improvement/skill-apply-journal.js +178 -0
  35. package/dist/agent/self-improvement/skill-behavior-benchmark.d.ts +3 -0
  36. package/dist/agent/self-improvement/skill-behavior-benchmark.js +19 -0
  37. package/dist/agent/self-improvement/skill-behavior-gate.d.ts +34 -0
  38. package/dist/agent/self-improvement/skill-behavior-gate.js +96 -0
  39. package/dist/agent/self-improvement/skill-engine.d.ts +22 -0
  40. package/dist/agent/self-improvement/skill-engine.js +385 -13
  41. package/dist/agent/self-improvement/skill-gate.d.ts +2 -0
  42. package/dist/agent/self-improvement/skill-gate.js +17 -2
  43. package/dist/agent/self-improvement/skill-mutator.d.ts +6 -1
  44. package/dist/agent/self-improvement/skill-mutator.js +51 -15
  45. package/dist/agent/self-improvement/skill-types.d.ts +2 -1
  46. package/dist/agent/self-improvement/types.d.ts +11 -0
  47. package/dist/agent/tool-handler.d.ts +1 -1
  48. package/dist/agent/tool-handler.js +28 -9
  49. package/dist/auth/profile-manager.js +1 -1
  50. package/dist/channels/companion-channel-turn.d.ts +53 -4
  51. package/dist/channels/companion-channel-turn.js +218 -16
  52. package/dist/channels/slash-parity.d.ts +4 -6
  53. package/dist/channels/slash-parity.js +9 -44
  54. package/dist/cli/session-commands.d.ts +9 -0
  55. package/dist/cli/session-commands.js +45 -5
  56. package/dist/cli/session-picker.d.ts +43 -0
  57. package/dist/cli/session-picker.js +128 -0
  58. package/dist/codebuddy/a2a-call-tool-defs.d.ts +26 -0
  59. package/dist/codebuddy/a2a-call-tool-defs.js +12 -0
  60. package/dist/codebuddy/fleet-tool-defs.d.ts +1 -0
  61. package/dist/codebuddy/fleet-tool-defs.js +24 -0
  62. package/dist/codebuddy/providers/chatgpt-headers.d.ts +18 -0
  63. package/dist/codebuddy/providers/chatgpt-headers.js +37 -0
  64. package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +2 -1
  65. package/dist/codebuddy/providers/provider-chatgpt-responses.js +10 -23
  66. package/dist/codebuddy/providers/provider-openai-compat.js +1 -1
  67. package/dist/codebuddy/ragchat-tool-defs.d.ts +31 -0
  68. package/dist/codebuddy/ragchat-tool-defs.js +19 -0
  69. package/dist/codebuddy/resource-catalog-tool-defs.d.ts +28 -0
  70. package/dist/codebuddy/resource-catalog-tool-defs.js +18 -0
  71. package/dist/codebuddy/tool-definitions/agent-tools.js +2 -2
  72. package/dist/codebuddy/tool-definitions/code-exec-tools.js +5 -1
  73. package/dist/codebuddy/tool-definitions/cron-tools.js +6 -0
  74. package/dist/codebuddy/tool-definitions/self-describe-tools.js +6 -1
  75. package/dist/codebuddy/tools.d.ts +0 -6
  76. package/dist/codebuddy/tools.js +25 -3
  77. package/dist/commands/cli/daemon-commands.js +3 -1
  78. package/dist/commands/cli/evolve-command.js +21 -5
  79. package/dist/commands/cli/fleet-collaboration-commands.d.ts +2 -0
  80. package/dist/commands/cli/fleet-collaboration-commands.js +57 -0
  81. package/dist/commands/cli/fleet-commands.js +29 -0
  82. package/dist/commands/cli/fleet-mission-commands.d.ts +3 -0
  83. package/dist/commands/cli/fleet-mission-commands.js +72 -0
  84. package/dist/commands/cli/fleet-rooms-commands.d.ts +10 -0
  85. package/dist/commands/cli/fleet-rooms-commands.js +289 -0
  86. package/dist/commands/cli/tools-commands.js +2 -1
  87. package/dist/commands/cli/triage-command.d.ts +8 -0
  88. package/dist/commands/cli/triage-command.js +72 -0
  89. package/dist/commands/cli/utility-commands.js +25 -1
  90. package/dist/commands/client-dispatcher.d.ts +7 -0
  91. package/dist/commands/client-dispatcher.js +73 -21
  92. package/dist/commands/cron-cli/index.d.ts +3 -1
  93. package/dist/commands/cron-cli/index.js +25 -0
  94. package/dist/commands/device-auth.d.ts +10 -0
  95. package/dist/commands/device-auth.js +91 -0
  96. package/dist/commands/enhanced-command-handler.d.ts +9 -0
  97. package/dist/commands/enhanced-command-handler.js +28 -6
  98. package/dist/commands/handlers/agent-handlers.d.ts +1 -1
  99. package/dist/commands/handlers/agent-handlers.js +4 -4
  100. package/dist/commands/handlers/channel-handlers.js +53 -2
  101. package/dist/commands/handlers/export-handlers.d.ts +3 -2
  102. package/dist/commands/handlers/export-handlers.js +55 -28
  103. package/dist/commands/handlers/extra-handlers.js +5 -1
  104. package/dist/commands/handlers/grill-me-handler.js +26 -18
  105. package/dist/commands/handlers/memory-handlers.d.ts +6 -2
  106. package/dist/commands/handlers/memory-handlers.js +35 -15
  107. package/dist/commands/handlers/missing-handlers.d.ts +2 -2
  108. package/dist/commands/handlers/missing-handlers.js +4 -4
  109. package/dist/commands/handlers/resources-handler.d.ts +12 -0
  110. package/dist/commands/handlers/resources-handler.js +52 -0
  111. package/dist/commands/handlers/starter-handlers.d.ts +1 -1
  112. package/dist/commands/handlers/starter-handlers.js +32 -2
  113. package/dist/commands/handlers/switch-handler.d.ts +1 -1
  114. package/dist/commands/handlers/switch-handler.js +15 -0
  115. package/dist/commands/handlers/test-handlers.js +4 -86
  116. package/dist/commands/handlers/ui-handlers.js +11 -5
  117. package/dist/commands/login-chatgpt.d.ts +8 -0
  118. package/dist/commands/login-chatgpt.js +17 -0
  119. package/dist/commands/mcp-import.d.ts +11 -0
  120. package/dist/commands/mcp-import.js +67 -0
  121. package/dist/commands/mcp-invoke.d.ts +2 -0
  122. package/dist/commands/mcp-invoke.js +68 -0
  123. package/dist/commands/mcp.js +4 -0
  124. package/dist/commands/pipeline.d.ts +7 -1
  125. package/dist/commands/pipeline.js +104 -7
  126. package/dist/commands/resources.d.ts +2 -0
  127. package/dist/commands/resources.js +64 -0
  128. package/dist/commands/skills-cli/index.js +35 -12
  129. package/dist/commands/slash/builtin-commands.js +16 -3
  130. package/dist/commands/slash/index.d.ts +3 -1
  131. package/dist/commands/slash/index.js +2 -0
  132. package/dist/commands/slash/surfaces.d.ts +53 -0
  133. package/dist/commands/slash/surfaces.js +177 -0
  134. package/dist/commands/slash/types.d.ts +17 -0
  135. package/dist/commands/try.d.ts +1 -1
  136. package/dist/commands/try.js +1 -1
  137. package/dist/companion/companion-identity.d.ts +43 -0
  138. package/dist/companion/companion-identity.js +136 -0
  139. package/dist/companion/companion-toolset.d.ts +66 -0
  140. package/dist/companion/companion-toolset.js +368 -0
  141. package/dist/companion/companion-turn.d.ts +10 -2
  142. package/dist/companion/companion-turn.js +38 -1
  143. package/dist/config/code-exec-policy.d.ts +25 -0
  144. package/dist/config/code-exec-policy.js +39 -0
  145. package/dist/config/model-tools.d.ts +9 -0
  146. package/dist/config/model-tools.js +4 -0
  147. package/dist/context/context-manager-v2.js +2 -2
  148. package/dist/context/lm-resizer-compressor.js +3 -2
  149. package/dist/context/lm-resizer-diagnostics.d.ts +9 -0
  150. package/dist/context/lm-resizer-diagnostics.js +30 -0
  151. package/dist/context/tool-output-masking.js +3 -4
  152. package/dist/curator/curator.d.ts +6 -0
  153. package/dist/curator/curator.js +53 -3
  154. package/dist/daemon/cron-agent-bridge.d.ts +6 -0
  155. package/dist/daemon/cron-agent-bridge.js +31 -4
  156. package/dist/doctor/index.d.ts +11 -2
  157. package/dist/doctor/index.js +84 -21
  158. package/dist/doctor/integrations.d.ts +179 -0
  159. package/dist/doctor/integrations.js +227 -0
  160. package/dist/doctor/triage.d.ts +108 -0
  161. package/dist/doctor/triage.js +304 -0
  162. package/dist/fleet/collaboration.d.ts +44 -0
  163. package/dist/fleet/collaboration.js +170 -0
  164. package/dist/fleet/fleet-listener.d.ts +1 -0
  165. package/dist/fleet/fleet-listener.js +87 -46
  166. package/dist/fleet/fleet-registry.d.ts +2 -0
  167. package/dist/fleet/resource-catalog.d.ts +245 -0
  168. package/dist/fleet/resource-catalog.js +211 -0
  169. package/dist/fleet/rooms/room-access.d.ts +138 -0
  170. package/dist/fleet/rooms/room-access.js +196 -0
  171. package/dist/fleet/rooms/room-client.d.ts +158 -0
  172. package/dist/fleet/rooms/room-client.js +685 -0
  173. package/dist/fleet/rooms/room-event.d.ts +139 -0
  174. package/dist/fleet/rooms/room-event.js +330 -0
  175. package/dist/fleet/rooms/room-filter.d.ts +48 -0
  176. package/dist/fleet/rooms/room-filter.js +152 -0
  177. package/dist/fleet/rooms/room-hub.d.ts +154 -0
  178. package/dist/fleet/rooms/room-hub.js +363 -0
  179. package/dist/fleet/rooms/room-identity.d.ts +26 -0
  180. package/dist/fleet/rooms/room-identity.js +82 -0
  181. package/dist/fleet/rooms/room-lock.d.ts +34 -0
  182. package/dist/fleet/rooms/room-lock.js +180 -0
  183. package/dist/fleet/rooms/room-observations.d.ts +7 -0
  184. package/dist/fleet/rooms/room-observations.js +61 -0
  185. package/dist/fleet/rooms/room-server.d.ts +28 -0
  186. package/dist/fleet/rooms/room-server.js +75 -0
  187. package/dist/fleet/rooms/room-store.d.ts +119 -0
  188. package/dist/fleet/rooms/room-store.js +448 -0
  189. package/dist/fleet/rooms/room-ws-bridge.d.ts +27 -0
  190. package/dist/fleet/rooms/room-ws-bridge.js +137 -0
  191. package/dist/fleet/sensory-mission-bridge.d.ts +42 -0
  192. package/dist/fleet/sensory-mission-bridge.js +136 -0
  193. package/dist/git/worktree-sessions.js +1 -0
  194. package/dist/harness/contract.d.ts +4 -4
  195. package/dist/harness/fleet-supervisor.d.ts +16 -0
  196. package/dist/harness/fleet-supervisor.js +64 -0
  197. package/dist/harness/index.d.ts +4 -0
  198. package/dist/harness/index.js +4 -0
  199. package/dist/harness/mission-runner.d.ts +3 -0
  200. package/dist/harness/mission-runner.js +53 -0
  201. package/dist/harness/mission-store.d.ts +93 -0
  202. package/dist/harness/mission-store.js +297 -0
  203. package/dist/harness/tool-harness.d.ts +56 -0
  204. package/dist/harness/tool-harness.js +153 -0
  205. package/dist/hooks/use-enhanced-input.js +48 -4
  206. package/dist/hooks/use-input-handler.d.ts +1 -0
  207. package/dist/hooks/use-input-handler.js +82 -22
  208. package/dist/hooks/use-input-history.js +1 -0
  209. package/dist/identity/lisa-introspection.js +5 -2
  210. package/dist/identity/operational-self-model.d.ts +2 -0
  211. package/dist/identity/operational-self-model.js +4 -0
  212. package/dist/index.js +52 -6
  213. package/dist/mcp/client.js +6 -0
  214. package/dist/mcp/config.d.ts +20 -0
  215. package/dist/mcp/config.js +121 -8
  216. package/dist/mcp/import-normalize.d.ts +12 -0
  217. package/dist/mcp/import-normalize.js +114 -0
  218. package/dist/mcp/transports.d.ts +10 -5
  219. package/dist/mcp/transports.js +61 -99
  220. package/dist/mcp/types.d.ts +5 -0
  221. package/dist/media/comfyui-recipe-contract.d.ts +7 -7
  222. package/dist/memory/facts-memory.d.ts +11 -1
  223. package/dist/memory/facts-memory.js +47 -8
  224. package/dist/memory/memory-candidate-queue.d.ts +2 -2
  225. package/dist/memory/memory-candidate-queue.js +6 -6
  226. package/dist/memory/persistent-memory.d.ts +13 -8
  227. package/dist/memory/persistent-memory.js +77 -30
  228. package/dist/nodes/device-node.js +5 -9
  229. package/dist/observability/mobile-supervision-gateway-contract.js +20 -7
  230. package/dist/observability/run-event-writer.d.ts +21 -0
  231. package/dist/observability/run-event-writer.js +81 -0
  232. package/dist/observability/run-store.d.ts +5 -0
  233. package/dist/observability/run-store.js +18 -13
  234. package/dist/observability/run-trajectory.js +1 -1
  235. package/dist/optimization/prompt-cache.d.ts +19 -2
  236. package/dist/optimization/prompt-cache.js +41 -6
  237. package/dist/orchestration/orchestrator.d.ts +21 -0
  238. package/dist/orchestration/orchestrator.js +122 -15
  239. package/dist/persistence/session-content.d.ts +8 -0
  240. package/dist/persistence/session-content.js +76 -0
  241. package/dist/persistence/session-handoff.d.ts +64 -0
  242. package/dist/persistence/session-handoff.js +80 -0
  243. package/dist/persistence/session-history.d.ts +4 -0
  244. package/dist/persistence/session-history.js +38 -0
  245. package/dist/persistence/session-store.d.ts +9 -0
  246. package/dist/persistence/session-store.js +48 -20
  247. package/dist/personas/persona-manager.js +17 -1
  248. package/dist/plugins/code-explorer/CodeExplorerManager.d.ts +10 -4
  249. package/dist/plugins/code-explorer/CodeExplorerManager.js +69 -16
  250. package/dist/plugins/plugin-manager.js +10 -1
  251. package/dist/prompts/prompt-manager.js +11 -4
  252. package/dist/prompts/system-base.js +6 -2
  253. package/dist/protocols/a2a/codebuddy-executor.d.ts +11 -2
  254. package/dist/protocols/a2a/codebuddy-executor.js +9 -8
  255. package/dist/protocols/a2a/index.d.ts +1 -1
  256. package/dist/protocols/a2a/jsonrpc-v1.d.ts +41 -0
  257. package/dist/protocols/a2a/jsonrpc-v1.js +145 -0
  258. package/dist/protocols/a2a/peer-config.d.ts +20 -0
  259. package/dist/protocols/a2a/peer-config.js +55 -0
  260. package/dist/providers/codex-oauth.js +30 -6
  261. package/dist/providers/model-provider-compat.js +2 -1
  262. package/dist/sandbox/os-sandbox.d.ts +1 -1
  263. package/dist/scheduler/cron-scheduler.d.ts +15 -2
  264. package/dist/scheduler/cron-scheduler.js +35 -4
  265. package/dist/scheduler/job-notepad.d.ts +69 -0
  266. package/dist/scheduler/job-notepad.js +339 -0
  267. package/dist/security/audit-logger.d.ts +1 -1
  268. package/dist/security/compute-confinement.d.ts +4 -0
  269. package/dist/security/compute-confinement.js +89 -0
  270. package/dist/security/session-encryption.d.ts +4 -0
  271. package/dist/security/session-encryption.js +31 -0
  272. package/dist/sensory/voice-loop.d.ts +17 -0
  273. package/dist/sensory/voice-loop.js +72 -15
  274. package/dist/server/auth/device-session-context.d.ts +9 -0
  275. package/dist/server/auth/device-session-context.js +13 -0
  276. package/dist/server/auth/device-store.d.ts +89 -0
  277. package/dist/server/auth/device-store.js +261 -0
  278. package/dist/server/auth/device-token.d.ts +3 -0
  279. package/dist/server/auth/device-token.js +6 -0
  280. package/dist/server/auth/jwt.js +4 -0
  281. package/dist/server/index.d.ts +0 -10
  282. package/dist/server/index.js +28 -0
  283. package/dist/server/middleware/auth.js +3 -2
  284. package/dist/server/routes/a2a-jsonrpc.d.ts +9 -0
  285. package/dist/server/routes/a2a-jsonrpc.js +66 -0
  286. package/dist/server/routes/a2a-protocol.d.ts +12 -0
  287. package/dist/server/routes/a2a-protocol.js +1 -1
  288. package/dist/server/routes/device-auth.d.ts +4 -0
  289. package/dist/server/routes/device-auth.js +40 -0
  290. package/dist/server/types.d.ts +4 -0
  291. package/dist/server/websocket/handler.d.ts +13 -0
  292. package/dist/server/websocket/handler.js +102 -17
  293. package/dist/services/prompt-builder.d.ts +2 -0
  294. package/dist/services/prompt-builder.js +13 -5
  295. package/dist/services/runtime-settings-context.d.ts +73 -0
  296. package/dist/services/runtime-settings-context.js +69 -0
  297. package/dist/skills/executor.js +2 -0
  298. package/dist/skills/hub.js +16 -14
  299. package/dist/skills/local-inventory.d.ts +21 -0
  300. package/dist/skills/local-inventory.js +72 -0
  301. package/dist/skills/skill-loader.js +2 -2
  302. package/dist/skills/skill-usage-store.d.ts +49 -0
  303. package/dist/skills/skill-usage-store.js +121 -0
  304. package/dist/testing/ai-integration-tests.js +8 -5
  305. package/dist/themes/theme-manager.d.ts +12 -0
  306. package/dist/themes/theme-manager.js +29 -0
  307. package/dist/themes/theme-schema.d.ts +24 -24
  308. package/dist/tools/a2a-call-tool.d.ts +34 -0
  309. package/dist/tools/a2a-call-tool.js +75 -0
  310. package/dist/tools/bash/streaming-executor.d.ts +1 -0
  311. package/dist/tools/bash/streaming-executor.js +78 -90
  312. package/dist/tools/code-exec-preflight-runner.d.ts +41 -0
  313. package/dist/tools/code-exec-preflight-runner.js +254 -0
  314. package/dist/tools/code-exec-preflight.d.ts +44 -0
  315. package/dist/tools/code-exec-preflight.js +501 -0
  316. package/dist/tools/code-exec-tool.d.ts +48 -1
  317. package/dist/tools/code-exec-tool.js +191 -47
  318. package/dist/tools/comment-watcher.js +29 -12
  319. package/dist/tools/core-code-inspection.d.ts +56 -0
  320. package/dist/tools/core-code-inspection.js +86 -0
  321. package/dist/tools/create-skill-tool.d.ts +2 -3
  322. package/dist/tools/create-skill-tool.js +16 -75
  323. package/dist/tools/cronjob-tool.d.ts +1 -0
  324. package/dist/tools/cronjob-tool.js +1 -0
  325. package/dist/tools/env-doctor-tool.d.ts +2 -0
  326. package/dist/tools/env-doctor-tool.js +4 -3
  327. package/dist/tools/execute-code-rpc-invoker.d.ts +1 -1
  328. package/dist/tools/execute-code-rpc-invoker.js +35 -19
  329. package/dist/tools/execute-code-runner.d.ts +2 -0
  330. package/dist/tools/execute-code-runner.js +52 -17
  331. package/dist/tools/fleet-room-tool.d.ts +104 -0
  332. package/dist/tools/fleet-room-tool.js +124 -0
  333. package/dist/tools/integration-tool-hints.d.ts +4 -0
  334. package/dist/tools/integration-tool-hints.js +19 -0
  335. package/dist/tools/list-peers-tool.d.ts +3 -0
  336. package/dist/tools/list-peers-tool.js +11 -4
  337. package/dist/tools/media-generation-tool.d.ts +16 -1
  338. package/dist/tools/media-generation-tool.js +341 -9
  339. package/dist/tools/merge-conflict-tool.js +53 -25
  340. package/dist/tools/metadata.d.ts +1 -5
  341. package/dist/tools/metadata.js +21 -28
  342. package/dist/tools/ragchat-tool.d.ts +39 -0
  343. package/dist/tools/ragchat-tool.js +105 -0
  344. package/dist/tools/registry/fleet-tools.d.ts +9 -0
  345. package/dist/tools/registry/fleet-tools.js +22 -1
  346. package/dist/tools/registry/knowledge-tools.d.ts +2 -2
  347. package/dist/tools/registry/knowledge-tools.js +2 -2
  348. package/dist/tools/registry/lessons-tools.d.ts +7 -0
  349. package/dist/tools/registry/lessons-tools.js +43 -3
  350. package/dist/tools/registry/memory-tools.d.ts +1 -1
  351. package/dist/tools/registry/memory-tools.js +8 -8
  352. package/dist/tools/registry/research-tools.d.ts +1 -0
  353. package/dist/tools/registry/research-tools.js +4 -13
  354. package/dist/tools/registry/self-describe-tools.d.ts +1 -1
  355. package/dist/tools/registry/self-describe-tools.js +43 -30
  356. package/dist/tools/resource-catalog-tool.d.ts +38 -0
  357. package/dist/tools/resource-catalog-tool.js +46 -0
  358. package/dist/tools/skills-inspection-tool.js +30 -1
  359. package/dist/tools/stream-tool-output.d.ts +3 -0
  360. package/dist/tools/stream-tool-output.js +33 -0
  361. package/dist/tools/tool-call-scheduler.d.ts +10 -0
  362. package/dist/tools/tool-call-scheduler.js +58 -0
  363. package/dist/tools/tool-effect.d.ts +5 -0
  364. package/dist/tools/tool-effect.js +26 -0
  365. package/dist/tools/tool-search.d.ts +12 -11
  366. package/dist/tools/tool-search.js +67 -26
  367. package/dist/tools/types.d.ts +6 -0
  368. package/dist/ui/components/ChatHistory.d.ts +1 -1
  369. package/dist/ui/components/ChatHistory.js +2 -2
  370. package/dist/ui/components/ChatInput.d.ts +2 -1
  371. package/dist/ui/components/ChatInput.js +42 -88
  372. package/dist/ui/components/ChatInterface.d.ts +4 -2
  373. package/dist/ui/components/ChatInterface.js +27 -63
  374. package/dist/ui/components/CommandSuggestions.d.ts +0 -1
  375. package/dist/ui/components/CommandSuggestions.js +4 -6
  376. package/dist/ui/components/FileAutocomplete.js +1 -1
  377. package/dist/ui/components/KeyboardHelp.js +8 -6
  378. package/dist/ui/components/ModelSelection.js +6 -5
  379. package/dist/ui/components/StatusBar.d.ts +2 -1
  380. package/dist/ui/components/StatusBar.js +8 -7
  381. package/dist/ui/context/theme-context.js +3 -2
  382. package/dist/ui/index.d.ts +1 -1
  383. package/dist/ui/index.js +1 -1
  384. package/dist/ui/utils/markdown-renderer.js +4 -13
  385. package/dist/utils/atomic-write.d.ts +22 -0
  386. package/dist/utils/atomic-write.js +29 -0
  387. package/dist/utils/bounded-output.d.ts +16 -0
  388. package/dist/utils/bounded-output.js +63 -0
  389. package/dist/utils/device-store-file.d.ts +4 -0
  390. package/dist/utils/device-store-file.js +40 -0
  391. package/dist/utils/export-manager.d.ts +9 -0
  392. package/dist/utils/export-manager.js +19 -0
  393. package/dist/utils/first-use-hints.d.ts +25 -0
  394. package/dist/utils/first-use-hints.js +58 -0
  395. package/dist/utils/history-manager.js +1 -1
  396. package/dist/utils/update-notifier.js +16 -18
  397. package/dist/wizard/environment-detection.d.ts +5 -1
  398. package/dist/wizard/environment-detection.js +49 -7
  399. package/dist/wizard/onboarding.d.ts +5 -1
  400. package/dist/wizard/onboarding.js +90 -30
  401. package/dist/wizard/provider-onboarding.d.ts +2 -0
  402. package/dist/wizard/provider-onboarding.js +55 -17
  403. package/package.json +7 -3
@@ -1,3 +1,4 @@
1
+ import { bindFactsMemorySession } from '../../memory/facts-memory.js';
1
2
  /**
2
3
  * Agent Executor Module
3
4
  *
@@ -7,6 +8,7 @@
7
8
  *
8
9
  * @module agent/execution
9
10
  */
11
+ import { BoundedOutput } from '../../utils/bounded-output.js';
10
12
  import { resolveFirstTokenStallTimeoutMs, resolveStallTimeoutMs, withStallGuard } from "../../utils/stream-stall-guard.js";
11
13
  import { startHeadlessPromptProgress } from "../../cli/headless-prompt-progress.js";
12
14
  import { HEADLESS_LOCAL_COMPACT_ALWAYS_INCLUDE, HEADLESS_LOCAL_COMPACT_MAX_TOOLS, isHeadlessLocalPromptCompact, } from "../../config/headless-local-prompt.js";
@@ -27,6 +29,9 @@ import { compress as tokenJuice, isTokenJuiceEnabled, JUICE_MIN_CHARS } from "..
27
29
  import { formatToolResultForRecovery, getRestorableCompressor, } from "../../context/restorable-compression.js";
28
30
  import { recordCompactionFork } from "../../context/compaction-fork.js";
29
31
  import { getActiveRunStore } from "../../observability/run-store.js";
32
+ import { ToolLoopGuard } from "./tool-loop-guard.js";
33
+ import { getGlobalEventBus } from "../../events/event-bus.js";
34
+ import { takeFirstUseHint } from "../../utils/first-use-hints.js";
30
35
  import { getTurnMetricsRecorder } from '../../observability/turn-metrics.js';
31
36
  import { shouldCompactBeforeToolExec, estimateToolResultTokens } from "../../context/proactive-compaction.js";
32
37
  import { formatTokenUsage, estimateCost } from "../../utils/token-display.js";
@@ -35,7 +40,7 @@ import { getModelToolConfig } from "../../config/model-tools.js";
35
40
  import { getLatencyOptimizer, getStreamingOptimizer } from "../../optimization/latency-optimizer.js";
36
41
  import { buildTextEmotionalPresenceContext } from "../../companion/reply-augment.js";
37
42
  import { guardRelationshipReply, SAFE_RELATIONSHIP_REPAIR, } from "../../conversation/relationship-safety.js";
38
- import { classifyLisaIntrospection, guardLisaOperationalSelfInspectionReply, isLisaEvolutionRequest, renderLisaOperationalSelfResponse, } from '../../identity/lisa-introspection.js';
43
+ import { classifyLisaIntrospection, guardLisaOperationalSelfInspectionReply, isLisaEvolutionRequest, isLisaSubjectiveConsciousnessQuestion, renderLisaOperationalSelfResponse, } from '../../identity/lisa-introspection.js';
39
44
  import { withLlmStreamRetry } from '../../codebuddy/llm-retry.js';
40
45
  import { getStreamingAdapter } from '../../tools/streaming-adapter.js';
41
46
  import { notify } from '../proactive/notification-default-sink.js';
@@ -467,16 +472,28 @@ export class AgentExecutor {
467
472
  /** Execute one tool and buffer optional streaming-adapter output. */
468
473
  async executeToolForBatch(toolCall, executionExtra, signal, startedAt = Date.now()) {
469
474
  const streamChunks = [];
475
+ const streamPreview = new BoundedOutput(256 * 1024);
476
+ let collapsed = false;
477
+ const appendStreamChunk = (chunk) => {
478
+ if (!chunk || signal?.aborted)
479
+ return;
480
+ streamPreview.append(chunk);
481
+ collapsed ||= streamPreview.omittedBytes > 0 || streamChunks.length >= 1024;
482
+ if (collapsed)
483
+ streamChunks.splice(0, streamChunks.length, streamPreview.text());
484
+ else
485
+ streamChunks.push(chunk);
486
+ };
470
487
  const extraWithSignal = signal
471
488
  ? { ...(executionExtra ?? {}), abortSignal: signal }
472
489
  : executionExtra;
473
490
  const execute = async () => {
474
- const streamingTools = new Set(['bash', 'reason', 'generate_document']);
491
+ const streamingTools = new Set(['bash', 'reason', 'generate_document', 'code_exec']);
475
492
  if (streamingTools.has(toolCall.function.name)) {
476
493
  const generator = this.deps.toolHandler.executeToolStreaming(toolCall, extraWithSignal);
477
494
  let generated = await generator.next();
478
495
  while (!generated.done) {
479
- streamChunks.push(generated.value);
496
+ appendStreamChunk(generated.value);
480
497
  generated = await generator.next();
481
498
  }
482
499
  return {
@@ -486,7 +503,7 @@ export class AgentExecutor {
486
503
  }
487
504
  const streamingAdapter = getStreamingAdapter();
488
505
  if (streamingAdapter.supportsStreaming(toolCall.function.name)) {
489
- const result = await streamingAdapter.wrapWithStreaming(toolCall.function.name, () => this.executeToolViaLane(toolCall, extraWithSignal), (chunk) => streamChunks.push(chunk));
506
+ const result = await streamingAdapter.wrapWithStreaming(toolCall.function.name, () => this.executeToolViaLane(toolCall, extraWithSignal), appendStreamChunk);
490
507
  return { result, streamChunks };
491
508
  }
492
509
  return {
@@ -740,7 +757,7 @@ export class AgentExecutor {
740
757
  const operationId = getLatencyOptimizer().startOperation('assistant_turn', startedAt);
741
758
  let recordedFirstVisibleResponse = false;
742
759
  try {
743
- for await (const event of this.runTurnLoop(message, history, messages, abortController, transientContext, relationshipSafety, surface, introspectionText)) {
760
+ for await (const event of bindFactsMemorySession(this.deps.client, this.runTurnLoop(message, history, messages, abortController, transientContext, relationshipSafety, surface, introspectionText))) {
744
761
  if (!recordedFirstVisibleResponse &&
745
762
  (event.type === 'content' || event.type === 'reasoning' || event.type === 'tool_calls')) {
746
763
  recordedFirstVisibleResponse = true;
@@ -783,7 +800,9 @@ export class AgentExecutor {
783
800
  const introspectionIntent = classifyLisaIntrospection(introspectionTextForTurn);
784
801
  const readOnlySelfInspection = introspectionIntent === 'describe' || introspectionIntent === 'inspect';
785
802
  const guardGenerativeSelfInspection = introspectionIntent === 'improve';
786
- const isolatedSharedHost = surface === 'http';
803
+ const codeResearch = surface === 'cli' && introspectionIntent === 'inspect' &&
804
+ !isLisaSubjectiveConsciousnessQuestion(introspectionTextForTurn);
805
+ const isolatedSharedHost = surface === 'http' || codeResearch;
787
806
  const turnCwd = typeof this.deps.toolHandler.getWorkingDirectory === 'function'
788
807
  ? this.deps.toolHandler.getWorkingDirectory()
789
808
  : process.cwd();
@@ -806,8 +825,9 @@ export class AgentExecutor {
806
825
  let permissionMode;
807
826
  let providerName;
808
827
  let operationalRobotName;
809
- if (introspectionIntent !== null) {
810
- operationalRobotName = await this.getOperationalRobotName();
828
+ if (introspectionIntent !== null || surface === 'cli') {
829
+ if (introspectionIntent !== null)
830
+ operationalRobotName = await this.getOperationalRobotName();
811
831
  try {
812
832
  const { getPermissionModeManager } = await import('../../security/permission-modes.js');
813
833
  permissionMode = getPermissionModeManager().getMode();
@@ -822,7 +842,7 @@ export class AgentExecutor {
822
842
  // Legacy/test clients may not expose provider metadata.
823
843
  }
824
844
  }
825
- if (readOnlySelfInspection) {
845
+ if (readOnlySelfInspection && !codeResearch) {
826
846
  if (isLisaEvolutionRequest(introspectionTextForTurn)) {
827
847
  const { formatEvolutionNotesForVoice, queryEvolutionNotes, readEvolutionNotes, } = await import('../../self-model/evolution-notes.js');
828
848
  const since = introspectionTextForTurn.match(/\b20\d{2}-\d{2}-\d{2}\b/)?.[0];
@@ -850,6 +870,8 @@ export class AgentExecutor {
850
870
  // every strict describe/inspect turn. No provider, plugin or generic
851
871
  // workspace tool participates in this path.
852
872
  const { buildOperationalSelfModel } = await import('../../identity/operational-self-model.js');
873
+ const { getRuntimeSettingsSnapshot } = await import('../../services/runtime-settings-context.js');
874
+ const activeSettings = getRuntimeSettingsSnapshot({ surface, model: this.deps.client.getCurrentModel(), provider: providerName, maxToolRounds: this.config.maxToolRounds });
853
875
  const selfModel = buildOperationalSelfModel({
854
876
  cwd: turnCwd,
855
877
  focus: introspectionTextForTurn,
@@ -859,6 +881,7 @@ export class AgentExecutor {
859
881
  : {}),
860
882
  runtime: {
861
883
  providerInvoked: false,
884
+ ...(activeSettings.theme ? { theme: activeSettings.theme.active } : {}),
862
885
  ...(this.deps.client.getCurrentModel()
863
886
  ? { model: this.deps.client.getCurrentModel() }
864
887
  : {}),
@@ -900,6 +923,11 @@ export class AgentExecutor {
900
923
  };
901
924
  const maxToolRounds = this.config.maxToolRounds;
902
925
  let toolRounds = 0;
926
+ // One guard per task: warnings/stops never leak into the next user turn.
927
+ const loopGuard = new ToolLoopGuard();
928
+ let pendingLoopDecision = null;
929
+ let loopGuardStopped = false;
930
+ let observationShortened = false;
903
931
  let totalOutputTokens = 0;
904
932
  let totalInputTokensForCost = 0;
905
933
  let providerPromptTokens = 0;
@@ -1042,6 +1070,15 @@ export class AgentExecutor {
1042
1070
  alwaysInclude: ['view_file', 'bash', 'search'],
1043
1071
  };
1044
1072
  }
1073
+ if (surface === 'cli' && !codeResearch) {
1074
+ const { runtimeInspectionTools } = await import('../../services/runtime-settings-context.js');
1075
+ const inspectionTools = runtimeInspectionTools(turnQueryText);
1076
+ if (inspectionTools.length)
1077
+ selectionOpts = { ...selectionOpts, alwaysInclude: [...(selectionOpts.alwaysInclude ?? []), ...inspectionTools] };
1078
+ }
1079
+ if (codeResearch) {
1080
+ selectionOpts = { ...selectionOpts, alwaysInclude: ['self_describe'] };
1081
+ }
1045
1082
  const selectionPromise = this.deps.toolSelectionStrategy.selectToolsForQuery(turnQueryText, selectionOpts);
1046
1083
  // Build context in a scratch array while the prompt and tools are being
1047
1084
  // prepared. It is appended only after transcript preparation so the
@@ -1094,7 +1131,9 @@ export class AgentExecutor {
1094
1131
  incrementalTokenCounter.invalidate();
1095
1132
  logger.debug(`[agent-executor] system prompt rebuilt query-aware (${rebuiltSystemPrompt.length} chars)`);
1096
1133
  }
1097
- let tools = selectionResult.tools;
1134
+ let tools = codeResearch
1135
+ ? selectionResult.tools.filter(tool => tool.function.name === 'self_describe')
1136
+ : selectionResult.tools;
1098
1137
  let forcedChatOnlyToolRunModel = null;
1099
1138
  if (toolRounds === 0) {
1100
1139
  this.deps.toolSelectionStrategy.cacheTools(tools, activeModelName);
@@ -1109,7 +1148,7 @@ export class AgentExecutor {
1109
1148
  tools = [];
1110
1149
  }
1111
1150
  }
1112
- const turnExecutionExtra = introspectionIntent
1151
+ const turnExecutionExtra = introspectionIntent || surface === 'cli'
1113
1152
  ? {
1114
1153
  ...(activeModelName ? { model: activeModelName } : {}),
1115
1154
  ...(providerName ? { provider: providerName } : {}),
@@ -1118,6 +1157,7 @@ export class AgentExecutor {
1118
1157
  ...(operationalRobotName
1119
1158
  ? { robotName: operationalRobotName }
1120
1159
  : {}),
1160
+ maxToolRounds,
1121
1161
  exposedToolNames: tools.map((tool) => tool.function.name),
1122
1162
  introspectionIntent,
1123
1163
  }
@@ -1142,6 +1182,27 @@ export class AgentExecutor {
1142
1182
  throw error;
1143
1183
  }
1144
1184
  preparedMessages.push(...contextBlocks);
1185
+ if (codeResearch) {
1186
+ preparedMessages.push({ role: 'system', content: 'Research your actual implementation using self_describe: operation=list/read/search, relative src/ paths (source checkout) or dist/ paths (installed package). Search literal symbols, read relevant code, and cite paths and line numbers. Only this confined read-only tool is exposed for this turn. Do not mistake the user project for your implementation. Do not claim a code graph is available without evidence.' });
1187
+ }
1188
+ if (surface === 'cli') {
1189
+ const { formatRuntimeSettingsContext } = await import('../../services/runtime-settings-context.js');
1190
+ preparedMessages.push({ role: 'system', content: formatRuntimeSettingsContext({
1191
+ surface, model: activeModelName, provider: providerName, maxToolRounds,
1192
+ }) });
1193
+ }
1194
+ else {
1195
+ // P5: other surfaces have no runtime_settings block; only a non-default
1196
+ // code_exec policy needs its short guidance.
1197
+ const { resolveCodeExecPolicy, CODE_EXEC_PREFER_HINT, CODE_EXEC_OFF_NOTICE } = await import('../../config/code-exec-policy.js');
1198
+ const codeExecPolicy = resolveCodeExecPolicy(activeModelName ?? undefined);
1199
+ if (codeExecPolicy.policy !== 'offer') {
1200
+ preparedMessages.push({
1201
+ role: 'system',
1202
+ content: `<code_exec_policy policy="${codeExecPolicy.policy}" source="${codeExecPolicy.source}">\n${codeExecPolicy.policy === 'prefer' ? CODE_EXEC_PREFER_HINT : CODE_EXEC_OFF_NOTICE}\n</code_exec_policy>`,
1203
+ });
1204
+ }
1205
+ }
1145
1206
  if (emotionalPresenceContext) {
1146
1207
  preparedMessages.push({
1147
1208
  role: 'system',
@@ -1190,6 +1251,9 @@ export class AgentExecutor {
1190
1251
  const progress = startHeadlessPromptProgress();
1191
1252
  const streamFactory = () => withStallGuard(this.deps.client.chatStream(preparedMessages, tools, {
1192
1253
  streamRetry: false,
1254
+ // An explicit request to inspect implementation needs an observation
1255
+ // before an answer. Only the confined self_describe reader is exposed.
1256
+ ...(codeResearch && toolRounds === 0 && tools.length ? { tool_choice: 'required' } : {}),
1193
1257
  turnMetrics: {
1194
1258
  recorder: getTurnMetricsRecorder(),
1195
1259
  inputTokens,
@@ -1297,6 +1361,11 @@ export class AgentExecutor {
1297
1361
  }
1298
1362
  toolCalls = filteredToolCalls.length > 0 ? filteredToolCalls : undefined;
1299
1363
  }
1364
+ if (codeResearch && toolCalls?.some(call => call.function.name !== 'self_describe')) {
1365
+ yield { type: 'content', content: 'Code inspection is read-only; the requested tool is not available in this inspection turn.' };
1366
+ yield { type: 'done' };
1367
+ return;
1368
+ }
1300
1369
  if (relationshipSafety && Array.isArray(toolCalls)) {
1301
1370
  toolCalls = toolCalls.map(prepareRelationshipSafeInteractiveToolCall);
1302
1371
  }
@@ -1471,8 +1540,15 @@ export class AgentExecutor {
1471
1540
  estimatedTokens,
1472
1541
  contextWindow,
1473
1542
  });
1543
+ // The results of this round's calls are pushed after execution:
1544
+ // name them so repair does not close them with a synthetic
1545
+ // '[result lost during compaction]' that would then win over
1546
+ // the real output.
1474
1547
  const compacted = compactTurnMessagesInPlace(this.deps.contextManager, messages, {
1475
1548
  isolatedSharedHost,
1549
+ pendingToolCallIds: toolCalls
1550
+ .map((call) => call.id)
1551
+ .filter((id) => typeof id === 'string' && id.length > 0),
1476
1552
  });
1477
1553
  if (compacted)
1478
1554
  incrementalTokenCounter.invalidate();
@@ -1565,6 +1641,18 @@ export class AgentExecutor {
1565
1641
  };
1566
1642
  }
1567
1643
  }
1644
+ // --- Tool loop guard (P1): observe the native result before any
1645
+ // optimizer so "same call + same result" means no progress. The
1646
+ // decision is applied at the batch boundary so tool_call/tool_result
1647
+ // pairs are never split.
1648
+ const loopDecision = loopGuard.observe({
1649
+ name: toolCall.function.name,
1650
+ argumentsJson: toolCall.function.arguments || '{}',
1651
+ result,
1652
+ });
1653
+ if (loopDecision.action === 'stop' || (loopDecision.action === 'warn' && !pendingLoopDecision)) {
1654
+ pendingLoopDecision = loopDecision;
1655
+ }
1568
1656
  // Expand the current turn's cached schema after discovery or live
1569
1657
  // authoring. Without this, a newly created tool is dispatchable but
1570
1658
  // invisible to the model until the next user turn.
@@ -1705,6 +1793,8 @@ export class AgentExecutor {
1705
1793
  signal: abortController?.signal,
1706
1794
  });
1707
1795
  let modelStreamContent = optimization.content;
1796
+ if (optimization.optimized)
1797
+ observationShortened = true;
1708
1798
  // lm-resizer owns the semantic budget when available. Its absence or
1709
1799
  // an intentionally raw failure still receives a model-aware hard cap;
1710
1800
  // the exact observation remains available through restore_context.
@@ -1715,6 +1805,7 @@ export class AgentExecutor {
1715
1805
  this.deps.tokenCounter.countTokens(modelStreamContent) > budgetTokens) {
1716
1806
  const truncated = semanticTruncate(modelStreamContent, { maxChars: hardLimitChars });
1717
1807
  if (truncated.truncated) {
1808
+ observationShortened = true;
1718
1809
  const recoveryNote = toolCall.id
1719
1810
  ? `\n\n[Full exact observation: restore_context({"identifier":${JSON.stringify(toolCall.id)}})]`
1720
1811
  : '';
@@ -1850,6 +1941,68 @@ export class AgentExecutor {
1850
1941
  yield { type: "done" };
1851
1942
  return;
1852
1943
  }
1944
+ // First shortened observation in this profile: one user-facing tip (P4 first-use hints).
1945
+ if (observationShortened) {
1946
+ observationShortened = false;
1947
+ const tip = takeFirstUseHint('restore_context');
1948
+ if (tip)
1949
+ yield { type: "content", content: `\n💡 ${tip}\n` };
1950
+ }
1951
+ // Tool-call/result pairs are complete here: apply the loop guard.
1952
+ if (pendingLoopDecision) {
1953
+ const decision = pendingLoopDecision;
1954
+ pendingLoopDecision = null;
1955
+ const loopData = {
1956
+ action: decision.action,
1957
+ loopType: decision.kind,
1958
+ toolNames: decision.toolNames,
1959
+ repetitions: decision.repetitions,
1960
+ toolRounds,
1961
+ };
1962
+ try {
1963
+ getGlobalEventBus().emit('agent:loop_detected', {
1964
+ loopType: decision.kind,
1965
+ detail: decision.message,
1966
+ count: decision.repetitions,
1967
+ turnIndex: toolRounds,
1968
+ });
1969
+ }
1970
+ catch (err) {
1971
+ logger.debug('[loop-guard] event emission failed', { error: String(err) });
1972
+ }
1973
+ if (decision.action === 'warn') {
1974
+ logger.warn('[loop-guard] loop warning injected', loopData);
1975
+ yield { type: "content", content: `\n⚠️ ${decision.message}\n` };
1976
+ messages.push({
1977
+ role: 'system',
1978
+ content: `<context type="loop-guard">\n${decision.message}\n</context>`,
1979
+ });
1980
+ }
1981
+ else {
1982
+ logger.warn('[loop-guard] turn stopped', loopData);
1983
+ const runId = typeof this.deps.toolHandler.getRunId === 'function'
1984
+ ? this.deps.toolHandler.getRunId()
1985
+ : undefined;
1986
+ if (runId) {
1987
+ try {
1988
+ getActiveRunStore()?.emit(runId, {
1989
+ type: 'decision',
1990
+ data: { kind: 'loop_guard_stopped', ...loopData },
1991
+ });
1992
+ }
1993
+ catch { /* observability is optional */ }
1994
+ }
1995
+ yield {
1996
+ type: 'run_event',
1997
+ runEvent: { runId: runId ?? '', eventType: 'loop_guard_stopped', data: loopData },
1998
+ };
1999
+ history.push({ type: 'assistant', content: decision.message, timestamp: new Date() });
2000
+ messages.push({ role: 'assistant', content: decision.message });
2001
+ yield { type: "content", content: `\n\n${decision.message}` };
2002
+ loopGuardStopped = true;
2003
+ break;
2004
+ }
2005
+ }
1853
2006
  // Tool-call/result pairs are complete at this boundary, so a steer
1854
2007
  // that arrived while tools were running can now be injected safely.
1855
2008
  const deferredSteering = this.deps.messageQueue?.hasSteeringMessage()
@@ -1956,7 +2109,7 @@ export class AgentExecutor {
1956
2109
  break;
1957
2110
  }
1958
2111
  }
1959
- if (toolRounds >= maxToolRounds && !terminateDetectedStreaming) {
2112
+ if (toolRounds >= maxToolRounds && !terminateDetectedStreaming && !loopGuardStopped) {
1960
2113
  const limitMessage = 'Maximum tool execution rounds reached.';
1961
2114
  history.push({ type: 'assistant', content: limitMessage, timestamp: new Date() });
1962
2115
  messages.push({ role: 'assistant', content: limitMessage });
@@ -59,9 +59,18 @@ export declare function prepareIsolatedTurnMessages(messages: CodeBuddyMessage[]
59
59
  * transcript never shrank, the middleware 'compact' action did nothing, and
60
60
  * proactive compaction re-fired forever while the provider limit approached.
61
61
  * Returns true when the transcript actually changed.
62
+ *
63
+ * `pendingToolCallIds` names the calls of the round being executed. Their
64
+ * results are pushed AFTER this compaction, so repairing them here injected
65
+ * `[result lost during compaction]`; the real result then lost the first-wins
66
+ * duplicate arbitration and the model never saw the tool output. Those
67
+ * placeholders are removed while every other repair — historical orphans,
68
+ * id-less calls, duplicates, ordering — is kept. The provider frontier stays
69
+ * `prepareTurnMessages`, which still closes any call left unanswered.
62
70
  */
63
71
  export declare function compactTurnMessagesInPlace(contextManager: ContextManagerV2, messages: CodeBuddyMessage[], options?: {
64
72
  isolatedSharedHost?: boolean;
73
+ pendingToolCallIds?: Iterable<string>;
65
74
  }): boolean;
66
75
  export interface InitialContextDeps {
67
76
  message: string;
@@ -151,6 +151,52 @@ export function prepareIsolatedTurnMessages(messages) {
151
151
  ? repairToolCallPairs(messages)
152
152
  : [...messages];
153
153
  }
154
+ /** Tool calls of the in-flight round that have no result in `messages` yet. */
155
+ function unansweredPendingCalls(messages, pendingToolCallIds) {
156
+ const pending = new Set();
157
+ for (const id of pendingToolCallIds ?? [])
158
+ if (id)
159
+ pending.add(id);
160
+ if (pending.size === 0)
161
+ return pending;
162
+ for (const message of messages) {
163
+ if (message.role !== 'tool')
164
+ continue;
165
+ const callId = message.tool_call_id;
166
+ if (callId)
167
+ pending.delete(callId);
168
+ }
169
+ return pending;
170
+ }
171
+ /**
172
+ * Drop the synthetic `[result lost during compaction]` placeholders that
173
+ * transcript repair injected for calls that are merely still running.
174
+ *
175
+ * Returns null when a pending call did not survive compaction at all: the
176
+ * caller then keeps the transcript untouched rather than leaving a call whose
177
+ * real result would become an unpairable orphan.
178
+ */
179
+ function withoutPendingPlaceholders(compacted, pending) {
180
+ const survivingCallIds = new Set();
181
+ for (const message of compacted) {
182
+ const calls = message.tool_calls;
183
+ if (Array.isArray(calls))
184
+ for (const call of calls)
185
+ if (call?.id)
186
+ survivingCallIds.add(call.id);
187
+ }
188
+ for (const id of pending)
189
+ if (!survivingCallIds.has(id))
190
+ return null;
191
+ // `pending` only holds ids with no result in `original`, so every tool
192
+ // message carrying one of them was invented by repair just now.
193
+ return compacted.filter((message) => {
194
+ if (message.role !== 'tool')
195
+ return true;
196
+ const callId = message.tool_call_id;
197
+ return !(callId && pending.has(callId));
198
+ });
199
+ }
154
200
  /**
155
201
  * Compact + repair IN PLACE — for mid-loop compaction sites where `messages`
156
202
  * is a SHARED reference (the turn loop and its helpers keep pushing into it).
@@ -159,11 +205,27 @@ export function prepareIsolatedTurnMessages(messages) {
159
205
  * transcript never shrank, the middleware 'compact' action did nothing, and
160
206
  * proactive compaction re-fired forever while the provider limit approached.
161
207
  * Returns true when the transcript actually changed.
208
+ *
209
+ * `pendingToolCallIds` names the calls of the round being executed. Their
210
+ * results are pushed AFTER this compaction, so repairing them here injected
211
+ * `[result lost during compaction]`; the real result then lost the first-wins
212
+ * duplicate arbitration and the model never saw the tool output. Those
213
+ * placeholders are removed while every other repair — historical orphans,
214
+ * id-less calls, duplicates, ordering — is kept. The provider frontier stays
215
+ * `prepareTurnMessages`, which still closes any call left unanswered.
162
216
  */
163
217
  export function compactTurnMessagesInPlace(contextManager, messages, options = {}) {
164
- const compacted = prepareTurnMessages(contextManager, messages, options);
165
- if (compacted === messages)
218
+ const pending = unansweredPendingCalls(messages, options.pendingToolCallIds);
219
+ const prepared = prepareTurnMessages(contextManager, messages, options);
220
+ if (prepared === messages)
166
221
  return false;
222
+ let compacted = prepared;
223
+ if (pending.size > 0) {
224
+ const protectedMessages = withoutPendingPlaceholders(prepared, pending);
225
+ if (!protectedMessages)
226
+ return false; // pending call dropped: never risk its real result
227
+ compacted = protectedMessages;
228
+ }
167
229
  const changed = compacted.length !== messages.length || compacted.some((m, i) => m !== messages[i]);
168
230
  if (!changed)
169
231
  return false;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * ToolLoopGuard — stops unproductive tool-call loops inside one agent task.
3
+ *
4
+ * New code written for Buddy (comparatif plan P1); nothing was copied from
5
+ * another project. It does not wire the older, unwired
6
+ * `src/agent/loop-detection-service.ts` for technical reasons: that service
7
+ * keys a loop on tool name + arguments only, so a polling call whose output
8
+ * changes (process status, job progress) is flagged as a loop; it has no
9
+ * per-tool exemption and no warn → stop escalation. This guard adds the result
10
+ * fingerprint, `repeatSafe` metadata and a one-warning-then-stop contract, and
11
+ * emits the same `agent:loop_detected` event, so the domain-event bridge and
12
+ * the `agent-loop-alert` rule template keep working unchanged.
13
+ *
14
+ * Contract:
15
+ * - "No progress" means the SAME tool, SAME canonical arguments AND the SAME
16
+ * result fingerprint. A polling call whose output changes is progress.
17
+ * - Tools whose metadata declares `repeatSafe: true` are never observed.
18
+ * - First detection in a task → `warn` (exactly once per task). Any loop
19
+ * pattern that persists for `stopAfterWarning` more observations after the
20
+ * warning → `stop`.
21
+ * - The guard is a validation, not a confirmation: nothing in the autonomy /
22
+ * YOLO configuration disables it. A new guard instance is created per task.
23
+ */
24
+ import type { ToolResult } from '../../types/index.js';
25
+ export type ToolLoopKind = 'repeated_call' | 'repeated_cycle';
26
+ export interface ToolLoopObservation {
27
+ name: string;
28
+ /** Raw JSON arguments string as sent by the model. */
29
+ argumentsJson: string;
30
+ result?: Pick<ToolResult, 'success' | 'output' | 'error'> | null;
31
+ }
32
+ export type ToolLoopDecision = {
33
+ action: 'none';
34
+ } | {
35
+ action: 'warn' | 'stop';
36
+ kind: ToolLoopKind;
37
+ toolNames: string[];
38
+ repetitions: number;
39
+ message: string;
40
+ };
41
+ export interface ToolLoopGuardOptions {
42
+ /** Identical no-progress observations (or cycle repetitions) before warning. Default 5. */
43
+ threshold?: number;
44
+ /** Additional looping observations after the warning before stopping. Default 3. */
45
+ stopAfterWarning?: number;
46
+ /** Longest cycle period inspected (A→B→A→B is period 2). Default 3. */
47
+ maxCyclePeriod?: number;
48
+ /** Override for tests; defaults to the `repeatSafe` flag in tool metadata. */
49
+ isRepeatSafe?: (toolName: string) => boolean;
50
+ }
51
+ export declare function isRepeatSafeTool(toolName: string): boolean;
52
+ /**
53
+ * Loop fingerprint of a successful `a2a_call`: every SendMessage creates a new remote task,
54
+ * so `taskId` / `contextId` change even when the peer returns the same text. For that tool
55
+ * only, and only when the output has exactly the task shape, those two technical ids are
56
+ * dropped; peer, state, text and instruction stay. Anything else (another tool, invalid JSON,
57
+ * another shape such as a message result) is fingerprinted raw, so changing output is progress.
58
+ */
59
+ export declare function loopFingerprintOutput(toolName: string, output: string): string;
60
+ export declare class ToolLoopGuard {
61
+ private readonly threshold;
62
+ private readonly stopAfterWarning;
63
+ private readonly maxCyclePeriod;
64
+ private readonly isRepeatSafe;
65
+ private history;
66
+ private warned;
67
+ private stopped;
68
+ constructor(options?: ToolLoopGuardOptions);
69
+ get hasWarned(): boolean;
70
+ get hasStopped(): boolean;
71
+ observe(observation: ToolLoopObservation): ToolLoopDecision;
72
+ /** Detect a repeated call or cycle repeated at least `repeats` times at the tail of history. */
73
+ private detect;
74
+ private warnMessage;
75
+ private stopMessage;
76
+ }