@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
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { performance } from "node:perf_hooks";
9
9
  import type { ClioSettings } from "../core/config.js";
10
+ import { attributedModelId } from "../core/response-model-id.js";
10
11
  import type { MiddlewareToolChoiceControl } from "../domains/middleware/index.js";
11
12
  import type { ObservabilityContract } from "../domains/observability/contract.js";
12
13
  import {
@@ -15,7 +16,7 @@ import {
15
16
  type ProvidersContract,
16
17
  refineRuntimeTargetWithModelHints,
17
18
  resolveRuntimeTarget,
18
- runtimeResolutionWarnings,
19
+ runtimeResolutionWarningsBesideThinkingNotice,
19
20
  type TargetStatus,
20
21
  targetRequiresAuth,
21
22
  } from "../domains/providers/index.js";
@@ -199,7 +200,12 @@ export function createTurnRuntime(deps: TurnRuntimeDeps): TurnRuntime {
199
200
  failure?.code ?? "admission-failed",
200
201
  );
201
202
  }
202
- for (const message of runtimeResolutionWarnings(resolved.diagnostics)) {
203
+ // emitThinkingClampNotice prints the one combined thinking line when the
204
+ // dial takes effect; the thinking diagnostics are its two halves.
205
+ for (const message of runtimeResolutionWarningsBesideThinkingNotice(
206
+ resolved.diagnostics,
207
+ resolved.target.modelRuntime.thinking.notice,
208
+ )) {
203
209
  const key = `${targetId}|${wireModelId}|${message}`;
204
210
  if (announcedResolutionWarnings.has(key)) continue;
205
211
  announcedResolutionWarnings.add(key);
@@ -229,6 +235,7 @@ export function createTurnRuntime(deps: TurnRuntimeDeps): TurnRuntime {
229
235
  */
230
236
  const TARGET_PROBE_TTL_MS = 5 * 60 * 1000;
231
237
  let lastTargetProbe: { key: string; at: number } | null = null;
238
+ const targetProbesInFlight = new Map<string, Promise<void>>();
232
239
  const ensureLiveCapabilitiesForSelectedModel = async (): Promise<void> => {
233
240
  const settings = deps.getSettings();
234
241
  const targetId = settings.orchestrator.target?.trim();
@@ -239,16 +246,29 @@ export function createTurnRuntime(deps: TurnRuntimeDeps): TurnRuntime {
239
246
  const runtimeDesc = deps.providers.getRuntime(target.runtime);
240
247
  if (!runtimeDesc || runtimeDesc.tier === "cloud") return;
241
248
  const key = `${targetId}|${wireModelId}`;
249
+ const inFlight = targetProbesInFlight.get(key);
250
+ if (inFlight) {
251
+ await inFlight;
252
+ return;
253
+ }
242
254
  const now = Date.now();
243
255
  if (lastTargetProbe?.key === key && now - lastTargetProbe.at < TARGET_PROBE_TTL_MS) return;
244
256
  lastTargetProbe = { key, at: now };
245
- let status: Awaited<ReturnType<ProvidersContract["probeTarget"]>> = null;
257
+ const probe = (async (): Promise<void> => {
258
+ let status: Awaited<ReturnType<ProvidersContract["probeTarget"]>> = null;
259
+ try {
260
+ status = await deps.providers.probeTarget(targetId);
261
+ } catch {
262
+ // Fall back to the last known target state.
263
+ }
264
+ announceColdModel(status, targetId, wireModelId);
265
+ })();
266
+ targetProbesInFlight.set(key, probe);
246
267
  try {
247
- status = await deps.providers.probeTarget(targetId);
248
- } catch {
249
- // Fall back to the last known target state.
268
+ await probe;
269
+ } finally {
270
+ if (targetProbesInFlight.get(key) === probe) targetProbesInFlight.delete(key);
250
271
  }
251
- announceColdModel(status, targetId, wireModelId);
252
272
  };
253
273
 
254
274
  /** One notice per target+model+state, so a repeated probe stays quiet. */
@@ -589,6 +609,7 @@ export function createTurnRuntime(deps: TurnRuntimeDeps): TurnRuntime {
589
609
  middlewareToolChoice.toolStarted(event.toolName);
590
610
  deps.toolStartTimes.set(event.toolCallId, eventClock);
591
611
  state.turnToolCalls += 1;
612
+ state.turnToolNames.push(event.toolName);
592
613
  } else if (event.type === "tool_execution_end") {
593
614
  toolsInFlight = Math.max(0, toolsInFlight - 1);
594
615
  const startedAt = deps.toolStartTimes.get(event.toolCallId);
@@ -713,7 +734,11 @@ export function createTurnRuntime(deps: TurnRuntimeDeps): TurnRuntime {
713
734
  if (summary.hadUsage && (summary.tokens > 0 || summary.costUsd > 0)) {
714
735
  deps.observability.recordTokens(
715
736
  localRuntime.targetId,
716
- localRuntime.wireModelId,
737
+ attributedModelId(
738
+ summary.lastResponseModelIdObservation,
739
+ localRuntime.wireModelId,
740
+ summary.lastDifferingResponseModelId,
741
+ ),
717
742
  summary.tokens,
718
743
  summary.costUsd,
719
744
  {
@@ -726,6 +751,10 @@ export function createTurnRuntime(deps: TurnRuntimeDeps): TurnRuntime {
726
751
  apiCalls: summary.apiCalls,
727
752
  },
728
753
  localRuntime.runtimeResolution.costProvenance,
754
+ {
755
+ requestedModelIds: [localRuntime.wireModelId],
756
+ responseModelIdObservationCounts: summary.responseModelIdObservationCounts,
757
+ },
729
758
  );
730
759
  }
731
760
  if (summary.output > 0 && firstAssistantDeltaAt !== null) {
@@ -97,6 +97,8 @@ export interface ChatTurnState {
97
97
  toolProseAssessedChars: number;
98
98
  /** Tool calls observed in the current turn; feeds turn_end metadata. */
99
99
  turnToolCalls: number;
100
+ /** Names of those calls, in order, so turn_end can tell a listing-only turn from a working one. */
101
+ turnToolNames: string[];
100
102
  /** A `[worker result]` note the operator shared entered this turn, as the prompt or as a steer; feeds turn_end metadata. */
101
103
  turnSharedWorkerNote: boolean;
102
104
  /** One stalled-turn nudge per user prompt; reset when a real prompt arrives. */
@@ -123,6 +125,7 @@ export function createTurnState(initialThinkingLevel: ThinkingLevel): ChatTurnSt
123
125
  streamStallReason: null,
124
126
  toolProseAssessedChars: 0,
125
127
  turnToolCalls: 0,
128
+ turnToolNames: [],
126
129
  turnSharedWorkerNote: false,
127
130
  stalledTurnNudgeSpent: false,
128
131
  pendingRequestContinuation: false,
@@ -0,0 +1,440 @@
1
+ /**
2
+ * The one projection of a worker's live progress.
3
+ *
4
+ * A dispatched run publishes its events on a single stream, and two surfaces
5
+ * read them: the transcript block a `/run` or `/delegate` opens, and the Fleet
6
+ * Runs board. Both used to interpret that stream themselves, so the same event
7
+ * could mean two things and the board could only manage a richer spinner while
8
+ * the transcript already held the answer. This module is the fold both now go
9
+ * through, so what an operator reads about a worker is one story told twice
10
+ * rather than two stories.
11
+ *
12
+ * Three rules it enforces rather than documents:
13
+ *
14
+ * - Arguments never arrive here. A tool's name comes from the `clio_*`
15
+ * telemetry events, and what the call is doing comes from the bounded
16
+ * {@link CallActionDescriptor} composed at the tool-admission seam. The
17
+ * `tool_execution_*` events, whose `args` carry the literal call arguments,
18
+ * are not read at all.
19
+ * - Reasoning content is never retained. A `thinking_delta` moves the phase
20
+ * and nothing else: the operator learns that the worker is thinking, never
21
+ * what it is thinking.
22
+ * - Everything is bounded before it is kept. Lines, bytes, the action trail,
23
+ * the distinct tool names, and the rate at which delta bytes are accepted
24
+ * all have explicit caps, so neither a long run nor a hostile one can grow
25
+ * what a render has to walk.
26
+ *
27
+ * Pure: no I/O, no bus, no clock of its own. `observe` takes the instant it
28
+ * should measure the rate window against, so every sequence below is testable.
29
+ */
30
+
31
+ import { durableAssistantTextFromEvent, WORKER_OUTPUT_MAX_BYTES } from "../domains/dispatch/event-pump.js";
32
+ import type { CallActionDescriptor } from "../domains/safety/call-target.js";
33
+ import { truncateUtf8 } from "../tools/truncate-utf8.js";
34
+
35
+ /**
36
+ * Lines of worker prose kept while a run is live. The tail is what a streaming
37
+ * answer is about, so the head is what gets dropped; once the run settles the
38
+ * body is replaced by the receipt-sealed answer and anchors at the head, which
39
+ * is the same rule the thinking block already follows.
40
+ */
41
+ export const WORKER_LIVE_TAIL_LINES = 40;
42
+
43
+ /**
44
+ * Bytes of live tail retained. The line bound alone leaves a worker that emits
45
+ * one enormous line unbounded, so the byte bound is the backstop: whole lines
46
+ * leave the head until the tail fits, and a single line larger than the bound
47
+ * is cut at its head.
48
+ */
49
+ export const WORKER_LIVE_TAIL_MAX_BYTES = 4096;
50
+
51
+ /** Distinct tool names carried on an entry. The receipt's call count carries the total. */
52
+ export const WORKER_TOOL_NAME_LIMIT = 8;
53
+
54
+ /** Finished actions kept as the recent trail, newest first. */
55
+ export const WORKER_ACTION_TRAIL_LIMIT = 4;
56
+
57
+ /**
58
+ * The rate window for accepted delta bytes. A worker streaming faster than an
59
+ * operator can read is not producing more information, only more work for the
60
+ * fold, so bytes past the budget in one window are counted and dropped rather
61
+ * than concatenated into a tail that would immediately discard them anyway.
62
+ */
63
+ export const WORKER_PROGRESS_WINDOW_MS = 250;
64
+
65
+ /** Delta bytes accepted per {@link WORKER_PROGRESS_WINDOW_MS}. */
66
+ export const WORKER_PROGRESS_WINDOW_BYTES = 16_384;
67
+
68
+ const TAIL_TRUNCATION_MARKER = "";
69
+
70
+ /**
71
+ * Where a worker is in its turn. Derived from the event stream and never from
72
+ * reasoning content: `thinking` says only that a reasoning block is open.
73
+ */
74
+ export type WorkerProgressPhase = "starting" | "thinking" | "writing" | "tool" | "waiting" | "settled";
75
+
76
+ /** One tool call as an operator surface may show it: the name plus its redacted descriptor. */
77
+ export interface WorkerAction {
78
+ tool: string;
79
+ /** Correlation identity carried by new streams. Absent on legacy events. */
80
+ toolCallId?: string;
81
+ /** Absent when the runtime emitted no descriptor, so the name stands alone. */
82
+ descriptor?: CallActionDescriptor;
83
+ }
84
+
85
+ /**
86
+ * Everything both surfaces read about a live worker, bounded. Snapshots are
87
+ * immutable and `revision` changes only when a visible field does, so a
88
+ * renderer can skip a repaint by identity rather than by diffing text.
89
+ */
90
+ export interface WorkerProgressSnapshot {
91
+ revision: number;
92
+ phase: WorkerProgressPhase;
93
+ /** Bounded worker prose: the live tail while running, the sealed answer once settled. */
94
+ tailText: string;
95
+ /** Lines the bounds dropped from `tailText`. */
96
+ droppedLines: number;
97
+ /** Bytes refused by the rate window or cut from an oversized line. */
98
+ droppedBytes: number;
99
+ /** The most recently started call still executing; null between calls and once settled. */
100
+ currentAction: WorkerAction | null;
101
+ /** Finished calls, newest first, bounded by {@link WORKER_ACTION_TRAIL_LIMIT}. */
102
+ recentActions: ReadonlyArray<WorkerAction>;
103
+ /** Distinct tool names in first-use order, bounded by {@link WORKER_TOOL_NAME_LIMIT}. */
104
+ toolNames: ReadonlyArray<string>;
105
+ settled: boolean;
106
+ }
107
+
108
+ export interface WorkerProgressFold {
109
+ /**
110
+ * Fold one dispatch progress event. Returns true when the snapshot changed,
111
+ * so a caller can decide whether the surface needs anything at all.
112
+ */
113
+ observe(event: unknown, nowMs?: number): boolean;
114
+ /**
115
+ * Seal the projection. `text` is the receipt's answer where one could be
116
+ * read; without it the live tail stands as the run's last honest word.
117
+ */
118
+ settle(text?: string): boolean;
119
+ /**
120
+ * A later attempt of the same work takes over. The tail and the trail are
121
+ * history the operator is already reading and stay; the current call, the
122
+ * settled mark, and the previous attempt's durable answer go, because an
123
+ * answer belongs to the attempt that produced it.
124
+ */
125
+ restart(): void;
126
+ snapshot(): WorkerProgressSnapshot;
127
+ /** The last durable assistant answer this run streamed, unbounded by the tail. */
128
+ durableText(): string;
129
+ }
130
+
131
+ function isRecord(value: unknown): value is Record<string, unknown> {
132
+ return typeof value === "object" && value !== null && !Array.isArray(value);
133
+ }
134
+
135
+ function nonEmptyString(value: unknown): string | undefined {
136
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
137
+ }
138
+
139
+ /**
140
+ * Incremental assistant prose carried by one worker event.
141
+ *
142
+ * Native workers slim `message_update` down to `assistantMessageEvent.delta`
143
+ * before it crosses the NDJSON seam (src/worker/event-projection.ts); the ACP
144
+ * mapper emits a top-level `text_delta` carrying `text`. Both spellings are
145
+ * read here so ACP peers need no separate UI path.
146
+ */
147
+ function workerTextDelta(event: unknown): string {
148
+ if (!isRecord(event)) return "";
149
+ if (event.type === "message_update") {
150
+ const assistantEvent = isRecord(event.assistantMessageEvent) ? event.assistantMessageEvent : null;
151
+ if (assistantEvent?.type !== "text_delta") return "";
152
+ return typeof assistantEvent.delta === "string" ? assistantEvent.delta : "";
153
+ }
154
+ if (event.type !== "text_delta") return "";
155
+ if (typeof event.delta === "string") return event.delta;
156
+ return typeof event.text === "string" ? event.text : "";
157
+ }
158
+
159
+ /** Whether this event opens or continues a reasoning block. Its content is never read. */
160
+ function isThinkingEvent(event: Record<string, unknown>): boolean {
161
+ if (event.type === "thinking_delta") return true;
162
+ if (event.type !== "message_update") return false;
163
+ const assistantEvent = isRecord(event.assistantMessageEvent) ? event.assistantMessageEvent : null;
164
+ return assistantEvent?.type === "thinking_delta";
165
+ }
166
+
167
+ /** The action a `clio_tool_start`/`clio_tool_finish` event names, or null when it is neither. */
168
+ function toolEventAction(event: Record<string, unknown>): WorkerAction | null {
169
+ if (event.type !== "clio_tool_start" && event.type !== "clio_tool_finish") return null;
170
+ const payload = isRecord(event.payload) ? event.payload : null;
171
+ const tool = nonEmptyString(payload?.tool);
172
+ if (tool === undefined) return null;
173
+ const toolCallId = nonEmptyString(payload?.toolCallId);
174
+ const raw = isRecord(payload?.action) ? (payload.action as Partial<CallActionDescriptor>) : undefined;
175
+ const verb = nonEmptyString(raw?.verb);
176
+ if (verb === undefined) return { tool, ...(toolCallId !== undefined ? { toolCallId } : {}) };
177
+ const object = nonEmptyString(raw?.object);
178
+ return {
179
+ tool,
180
+ ...(toolCallId !== undefined ? { toolCallId } : {}),
181
+ descriptor: {
182
+ verb,
183
+ ...(object !== undefined ? { object } : {}),
184
+ ...(raw?.truncated === true ? { truncated: true } : {}),
185
+ },
186
+ };
187
+ }
188
+
189
+ function lineCount(text: string): number {
190
+ if (text.length === 0) return 0;
191
+ let lines = 1;
192
+ for (let index = 0; index < text.length; index += 1) {
193
+ if (text[index] === "\n") lines += 1;
194
+ }
195
+ return lines;
196
+ }
197
+
198
+ /** Keep the newest bytes of one oversized line, cutting on a UTF-8 boundary. */
199
+ function keepTailBytes(text: string, maxBytes: number): string {
200
+ const buffer = Buffer.from(text, "utf8");
201
+ if (buffer.byteLength <= maxBytes) return text;
202
+ let cut = buffer.byteLength - maxBytes;
203
+ while (cut < buffer.byteLength) {
204
+ const byte = buffer[cut];
205
+ if (byte === undefined || (byte & 0xc0) !== 0x80) break;
206
+ cut += 1;
207
+ }
208
+ return buffer.subarray(cut).toString("utf8");
209
+ }
210
+
211
+ /**
212
+ * Bound the live tail: newest lines first, then whole lines from the head until
213
+ * the byte cap is met, and finally a head cut on a single line larger than the
214
+ * cap on its own.
215
+ */
216
+ function boundLiveTail(text: string): { text: string; droppedLines: number; droppedBytes: number } {
217
+ const lines = text.split("\n");
218
+ let droppedLines = 0;
219
+ if (lines.length > WORKER_LIVE_TAIL_LINES) {
220
+ droppedLines = lines.length - WORKER_LIVE_TAIL_LINES;
221
+ lines.splice(0, droppedLines);
222
+ }
223
+ while (lines.length > 1 && Buffer.byteLength(lines.join("\n"), "utf8") > WORKER_LIVE_TAIL_MAX_BYTES) {
224
+ lines.shift();
225
+ droppedLines += 1;
226
+ }
227
+ const joined = lines.join("\n");
228
+ const bytes = Buffer.byteLength(joined, "utf8");
229
+ if (bytes <= WORKER_LIVE_TAIL_MAX_BYTES) return { text: joined, droppedLines, droppedBytes: 0 };
230
+ const kept = keepTailBytes(joined, WORKER_LIVE_TAIL_MAX_BYTES);
231
+ return { text: kept, droppedLines, droppedBytes: bytes - Buffer.byteLength(kept, "utf8") };
232
+ }
233
+
234
+ /** Head-anchored byte bound for a settled answer, matching the receipt's own bound. */
235
+ export function boundSettledText(text: string): { text: string; dropped: number } {
236
+ const bounded = truncateUtf8(text, WORKER_OUTPUT_MAX_BYTES, TAIL_TRUNCATION_MARKER);
237
+ if (bounded === text) return { text, dropped: 0 };
238
+ return { text: bounded, dropped: Math.max(0, lineCount(text) - lineCount(bounded)) };
239
+ }
240
+
241
+ const EMPTY_SNAPSHOT: WorkerProgressSnapshot = {
242
+ revision: 0,
243
+ phase: "starting",
244
+ tailText: "",
245
+ droppedLines: 0,
246
+ droppedBytes: 0,
247
+ currentAction: null,
248
+ recentActions: [],
249
+ toolNames: [],
250
+ settled: false,
251
+ };
252
+
253
+ export function createWorkerProgressFold(): WorkerProgressFold {
254
+ let revision = 0;
255
+ let phase: WorkerProgressPhase = "starting";
256
+ let tailText = "";
257
+ let droppedLines = 0;
258
+ let droppedBytes = 0;
259
+ let currentAction: WorkerAction | null = null;
260
+ const recentActions: WorkerAction[] = [];
261
+ const toolNames: string[] = [];
262
+ let settled = false;
263
+ let durable = "";
264
+ /** Starts still awaiting a finish, oldest first. */
265
+ const pendingActions: WorkerAction[] = [];
266
+ /** New streams resolve exact starts through this index. */
267
+ const pendingActionsById = new Map<string, WorkerAction>();
268
+ let windowStartMs = 0;
269
+ let windowBytes = 0;
270
+ let cached: WorkerProgressSnapshot | null = EMPTY_SNAPSHOT;
271
+
272
+ const touch = (): true => {
273
+ revision += 1;
274
+ cached = null;
275
+ return true;
276
+ };
277
+
278
+ const setPhase = (next: WorkerProgressPhase): boolean => {
279
+ if (phase === next) return false;
280
+ phase = next;
281
+ return touch();
282
+ };
283
+
284
+ /**
285
+ * Admit delta bytes against the rate window. Returns the text to append,
286
+ * which is empty once the window's budget is spent; the refused bytes are
287
+ * counted so the surface can say the stream outran it rather than quietly
288
+ * showing less.
289
+ */
290
+ const admitDelta = (delta: string, nowMs: number): string => {
291
+ if (nowMs - windowStartMs >= WORKER_PROGRESS_WINDOW_MS) {
292
+ windowStartMs = nowMs;
293
+ windowBytes = 0;
294
+ }
295
+ const bytes = Buffer.byteLength(delta, "utf8");
296
+ if (windowBytes + bytes <= WORKER_PROGRESS_WINDOW_BYTES) {
297
+ windowBytes += bytes;
298
+ return delta;
299
+ }
300
+ windowBytes = WORKER_PROGRESS_WINDOW_BYTES;
301
+ droppedBytes += bytes;
302
+ return "";
303
+ };
304
+
305
+ const appendTail = (text: string): boolean => {
306
+ if (text.length === 0) return false;
307
+ const bounded = boundLiveTail(tailText + text);
308
+ tailText = bounded.text;
309
+ droppedLines += bounded.droppedLines;
310
+ droppedBytes += bounded.droppedBytes;
311
+ return touch();
312
+ };
313
+
314
+ const noteToolName = (tool: string): boolean => {
315
+ if (toolNames.includes(tool) || toolNames.length >= WORKER_TOOL_NAME_LIMIT) return false;
316
+ toolNames.push(tool);
317
+ return touch();
318
+ };
319
+
320
+ const removePendingAction = (action: WorkerAction): void => {
321
+ const index = pendingActions.lastIndexOf(action);
322
+ if (index >= 0) pendingActions.splice(index, 1);
323
+ if (action.toolCallId !== undefined && pendingActionsById.get(action.toolCallId) === action) {
324
+ pendingActionsById.delete(action.toolCallId);
325
+ }
326
+ };
327
+
328
+ const lastPendingActionForTool = (tool: string): WorkerAction | undefined => {
329
+ for (let index = pendingActions.length - 1; index >= 0; index -= 1) {
330
+ const candidate = pendingActions[index];
331
+ if (candidate?.tool === tool) return candidate;
332
+ }
333
+ return undefined;
334
+ };
335
+
336
+ return {
337
+ observe(event: unknown, nowMs = Date.now()): boolean {
338
+ if (!isRecord(event) || settled) return false;
339
+ let changed = false;
340
+
341
+ const delta = workerTextDelta(event);
342
+ if (delta.length > 0) {
343
+ changed = appendTail(admitDelta(delta, nowMs)) || changed;
344
+ changed = setPhase("writing") || changed;
345
+ } else if (isThinkingEvent(event)) {
346
+ // The phase moves and nothing else: reasoning content is never kept.
347
+ changed = setPhase("thinking") || changed;
348
+ }
349
+
350
+ // A durable message_end is the answer as the receipt will seal it. An
351
+ // ACP peer that streamed no deltas reaches a surface only here.
352
+ const durableText = durableAssistantTextFromEvent(event);
353
+ if (durableText.trim().length > 0) {
354
+ durable = durableText;
355
+ if (delta.length === 0 && tailText.trim().length === 0) {
356
+ changed = appendTail(durableText) || changed;
357
+ }
358
+ }
359
+
360
+ const action = toolEventAction(event);
361
+ if (action !== null && event.type === "clio_tool_start") {
362
+ if (action.toolCallId !== undefined) {
363
+ const duplicate = pendingActionsById.get(action.toolCallId);
364
+ if (duplicate !== undefined) removePendingAction(duplicate);
365
+ pendingActionsById.set(action.toolCallId, action);
366
+ }
367
+ pendingActions.push(action);
368
+ currentAction = action;
369
+ noteToolName(action.tool);
370
+ touch();
371
+ setPhase("tool");
372
+ changed = true;
373
+ } else if (action !== null && event.type === "clio_tool_finish") {
374
+ // An id is authoritative when both sides carry it. If either side came
375
+ // from an older or incomplete producer, retain the historical name match.
376
+ const finished =
377
+ (action.toolCallId !== undefined ? pendingActionsById.get(action.toolCallId) : undefined) ??
378
+ lastPendingActionForTool(action.tool) ??
379
+ action;
380
+ removePendingAction(finished);
381
+ currentAction = pendingActions[pendingActions.length - 1] ?? null;
382
+ recentActions.unshift(finished);
383
+ if (recentActions.length > WORKER_ACTION_TRAIL_LIMIT) recentActions.length = WORKER_ACTION_TRAIL_LIMIT;
384
+ noteToolName(finished.tool);
385
+ touch();
386
+ setPhase(currentAction === null ? "waiting" : "tool");
387
+ changed = true;
388
+ }
389
+
390
+ if (event.type === "agent_start") changed = setPhase("starting") || changed;
391
+ return changed;
392
+ },
393
+
394
+ settle(text?: string): boolean {
395
+ const sealed = nonEmptyString(text) ?? nonEmptyString(durable);
396
+ if (sealed !== undefined) {
397
+ const bounded = boundSettledText(sealed);
398
+ tailText = bounded.text;
399
+ droppedLines = bounded.dropped;
400
+ }
401
+ currentAction = null;
402
+ pendingActions.length = 0;
403
+ pendingActionsById.clear();
404
+ settled = true;
405
+ phase = "settled";
406
+ touch();
407
+ return true;
408
+ },
409
+
410
+ restart(): void {
411
+ currentAction = null;
412
+ pendingActions.length = 0;
413
+ pendingActionsById.clear();
414
+ durable = "";
415
+ settled = false;
416
+ phase = "starting";
417
+ touch();
418
+ },
419
+
420
+ snapshot(): WorkerProgressSnapshot {
421
+ if (cached !== null) return cached;
422
+ cached = {
423
+ revision,
424
+ phase,
425
+ tailText,
426
+ droppedLines,
427
+ droppedBytes,
428
+ currentAction: currentAction === null ? null : { ...currentAction },
429
+ recentActions: recentActions.map((entry) => ({ ...entry })),
430
+ toolNames: [...toolNames],
431
+ settled,
432
+ };
433
+ return cached;
434
+ },
435
+
436
+ durableText(): string {
437
+ return durable;
438
+ },
439
+ };
440
+ }