@iowarp/clio-coder 0.3.3 → 0.3.6

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 (370) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/CONTRIBUTING.md +7 -7
  3. package/README.md +3 -3
  4. package/dist/{acp-P2AQILE2.js → acp-2BEHC4DL.js} +9 -8
  5. package/dist/{agents-72W3BI7I.js → agents-LNNFTM53.js} +29 -24
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-5TWEIYDN.js → auth-KXXFI2VS.js} +14 -10
  8. package/dist/{chunk-GGXXDWE4.js → chunk-22NAGB7X.js} +2 -2
  9. package/dist/{chunk-YCWGATWI.js → chunk-24I7BN55.js} +2 -2
  10. package/dist/{chunk-EKMEHE4H.js → chunk-33YXPOE3.js} +2 -3
  11. package/dist/chunk-3BPUFZDL.js +37 -0
  12. package/dist/chunk-43AOLP7E.js +375 -0
  13. package/dist/{chunk-ZDOOVTXZ.js → chunk-4OC57DA6.js} +27 -4
  14. package/dist/{chunk-6SGHMWE3.js → chunk-5JGRAMKL.js} +5 -5
  15. package/dist/{chunk-V6RTAOC2.js → chunk-6US73PDB.js} +572 -51
  16. package/dist/{chunk-5UFT4SUX.js → chunk-6XXKFVSN.js} +3 -3
  17. package/dist/{chunk-A3CYT5EX.js → chunk-AD2SYQYC.js} +55 -2
  18. package/dist/chunk-AOCYTWAV.js +449 -0
  19. package/dist/chunk-CFGTUFWB.js +67 -0
  20. package/dist/chunk-CJUB2JJ2.js +1478 -0
  21. package/dist/{chunk-FNTMWMX5.js → chunk-CKXWIANG.js} +14 -12
  22. package/dist/{chunk-PIWWS5BL.js → chunk-CYQKWTG3.js} +63 -78
  23. package/dist/{chunk-LZSJBIVT.js → chunk-DJVECN66.js} +271 -762
  24. package/dist/{chunk-CBCAPZAA.js → chunk-E25LMLRW.js} +2 -2
  25. package/dist/{chunk-ZWMF7253.js → chunk-E2ER4LJF.js} +304 -9
  26. package/dist/{chunk-STBPMHSX.js → chunk-EKY57CSP.js} +51 -84
  27. package/dist/{chunk-DUYJ5IO6.js → chunk-EYPA3EGJ.js} +12 -4
  28. package/dist/{chunk-M6SHUN7Q.js → chunk-FO5ZOVUY.js} +2 -2
  29. package/dist/chunk-FYYLNIL5.js +313 -0
  30. package/dist/{chunk-OQ33BKR3.js → chunk-G7MUEIGA.js} +3 -60
  31. package/dist/chunk-GEYXPTRF.js +613 -0
  32. package/dist/chunk-GOXNB3AO.js +261 -0
  33. package/dist/{chunk-G4BMMOKF.js → chunk-HVDIIIQW.js} +2 -2
  34. package/dist/chunk-HWUFFB6L.js +83 -0
  35. package/dist/{chunk-4XUGQOHA.js → chunk-K7T3E2SR.js} +15 -8
  36. package/dist/chunk-K7VKOLQQ.js +15 -0
  37. package/dist/{chunk-UFIIWP2H.js → chunk-KHSFENX2.js} +8 -8
  38. package/dist/{chunk-BMEMKKIT.js → chunk-KOHPCX4K.js} +2 -2
  39. package/dist/chunk-LCGCVYZ4.js +57 -0
  40. package/dist/chunk-LL4KHSZI.js +22 -0
  41. package/dist/{chunk-PAJK6MAQ.js → chunk-LYF7OHWH.js} +42 -15
  42. package/dist/{chunk-POHLU5DW.js → chunk-M6L6IDJG.js} +3 -3
  43. package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
  44. package/dist/{chunk-X4RCMKVQ.js → chunk-NDINPTJ4.js} +2 -2
  45. package/dist/{chunk-TZK7PACC.js → chunk-NILBFAPG.js} +14 -8
  46. package/dist/chunk-ODFEOB4F.js +1082 -0
  47. package/dist/{chunk-AGYYIBLL.js → chunk-OH3TOQTB.js} +6 -2
  48. package/dist/chunk-OZNBF4L3.js +23 -0
  49. package/dist/{chunk-DSELYM6W.js → chunk-PBTHKCPN.js} +30 -10
  50. package/dist/{verify-375KUB3Y.js → chunk-PCZJO5TI.js} +127 -42
  51. package/dist/{chunk-ED4KHGC3.js → chunk-PPAMZ32Z.js} +9 -2
  52. package/dist/{chunk-SRDMMSEP.js → chunk-QM3F2GKX.js} +1063 -1645
  53. package/dist/{chunk-X6IAEBZR.js → chunk-QNQHSOLF.js} +7 -7
  54. package/dist/{chunk-OC7FIQPC.js → chunk-R46L2BIR.js} +10 -7
  55. package/dist/{chunk-2TLUCQVG.js → chunk-RD5U66HV.js} +3 -3
  56. package/dist/{chunk-6N5PTWMY.js → chunk-RY3LY4J5.js} +50 -13
  57. package/dist/{chunk-OKGUZO2U.js → chunk-SPULKLCF.js} +4 -3
  58. package/dist/{chunk-OOJYHWRB.js → chunk-TSHXZTOQ.js} +6 -5
  59. package/dist/chunk-TZSKNMZG.js +434 -0
  60. package/dist/{chunk-7MNJORFF.js → chunk-UL3WSD3F.js} +6 -1
  61. package/dist/{chunk-VJWL6YS5.js → chunk-UUVG37B4.js} +2 -2
  62. package/dist/{chunk-COU2UHX6.js → chunk-VEZEGCGW.js} +170 -2
  63. package/dist/chunk-W6GROXXM.js +69 -0
  64. package/dist/{chunk-OAO4GE4M.js → chunk-WHGPSPT5.js} +2 -2
  65. package/dist/chunk-WHJYKASB.js +677 -0
  66. package/dist/{chunk-ORBHGJC5.js → chunk-WR67VIZY.js} +3 -3
  67. package/dist/{chunk-YHZX5GEU.js → chunk-XAKHZX5N.js} +2 -2
  68. package/dist/{chunk-TZTZS7QK.js → chunk-XE2VEJHX.js} +5 -3
  69. package/dist/{chunk-LM5TQCJZ.js → chunk-XF5N4U5A.js} +8 -7
  70. package/dist/{chunk-LWLEKMDQ.js → chunk-XXQNGV4M.js} +1073 -552
  71. package/dist/{chunk-KZWTDYJF.js → chunk-XYDYPRZI.js} +7 -7
  72. package/dist/chunk-ZGVHUX3M.js +66 -0
  73. package/dist/{chunk-LW6DSM3M.js → chunk-ZRGEBJ4T.js} +1192 -1119
  74. package/dist/{chunk-2DJ2KNFG.js → chunk-ZXF4XRKW.js} +202 -40
  75. package/dist/chunk-ZZMN5OM4.js +122 -0
  76. package/dist/cli/index.js +34 -30
  77. package/dist/{clio-JOU4FXVA.js → clio-M2KGYUFZ.js} +7 -6
  78. package/dist/{code-nav-7AX6FYE6.js → code-nav-GQNL7XA6.js} +8 -6
  79. package/dist/codewiki/build-worker.js +4 -4
  80. package/dist/{components-KELWS457.js → components-5TTYYX6G.js} +3 -3
  81. package/dist/{config-XCDVKR23.js → config-XUUYQIWO.js} +47 -35
  82. package/dist/{configure-4GAP54ZW.js → configure-IHJ7YOMV.js} +18 -15
  83. package/dist/{context-77FM5DV5.js → context-74JLXAWD.js} +18 -10
  84. package/dist/{context-4UOGGLQ5.js → context-75MIWW3U.js} +41 -29
  85. package/dist/{context-5VKGUVJJ.js → context-ZQ7SIFJV.js} +85 -9
  86. package/dist/{context-clear-XXJRLCJJ.js → context-clear-GYKWNUML.js} +41 -29
  87. package/dist/{context-index-BZ4UYMTC.js → context-index-SSR5ECNE.js} +3 -3
  88. package/dist/context-working-set-UX5KEP4J.js +1553 -0
  89. package/dist/{dispatch-runner-QPRDDBDX.js → dispatch-runner-GIJBHNFL.js} +47 -32
  90. package/dist/{docs-2C2LTVT2.js → docs-6FZSCG5B.js} +3 -3
  91. package/dist/{doctor-HR46URBJ.js → doctor-SVJ5BZCW.js} +12 -12
  92. package/dist/{eval-XSSNATB4.js → eval-CG6LLBLD.js} +54 -238
  93. package/dist/{evidence-6HG2PY2B.js → evidence-ZYFIEN42.js} +57 -28
  94. package/dist/{evolve-K7YU3NCY.js → evolve-QGEXEMDW.js} +36 -25
  95. package/dist/{extensions-QVDOHDGJ.js → extensions-ADGNCJJD.js} +3 -3
  96. package/dist/{fleet-VY3HHKN6.js → fleet-S5R4ZOQY.js} +73 -44
  97. package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-BHSNPBMH.js} +3 -3
  98. package/dist/{init-JYGXI3FK.js → init-5DRU55YR.js} +49 -37
  99. package/dist/memory-7YKKR6UC.js +467 -0
  100. package/dist/{models-I5QWSEOM.js → models-ZPOLRU2C.js} +24 -21
  101. package/dist/{monitor-GE4ID3IA.js → monitor-US5F5YGZ.js} +73 -46
  102. package/dist/{orchestrator-EM5MC3HM.js → orchestrator-E2AL4T5N.js} +1624 -1007
  103. package/dist/{paths-UXLN5YYZ.js → paths-E7KYAQWE.js} +3 -3
  104. package/dist/{reset-L2FQEE3E.js → reset-KZ652EK6.js} +6 -5
  105. package/dist/{run-ZU3QMZPZ.js → run-SRNBKDWD.js} +76 -54
  106. package/dist/{share-S5BZQC5I.js → share-CGZE33UP.js} +7 -6
  107. package/dist/{skills-X5VXCRNQ.js → skills-S2X4DLY5.js} +4 -4
  108. package/dist/{skills-eval-WKIHWTHR.js → skills-eval-W2GGIC4R.js} +40 -29
  109. package/dist/{targets-SNCPI2NR.js → targets-54SWINWB.js} +28 -23
  110. package/dist/{terminal-lease-BNAHVHBS.js → terminal-lease-SAIF2OGY.js} +6 -4
  111. package/dist/{uninstall-FZCQCDKC.js → uninstall-BVLWXKBT.js} +3 -3
  112. package/dist/{upgrade-JQHHPQ4K.js → upgrade-JKAR27XC.js} +20 -19
  113. package/dist/{usage-OR4O5SMZ.js → usage-MSAWCLX4.js} +79 -36
  114. package/dist/verifiers-NCBTHHN2.js +1220 -0
  115. package/dist/verify-X5HDROLA.js +25 -0
  116. package/dist/{wiki-generate-UEXP2ARI.js → wiki-generate-GUSOQ6ZP.js} +50 -37
  117. package/dist/worker/entry.js +90 -70
  118. package/dist/{workspace-G4ZWUIPR.js → workspace-ZJ6BFM3Q.js} +4 -4
  119. package/docs/README.md +8 -7
  120. package/docs/acp.md +1 -1
  121. package/docs/alcf-provider.md +1 -1
  122. package/docs/architecture.md +2 -2
  123. package/docs/artifact-placement.md +1 -2
  124. package/docs/artifact-versions.md +1 -1
  125. package/docs/built-in-agents.md +1 -1
  126. package/docs/capacity-and-scheduling.md +1 -1
  127. package/docs/commands-and-modes.md +60 -26
  128. package/docs/config-knobs-audit.md +1 -2
  129. package/docs/configuration-and-targets.md +26 -1
  130. package/docs/context-engine.md +67 -13
  131. package/docs/context-working-set.md +194 -0
  132. package/docs/development-pipeline.md +1 -1
  133. package/docs/documentation-coverage.md +6 -6
  134. package/docs/documentation-guide.md +7 -6
  135. package/docs/environment-variables.md +2 -1
  136. package/docs/eval-runner.md +1 -1
  137. package/docs/evals-internal.md +4 -32
  138. package/docs/evidence-and-memory.md +139 -7
  139. package/docs/evolution.md +1 -1
  140. package/docs/exit-codes-and-output.md +1 -1
  141. package/docs/extensions-and-sharing.md +2 -2
  142. package/docs/fleet-dispatch.md +49 -8
  143. package/docs/glossary.md +21 -1
  144. package/docs/installation-and-lifecycle.md +2 -2
  145. package/docs/middleware-and-components.md +19 -2
  146. package/docs/model-catalog.md +7 -9
  147. package/docs/observability.md +4 -4
  148. package/docs/proactive-memory.md +26 -16
  149. package/docs/prompt-envelope-and-tools.md +7 -5
  150. package/docs/provider-adapter-cookbook.md +1 -1
  151. package/docs/release-cut-checklist.md +43 -40
  152. package/docs/safety-model.md +49 -8
  153. package/docs/scientific-validation.md +21 -3
  154. package/docs/session-lifecycle.md +3 -3
  155. package/docs/skills-marketplace.md +1 -1
  156. package/docs/tool-usage.md +79 -12
  157. package/docs/trace-store.md +1 -1
  158. package/docs/troubleshooting.md +1 -1
  159. package/docs/tui-design.md +38 -4
  160. package/docs/worker-dispatch-mechanics.md +11 -1
  161. package/package.json +13 -13
  162. package/skills/meta/clio-test/SKILL.md +20 -17
  163. package/skills/meta/clio-test/evals.md +3 -3
  164. package/skills/meta/clio-test/references/harness.md +35 -6
  165. package/skills/meta/clio-test/references/test-map.md +20 -10
  166. package/skills/registry.yaml +2 -2
  167. package/skills/skill-marketplace.json +1 -1
  168. package/src/cli/agents.ts +2 -3
  169. package/src/cli/argv.ts +14 -1
  170. package/src/cli/context-working-set.ts +513 -0
  171. package/src/cli/context.ts +8 -0
  172. package/src/cli/evidence.ts +20 -2
  173. package/src/cli/fleet.ts +15 -0
  174. package/src/cli/index.ts +5 -1
  175. package/src/cli/memory.ts +272 -10
  176. package/src/cli/modes/json-stream.ts +2 -2
  177. package/src/cli/modes/print.ts +12 -1
  178. package/src/cli/run.ts +22 -2
  179. package/src/cli/targets.ts +12 -3
  180. package/src/cli/usage.ts +55 -7
  181. package/src/cli/verifiers.ts +325 -0
  182. package/src/core/bash-exec.ts +39 -14
  183. package/src/core/bus-events.ts +22 -4
  184. package/src/core/config.ts +54 -0
  185. package/src/core/defaults.ts +50 -3
  186. package/src/core/response-model-id.ts +134 -0
  187. package/src/core/toml.ts +62 -0
  188. package/src/core/verification-scripts.ts +6 -0
  189. package/src/core/workspace-files.ts +0 -1
  190. package/src/domains/agents/builtins/architect.md +1 -1
  191. package/src/domains/agents/builtins/verifier.md +3 -0
  192. package/src/domains/agents/catalog.ts +5 -4
  193. package/src/domains/agents/recipe.ts +54 -14
  194. package/src/domains/agents/result-contract.ts +7 -4
  195. package/src/domains/config/classify.ts +1 -0
  196. package/src/domains/context/bootstrap.ts +36 -27
  197. package/src/domains/context/project-metadata.ts +19 -63
  198. package/src/domains/context/prompt-context.ts +8 -0
  199. package/src/domains/context/working-set/contract.ts +161 -0
  200. package/src/domains/context/working-set/defaults.ts +28 -0
  201. package/src/domains/context/working-set/engine.ts +203 -0
  202. package/src/domains/context/working-set/fold.ts +62 -0
  203. package/src/domains/context/working-set/horizon.ts +38 -0
  204. package/src/domains/context/working-set/marker.ts +103 -0
  205. package/src/domains/context/working-set/path-index.ts +436 -0
  206. package/src/domains/context/working-set/payload.ts +152 -0
  207. package/src/domains/context/working-set/policies/age-horizon.ts +55 -0
  208. package/src/domains/context/working-set/policies/index.ts +20 -0
  209. package/src/domains/context/working-set/policies/structural.ts +160 -0
  210. package/src/domains/context/working-set/project.ts +132 -0
  211. package/src/domains/context/working-set/protect.ts +109 -0
  212. package/src/domains/context/working-set/recall.ts +177 -0
  213. package/src/domains/context/working-set/replay/controls.ts +112 -0
  214. package/src/domains/context/working-set/replay/load-clio.ts +199 -0
  215. package/src/domains/context/working-set/replay/metrics.ts +185 -0
  216. package/src/domains/context/working-set/replay/reference-graph.ts +79 -0
  217. package/src/domains/context/working-set/replay/report.ts +139 -0
  218. package/src/domains/context/working-set/replay/runner.ts +325 -0
  219. package/src/domains/context/working-set/replay/synthetic.ts +422 -0
  220. package/src/domains/context/working-set/replay/trace.ts +21 -0
  221. package/src/domains/context/working-set/visible.ts +54 -0
  222. package/src/domains/dispatch/budget-envelope.ts +396 -0
  223. package/src/domains/dispatch/contract.ts +2 -0
  224. package/src/domains/dispatch/extension.ts +81 -27
  225. package/src/domains/dispatch/orphan-recovery.ts +1 -0
  226. package/src/domains/dispatch/receipt-integrity.ts +4 -0
  227. package/src/domains/dispatch/state.ts +1 -0
  228. package/src/domains/dispatch/types.ts +10 -3
  229. package/src/domains/dispatch/validation.ts +14 -0
  230. package/src/domains/dispatch/worker-spawn.ts +14 -3
  231. package/src/domains/eval/metrics/evidence.ts +0 -116
  232. package/src/domains/eval/metrics/invariants.ts +1 -1
  233. package/src/domains/eval/runners/clio-run.ts +1 -10
  234. package/src/domains/eval/runners/external-command.ts +2 -29
  235. package/src/domains/eval/schema/suite.ts +0 -7
  236. package/src/domains/eval/suites/run.ts +1 -7
  237. package/src/domains/evidence/build.ts +112 -45
  238. package/src/domains/evidence/eval.ts +24 -7
  239. package/src/domains/evidence/index.ts +53 -0
  240. package/src/domains/evidence/ordering.ts +12 -0
  241. package/src/domains/evidence/run-trust.ts +221 -0
  242. package/src/domains/evidence/store.ts +46 -6
  243. package/src/domains/evidence/trust-status.ts +854 -0
  244. package/src/domains/evidence/types.ts +26 -0
  245. package/src/domains/memory/index.ts +22 -0
  246. package/src/domains/memory/operations.ts +58 -1
  247. package/src/domains/memory/promotion.ts +281 -0
  248. package/src/domains/memory/prompt-section.ts +25 -5
  249. package/src/domains/memory/proposal.ts +51 -7
  250. package/src/domains/memory/task-bank.ts +3 -2
  251. package/src/domains/memory/task-memory-handoff.ts +181 -24
  252. package/src/domains/memory/task-memory-policy.ts +3 -1
  253. package/src/domains/memory/types.ts +37 -0
  254. package/src/domains/memory/validate.ts +178 -0
  255. package/src/domains/middleware/memory-intervention.ts +38 -25
  256. package/src/domains/middleware/runtime.ts +6 -0
  257. package/src/domains/middleware/skills-reminder.ts +19 -4
  258. package/src/domains/middleware/stalled-turn.ts +208 -5
  259. package/src/domains/middleware/types.ts +10 -0
  260. package/src/domains/observability/contract.ts +6 -1
  261. package/src/domains/observability/cost.ts +20 -4
  262. package/src/domains/observability/extension.ts +2 -2
  263. package/src/domains/providers/index.ts +3 -0
  264. package/src/domains/providers/model-discovery.ts +9 -0
  265. package/src/domains/providers/runtime-resolution.ts +38 -1
  266. package/src/domains/providers/runtimes/common/probe-helpers.ts +97 -16
  267. package/src/domains/providers/types/context-window-slots.ts +18 -0
  268. package/src/domains/providers/types/runtime-descriptor.ts +3 -1
  269. package/src/domains/safety/autonomy.ts +1 -1
  270. package/src/domains/safety/call-target.ts +211 -14
  271. package/src/domains/safety/decision-presentation.ts +268 -0
  272. package/src/domains/safety/default-path-policy.ts +8 -0
  273. package/src/domains/safety/finish-contract.ts +4 -3
  274. package/src/domains/safety/policy-engine.ts +48 -6
  275. package/src/domains/safety/redaction.ts +73 -0
  276. package/src/domains/session/compaction/compact.ts +23 -1
  277. package/src/domains/session/compaction/cut-point.ts +2 -0
  278. package/src/domains/session/compaction/tokens.ts +16 -1
  279. package/src/domains/session/context-ledger.ts +12 -1
  280. package/src/domains/session/decision-board.ts +4 -0
  281. package/src/domains/session/entries.ts +110 -1
  282. package/src/domains/session/history.ts +68 -19
  283. package/src/domains/session/manager.ts +9 -2
  284. package/src/domains/session/migrations/index.ts +22 -3
  285. package/src/domains/session/usage.ts +24 -7
  286. package/src/engine/acp/event-mapper.ts +7 -0
  287. package/src/engine/acp/server.ts +32 -2
  288. package/src/engine/agent.ts +18 -1
  289. package/src/engine/apis/lmstudio.ts +25 -4
  290. package/src/engine/apis/openai-completions.ts +147 -22
  291. package/src/engine/claude/sdk-runtime.ts +8 -2
  292. package/src/engine/claude/tool-safety.ts +13 -0
  293. package/src/engine/loop-guard.ts +27 -3
  294. package/src/engine/session.ts +9 -3
  295. package/src/engine/worker-events.ts +4 -3
  296. package/src/engine/worker-runtime.ts +59 -54
  297. package/src/entry/orchestrator.ts +34 -5
  298. package/src/interactive/chat-loop-messages.ts +40 -6
  299. package/src/interactive/chat-loop.ts +13 -0
  300. package/src/interactive/chat-panel.ts +17 -1
  301. package/src/interactive/chat-renderer.ts +49 -24
  302. package/src/interactive/clio-editor.ts +44 -7
  303. package/src/interactive/context-meter.ts +10 -0
  304. package/src/interactive/context-overlay.ts +120 -7
  305. package/src/interactive/context-recall-command.ts +110 -0
  306. package/src/interactive/cost-overlay.ts +39 -8
  307. package/src/interactive/dispatch-board.ts +212 -35
  308. package/src/interactive/footer/widgets.ts +13 -0
  309. package/src/interactive/interactive-application.ts +6 -1
  310. package/src/interactive/interactive-input-runtime.ts +11 -1
  311. package/src/interactive/interactive-presentation.ts +11 -1
  312. package/src/interactive/interactive-slash-runtime.ts +37 -1
  313. package/src/interactive/memory-overlay.ts +89 -4
  314. package/src/interactive/model-session-replay.ts +21 -0
  315. package/src/interactive/overlay-ask-user-lifecycle.ts +1 -1
  316. package/src/interactive/overlay-frame.ts +5 -2
  317. package/src/interactive/overlay-general-openers.ts +46 -1
  318. package/src/interactive/overlay-key-routing.ts +41 -1
  319. package/src/interactive/overlay-lifecycle.ts +11 -4
  320. package/src/interactive/overlay-permission-lifecycle.ts +23 -8
  321. package/src/interactive/overlay-session-lifecycle.ts +8 -4
  322. package/src/interactive/overlay-transitions.ts +11 -0
  323. package/src/interactive/overlays/ask-user.ts +74 -30
  324. package/src/interactive/overlays/decisions.ts +3 -1
  325. package/src/interactive/permission-hint.ts +35 -0
  326. package/src/interactive/permission-overlay.ts +95 -45
  327. package/src/interactive/renderers/tool-execution.ts +37 -51
  328. package/src/interactive/session-last-turn.ts +8 -1
  329. package/src/interactive/session-transcript.ts +2 -2
  330. package/src/interactive/session-usage-reseed.ts +36 -10
  331. package/src/interactive/slash-commands.ts +31 -4
  332. package/src/interactive/status/summary.ts +5 -0
  333. package/src/interactive/status/types.ts +5 -0
  334. package/src/interactive/terminal-lease.ts +1 -0
  335. package/src/interactive/turn-context.ts +333 -110
  336. package/src/interactive/turn-middleware.ts +7 -6
  337. package/src/interactive/turn-runtime.ts +37 -8
  338. package/src/interactive/turn-state.ts +3 -0
  339. package/src/interactive/worker-progress.ts +440 -0
  340. package/src/interactive/worker-stream.ts +51 -110
  341. package/src/tools/agent-tools.ts +39 -7
  342. package/src/tools/ask-user.ts +21 -1
  343. package/src/tools/bash.ts +144 -82
  344. package/src/tools/builtin-tool-catalog.ts +11 -5
  345. package/src/tools/context/index.ts +107 -5
  346. package/src/tools/context/surface.ts +3 -2
  347. package/src/tools/core-bootstrap.ts +21 -0
  348. package/src/tools/dispatch-arguments.ts +8 -0
  349. package/src/tools/dispatch-event-text.ts +19 -0
  350. package/src/tools/dispatch-runner.ts +9 -7
  351. package/src/tools/dispatch.ts +24 -1
  352. package/src/tools/monitor.ts +43 -20
  353. package/src/tools/registry.ts +72 -10
  354. package/src/tools/result-disposition.ts +706 -0
  355. package/src/tools/result-shaping.ts +321 -20
  356. package/src/tools/safe-exec.ts +2 -0
  357. package/src/tools/verify/authoring.ts +1120 -0
  358. package/src/tools/verify/catalog.ts +346 -0
  359. package/src/tools/verify/index.ts +13 -3
  360. package/src/tools/verify/scripts.ts +135 -37
  361. package/src/tools/verify/surface.ts +9 -5
  362. package/src/tools/worker-evidence.ts +54 -12
  363. package/src/worker/spec-contract.ts +43 -3
  364. package/dist/chunk-J7CWMCQD.js +0 -255
  365. package/dist/chunk-T6YILFSB.js +0 -80
  366. package/dist/chunk-VAKQQHWR.js +0 -434
  367. package/dist/chunk-VPAYEGVX.js +0 -184
  368. package/dist/chunk-XBXAASKX.js +0 -18
  369. package/dist/memory-WFZMGYHX.js +0 -236
  370. package/src/domains/eval/metrics/chaos-stream.ts +0 -93
@@ -9,6 +9,14 @@ import {
9
9
  import type { SafeEventBus } from "../core/event-bus.js";
10
10
  import { rawDurationMs } from "../core/timers.js";
11
11
  import type { AgentAudience } from "../domains/agents/spec.js";
12
+ import {
13
+ cloneRunToolBudgetEnvelope,
14
+ formatBudgetPolicy,
15
+ formatBudgetReasons,
16
+ formatBudgetRequest,
17
+ formatEffectiveBudget,
18
+ type RunToolBudgetEnvelope,
19
+ } from "../domains/dispatch/budget-envelope.js";
12
20
  import type { DispatchSnapshot } from "../domains/dispatch/contract.js";
13
21
  import {
14
22
  type DispatchRequestOrigin,
@@ -25,7 +33,7 @@ import {
25
33
  } from "../domains/observability/index.js";
26
34
  import type { CostProvenance } from "../domains/providers/index.js";
27
35
  import { sanitizeCallTargetText } from "../domains/safety/call-target.js";
28
- import { type Component, truncateToWidth, visibleWidth } from "../engine/tui.js";
36
+ import { type Component, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../engine/tui.js";
29
37
  import { formatWorkerContextMeter } from "./context-meter.js";
30
38
  import { formatFooterTokens } from "./footer-panel.js";
31
39
  import {
@@ -42,6 +50,13 @@ import {
42
50
  screenTitle,
43
51
  spinnerFrame,
44
52
  } from "./theme/index.js";
53
+ import {
54
+ createWorkerProgressFold,
55
+ type WorkerAction,
56
+ type WorkerProgressFold,
57
+ type WorkerProgressSnapshot,
58
+ } from "./worker-progress.js";
59
+ import type { WorkerReceiptReader } from "./worker-stream.js";
45
60
 
46
61
  export type DispatchBoardStatus =
47
62
  | Extract<RunStatus, "running" | "completed" | "failed" | "stale" | "dead">
@@ -72,6 +87,8 @@ export interface DispatchBoardRow {
72
87
  wireModelId: string;
73
88
  /** Sanitized, one-line summary of the task assigned to this run. */
74
89
  taskSummary?: string;
90
+ /** Immutable budget admission provenance for this run. */
91
+ budget?: RunToolBudgetEnvelope;
75
92
  status: DispatchBoardStatus;
76
93
  elapsedMs: number;
77
94
  tokenCount: number;
@@ -94,10 +111,17 @@ export interface DispatchBoardRow {
94
111
  contextWindow?: number;
95
112
  /** Last assistant message's input+cacheRead+output: current context occupancy. */
96
113
  lastContextTokens?: number;
97
- /** Tool currently executing in the worker; null between calls. */
114
+ /** Tool currently executing in the worker; null between calls. Projected from `progress`. */
98
115
  currentTool?: string | null;
99
- /** Recently finished tools, newest first, bounded. */
116
+ /** Recently finished tools, newest first, bounded. Projected from `progress`. */
100
117
  recentTools?: ReadonlyArray<string>;
118
+ /**
119
+ * The canonical worker-progress projection for this run: the bounded answer
120
+ * tail, the phase, and the redacted action descriptors. The same projection
121
+ * the transcript's worker block reads, so opening a run here shows what the
122
+ * worker is doing rather than a richer spinner.
123
+ */
124
+ progress?: WorkerProgressSnapshot;
101
125
  /**
102
126
  * Id of the receipt sealed for this run (`receipts/<runId>.json`, the id
103
127
  * `clio-coder trace` takes). Set only once a terminal dispatch event has
@@ -126,7 +150,10 @@ export function isDispatchBoardRowCancellable(row: DispatchBoardRow): boolean {
126
150
  }
127
151
 
128
152
  interface DispatchBoardEntry
129
- extends Omit<DispatchBoardRow, "elapsedMs" | "recentTools" | "lastContextTokens" | "currentTool" | "retry"> {
153
+ extends Omit<
154
+ DispatchBoardRow,
155
+ "elapsedMs" | "recentTools" | "lastContextTokens" | "currentTool" | "retry" | "progress"
156
+ > {
130
157
  sequence: number;
131
158
  enqueuedAtMs: number;
132
159
  startedAtMs: number | null;
@@ -139,15 +166,19 @@ interface DispatchBoardEntry
139
166
  finishedAtMs: number | null;
140
167
  durationMs: number | null;
141
168
  lastContextTokens: number;
142
- currentTool: string | null;
143
- recentTools: string[];
169
+ /**
170
+ * The one fold that reads this run's worker events. Tool activity and the
171
+ * answer tail are its output, so the board no longer interprets the stream
172
+ * alongside the transcript.
173
+ */
174
+ progress: WorkerProgressFold;
144
175
  retry?: DispatchRetryPresentation;
145
176
  }
146
177
 
147
- /** Bound on the recent-tool trail rendered on a card. */
148
- const RECENT_TOOL_TRAIL_LIMIT = 4;
149
178
  /** Keep raw task text out of long-lived TUI rows and bound hostile/user-sized input. */
150
179
  const TASK_SUMMARY_MAX_WIDTH = 240;
180
+ /** Rows of worker prose an expanded card shows before it defers to `/view`. */
181
+ const WORKER_PROGRESS_CARD_ROWS = 6;
151
182
 
152
183
  interface WorkerEventShape {
153
184
  type?: unknown;
@@ -433,11 +464,92 @@ function retryCountdown(retry: DispatchRetryPresentation, now = Date.now()): str
433
464
  return remainingMs === 0 ? "now" : `in ${formatCompactMs(remainingMs)}`;
434
465
  }
435
466
 
467
+ /**
468
+ * One action as a phrase: the tool name, then the verb and object of its
469
+ * redacted descriptor. The descriptor was bounded and scrubbed at the worker
470
+ * seam, so this composes text and never inspects an argument.
471
+ */
472
+ function actionPhrase(action: WorkerAction): string {
473
+ const descriptor = action.descriptor;
474
+ if (descriptor === undefined) return action.tool;
475
+ const object = descriptor.object === undefined ? "" : ` ${descriptor.object}${descriptor.truncated ? "…" : ""}`;
476
+ return `${action.tool} ${descriptor.verb}${object}`;
477
+ }
478
+
479
+ /** The phase word an expanded card names, or null for a phase that says nothing new. */
480
+ function progressPhaseUnit(theme: ClioTheme, progress: WorkerProgressSnapshot): string | null {
481
+ switch (progress.phase) {
482
+ case "thinking":
483
+ // The phase only. Reasoning content never reaches an operator surface.
484
+ return theme.fg("reason", `${GLYPH.phaseThinking} thinking`);
485
+ case "writing":
486
+ return theme.fg("accent", `${GLYPH.phaseWriting} writing`);
487
+ case "tool":
488
+ return theme.fg("action", `${GLYPH.phaseTool} tool`);
489
+ case "waiting":
490
+ return theme.fg("info", `${GLYPH.phaseWaiting} waiting`);
491
+ case "settled":
492
+ case "starting":
493
+ return null;
494
+ }
495
+ }
496
+
497
+ /** The `doing` row: the phase, then the running call or the most recent one, with its object. */
498
+ function progressActionLine(theme: ClioTheme, progress: WorkerProgressSnapshot, contentWidth: number): string | null {
499
+ const phase = progressPhaseUnit(theme, progress);
500
+ const current = progress.currentAction;
501
+ const recent = progress.recentActions[0];
502
+ const units: string[] = [];
503
+ if (phase !== null) units.push(phase);
504
+ if (current !== null) units.push(theme.fg("muted", actionPhrase(current)));
505
+ else if (recent !== undefined) units.push(theme.fg("dim", `last ${actionPhrase(recent)}`));
506
+ if (units.length === 0) return null;
507
+ return cardUnitsLine(theme, "doing", units, contentWidth);
508
+ }
509
+
510
+ /**
511
+ * The `answer` block: the newest rows of the worker's bounded prose on a rail,
512
+ * then what is not shown and where to read it. Wrapping happens before the row
513
+ * cap, so the block is at most {@link WORKER_PROGRESS_CARD_ROWS} rows tall
514
+ * whatever the terminal width, and a streaming answer cannot make the card grow
515
+ * under the operator.
516
+ */
517
+ function progressAnswerLines(
518
+ theme: ClioTheme,
519
+ progress: WorkerProgressSnapshot,
520
+ runId: string,
521
+ contentWidth: number,
522
+ ): string[] {
523
+ if (progress.tailText.length === 0) return [];
524
+ const gutter = CARD_KV_KEY_WIDTH + 1;
525
+ const railWidth = Math.max(1, contentWidth - gutter - 2);
526
+ const wrapped: string[] = [];
527
+ for (const line of progress.tailText.split("\n")) {
528
+ for (const row of wrapTextWithAnsi(sanitizeCallTargetText(line), railWidth)) wrapped.push(row);
529
+ }
530
+ const shown = wrapped.slice(Math.max(0, wrapped.length - WORKER_PROGRESS_CARD_ROWS));
531
+ const hiddenRows = wrapped.length - shown.length;
532
+ const rail = theme.fg("dim", `${GLYPH.rail} `);
533
+ const body = shown.map((row) => `${rail}${theme.fg("muted", row)}`);
534
+ const hiddenLines = progress.droppedLines + hiddenRows;
535
+ if (hiddenLines > 0 || progress.droppedBytes > 0) {
536
+ const facts = [
537
+ ...(hiddenLines > 0 ? [`${hiddenLines} more line${hiddenLines === 1 ? "" : "s"}`] : []),
538
+ ...(progress.droppedBytes > 0 ? [`${progress.droppedBytes} bytes outran the view`] : []),
539
+ `/view dispatch:${runId}`,
540
+ ];
541
+ body.push(`${rail}${theme.fg("dim", truncateToWidth(facts.join(" · "), railWidth, "…", false))}`);
542
+ }
543
+ // The key labels the block once and the rest hangs under it, which is the
544
+ // card's key-value grammar applied to a body rather than to one value.
545
+ return body.map((row, index) => `${index === 0 ? cardKvKey(theme, "answer") : " ".repeat(gutter)}${row}`);
546
+ }
547
+
436
548
  export function renderDispatchCard(
437
549
  row: DispatchBoardRow,
438
550
  width: number,
439
551
  evidence?: RunEvidencePresentation,
440
- options: { selected?: boolean } = {},
552
+ options: { selected?: boolean; expanded?: boolean } = {},
441
553
  ): string[] {
442
554
  const theme = clioTheme();
443
555
  const contentWidth = Math.max(0, width - 4);
@@ -506,6 +618,22 @@ export function renderDispatchCard(
506
618
  ? [truncateToWidth(`${cardKvKey(theme, "task")}${theme.fg("muted", row.taskSummary)}`, contentWidth, "…", false)]
507
619
  : []),
508
620
  cardUnitsLine(theme, "status", statusUnits, contentWidth),
621
+ ...(row.budget !== undefined
622
+ ? [
623
+ truncateToWidth(
624
+ `${cardKvKey(theme, "policy")}${theme.fg("muted", `${formatBudgetPolicy(row.budget)}; requested ${formatBudgetRequest(row.budget)}`)}`,
625
+ contentWidth,
626
+ "…",
627
+ false,
628
+ ),
629
+ truncateToWidth(
630
+ `${cardKvKey(theme, "budget")}${theme.fg("muted", `${formatEffectiveBudget(row.budget)}; reason ${formatBudgetReasons(row.budget)}`)}`,
631
+ contentWidth,
632
+ "…",
633
+ false,
634
+ ),
635
+ ]
636
+ : []),
509
637
  cardUnitsLine(theme, "telemetry", [up, down, total, ...(contextUnit !== null ? [contextUnit] : [])], contentWidth),
510
638
  ];
511
639
  if (row.retry) {
@@ -533,6 +661,13 @@ export function renderDispatchCard(
533
661
  ];
534
662
  bodyLines.push(cardUnitsLine(theme, "tools", toolUnits, contentWidth));
535
663
  }
664
+ // Live worker progress is expanded detail, never a default row: five running
665
+ // scouts must stay five compact cards until the operator opens one.
666
+ if (options.expanded === true && row.progress) {
667
+ const doing = progressActionLine(theme, row.progress, contentWidth);
668
+ if (doing !== null) bodyLines.push(doing);
669
+ bodyLines.push(...progressAnswerLines(theme, row.progress, row.runId, contentWidth));
670
+ }
536
671
  if (row.steerAcknowledgement) {
537
672
  bodyLines.push(
538
673
  cardUnitsLine(
@@ -610,6 +745,8 @@ export function formatDispatchBoardLines(
610
745
  width = 76,
611
746
  observability?: ObservabilitySnapshot,
612
747
  selectedRunId?: string | null,
748
+ /** Whether the selected row renders its worker-progress detail. Off by default. */
749
+ detailExpanded = false,
613
750
  ): string[] {
614
751
  if (rows.length === 0) {
615
752
  const theme = clioTheme();
@@ -623,6 +760,7 @@ export function formatDispatchBoardLines(
623
760
  const cards = rows.map((row) =>
624
761
  renderDispatchCard(row, width, deriveRunEvidenceState(observability, row.runId), {
625
762
  selected: row.runId === selectedRunId,
763
+ expanded: detailExpanded && row.runId === selectedRunId,
626
764
  }),
627
765
  );
628
766
  const body: string[] = [];
@@ -648,6 +786,9 @@ export interface DispatchBoardView extends Component {
648
786
  selectPrevious(): void;
649
787
  selectNext(): void;
650
788
  resetSelection(): void;
789
+ /** Open or close the selected row's worker-progress detail. */
790
+ toggleDetail(): void;
791
+ detailExpanded(): boolean;
651
792
  }
652
793
 
653
794
  export function createDispatchBoardView(
@@ -655,6 +796,9 @@ export function createDispatchBoardView(
655
796
  observability: () => ObservabilitySnapshot | undefined,
656
797
  ): DispatchBoardView {
657
798
  let selectedRunId: string | null = null;
799
+ // Detail is a property of the operator's attention, not of a run, so it
800
+ // follows the cursor rather than pinning to the row it was opened on.
801
+ let expanded = false;
658
802
 
659
803
  const normalizeSelection = (currentRows: ReadonlyArray<DispatchBoardRow>): DispatchBoardRow | null => {
660
804
  if (currentRows.length === 0) {
@@ -679,7 +823,7 @@ export function createDispatchBoardView(
679
823
  render(width: number): string[] {
680
824
  const currentRows = rows();
681
825
  normalizeSelection(currentRows);
682
- return formatDispatchBoardLines(currentRows, Math.max(1, width), observability(), selectedRunId);
826
+ return formatDispatchBoardLines(currentRows, Math.max(1, width), observability(), selectedRunId, expanded);
683
827
  },
684
828
  selectedRow(): DispatchBoardRow | null {
685
829
  return normalizeSelection(rows());
@@ -692,8 +836,15 @@ export function createDispatchBoardView(
692
836
  },
693
837
  resetSelection(): void {
694
838
  selectedRunId = null;
839
+ expanded = false;
695
840
  normalizeSelection(rows());
696
841
  },
842
+ toggleDetail(): void {
843
+ expanded = !expanded;
844
+ },
845
+ detailExpanded(): boolean {
846
+ return expanded;
847
+ },
697
848
  invalidate(): void {},
698
849
  };
699
850
  }
@@ -879,6 +1030,7 @@ function readRunningSnapshot(snapshot: DispatchSnapshot): Map<
879
1030
  costUsd: number;
880
1031
  costProvenance: CostProvenance;
881
1032
  outcomePhase: string;
1033
+ budget?: RunToolBudgetEnvelope;
882
1034
  }
883
1035
  > {
884
1036
  const running = new Map<
@@ -890,12 +1042,14 @@ function readRunningSnapshot(snapshot: DispatchSnapshot): Map<
890
1042
  costUsd: number;
891
1043
  costProvenance: CostProvenance;
892
1044
  outcomePhase: string;
1045
+ budget?: RunToolBudgetEnvelope;
893
1046
  }
894
1047
  >();
895
1048
  try {
896
1049
  for (const value of snapshot.running) {
897
1050
  const runId = parseRunId(value.runId);
898
1051
  if (!runId) continue;
1052
+ const budget = cloneRunToolBudgetEnvelope(value.budget);
899
1053
  running.set(runId, {
900
1054
  inputTokens: parseFiniteNumberOrZero(value.tokens.input),
901
1055
  outputTokens: parseFiniteNumberOrZero(value.tokens.output),
@@ -903,6 +1057,7 @@ function readRunningSnapshot(snapshot: DispatchSnapshot): Map<
903
1057
  costUsd: parseFiniteNumberOrZero(value.costUsd),
904
1058
  costProvenance: value.costProvenance ?? "unknown",
905
1059
  outcomePhase: value.outcomePhase,
1060
+ ...(budget !== undefined ? { budget } : {}),
906
1061
  });
907
1062
  }
908
1063
  } catch {
@@ -920,7 +1075,9 @@ function resolveElapsedMs(entry: DispatchBoardEntry, now: number): number {
920
1075
 
921
1076
  function toRow(entry: DispatchBoardEntry, now: number): DispatchBoardRow {
922
1077
  const retry = entry.retry;
1078
+ const progress = entry.progress.snapshot();
923
1079
  return {
1080
+ progress,
924
1081
  runId: entry.runId,
925
1082
  agentId: entry.agentId,
926
1083
  ...(entry.agentAudience !== undefined ? { agentAudience: entry.agentAudience } : {}),
@@ -930,6 +1087,7 @@ function toRow(entry: DispatchBoardEntry, now: number): DispatchBoardRow {
930
1087
  targetId: entry.targetId,
931
1088
  wireModelId: entry.wireModelId,
932
1089
  ...(entry.taskSummary !== undefined ? { taskSummary: entry.taskSummary } : {}),
1090
+ ...(entry.budget !== undefined ? { budget: entry.budget } : {}),
933
1091
  status: retry ? "retrying" : entry.status,
934
1092
  elapsedMs: resolveElapsedMs(entry, now),
935
1093
  tokenCount: entry.tokenCount,
@@ -946,8 +1104,10 @@ function toRow(entry: DispatchBoardEntry, now: number): DispatchBoardRow {
946
1104
  ...(entry.contextWindow !== undefined ? { contextWindow: entry.contextWindow } : {}),
947
1105
  lastContextTokens: entry.lastContextTokens,
948
1106
  ...(entry.receiptId !== undefined ? { receiptId: entry.receiptId } : {}),
949
- currentTool: retry ? null : entry.currentTool,
950
- recentTools: [...entry.recentTools],
1107
+ // Both tool fields are views of the one projection, so the compact card and
1108
+ // the expanded detail can never disagree about what is running.
1109
+ currentTool: retry ? null : (progress.currentAction?.tool ?? null),
1110
+ recentTools: progress.recentActions.map((action) => action.tool),
951
1111
  ...(retry ? { retry: { ...retry } } : {}),
952
1112
  ...(entry.steerAcknowledgement ? { steerAcknowledgement: { ...entry.steerAcknowledgement } } : {}),
953
1113
  };
@@ -980,6 +1140,12 @@ function pruneEntries(entries: Map<string, DispatchBoardEntry>): void {
980
1140
  export function createDispatchBoardStore(
981
1141
  bus: SafeEventBus,
982
1142
  snapshot?: () => DispatchSnapshot,
1143
+ /**
1144
+ * Sealed terminal facts for a finished run. Settlement replaces the
1145
+ * provisional live tail with the receipt's answer where one can be read; the
1146
+ * run's own durable message stands in where it cannot.
1147
+ */
1148
+ readReceipt?: WorkerReceiptReader,
983
1149
  ): {
984
1150
  rows(): ReadonlyArray<DispatchBoardRow>;
985
1151
  activeRows(): ReadonlyArray<DispatchBoardRow>;
@@ -990,6 +1156,16 @@ export function createDispatchBoardStore(
990
1156
  let nextSequence = 0;
991
1157
  let reconciledAtMs = Date.now();
992
1158
 
1159
+ /**
1160
+ * Seal a run's projection on the sealed receipt's answer. Only for the
1161
+ * terminal bus events: the dispatch domain writes `receipts/<runId>.json`
1162
+ * before it publishes one, so this is the first moment the file exists.
1163
+ */
1164
+ const settleFromReceipt = (entry: DispatchBoardEntry): void => {
1165
+ const text = readReceipt?.(entry.runId)?.text;
1166
+ entry.progress.settle(typeof text === "string" && text.trim().length > 0 ? text : undefined);
1167
+ };
1168
+
993
1169
  // Payloads arrive typed off the bus, but the board keeps its runtime
994
1170
  // parsing (parse* helpers) because events are not validated at runtime.
995
1171
  const upsertBase = (
@@ -1007,6 +1183,7 @@ export function createDispatchBoardStore(
1007
1183
  const rerouteCount = parsePositiveInt(raw.rerouteCount) ?? previous?.rerouteCount;
1008
1184
  const contextWindow = parsePositiveInt(raw.contextWindow) ?? previous?.contextWindow;
1009
1185
  const taskSummary = parseTaskSummary(raw, previous?.taskSummary);
1186
+ const budget = cloneRunToolBudgetEnvelope(raw.budget) ?? previous?.budget;
1010
1187
  const entry: DispatchBoardEntry = {
1011
1188
  runId,
1012
1189
  agentId: parseText(raw.agentId, previous?.agentId ?? "-"),
@@ -1017,6 +1194,7 @@ export function createDispatchBoardStore(
1017
1194
  targetId: parseText(raw.targetId, previous?.targetId ?? "-"),
1018
1195
  wireModelId: parseText(raw.wireModelId, previous?.wireModelId ?? "-"),
1019
1196
  ...(taskSummary !== undefined ? { taskSummary } : {}),
1197
+ ...(budget !== undefined ? { budget } : {}),
1020
1198
  status,
1021
1199
  tokenCount: previous?.tokenCount ?? 0,
1022
1200
  costUsd: previous?.costUsd ?? 0,
@@ -1037,8 +1215,7 @@ export function createDispatchBoardStore(
1037
1215
  ...(contextWindow !== undefined ? { contextWindow } : {}),
1038
1216
  lastContextTokens: previous?.lastContextTokens ?? 0,
1039
1217
  ...(previous?.receiptId !== undefined ? { receiptId: previous.receiptId } : {}),
1040
- currentTool: previous?.currentTool ?? null,
1041
- recentTools: previous?.recentTools ?? [],
1218
+ progress: previous?.progress ?? createWorkerProgressFold(),
1042
1219
  ...(previous?.retry ? { retry: { ...previous.retry } } : {}),
1043
1220
  ...(previous?.steerAcknowledgement ? { steerAcknowledgement: { ...previous.steerAcknowledgement } } : {}),
1044
1221
  };
@@ -1074,7 +1251,7 @@ export function createDispatchBoardStore(
1074
1251
  entry.costUsd = parseFiniteNumber(payload.costUsd, entry.costUsd);
1075
1252
  entry.costProvenance = payload.costProvenance ?? "unknown";
1076
1253
  entry.outcomeDetail = null;
1077
- entry.currentTool = null;
1254
+ settleFromReceipt(entry);
1078
1255
  // A terminal dispatch event is published only after the run's receipt is
1079
1256
  // sealed at receipts/<runId>.json, so the run id is the receipt id here.
1080
1257
  entry.receiptId = entry.runId;
@@ -1102,7 +1279,7 @@ export function createDispatchBoardStore(
1102
1279
  entry.costUsd = parseFiniteNumber(payload.costUsd, entry.costUsd);
1103
1280
  entry.costProvenance = payload.costProvenance ?? "unknown";
1104
1281
  entry.outcomeDetail = resolveFailureDetail(payload, entry.outcomeDetail);
1105
- entry.currentTool = null;
1282
+ settleFromReceipt(entry);
1106
1283
  // A denied retry never reached a run, so no receipt was sealed for it;
1107
1284
  // every other failure finalized through recordReceipt like a success.
1108
1285
  if (payload.reason !== "retry_denied") entry.receiptId = entry.runId;
@@ -1126,7 +1303,7 @@ export function createDispatchBoardStore(
1126
1303
  // wind down, so the history row can become terminal immediately.
1127
1304
  entry.status = "aborted";
1128
1305
  entry.finishedAtMs = Date.now();
1129
- entry.currentTool = null;
1306
+ entry.progress.settle();
1130
1307
  } else {
1131
1308
  if (isTerminalStatus(entry.status) && !wasRetrying) return;
1132
1309
  entry.status = "cancelling";
@@ -1148,7 +1325,10 @@ export function createDispatchBoardStore(
1148
1325
  entry.status = status;
1149
1326
  if (status === "dead") {
1150
1327
  entry.finishedAtMs ??= Date.now();
1151
- entry.currentTool = null;
1328
+ // A worker-side terminal signal arrives before any receipt is
1329
+ // sealed, so the run's own last durable message is the best truth
1330
+ // there is; the terminal bus event replaces it with the sealed one.
1331
+ entry.progress.settle();
1152
1332
  delete entry.retry;
1153
1333
  }
1154
1334
  return;
@@ -1159,7 +1339,9 @@ export function createDispatchBoardStore(
1159
1339
  entry.failoverHops = (entry.failoverHops ?? 0) + 1;
1160
1340
  entry.status = "running";
1161
1341
  entry.finishedAtMs = null;
1162
- entry.currentTool = null;
1342
+ // The projection keeps the tail the operator is reading and drops the
1343
+ // finished attempt's live state, exactly as the transcript block does.
1344
+ entry.progress.restart();
1163
1345
  delete entry.retry;
1164
1346
  return;
1165
1347
  }
@@ -1183,20 +1365,10 @@ export function createDispatchBoardStore(
1183
1365
  entry.ttftMs = Math.round(performance.now() - entry.startedAtClockMs);
1184
1366
  }
1185
1367
  }
1186
- if (type === "clio_tool_start") {
1187
- const payload = (workerEvent as { payload?: { tool?: unknown } }).payload;
1188
- const tool = parseNonEmptyString(payload?.tool);
1189
- if (tool !== undefined) entry.currentTool = tool;
1190
- }
1191
- if (type === "clio_tool_finish") {
1192
- const payload = (workerEvent as { payload?: { tool?: unknown } }).payload;
1193
- const tool = parseNonEmptyString(payload?.tool);
1194
- entry.currentTool = null;
1195
- if (tool !== undefined) {
1196
- entry.recentTools.unshift(tool);
1197
- if (entry.recentTools.length > RECENT_TOOL_TRAIL_LIMIT) entry.recentTools.length = RECENT_TOOL_TRAIL_LIMIT;
1198
- }
1199
- }
1368
+ // The one fold that reads worker prose and tool activity. Everything the
1369
+ // board says about what a worker is saying or touching comes from here,
1370
+ // so the board and the transcript block cannot tell two stories.
1371
+ entry.progress.observe(payload.event);
1200
1372
  if (type === "clio_steer_received") {
1201
1373
  const steerPayload = (workerEvent as { payload?: { chars?: unknown } }).payload;
1202
1374
  entry.steerAcknowledgement = {
@@ -1221,7 +1393,10 @@ export function createDispatchBoardStore(
1221
1393
  if (!status) return;
1222
1394
  entry.status = status;
1223
1395
  entry.finishedAtMs ??= Date.now();
1224
- entry.currentTool = null;
1396
+ // The worker says it is done before the domain seals its receipt. Settle
1397
+ // on the durable message now so the card stops reading as live; the
1398
+ // terminal bus event seals it on the receipt shortly after.
1399
+ entry.progress.settle();
1225
1400
  delete entry.retry;
1226
1401
  }
1227
1402
  }),
@@ -1270,7 +1445,8 @@ export function createDispatchBoardStore(
1270
1445
  now,
1271
1446
  );
1272
1447
  if (entry) {
1273
- entry.currentTool = null;
1448
+ // A waiting retry has no live worker; the row projection already
1449
+ // renders a retrying row with no current tool.
1274
1450
  entry.retry = { ...projection.retry };
1275
1451
  if (entry.taskSummary === undefined && projection.taskSummary !== undefined) {
1276
1452
  entry.taskSummary = projection.taskSummary;
@@ -1286,6 +1462,7 @@ export function createDispatchBoardStore(
1286
1462
  entry.tokenCount = live.tokenCount;
1287
1463
  entry.costUsd = live.costUsd;
1288
1464
  entry.costProvenance = live.costProvenance;
1465
+ if (live.budget !== undefined) entry.budget = live.budget;
1289
1466
  if (live.outcomePhase === "aborting") {
1290
1467
  if (entry.status !== "completed" && entry.status !== "aborted") {
1291
1468
  entry.status = "cancelling";
@@ -1,4 +1,5 @@
1
1
  import type { OutputVerbosity } from "../../core/defaults.js";
2
+ import { responseModelIdObservationLabel } from "../../core/response-model-id.js";
2
3
  import { ToolNames } from "../../core/tool-names.js";
3
4
  import {
4
5
  type CostAggregate,
@@ -27,6 +28,7 @@ import {
27
28
  type TurnSummary,
28
29
  } from "../status/index.js";
29
30
  import {
31
+ abbreviateModelId,
30
32
  type ClioTheme,
31
33
  type ClioToken,
32
34
  clioTheme,
@@ -724,6 +726,17 @@ export function formatLastTurn(theme: ClioTheme, summary: TurnSummary): string {
724
726
  }
725
727
  if (summary.watchdogPeak >= 2) parts.push(theme.fg("warning", "slow"));
726
728
  if (summary.truncated) parts.push(theme.fg("warning", "trunc"));
729
+ const observation = summary.responseModelIdObservation;
730
+ const observedId =
731
+ observation.state === "reported"
732
+ ? observation.reportedModelId
733
+ : observation.state === "legacy-difference-only"
734
+ ? observation.differingModelId
735
+ : null;
736
+ const observationText = `response model id observation ${responseModelIdObservationLabel(observation)}${
737
+ observedId === null ? "" : ` ${abbreviateModelId(observedId)}`
738
+ }`;
739
+ parts.push(theme.fg("warning", observationText));
727
740
  return parts.join(theme.fg("dim", " · "));
728
741
  }
729
742
 
@@ -482,10 +482,16 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
482
482
  * here for the same reason as the leader flag above.
483
483
  */
484
484
  let shutdownArmed = false;
485
+ /**
486
+ * Assigned after the presentation because it renders into it. The composer
487
+ * rail reads it through the optional chain before then as "no prompt".
488
+ */
489
+ let overlayLifecycle: OverlayLifecycleController;
485
490
  const presentation = createInteractivePresentation({
486
491
  bus: deps.bus,
487
492
  getLeaderArmed: () => leaderArmed,
488
493
  getShutdownArmed: () => shutdownArmed,
494
+ isAwaitingApproval: () => overlayLifecycle?.getState() === "permission-confirm",
489
495
  resolveVisibleEventSequence: (event) => visibleEventIngress.get(event)?.traceSequence ?? null,
490
496
  resolveStreamIngress: (event) => visibleEventIngress.get(event) ?? null,
491
497
  commitFrame: (reason) => shell.commitCurrentFrame(reason === "teardown" ? 300 : 30_000),
@@ -668,7 +674,6 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
668
674
  });
669
675
  editor.onSubmit = editorSubmit.submitEditorText;
670
676
 
671
- let overlayLifecycle: OverlayLifecycleController;
672
677
  const interactiveTickers = createInteractiveTickers({
673
678
  tui,
674
679
  dispatchBoardStore,
@@ -76,13 +76,14 @@ export interface InteractiveInputRuntimeDeps {
76
76
  dispatchBoard: {
77
77
  selectPrevious(): void;
78
78
  selectNext(): void;
79
+ toggleDetail(): void;
79
80
  };
80
81
  steerSelectedDispatch: () => void;
81
82
  cancelSelectedDispatch: () => void;
82
83
  cancelActiveEditorBash: () => boolean;
83
84
  isStreaming: () => boolean;
84
85
  cancelActiveRun: () => void;
85
- editor: { getText(): string; setText(text: string): void };
86
+ editor: { getText(): string; setText(text: string): void; handleInput?(data: string): void };
86
87
  editorSubmit: {
87
88
  openExternalEditorForInput(): void;
88
89
  queueFollowUpFromEditor(): void;
@@ -211,6 +212,11 @@ export function createInteractiveInputRuntime(deps: InteractiveInputRuntimeDeps)
211
212
  cancelPermission: () => deps.overlay.closeOverlay(),
212
213
  confirmPermission: () => deps.overlay.confirmPermission(),
213
214
  stopTurnFromPermission: () => deps.overlay.stopTurnFromPermission(),
215
+ composerHasDraft: () => deps.editor.getText().length > 0,
216
+ editDraft: (data) => {
217
+ deps.editor.handleInput?.(data);
218
+ deps.requestRender();
219
+ },
214
220
  closeOverlay: () => deps.overlay.closeOverlay(),
215
221
  selectPreviousDispatch: () => {
216
222
  deps.dispatchBoard.selectPrevious();
@@ -220,6 +226,10 @@ export function createInteractiveInputRuntime(deps: InteractiveInputRuntimeDeps)
220
226
  deps.dispatchBoard.selectNext();
221
227
  deps.requestRender();
222
228
  },
229
+ toggleSelectedDispatchDetail: () => {
230
+ deps.dispatchBoard.toggleDetail();
231
+ deps.requestRender();
232
+ },
223
233
  steerSelectedDispatch: deps.steerSelectedDispatch,
224
234
  cancelSelectedDispatch: deps.cancelSelectedDispatch,
225
235
  cancelAskUser: () => deps.overlay.cancelAskUser(),
@@ -39,6 +39,7 @@ import type { SmoothStreamingMode } from "./stream-pacer.js";
39
39
  import { processAutoPacingAllowed, resolveSmoothStreamingMode } from "./stream-pacing-policy.js";
40
40
  import { formatTargetLabel } from "./theme/index.js";
41
41
  import { createWelcomeDashboard, type WelcomeDashboardComponent } from "./welcome-dashboard.js";
42
+ import { readWorkerReceiptFacts } from "./worker-receipts.js";
42
43
  import type { WorkspaceFacts } from "./workspace-facts.js";
43
44
 
44
45
  export interface PresentationTickerHandle {
@@ -96,6 +97,8 @@ export interface InteractivePresentationDeps {
96
97
  getLeaderArmed?: () => boolean;
97
98
  /** Whether a Ctrl+C armed the double tap and its window is still open. */
98
99
  getShutdownArmed?: () => boolean;
100
+ /** Whether a permission prompt owns the keyboard, for the composer's CONFIRM rail. */
101
+ isAwaitingApproval?: () => boolean;
99
102
  getCwd?: () => string;
100
103
  resolveVisibleEventSequence?: (event: ChatLoopEvent) => number | null;
101
104
  resolveStreamIngress?: (
@@ -242,7 +245,13 @@ export function createInteractivePresentation(deps: InteractivePresentationDeps)
242
245
  bus: deps.bus,
243
246
  ...(deps.getSettings ? { getSettings: deps.getSettings } : {}),
244
247
  });
245
- const dispatchBoardStore = factories.createDispatchBoardStore(deps.bus, () => deps.dispatch.snapshot());
248
+ // The board settles a finished run on the same sealed receipt the transcript
249
+ // block reads, so the two surfaces agree on a worker's terminal answer.
250
+ const dispatchBoardStore = factories.createDispatchBoardStore(
251
+ deps.bus,
252
+ () => deps.dispatch.snapshot(),
253
+ (runId) => readWorkerReceiptFacts(runId),
254
+ );
246
255
  const contextActivityStore = factories.createContextActivityStore(deps.bus);
247
256
 
248
257
  const footerToolCounts = new Map<string, number>();
@@ -408,6 +417,7 @@ export function createInteractivePresentation(deps: InteractivePresentationDeps)
408
417
  );
409
418
  },
410
419
  isStreaming: () => deps.chat.isStreaming(),
420
+ ...(deps.isAwaitingApproval ? { isAwaitingApproval: deps.isAwaitingApproval } : {}),
411
421
  willEnterSteer: (text) => willEnterSteerActiveWork(deps, text),
412
422
  getSubmitKeyLabel: () => formatKeyLabel(keybindings.getKeys("tui.input.submit")[0], "Enter"),
413
423
  getNewlineKeyLabel: () => formatKeyLabel(keybindings.getKeys("tui.input.newLine")[0], "Shift+Enter"),