@calliopelabs/cli 2.5.0 → 3.0.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 (476) hide show
  1. package/README.md +45 -661
  2. package/dist/acp.d.ts +78 -0
  3. package/dist/acp.d.ts.map +1 -0
  4. package/dist/acp.js +572 -0
  5. package/dist/acp.js.map +1 -0
  6. package/dist/bin.d.ts +3 -7
  7. package/dist/bin.d.ts.map +1 -1
  8. package/dist/bin.js +56 -79
  9. package/dist/bin.js.map +1 -1
  10. package/dist/budget.d.ts +64 -0
  11. package/dist/budget.d.ts.map +1 -0
  12. package/dist/budget.js +157 -0
  13. package/dist/budget.js.map +1 -0
  14. package/dist/checkpoint.d.ts +63 -30
  15. package/dist/checkpoint.d.ts.map +1 -1
  16. package/dist/checkpoint.js +247 -160
  17. package/dist/checkpoint.js.map +1 -1
  18. package/dist/circuit-breaker/breaker.js.map +1 -1
  19. package/dist/config.d.ts +86 -81
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js +236 -304
  22. package/dist/config.js.map +1 -1
  23. package/dist/cost.d.ts +114 -0
  24. package/dist/cost.d.ts.map +1 -0
  25. package/dist/cost.js +499 -0
  26. package/dist/cost.js.map +1 -0
  27. package/dist/diff.d.ts.map +1 -1
  28. package/dist/diff.js +11 -7
  29. package/dist/diff.js.map +1 -1
  30. package/dist/errors.js +3 -3
  31. package/dist/errors.js.map +1 -1
  32. package/dist/files.js.map +1 -1
  33. package/dist/fleet.d.ts +30 -0
  34. package/dist/fleet.d.ts.map +1 -0
  35. package/dist/fleet.js +86 -0
  36. package/dist/fleet.js.map +1 -0
  37. package/dist/fuzzy-search.d.ts.map +1 -1
  38. package/dist/fuzzy-search.js +2 -1
  39. package/dist/fuzzy-search.js.map +1 -1
  40. package/dist/git-status.js.map +1 -1
  41. package/dist/headless.d.ts +1 -2
  42. package/dist/headless.d.ts.map +1 -1
  43. package/dist/headless.js +166 -24
  44. package/dist/headless.js.map +1 -1
  45. package/dist/hud/api.d.ts +3 -18
  46. package/dist/hud/api.d.ts.map +1 -1
  47. package/dist/hud/api.js +9 -352
  48. package/dist/hud/api.js.map +1 -1
  49. package/dist/hud/palettes.d.ts.map +1 -1
  50. package/dist/hud/palettes.js +0 -180
  51. package/dist/hud/palettes.js.map +1 -1
  52. package/dist/hud/skins.d.ts.map +1 -1
  53. package/dist/hud/skins.js +0 -263
  54. package/dist/hud/skins.js.map +1 -1
  55. package/dist/hud/types.d.ts +2 -5
  56. package/dist/hud/types.d.ts.map +1 -1
  57. package/dist/index.d.ts +1 -4
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +1 -4
  60. package/dist/index.js.map +1 -1
  61. package/dist/iteration-ledger.d.ts.map +1 -1
  62. package/dist/iteration-ledger.js +3 -2
  63. package/dist/iteration-ledger.js.map +1 -1
  64. package/dist/local-model.d.ts +150 -0
  65. package/dist/local-model.d.ts.map +1 -0
  66. package/dist/local-model.js +408 -0
  67. package/dist/local-model.js.map +1 -0
  68. package/dist/mcp.d.ts.map +1 -1
  69. package/dist/mcp.js +5 -2
  70. package/dist/mcp.js.map +1 -1
  71. package/dist/memory.d.ts.map +1 -1
  72. package/dist/memory.js +6 -4
  73. package/dist/memory.js.map +1 -1
  74. package/dist/model-detection.js +2 -2
  75. package/dist/model-detection.js.map +1 -1
  76. package/dist/parallel-tools.js.map +1 -1
  77. package/dist/plugins.d.ts +32 -1
  78. package/dist/plugins.d.ts.map +1 -1
  79. package/dist/plugins.js +116 -5
  80. package/dist/plugins.js.map +1 -1
  81. package/dist/policy.d.ts +43 -0
  82. package/dist/policy.d.ts.map +1 -0
  83. package/dist/policy.js +139 -0
  84. package/dist/policy.js.map +1 -0
  85. package/dist/project-config.d.ts +1 -2
  86. package/dist/project-config.d.ts.map +1 -1
  87. package/dist/project-config.js +0 -3
  88. package/dist/project-config.js.map +1 -1
  89. package/dist/providers/bedrock.d.ts.map +1 -1
  90. package/dist/providers/bedrock.js +2 -5
  91. package/dist/providers/bedrock.js.map +1 -1
  92. package/dist/providers/compat.d.ts +21 -0
  93. package/dist/providers/compat.d.ts.map +1 -1
  94. package/dist/providers/compat.js +176 -6
  95. package/dist/providers/compat.js.map +1 -1
  96. package/dist/providers/google.js.map +1 -1
  97. package/dist/providers/index.d.ts +19 -4
  98. package/dist/providers/index.d.ts.map +1 -1
  99. package/dist/providers/index.js +61 -10
  100. package/dist/providers/index.js.map +1 -1
  101. package/dist/providers/ollama.d.ts +2 -2
  102. package/dist/providers/ollama.d.ts.map +1 -1
  103. package/dist/providers/ollama.js +26 -6
  104. package/dist/providers/ollama.js.map +1 -1
  105. package/dist/providers/openai.d.ts.map +1 -1
  106. package/dist/providers/openai.js +14 -10
  107. package/dist/providers/openai.js.map +1 -1
  108. package/dist/providers/types.d.ts +8 -0
  109. package/dist/providers/types.d.ts.map +1 -1
  110. package/dist/providers/types.js.map +1 -1
  111. package/dist/replay.d.ts +37 -0
  112. package/dist/replay.d.ts.map +1 -0
  113. package/dist/replay.js +168 -0
  114. package/dist/replay.js.map +1 -0
  115. package/dist/router.d.ts +133 -0
  116. package/dist/router.d.ts.map +1 -0
  117. package/dist/{model-router.js → router.js} +329 -3
  118. package/dist/router.js.map +1 -0
  119. package/dist/runlog.d.ts +185 -0
  120. package/dist/runlog.d.ts.map +1 -0
  121. package/dist/runlog.js +398 -0
  122. package/dist/runlog.js.map +1 -0
  123. package/dist/{sandbox.d.ts → sandbox/docker.d.ts} +1 -1
  124. package/dist/sandbox/docker.d.ts.map +1 -0
  125. package/dist/{sandbox.js → sandbox/docker.js} +1 -1
  126. package/dist/sandbox/docker.js.map +1 -0
  127. package/dist/sandbox/index.d.ts +36 -0
  128. package/dist/sandbox/index.d.ts.map +1 -0
  129. package/dist/sandbox/index.js +62 -0
  130. package/dist/sandbox/index.js.map +1 -0
  131. package/dist/{sandbox-native.d.ts → sandbox/native.d.ts} +1 -1
  132. package/dist/sandbox/native.d.ts.map +1 -0
  133. package/dist/{sandbox-native.js → sandbox/native.js} +1 -1
  134. package/dist/sandbox/native.js.map +1 -0
  135. package/dist/scope.js +1 -1
  136. package/dist/scope.js.map +1 -1
  137. package/dist/scuttlebot/config.js.map +1 -1
  138. package/dist/scuttlebot/irc-client.js.map +1 -1
  139. package/dist/setup.d.ts.map +1 -1
  140. package/dist/setup.js +22 -54
  141. package/dist/setup.js.map +1 -1
  142. package/dist/skills.d.ts +39 -0
  143. package/dist/skills.d.ts.map +1 -1
  144. package/dist/skills.js +90 -8
  145. package/dist/skills.js.map +1 -1
  146. package/dist/storage.d.ts.map +1 -1
  147. package/dist/storage.js +6 -3
  148. package/dist/storage.js.map +1 -1
  149. package/dist/streaming.d.ts +25 -0
  150. package/dist/streaming.d.ts.map +1 -1
  151. package/dist/streaming.js +59 -0
  152. package/dist/streaming.js.map +1 -1
  153. package/dist/styles.d.ts +6 -0
  154. package/dist/styles.d.ts.map +1 -1
  155. package/dist/styles.js +11 -0
  156. package/dist/styles.js.map +1 -1
  157. package/dist/summarization.js.map +1 -1
  158. package/dist/terminal-image.js +1 -1
  159. package/dist/terminal-image.js.map +1 -1
  160. package/dist/themes.d.ts +5 -4
  161. package/dist/themes.d.ts.map +1 -1
  162. package/dist/themes.js +35 -90
  163. package/dist/themes.js.map +1 -1
  164. package/dist/tools.d.ts +27 -3
  165. package/dist/tools.d.ts.map +1 -1
  166. package/dist/tools.js +128 -179
  167. package/dist/tools.js.map +1 -1
  168. package/dist/types.d.ts +4 -3
  169. package/dist/types.d.ts.map +1 -1
  170. package/dist/types.js +21 -40
  171. package/dist/types.js.map +1 -1
  172. package/dist/ui/agent.d.ts +3 -6
  173. package/dist/ui/agent.d.ts.map +1 -1
  174. package/dist/ui/agent.js +328 -38
  175. package/dist/ui/agent.js.map +1 -1
  176. package/dist/ui/animations.d.ts +37 -0
  177. package/dist/ui/animations.d.ts.map +1 -0
  178. package/dist/ui/animations.js +227 -0
  179. package/dist/ui/animations.js.map +1 -0
  180. package/dist/ui/chat-input-keys.d.ts +42 -0
  181. package/dist/ui/chat-input-keys.d.ts.map +1 -0
  182. package/dist/ui/chat-input-keys.js +360 -0
  183. package/dist/ui/chat-input-keys.js.map +1 -0
  184. package/dist/ui/chat-input.d.ts +2 -1
  185. package/dist/ui/chat-input.d.ts.map +1 -1
  186. package/dist/ui/chat-input.js +31 -350
  187. package/dist/ui/chat-input.js.map +1 -1
  188. package/dist/ui/commands.d.ts +14 -46
  189. package/dist/ui/commands.d.ts.map +1 -1
  190. package/dist/ui/commands.js +416 -2899
  191. package/dist/ui/commands.js.map +1 -1
  192. package/dist/ui/completions.d.ts +1 -0
  193. package/dist/ui/completions.d.ts.map +1 -1
  194. package/dist/ui/completions.js +36 -107
  195. package/dist/ui/completions.js.map +1 -1
  196. package/dist/ui/components.d.ts +14 -6
  197. package/dist/ui/components.d.ts.map +1 -1
  198. package/dist/ui/components.js +17 -226
  199. package/dist/ui/components.js.map +1 -1
  200. package/dist/ui/context.js +6 -6
  201. package/dist/ui/context.js.map +1 -1
  202. package/dist/ui/debug-log.d.ts +11 -0
  203. package/dist/ui/debug-log.d.ts.map +1 -0
  204. package/dist/ui/debug-log.js +21 -0
  205. package/dist/ui/debug-log.js.map +1 -0
  206. package/dist/ui/error-boundary.d.ts.map +1 -1
  207. package/dist/ui/error-boundary.js.map +1 -1
  208. package/dist/ui/frame.d.ts.map +1 -1
  209. package/dist/ui/frame.js +1 -3
  210. package/dist/ui/frame.js.map +1 -1
  211. package/dist/ui/index.d.ts +11 -3
  212. package/dist/ui/index.d.ts.map +1 -1
  213. package/dist/ui/index.js +45 -1076
  214. package/dist/ui/index.js.map +1 -1
  215. package/dist/ui/messages.d.ts +4 -7
  216. package/dist/ui/messages.d.ts.map +1 -1
  217. package/dist/ui/messages.js +15 -30
  218. package/dist/ui/messages.js.map +1 -1
  219. package/dist/ui/modals/index.d.ts +11 -0
  220. package/dist/ui/modals/index.d.ts.map +1 -0
  221. package/dist/ui/modals/index.js +10 -0
  222. package/dist/ui/modals/index.js.map +1 -0
  223. package/dist/ui/modals/prompts.d.ts +37 -0
  224. package/dist/ui/modals/prompts.d.ts.map +1 -0
  225. package/dist/ui/modals/prompts.js +124 -0
  226. package/dist/ui/modals/prompts.js.map +1 -0
  227. package/dist/ui/modals/selectors.d.ts +33 -0
  228. package/dist/ui/modals/selectors.d.ts.map +1 -0
  229. package/dist/ui/modals/selectors.js +111 -0
  230. package/dist/ui/modals/selectors.js.map +1 -0
  231. package/dist/ui/modals/setup.d.ts +17 -0
  232. package/dist/ui/modals/setup.d.ts.map +1 -0
  233. package/dist/ui/modals/setup.js +51 -0
  234. package/dist/ui/modals/setup.js.map +1 -0
  235. package/dist/ui/regions/input-region.d.ts +40 -0
  236. package/dist/ui/regions/input-region.d.ts.map +1 -0
  237. package/dist/ui/regions/input-region.js +116 -0
  238. package/dist/ui/regions/input-region.js.map +1 -0
  239. package/dist/ui/regions/modal-host.d.ts +41 -0
  240. package/dist/ui/regions/modal-host.d.ts.map +1 -0
  241. package/dist/ui/regions/modal-host.js +51 -0
  242. package/dist/ui/regions/modal-host.js.map +1 -0
  243. package/dist/ui/regions/render-probe.d.ts +22 -0
  244. package/dist/ui/regions/render-probe.d.ts.map +1 -0
  245. package/dist/ui/regions/render-probe.js +45 -0
  246. package/dist/ui/regions/render-probe.js.map +1 -0
  247. package/dist/ui/regions/static-scrollback.d.ts +39 -0
  248. package/dist/ui/regions/static-scrollback.d.ts.map +1 -0
  249. package/dist/ui/regions/static-scrollback.js +68 -0
  250. package/dist/ui/regions/static-scrollback.js.map +1 -0
  251. package/dist/ui/regions/status-region.d.ts +24 -0
  252. package/dist/ui/regions/status-region.d.ts.map +1 -0
  253. package/dist/ui/regions/status-region.js +17 -0
  254. package/dist/ui/regions/status-region.js.map +1 -0
  255. package/dist/ui/regions/transcript-region.d.ts +41 -0
  256. package/dist/ui/regions/transcript-region.d.ts.map +1 -0
  257. package/dist/ui/regions/transcript-region.js +52 -0
  258. package/dist/ui/regions/transcript-region.js.map +1 -0
  259. package/dist/ui/self-restart.d.ts +9 -0
  260. package/dist/ui/self-restart.d.ts.map +1 -0
  261. package/dist/ui/self-restart.js +24 -0
  262. package/dist/ui/self-restart.js.map +1 -0
  263. package/dist/ui/state/use-chat-controller.d.ts +23 -0
  264. package/dist/ui/state/use-chat-controller.d.ts.map +1 -0
  265. package/dist/ui/state/use-chat-controller.js +643 -0
  266. package/dist/ui/state/use-chat-controller.js.map +1 -0
  267. package/dist/ui/state/use-loop-state.d.ts +23 -0
  268. package/dist/ui/state/use-loop-state.d.ts.map +1 -0
  269. package/dist/ui/state/use-loop-state.js +39 -0
  270. package/dist/ui/state/use-loop-state.js.map +1 -0
  271. package/dist/ui/state/use-modal-state.d.ts +52 -0
  272. package/dist/ui/state/use-modal-state.d.ts.map +1 -0
  273. package/dist/ui/state/use-modal-state.js +44 -0
  274. package/dist/ui/state/use-modal-state.js.map +1 -0
  275. package/dist/ui/state/use-model-state.d.ts +29 -0
  276. package/dist/ui/state/use-model-state.d.ts.map +1 -0
  277. package/dist/ui/state/use-model-state.js +47 -0
  278. package/dist/ui/state/use-model-state.js.map +1 -0
  279. package/dist/ui/state/use-processing-state.d.ts +24 -0
  280. package/dist/ui/state/use-processing-state.d.ts.map +1 -0
  281. package/dist/ui/state/use-processing-state.js +46 -0
  282. package/dist/ui/state/use-processing-state.js.map +1 -0
  283. package/dist/ui/state/use-queue-state.d.ts +19 -0
  284. package/dist/ui/state/use-queue-state.d.ts.map +1 -0
  285. package/dist/ui/state/use-queue-state.js +30 -0
  286. package/dist/ui/state/use-queue-state.js.map +1 -0
  287. package/dist/ui/state/use-session-init.d.ts +22 -0
  288. package/dist/ui/state/use-session-init.d.ts.map +1 -0
  289. package/dist/ui/state/use-session-init.js +77 -0
  290. package/dist/ui/state/use-session-init.js.map +1 -0
  291. package/dist/ui/state/use-session-stats.d.ts +16 -0
  292. package/dist/ui/state/use-session-stats.d.ts.map +1 -0
  293. package/dist/ui/state/use-session-stats.js +18 -0
  294. package/dist/ui/state/use-session-stats.js.map +1 -0
  295. package/dist/ui/state/use-terminal-width.d.ts +8 -0
  296. package/dist/ui/state/use-terminal-width.d.ts.map +1 -0
  297. package/dist/ui/state/use-terminal-width.js +22 -0
  298. package/dist/ui/state/use-terminal-width.js.map +1 -0
  299. package/dist/ui/state/use-transcript-state.d.ts +21 -0
  300. package/dist/ui/state/use-transcript-state.d.ts.map +1 -0
  301. package/dist/ui/state/use-transcript-state.js +49 -0
  302. package/dist/ui/state/use-transcript-state.js.map +1 -0
  303. package/dist/ui/status-bar.d.ts.map +1 -1
  304. package/dist/ui/status-bar.js +2 -3
  305. package/dist/ui/status-bar.js.map +1 -1
  306. package/dist/version-check.d.ts.map +1 -1
  307. package/dist/version-check.js +10 -0
  308. package/dist/version-check.js.map +1 -1
  309. package/package.json +10 -3
  310. package/dist/agents/agent-config-loader.d.ts +0 -60
  311. package/dist/agents/agent-config-loader.d.ts.map +0 -1
  312. package/dist/agents/agent-config-loader.js +0 -402
  313. package/dist/agents/agent-config-loader.js.map +0 -1
  314. package/dist/agents/agent-config-presets.d.ts +0 -10
  315. package/dist/agents/agent-config-presets.d.ts.map +0 -1
  316. package/dist/agents/agent-config-presets.js +0 -940
  317. package/dist/agents/agent-config-presets.js.map +0 -1
  318. package/dist/agents/agent-config-types.d.ts +0 -145
  319. package/dist/agents/agent-config-types.d.ts.map +0 -1
  320. package/dist/agents/agent-config-types.js +0 -12
  321. package/dist/agents/agent-config-types.js.map +0 -1
  322. package/dist/agents/agent-detection.d.ts +0 -35
  323. package/dist/agents/agent-detection.d.ts.map +0 -1
  324. package/dist/agents/agent-detection.js +0 -110
  325. package/dist/agents/agent-detection.js.map +0 -1
  326. package/dist/agents/aggregator.d.ts +0 -19
  327. package/dist/agents/aggregator.d.ts.map +0 -1
  328. package/dist/agents/aggregator.js +0 -141
  329. package/dist/agents/aggregator.js.map +0 -1
  330. package/dist/agents/cli-backend.d.ts +0 -32
  331. package/dist/agents/cli-backend.d.ts.map +0 -1
  332. package/dist/agents/cli-backend.js +0 -271
  333. package/dist/agents/cli-backend.js.map +0 -1
  334. package/dist/agents/council-types.d.ts +0 -115
  335. package/dist/agents/council-types.d.ts.map +0 -1
  336. package/dist/agents/council-types.js +0 -81
  337. package/dist/agents/council-types.js.map +0 -1
  338. package/dist/agents/council.d.ts +0 -112
  339. package/dist/agents/council.d.ts.map +0 -1
  340. package/dist/agents/council.js +0 -722
  341. package/dist/agents/council.js.map +0 -1
  342. package/dist/agents/decomposer.d.ts +0 -33
  343. package/dist/agents/decomposer.d.ts.map +0 -1
  344. package/dist/agents/decomposer.js +0 -138
  345. package/dist/agents/decomposer.js.map +0 -1
  346. package/dist/agents/dynamic-tools.d.ts +0 -52
  347. package/dist/agents/dynamic-tools.d.ts.map +0 -1
  348. package/dist/agents/dynamic-tools.js +0 -424
  349. package/dist/agents/dynamic-tools.js.map +0 -1
  350. package/dist/agents/index.d.ts +0 -29
  351. package/dist/agents/index.d.ts.map +0 -1
  352. package/dist/agents/index.js +0 -29
  353. package/dist/agents/index.js.map +0 -1
  354. package/dist/agents/installer.d.ts +0 -39
  355. package/dist/agents/installer.d.ts.map +0 -1
  356. package/dist/agents/installer.js +0 -205
  357. package/dist/agents/installer.js.map +0 -1
  358. package/dist/agents/orchestrator.d.ts +0 -106
  359. package/dist/agents/orchestrator.d.ts.map +0 -1
  360. package/dist/agents/orchestrator.js +0 -363
  361. package/dist/agents/orchestrator.js.map +0 -1
  362. package/dist/agents/sdk-backend.d.ts +0 -63
  363. package/dist/agents/sdk-backend.d.ts.map +0 -1
  364. package/dist/agents/sdk-backend.js +0 -489
  365. package/dist/agents/sdk-backend.js.map +0 -1
  366. package/dist/agents/swarm-types.d.ts +0 -84
  367. package/dist/agents/swarm-types.d.ts.map +0 -1
  368. package/dist/agents/swarm-types.js +0 -20
  369. package/dist/agents/swarm-types.js.map +0 -1
  370. package/dist/agents/swarm.d.ts +0 -79
  371. package/dist/agents/swarm.d.ts.map +0 -1
  372. package/dist/agents/swarm.js +0 -375
  373. package/dist/agents/swarm.js.map +0 -1
  374. package/dist/agents/tools.d.ts +0 -26
  375. package/dist/agents/tools.d.ts.map +0 -1
  376. package/dist/agents/tools.js +0 -776
  377. package/dist/agents/tools.js.map +0 -1
  378. package/dist/agents/types.d.ts +0 -108
  379. package/dist/agents/types.d.ts.map +0 -1
  380. package/dist/agents/types.js +0 -43
  381. package/dist/agents/types.js.map +0 -1
  382. package/dist/api-server.d.ts +0 -45
  383. package/dist/api-server.d.ts.map +0 -1
  384. package/dist/api-server.js +0 -326
  385. package/dist/api-server.js.map +0 -1
  386. package/dist/auto-checkpoint.d.ts +0 -35
  387. package/dist/auto-checkpoint.d.ts.map +0 -1
  388. package/dist/auto-checkpoint.js +0 -176
  389. package/dist/auto-checkpoint.js.map +0 -1
  390. package/dist/background-jobs.d.ts +0 -45
  391. package/dist/background-jobs.d.ts.map +0 -1
  392. package/dist/background-jobs.js +0 -127
  393. package/dist/background-jobs.js.map +0 -1
  394. package/dist/branching.d.ts +0 -56
  395. package/dist/branching.d.ts.map +0 -1
  396. package/dist/branching.js +0 -224
  397. package/dist/branching.js.map +0 -1
  398. package/dist/cli/agent.d.ts +0 -9
  399. package/dist/cli/agent.d.ts.map +0 -1
  400. package/dist/cli/agent.js +0 -330
  401. package/dist/cli/agent.js.map +0 -1
  402. package/dist/cli/commands.d.ts +0 -12
  403. package/dist/cli/commands.d.ts.map +0 -1
  404. package/dist/cli/commands.js +0 -1042
  405. package/dist/cli/commands.js.map +0 -1
  406. package/dist/cli/index.d.ts +0 -8
  407. package/dist/cli/index.d.ts.map +0 -1
  408. package/dist/cli/index.js +0 -268
  409. package/dist/cli/index.js.map +0 -1
  410. package/dist/cli/types.d.ts +0 -33
  411. package/dist/cli/types.d.ts.map +0 -1
  412. package/dist/cli/types.js +0 -21
  413. package/dist/cli/types.js.map +0 -1
  414. package/dist/companions.d.ts +0 -54
  415. package/dist/companions.d.ts.map +0 -1
  416. package/dist/companions.js +0 -440
  417. package/dist/companions.js.map +0 -1
  418. package/dist/file-watcher.d.ts +0 -91
  419. package/dist/file-watcher.d.ts.map +0 -1
  420. package/dist/file-watcher.js +0 -269
  421. package/dist/file-watcher.js.map +0 -1
  422. package/dist/hud/theme-packs/api.d.ts +0 -51
  423. package/dist/hud/theme-packs/api.d.ts.map +0 -1
  424. package/dist/hud/theme-packs/api.js +0 -145
  425. package/dist/hud/theme-packs/api.js.map +0 -1
  426. package/dist/hud/theme-packs/index.d.ts +0 -18
  427. package/dist/hud/theme-packs/index.d.ts.map +0 -1
  428. package/dist/hud/theme-packs/index.js +0 -38
  429. package/dist/hud/theme-packs/index.js.map +0 -1
  430. package/dist/hud/theme-packs/types.d.ts +0 -29
  431. package/dist/hud/theme-packs/types.d.ts.map +0 -1
  432. package/dist/hud/theme-packs/types.js +0 -9
  433. package/dist/hud/theme-packs/types.js.map +0 -1
  434. package/dist/idle-eviction.d.ts +0 -34
  435. package/dist/idle-eviction.d.ts.map +0 -1
  436. package/dist/idle-eviction.js +0 -85
  437. package/dist/idle-eviction.js.map +0 -1
  438. package/dist/model-router.d.ts +0 -67
  439. package/dist/model-router.d.ts.map +0 -1
  440. package/dist/model-router.js.map +0 -1
  441. package/dist/providers/openai-compat-shims.d.ts +0 -31
  442. package/dist/providers/openai-compat-shims.d.ts.map +0 -1
  443. package/dist/providers/openai-compat-shims.js +0 -179
  444. package/dist/providers/openai-compat-shims.js.map +0 -1
  445. package/dist/sandbox-native.d.ts.map +0 -1
  446. package/dist/sandbox-native.js.map +0 -1
  447. package/dist/sandbox.d.ts.map +0 -1
  448. package/dist/sandbox.js.map +0 -1
  449. package/dist/session-timeout.d.ts +0 -31
  450. package/dist/session-timeout.d.ts.map +0 -1
  451. package/dist/session-timeout.js +0 -100
  452. package/dist/session-timeout.js.map +0 -1
  453. package/dist/smart-router.d.ts +0 -73
  454. package/dist/smart-router.d.ts.map +0 -1
  455. package/dist/smart-router.js +0 -332
  456. package/dist/smart-router.js.map +0 -1
  457. package/dist/terminal-recording.d.ts +0 -55
  458. package/dist/terminal-recording.d.ts.map +0 -1
  459. package/dist/terminal-recording.js +0 -182
  460. package/dist/terminal-recording.js.map +0 -1
  461. package/dist/tmux.d.ts +0 -35
  462. package/dist/tmux.d.ts.map +0 -1
  463. package/dist/tmux.js +0 -106
  464. package/dist/tmux.js.map +0 -1
  465. package/dist/ui/modals.d.ts +0 -72
  466. package/dist/ui/modals.d.ts.map +0 -1
  467. package/dist/ui/modals.js +0 -266
  468. package/dist/ui/modals.js.map +0 -1
  469. package/dist/ui/pack-picker.d.ts +0 -12
  470. package/dist/ui/pack-picker.d.ts.map +0 -1
  471. package/dist/ui/pack-picker.js +0 -101
  472. package/dist/ui/pack-picker.js.map +0 -1
  473. package/dist/ui/theme-picker.d.ts +0 -24
  474. package/dist/ui/theme-picker.d.ts.map +0 -1
  475. package/dist/ui/theme-picker.js +0 -190
  476. package/dist/ui/theme-picker.js.map +0 -1
package/dist/ui/agent.js CHANGED
@@ -15,18 +15,21 @@ import { formatError, classifyError } from '../errors.js';
15
15
  import { getAvailableProviders } from '../providers/index.js';
16
16
  import * as storage from '../storage.js';
17
17
  import * as hooks from '../hooks.js';
18
- import * as modelRouter from '../model-router.js';
19
- import { scuttlebotClient } from '../scuttlebot/index.js';
18
+ import * as router from '../router.js';
19
+ import { fleetActive, fleetMirrorAssistant } from '../fleet.js';
20
20
  import * as summarization from '../summarization.js';
21
21
  import { autoCompress } from '../auto-compressor.js';
22
+ import { createStreamFlusher } from '../streaming.js';
22
23
  import { executeParallel, getParallelizationStats } from '../parallel-tools.js';
23
- import { setMood } from '../companions.js';
24
24
  import { checkAndWarnContextLimit } from './context.js';
25
- import { smartRoute } from '../smart-router.js';
26
- import { shouldCheckpoint, createCheckpoint } from '../auto-checkpoint.js';
27
- import { recordEvent } from '../terminal-recording.js';
25
+ import { smartRoute } from '../router.js';
26
+ import { isLocalBackend, detectMalformedToolCall, buildRepairMessage, buildToolCallEnvelopeSchema, extractRepairedToolCall, getLocalModelProfile, } from '../local-model.js';
27
+ import { shouldCheckpoint, createCheckpoint } from '../checkpoint.js';
28
28
  import { startPreventSleep, stopPreventSleep } from '../prevent-sleep.js';
29
29
  import { resolveIterationLimit, formatIterationProgress, isFiniteIterationLimit, } from '../iteration-limit.js';
30
+ import { RunLog } from '../runlog.js';
31
+ import { getBudgetCaps, evaluateBudget, hasBudgetCaps, recordProjectSpend, loadProjectSpend, formatBudgetHalt, } from '../budget.js';
32
+ import { evaluatePolicy, isPolicyEnabled } from '../policy.js';
30
33
  // ============================================================================
31
34
  // Tool Result Truncation
32
35
  // ============================================================================
@@ -101,15 +104,116 @@ export function validateAndRepairMessagesImpl(ctx) {
101
104
  return repaired;
102
105
  }
103
106
  // ============================================================================
107
+ // Local-model tool-call repair (features 2 & 3)
108
+ // ============================================================================
109
+ /**
110
+ * When a LOCAL backend returns a malformed tool call, spend ONE corrective
111
+ * round-trip trying to fix it before the error surfaces naturally through
112
+ * executeTool. At most one call is repaired per response (cost control on
113
+ * self-hosted hardware); each call id gets at most one repair (`repairedCallIds`).
114
+ *
115
+ * On Ollama we grammar-constrain the repair reply with the tool-call envelope
116
+ * schema (the `format` param) — but only here, never on normal prose turns.
117
+ *
118
+ * Cloud backends and well-formed responses pass straight through unchanged.
119
+ */
120
+ async function maybeRepairLocalToolCalls(ctx, baseMessages, response, effectiveModel, repairedCallIds) {
121
+ if (!response.toolCalls?.length)
122
+ return response;
123
+ const provider = ctx.actualProvider;
124
+ if (!isLocalBackend(provider))
125
+ return response;
126
+ const tools = getTools();
127
+ let target;
128
+ for (const call of response.toolCalls) {
129
+ if (repairedCallIds.has(call.id))
130
+ continue;
131
+ const fault = detectMalformedToolCall(call, tools);
132
+ if (fault) {
133
+ target = { call, reason: fault.reason };
134
+ break;
135
+ }
136
+ }
137
+ if (!target)
138
+ return response;
139
+ const { call, reason } = target;
140
+ repairedCallIds.add(call.id); // one repair per tool call, whatever the outcome
141
+ ctx.debugLog('repair', 'malformed local tool call', call.name, reason);
142
+ ctx.addMessage('system', `🔧 Malformed tool call from local model (${reason}); attempting one repair…`);
143
+ // Grammar-constrain the reply when the backend supports JSON-schema format.
144
+ let format;
145
+ try {
146
+ const profile = await getLocalModelProfile(provider, effectiveModel || ctx.actualModel);
147
+ if (profile.supportsJsonSchemaFormat) {
148
+ format = buildToolCallEnvelopeSchema(tools.map(t => t.name));
149
+ }
150
+ }
151
+ catch {
152
+ // No format constraint — the corrective message alone still helps.
153
+ }
154
+ const repairMessages = [
155
+ ...baseMessages,
156
+ { role: 'assistant', content: typeof response.content === 'string' ? response.content : '', toolCalls: response.toolCalls },
157
+ { role: 'user', content: buildRepairMessage(call, { reason }) },
158
+ ];
159
+ let repaired;
160
+ try {
161
+ repaired = await chat(ctx.provider, repairMessages, tools, effectiveModel, undefined, undefined, format ? { format } : undefined);
162
+ }
163
+ catch (err) {
164
+ ctx.ledger?.recordAction('repair', { tool: call.name }, 'error', reason);
165
+ ctx.debugLog('repair', 'repair round-trip threw', err instanceof Error ? err.message : String(err));
166
+ return response; // let the original error surface through executeTool
167
+ }
168
+ const corrected = extractRepairedToolCall(typeof repaired.content === 'string' ? repaired.content : '', repaired.toolCalls, call.id);
169
+ if (!corrected) {
170
+ ctx.ledger?.recordAction('repair', { tool: call.name }, 'error', reason);
171
+ ctx.addMessage('system', '🔧 Repair produced no usable tool call; surfacing the original error.');
172
+ return response;
173
+ }
174
+ const stillBad = detectMalformedToolCall(corrected, tools);
175
+ ctx.ledger?.recordAction('repair', { tool: call.name, to: corrected.name }, stillBad ? 'error' : 'ok', stillBad?.reason);
176
+ ctx.addMessage('system', stillBad
177
+ ? '🔧 Repair did not resolve the malformed call; surfacing the error.'
178
+ : `🔧 Repaired tool call → ${corrected.name}`);
179
+ return { ...response, toolCalls: response.toolCalls.map(tc => (tc.id === call.id ? corrected : tc)) };
180
+ }
181
+ // ============================================================================
182
+ // Provider warnings
183
+ // ============================================================================
184
+ // Provider warnings already surfaced this process, keyed by sessionId::warning,
185
+ // so a repeated model substitution across turns/iterations isn't shown twice
186
+ // (#217). Non-fatal notices must be surfaced, never swallowed.
187
+ const surfacedWarnings = new Set();
188
+ function surfaceResponseWarnings(ctx, response) {
189
+ if (!response.warnings?.length)
190
+ return;
191
+ const sessionId = ctx.sessionRef.current?.id ?? 'session_adhoc';
192
+ for (const warning of response.warnings) {
193
+ const key = `${sessionId}::${warning}`;
194
+ if (surfacedWarnings.has(key))
195
+ continue;
196
+ surfacedWarnings.add(key);
197
+ ctx.addMessage('system', warning);
198
+ }
199
+ }
200
+ // ============================================================================
104
201
  // Run Agent
105
202
  // ============================================================================
106
203
  /**
107
204
  * Run agent with user prompt. Core execution loop that handles LLM calls,
108
205
  * tool execution (parallel + sequential), auto-compaction, and queued messages.
109
206
  */
207
+ // #231: remember the previous turn's mode so the first work-mode turn after
208
+ // a plan-mode turn can carry an explicit execution directive. Module scope is
209
+ // intentional — mode itself outlives session resets.
210
+ let previousTurnMode = null;
211
+ /** Test seam: clear the plan-to-work transition tracking. */
212
+ export function _resetModeTracking() {
213
+ previousTurnMode = null;
214
+ }
110
215
  export async function runAgentImpl(ctx, content) {
111
216
  ctx.debugLog('runAgent', 'ENTER', typeof content === 'string' ? content.substring(0, 50) : '[complex]');
112
- setMood('thinking');
113
217
  // Validate message history before adding new content
114
218
  ctx.validateAndRepairMessages();
115
219
  ctx.llmMessages.current.push({ role: 'user', content });
@@ -130,7 +234,7 @@ export async function runAgentImpl(ctx, content) {
130
234
  }
131
235
  }
132
236
  else if (ctx.autoRoute && typeof content === 'string') {
133
- const routeDecision = modelRouter.routeRequest(content, ctx.provider, {
237
+ const routeDecision = router.routeRequest(content, ctx.provider, {
134
238
  messageCount: ctx.stats.messageCount,
135
239
  hasCode: content.includes('```') || /\.(ts|js|py|go|rs|java)/.test(content),
136
240
  });
@@ -152,6 +256,57 @@ export async function runAgentImpl(ctx, content) {
152
256
  : undefined;
153
257
  let runStatus;
154
258
  let runErrorSummary;
259
+ // Local-backend behaviours (features 2 & 4): whether to append anchor hashes
260
+ // to read_file output, and which tool-call ids have already spent their one
261
+ // repair round-trip.
262
+ const localBackend = isLocalBackend(ctx.actualProvider);
263
+ const executeOptions = { appendAnchorHash: localBackend };
264
+ const repairedCallIds = new Set();
265
+ // Governance (#189): audit run log, budget caps, policy hook. The run log is a
266
+ // per-session append-only JSONL trace; each agent turn is one run within it.
267
+ const sessionId = ctx.sessionRef.current?.id ?? 'session_adhoc';
268
+ const projectDir = ctx.sessionRef.current?.projectPath ?? process.cwd();
269
+ const runlog = RunLog.open(sessionId);
270
+ const budgetCaps = getBudgetCaps();
271
+ // Only maintain the cross-run project ledger when a project cap is active.
272
+ const trackProjectBudget = typeof budgetCaps.maxCostPerProject === 'number';
273
+ const runStartedAt = Date.now();
274
+ let runInputTokens = 0;
275
+ let runOutputTokens = 0;
276
+ let runCostUsd = 0;
277
+ let runToolCalls = 0;
278
+ let runEnded = false;
279
+ let runExitReason;
280
+ let budgetVerdict;
281
+ runlog.runStart({
282
+ session: sessionId,
283
+ cwd: projectDir,
284
+ provider: effectiveProvider,
285
+ model: effectiveModel || ctx.actualModel,
286
+ config: config.getConfig(),
287
+ });
288
+ runlog.userPrompt(summarizeMessageContent(content));
289
+ // #231: a terse reply right after leaving plan mode is an approval, not a
290
+ // conversation — bind it to execution explicitly or weak models answer
291
+ // "done" without doing anything.
292
+ const justLeftPlanMode = previousTurnMode === 'plan' && ctx.mode !== 'plan';
293
+ previousTurnMode = ctx.mode;
294
+ const finalizeRun = (exitReason) => {
295
+ if (runEnded)
296
+ return;
297
+ runEnded = true;
298
+ runlog.runEnd({
299
+ totals: {
300
+ inputTokens: runInputTokens,
301
+ outputTokens: runOutputTokens,
302
+ cost: runCostUsd,
303
+ toolCalls: runToolCalls,
304
+ durationMs: Date.now() - runStartedAt,
305
+ },
306
+ exitReason,
307
+ });
308
+ void runlog.flush();
309
+ };
155
310
  // Check context limit and warn if approaching capacity
156
311
  // Uses model's actual context length from API when available
157
312
  let currentContextTokens = ctx.estimateContextTokens();
@@ -170,7 +325,7 @@ export async function runAgentImpl(ctx, content) {
170
325
  }
171
326
  else if (contextPercentage > 65) {
172
327
  ctx.addMessage('system', `⚠️ Context at ${Math.round(contextPercentage)}% capacity (${Math.round(currentContextTokens / 1000)}K/${Math.round(modelLimit / 1000)}K tokens)
173
- Consider: /summarize compact | /clear | shorter messages`);
328
+ Consider: /compact | /clear | shorter messages`);
174
329
  }
175
330
  // Inject failed approaches into context so the agent avoids repeating mistakes
176
331
  if (ctx.ledger) {
@@ -194,6 +349,14 @@ export async function runAgentImpl(ctx, content) {
194
349
  ctx.addMessage('system', `🔄 Auto-compressed ${iterAutoCompressResult.summarizedCount} messages during iteration ${i + 1} (${iterAutoCompressResult.method})`);
195
350
  }
196
351
  }
352
+ // Frame-bounded streaming: coalesce provider tokens so setStreamingResponse
353
+ // fires at most ~30fps instead of once per token (which re-rendered the
354
+ // transcript region on every token). The flusher is per-iteration; its
355
+ // pending timer is drained by flush() on success and cleared by destroy()
356
+ // on the error/cancel path below.
357
+ const streamFlusher = createStreamFlusher((delta) => {
358
+ ctx.setStreamingResponse(prev => prev + delta);
359
+ });
197
360
  try {
198
361
  // Update thinking state for LLM call
199
362
  ctx.setThinkingState({
@@ -207,10 +370,15 @@ export async function runAgentImpl(ctx, content) {
207
370
  target: `iteration ${i + 1}`,
208
371
  startTime: Date.now(),
209
372
  });
210
- // Streaming callback for final response
373
+ // Streaming callback for final response. Clear the thinking indicator on
374
+ // the first token, then hand the token to the frame-bounded flusher.
375
+ let streamStarted = false;
211
376
  const onToken = (token) => {
212
- ctx.setThinkingState(null); // Clear thinking when streaming starts
213
- ctx.setStreamingResponse(prev => prev + token);
377
+ if (!streamStarted) {
378
+ ctx.setThinkingState(null); // Clear thinking when streaming starts
379
+ streamStarted = true;
380
+ }
381
+ streamFlusher.push(token);
214
382
  };
215
383
  // Retry callback for error recovery
216
384
  const onRetry = (attempt, error, delayMs) => {
@@ -228,8 +396,36 @@ export async function runAgentImpl(ctx, content) {
228
396
  ctx.debugLog('chat', 'CLEANED orphaned tool results', `removed=${ctx.llmMessages.current.length - validatedMessages.length}`);
229
397
  ctx.llmMessages.current = validatedMessages;
230
398
  }
399
+ // Plan mode: tell the model it is planning and that plans need receipts.
400
+ // Injected per-request (not persisted) so mode switches take effect
401
+ // immediately and history stays clean.
402
+ if (ctx.mode === 'plan') {
403
+ validatedMessages = [
404
+ ...validatedMessages,
405
+ {
406
+ role: 'system',
407
+ content: 'You are in PLAN mode: no mutating tools will execute. '
408
+ + 'Read-only tools (read_file, list_files, think, create_plan, ask_question) ARE available — use them. '
409
+ + 'Before proposing any plan, read the files you intend to change and cite file:line for every claim. '
410
+ + 'State explicitly what you verified versus what you assume. '
411
+ + 'A plan produced without reading anything will be marked unverified.',
412
+ },
413
+ ];
414
+ }
415
+ if (justLeftPlanMode && i === 0) {
416
+ validatedMessages = [
417
+ ...validatedMessages,
418
+ {
419
+ role: 'system',
420
+ content: 'The user has just switched from plan mode to work mode and replied. '
421
+ + 'Treat their message as approval to execute the plan discussed above. '
422
+ + 'Carry it out now using tools, step by step. '
423
+ + 'Never state that work is done unless you performed it with tool calls in this turn.',
424
+ },
425
+ ];
426
+ }
231
427
  // Pre-request summarization check - summarize BEFORE sending if context is too large
232
- const tools = getTools(ctx.agtermEnabled);
428
+ const tools = getTools();
233
429
  const contextCheck = estimateContextUsage(ctx.provider, effectiveModel || DEFAULT_MODELS[ctx.provider], validatedMessages, tools);
234
430
  ctx.debugLog('chat', 'CONTEXT CHECK', `estimated=${contextCheck.estimated}, limit=${contextCheck.limit}, percent=${contextCheck.percent}%`);
235
431
  if (contextCheck.needsSummarization) {
@@ -241,8 +437,16 @@ export async function runAgentImpl(ctx, content) {
241
437
  ctx.debugLog('chat', 'PRE-SUMMARIZED', `removed=${result.summarizedCount} messages, reduced from ${result.originalTokens} to ${result.reducedTokens}`);
242
438
  }
243
439
  }
244
- const response = await chat(ctx.provider, validatedMessages, tools, effectiveModel, onToken, onRetry);
440
+ let response = await chat(ctx.provider, validatedMessages, tools, effectiveModel, onToken, onRetry);
441
+ streamFlusher.flush(); // drain any batched tail before we swap to history
245
442
  ctx.debugLog('chat', 'GOT response', `toolCalls=${response.toolCalls?.length ?? 0}`);
443
+ // Local backends: give a malformed tool call ONE corrective round-trip
444
+ // before it surfaces as an execution error (features 2 & 3). No-op for
445
+ // cloud providers and well-formed responses.
446
+ response = await maybeRepairLocalToolCalls(ctx, validatedMessages, response, effectiveModel, repairedCallIds);
447
+ // Surface provider warnings (e.g. Ollama model substitution) once per
448
+ // unique message per session — never swallow them (#217).
449
+ surfaceResponseWarnings(ctx, response);
246
450
  // Update token stats and cost
247
451
  if (response.usage) {
248
452
  const usageCost = calculateCost(ctx.model || DEFAULT_MODELS[ctx.provider], response.usage.inputTokens, response.usage.outputTokens);
@@ -267,6 +471,33 @@ export async function runAgentImpl(ctx, content) {
267
471
  }
268
472
  }
269
473
  }
474
+ // Governance: accrue run spend, audit the assistant turn, evaluate budget.
475
+ {
476
+ const turnCost = response.usage
477
+ ? calculateCost(ctx.model || DEFAULT_MODELS[ctx.provider], response.usage.inputTokens, response.usage.outputTokens)
478
+ : 0;
479
+ if (response.usage) {
480
+ runInputTokens += response.usage.inputTokens;
481
+ runOutputTokens += response.usage.outputTokens;
482
+ runCostUsd += turnCost;
483
+ if (trackProjectBudget)
484
+ recordProjectSpend(projectDir, turnCost);
485
+ }
486
+ runlog.assistantMessage({
487
+ content: response.content,
488
+ tokens: { input: response.usage?.inputTokens ?? 0, output: response.usage?.outputTokens ?? 0 },
489
+ cost: turnCost,
490
+ });
491
+ if (hasBudgetCaps(budgetCaps)) {
492
+ const verdict = evaluateBudget(budgetCaps, {
493
+ runCostUsd,
494
+ runTokens: runInputTokens + runOutputTokens,
495
+ projectCostUsd: loadProjectSpend(projectDir).spentUsd,
496
+ });
497
+ if (verdict.exceeded)
498
+ budgetVerdict = verdict;
499
+ }
500
+ }
270
501
  // Circuit breaker check after each iteration
271
502
  if (ctx.circuitBreaker) {
272
503
  const iterData = {
@@ -296,13 +527,15 @@ export async function runAgentImpl(ctx, content) {
296
527
  toolCalls: response.toolCalls,
297
528
  });
298
529
  // Mirror any text the assistant produced alongside the tool calls
299
- if (scuttlebotClient.isEnabled() && response.content) {
300
- scuttlebotClient.mirrorAssistant(response.content).catch(() => { });
530
+ if (fleetActive() && response.content) {
531
+ fleetMirrorAssistant(response.content);
301
532
  }
302
533
  const preChecks = [];
303
534
  const executableTools = [];
304
535
  for (const toolCall of response.toolCalls) {
305
536
  const args = toolCall.arguments;
537
+ runlog.toolCall({ id: toolCall.id, name: toolCall.name, args });
538
+ runToolCalls++;
306
539
  const toolPreview = String(args.command || args.path || '...');
307
540
  const risk = assessToolRisk(toolCall);
308
541
  const riskConfig = RISK_CONFIG[risk.level];
@@ -344,15 +577,38 @@ export async function runAgentImpl(ctx, content) {
344
577
  ctx.addMessage('tool', `🛑 Blocked by hook: ${preHookResult.reason}`);
345
578
  }
346
579
  else {
347
- // Tool can be executed
348
- executableTools.push(toolCall);
349
- ctx.addMessage('tool', `⚡ ${toolCall.name}: ${toolPreview}${riskDisplay}`);
580
+ // Pre-tool policy gate (fail closed). Distinct from the hook above:
581
+ // the policy engine receives the tool-call JSON on stdin and denies
582
+ // via a non-zero exit code (see policy.ts / docs/governance.md).
583
+ const policyResult = isPolicyEnabled() ? await evaluatePolicy(toolCall) : undefined;
584
+ if (policyResult) {
585
+ runlog.policyEvent({
586
+ tool: toolCall.name,
587
+ decision: policyResult.decision,
588
+ source: policyResult.source,
589
+ reason: policyResult.reason,
590
+ durationMs: policyResult.durationMs,
591
+ });
592
+ }
593
+ if (policyResult && policyResult.decision === 'deny') {
594
+ preCheck.blocked = true;
595
+ preCheck.blockReason = 'denied by policy';
596
+ preCheck.blockContent = `[Denied by policy: ${policyResult.reason || 'no reason given'}]`;
597
+ ctx.addMessage('tool', `⚡ ${toolCall.name}: ${toolPreview}${riskDisplay}`);
598
+ ctx.addMessage('tool', `⛨ Denied by policy: ${policyResult.reason || 'no reason given'}`);
599
+ }
600
+ else {
601
+ // Tool can be executed
602
+ executableTools.push(toolCall);
603
+ ctx.addMessage('tool', `⚡ ${toolCall.name}: ${toolPreview}${riskDisplay}`);
604
+ }
350
605
  }
351
606
  }
352
607
  preChecks.push(preCheck);
353
608
  // Record blocked tools in ledger and add to LLM messages
354
609
  if (preCheck.blocked) {
355
610
  ctx.ledger?.recordAction(toolCall.name, args, 'blocked', preCheck.blockReason);
611
+ runlog.toolResult({ id: toolCall.id, result: preCheck.blockContent, isError: true, durationMs: 0 });
356
612
  ctx.llmMessages.current.push({
357
613
  role: 'tool',
358
614
  content: preCheck.blockContent,
@@ -382,7 +638,7 @@ export async function runAgentImpl(ctx, content) {
382
638
  // Execute in parallel using dependency-aware staging
383
639
  ctx.debugLog('tools', 'PARALLEL exec start', `count=${executableTools.length}`);
384
640
  const results = await executeParallel(executableTools, async (call) => {
385
- const result = await executeTool(call, ctx.sessionRef.current?.projectPath ?? process.cwd());
641
+ const result = await executeTool(call, ctx.sessionRef.current?.projectPath ?? process.cwd(), 60000, undefined, executeOptions);
386
642
  return { result: result.result, isError: result.isError };
387
643
  }, (completed, total, current) => {
388
644
  const args = current.arguments;
@@ -408,10 +664,10 @@ export async function runAgentImpl(ctx, content) {
408
664
  // both count as a failure — mirror the sequential branch.
409
665
  const failed = result.isError || !!result.error;
410
666
  const failureText = result.error || result.result;
411
- recordEvent('tool_call', toolCall.name, { name: toolCall.name, arguments: args });
412
- recordEvent('tool_result', (result.result || result.error || '').slice(0, 1000), { name: toolCall.name, isError: failed });
413
667
  // Record in iteration ledger
414
668
  ctx.ledger?.recordAction(toolCall.name, args, failed ? 'error' : 'ok', failed ? failureText : undefined);
669
+ // Audit the tool result (parallel exec has no per-call duration).
670
+ runlog.toolResult({ id: toolCall.id, result: failed ? failureText : result.result, isError: failed, durationMs: 0 });
415
671
  // Execute post-tool hooks
416
672
  hooks.executeHooks('post-tool', {
417
673
  tool: toolCall.name,
@@ -483,12 +739,11 @@ export async function runAgentImpl(ctx, content) {
483
739
  });
484
740
  }
485
741
  ctx.debugLog('tools', 'EXEC', toolCall.name, toolPreview.substring(0, 30));
486
- recordEvent('tool_call', toolCall.name, { name: toolCall.name, arguments: args });
487
742
  // Auto-checkpoint before destructive operations
488
743
  if (shouldCheckpoint(toolCall.name, args)) {
489
744
  const hash = createCheckpoint(toolCall.name, args);
490
745
  if (hash) {
491
- ctx.debugLog('checkpoint', `auto-checkpoint ${hash} before ${toolCall.name}`);
746
+ ctx.debugLog('checkpoint', `checkpoint ${hash} before ${toolCall.name}`);
492
747
  }
493
748
  }
494
749
  // Stream shell output in real-time (#15)
@@ -500,11 +755,13 @@ export async function runAgentImpl(ctx, content) {
500
755
  detail: chunk.trimEnd().split('\n').pop()?.substring(0, 60),
501
756
  });
502
757
  } : undefined;
503
- const result = await executeTool(toolCall, ctx.sessionRef.current?.projectPath ?? process.cwd(), 60000, shellStreamCallback);
758
+ const seqStart = Date.now();
759
+ const result = await executeTool(toolCall, ctx.sessionRef.current?.projectPath ?? process.cwd(), 60000, shellStreamCallback, executeOptions);
504
760
  ctx.debugLog('tools', 'DONE', toolCall.name);
505
- recordEvent('tool_result', result.result.slice(0, 1000), { name: toolCall.name, isError: result.isError });
506
761
  // Record in iteration ledger
507
762
  ctx.ledger?.recordAction(toolCall.name, args, result.isError ? 'error' : 'ok', result.isError ? result.result : undefined);
763
+ // Audit the tool result.
764
+ runlog.toolResult({ id: toolCall.id, result: result.result, isError: result.isError || false, durationMs: Date.now() - seqStart });
508
765
  // Execute post-tool hooks
509
766
  hooks.executeHooks('post-tool', {
510
767
  tool: toolCall.name,
@@ -547,12 +804,12 @@ export async function runAgentImpl(ctx, content) {
547
804
  if (planReasoning)
548
805
  planMsg += `\n ${planReasoning}\n`;
549
806
  planMsg += '\n' + planSteps.map((s, idx) => ` ${idx + 1}. [ ] ${s}`).join('\n');
550
- planMsg += '\n\n Type /approve to execute, or provide feedback to revise.';
807
+ planMsg += '\n\n Switch to work mode (Shift+Tab) and reply to execute, or give feedback to revise.';
551
808
  ctx.addMessage('assistant', planMsg);
552
809
  // Tell the LLM to wait for user approval
553
810
  ctx.llmMessages.current.push({
554
811
  role: 'tool',
555
- content: '[Plan displayed to user. Waiting for approval. The user will either type /approve to execute the plan, or provide feedback to revise it. Do NOT proceed with execution until the user approves.]',
812
+ content: '[Plan displayed to user. Waiting for approval. The user will reply to approve and execute the plan (they may switch to work mode first), or provide feedback to revise it. Do NOT proceed with execution until the user approves.]',
556
813
  toolCallId: toolCall.id,
557
814
  });
558
815
  // Break out of tool loop - wait for user approval
@@ -574,6 +831,24 @@ export async function runAgentImpl(ctx, content) {
574
831
  }
575
832
  }
576
833
  ctx.ledger?.endIteration();
834
+ // Budget halt: the current tool result is finished; stop before the next
835
+ // provider call. Record a budget_event and summarize spend vs cap.
836
+ if (budgetVerdict?.exceeded) {
837
+ const summary = formatBudgetHalt(budgetVerdict);
838
+ runlog.budgetEvent({
839
+ scope: budgetVerdict.scope ?? 'run',
840
+ kind: budgetVerdict.kind ?? 'cost',
841
+ spent: budgetVerdict.spent ?? 0,
842
+ cap: budgetVerdict.cap ?? 0,
843
+ message: budgetVerdict.message ?? summary,
844
+ });
845
+ ctx.addMessage('system', `‖ ${summary}`);
846
+ runStatus = 'stopped';
847
+ runErrorSummary = summary;
848
+ runExitReason = 'budget';
849
+ completedNaturally = true;
850
+ break;
851
+ }
577
852
  if (completedNaturally)
578
853
  break; // ask_question pauses for user input (#42)
579
854
  continue;
@@ -582,17 +857,13 @@ export async function runAgentImpl(ctx, content) {
582
857
  ctx.setThinkingState(null);
583
858
  ctx.llmMessages.current.push({ role: 'assistant', content: response.content });
584
859
  ctx.addMessage('assistant', response.content);
585
- recordEvent('output', response.content.slice(0, 5000));
586
- // Mirror assistant message to scuttlebot
587
- if (scuttlebotClient.isEnabled()) {
588
- scuttlebotClient.mirrorAssistant(response.content).catch(() => {
589
- // Silent fail
590
- });
860
+ // Mirror assistant message to the fleet channel
861
+ if (fleetActive()) {
862
+ fleetMirrorAssistant(response.content);
591
863
  }
592
864
  ctx.setStreamingResponse('');
593
865
  ctx.setContextTokens(ctx.estimateContextTokens());
594
866
  checkAndWarnContextLimit(ctx.actualProvider, ctx.actualModel, ctx.estimateContextTokens(), ctx.addMessage);
595
- setMood('success');
596
867
  // Auto-continue if response was truncated due to length
597
868
  if (response.finishReason === 'length') {
598
869
  ctx.addMessage('system', '(auto-continuing...)');
@@ -601,6 +872,20 @@ export async function runAgentImpl(ctx, content) {
601
872
  }
602
873
  completedNaturally = true;
603
874
  runStatus = 'completed';
875
+ // The answer is already shown; if this final turn crossed a budget cap,
876
+ // still record it so the audit trail reflects why spend stopped here.
877
+ if (budgetVerdict?.exceeded) {
878
+ const summary = formatBudgetHalt(budgetVerdict);
879
+ runlog.budgetEvent({
880
+ scope: budgetVerdict.scope ?? 'run',
881
+ kind: budgetVerdict.kind ?? 'cost',
882
+ spent: budgetVerdict.spent ?? 0,
883
+ cap: budgetVerdict.cap ?? 0,
884
+ message: budgetVerdict.message ?? summary,
885
+ });
886
+ ctx.addMessage('system', `‖ ${summary}`);
887
+ runExitReason = 'budget';
888
+ }
604
889
  // End iteration ledger entry
605
890
  ctx.ledger?.endIteration('success');
606
891
  // Auto-save full message history for session persistence
@@ -608,13 +893,13 @@ export async function runAgentImpl(ctx, content) {
608
893
  break;
609
894
  }
610
895
  catch (error) {
896
+ streamFlusher.destroy(); // stop any pending flush timer on error/cancel
611
897
  ctx.setThinkingState(null);
612
898
  ctx.setActivityState(null);
613
899
  ctx.setStreamingResponse('');
614
900
  // End iteration ledger entry with error
615
901
  ctx.ledger?.endIteration('error');
616
902
  // Format error with provider context for better suggestions
617
- setMood('error');
618
903
  const errorMsg = formatError(error, { provider: ctx.actualProvider });
619
904
  ctx.addMessage('error', errorMsg);
620
905
  // Classify error to provide additional recovery suggestions
@@ -643,7 +928,7 @@ export async function runAgentImpl(ctx, content) {
643
928
  // Suggest alternatives based on error type
644
929
  if (classified.category === 'rate_limit' || classified.category === 'server') {
645
930
  if (otherProviders.length > 0) {
646
- ctx.addMessage('system', `\u{1f4a1} Try switching providers: /provider ${otherProviders[0]} or /models to see alternatives`);
931
+ ctx.addMessage('system', `\u{1f4a1} Try switching providers: /provider ${otherProviders[0]} or /model to see alternatives`);
647
932
  }
648
933
  }
649
934
  else if (classified.category === 'timeout' || classified.category === 'network') {
@@ -671,6 +956,7 @@ export async function runAgentImpl(ctx, content) {
671
956
  if (runId && runStatus) {
672
957
  ctx.ledger?.finishRun(runId, runStatus, { errorSummary: runErrorSummary });
673
958
  }
959
+ finalizeRun(runExitReason ?? runStatus ?? 'error');
674
960
  return; // Exit early on error - don't process queued messages
675
961
  }
676
962
  }
@@ -685,10 +971,15 @@ export async function runAgentImpl(ctx, content) {
685
971
  if (runId) {
686
972
  ctx.ledger?.finishRun(runId, runStatus || 'completed', { errorSummary: runErrorSummary });
687
973
  }
974
+ finalizeRun(runExitReason ?? runStatus ?? 'completed');
688
975
  // Update context tokens after agent run
689
976
  ctx.setContextTokens(ctx.estimateContextTokens());
690
977
  // Process any queued messages (human-in-the-loop feedback)
691
978
  // CRITICAL: Use ref to get current value, not stale closure
979
+ // #224: a plan produced with zero tool calls carries no evidence — say so.
980
+ if (ctx.mode === 'plan' && runToolCalls === 0) {
981
+ ctx.addMessage('system', '⚠ Unverified plan — the agent read nothing to produce this. Ask it to verify (it can read files in plan mode), or treat claims as assumptions.');
982
+ }
692
983
  const currentQueued = ctx.queuedMessagesRef.current;
693
984
  ctx.debugLog('runAgent', 'EXIT loop', `queued=${currentQueued.length}`);
694
985
  if (currentQueued.length > 0) {
@@ -726,7 +1017,6 @@ export async function runAgentImpl(ctx, content) {
726
1017
  */
727
1018
  export async function runLoopImpl(ctx, prompt, maxIter, completionPromise) {
728
1019
  ctx.setIsProcessing(true);
729
- setMood('focused');
730
1020
  // Prevent system sleep during long agent loops (macOS)
731
1021
  startPreventSleep();
732
1022
  let completedIterations = 0;