@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
@@ -1,3 +1,4 @@
1
+ import { TextDecoder } from "node:util";
1
2
  import {
2
3
  type Api,
3
4
  type AssistantMessage,
@@ -15,6 +16,7 @@ import {
15
16
  type Usage,
16
17
  } from "@earendil-works/pi-ai";
17
18
  import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
19
+ import type { ResponseModelIdObservation } from "../../core/response-model-id.js";
18
20
  import {
19
21
  type AppliedThinking,
20
22
  type ResolvedModelRuntimeCapabilities,
@@ -35,6 +37,13 @@ import { residencyManagedFor } from "./residency.js";
35
37
  import { mergeSamplingOverride } from "./sampling-overrides.js";
36
38
  import type { EngineApiProvider } from "./types.js";
37
39
 
40
+ declare module "@earendil-works/pi-ai" {
41
+ interface AssistantMessage {
42
+ /** Direct observation of model-id presence in an OpenAI-compatible response. */
43
+ responseModelIdObservation?: ResponseModelIdObservation;
44
+ }
45
+ }
46
+
38
47
  const piOpenAICompletions = openAICompletionsApi();
39
48
 
40
49
  /**
@@ -83,6 +92,124 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
83
92
  return value !== null && typeof value === "object" && !Array.isArray(value);
84
93
  }
85
94
 
95
+ interface ResponseModelIdCapture {
96
+ reportedModelId: string | null;
97
+ observed: boolean;
98
+ done: boolean;
99
+ buffer: string;
100
+ decoder: TextDecoder | null;
101
+ }
102
+
103
+ function observeResponseModelIdLine(line: string, capture: ResponseModelIdCapture): void {
104
+ if (capture.done) return;
105
+ const normalized = line.endsWith("\r") ? line.slice(0, -1) : line;
106
+ if (!normalized.startsWith("data:")) return;
107
+ const data = normalized.slice("data:".length).trimStart();
108
+ if (data.length === 0 || data === "[DONE]") return;
109
+ try {
110
+ const payload = JSON.parse(data) as unknown;
111
+ if (!isPlainRecord(payload)) return;
112
+ const model = payload.model;
113
+ if (typeof model === "string" && model.trim().length > 0) {
114
+ capture.reportedModelId = model.trim();
115
+ capture.done = true;
116
+ }
117
+ } catch {
118
+ // A partial or vendor-specific event is pi-ai's parsing concern. This
119
+ // observer records only complete OpenAI-compatible JSON data lines.
120
+ }
121
+ }
122
+
123
+ function observeResponseModelIdBytes(
124
+ chunk: Uint8Array | undefined,
125
+ capture: ResponseModelIdCapture,
126
+ flush = false,
127
+ ): void {
128
+ if (capture.done || !capture.decoder) return;
129
+ capture.buffer += chunk ? capture.decoder.decode(chunk, { stream: !flush }) : capture.decoder.decode();
130
+ let newline = capture.buffer.indexOf("\n");
131
+ while (newline >= 0) {
132
+ const line = capture.buffer.slice(0, newline);
133
+ capture.buffer = capture.buffer.slice(newline + 1);
134
+ observeResponseModelIdLine(line, capture);
135
+ if (capture.done) {
136
+ capture.buffer = "";
137
+ capture.decoder = null;
138
+ return;
139
+ }
140
+ newline = capture.buffer.indexOf("\n");
141
+ }
142
+ if (flush && capture.buffer.length > 0) {
143
+ observeResponseModelIdLine(capture.buffer, capture);
144
+ capture.buffer = "";
145
+ }
146
+ if (capture.done || flush) capture.decoder = null;
147
+ }
148
+
149
+ function captureResponseModelId(response: Response, capture: ResponseModelIdCapture): Response {
150
+ if (!response.body || !response.headers.get("content-type")?.toLowerCase().includes("text/event-stream")) {
151
+ return response;
152
+ }
153
+ const body = response.body.pipeThrough(
154
+ new TransformStream<Uint8Array, Uint8Array>({
155
+ transform(chunk, controller) {
156
+ capture.observed = true;
157
+ if (!capture.done) observeResponseModelIdBytes(chunk, capture);
158
+ controller.enqueue(chunk);
159
+ },
160
+ flush() {
161
+ capture.observed = true;
162
+ if (!capture.done) observeResponseModelIdBytes(undefined, capture, true);
163
+ },
164
+ }),
165
+ );
166
+ return new Response(body, {
167
+ status: response.status,
168
+ statusText: response.statusText,
169
+ headers: response.headers,
170
+ });
171
+ }
172
+
173
+ function withResponseModelIdCapture<TOptions extends StreamOptions>(
174
+ options: TOptions,
175
+ sourceFactory: (capturedOptions: TOptions) => AssistantMessageEventStream,
176
+ ): AssistantMessageEventStream {
177
+ const capture: ResponseModelIdCapture = {
178
+ reportedModelId: null,
179
+ observed: false,
180
+ done: false,
181
+ buffer: "",
182
+ decoder: new TextDecoder(),
183
+ };
184
+ const fetchImpl: NonNullable<StreamOptions["fetch"]> =
185
+ options.fetch ?? ((input, init) => globalThis.fetch(input, init));
186
+ const capturedOptions = {
187
+ ...options,
188
+ fetch: async (input, init) => captureResponseModelId(await fetchImpl(input, init), capture),
189
+ } as TOptions;
190
+ const source = sourceFactory(capturedOptions);
191
+ const annotated = createAssistantMessageEventStream();
192
+ (async () => {
193
+ try {
194
+ for await (const event of source) {
195
+ const observation: ResponseModelIdObservation = capture.observed
196
+ ? capture.reportedModelId === null
197
+ ? { state: "not-reported" }
198
+ : { state: "reported", reportedModelId: capture.reportedModelId }
199
+ : { state: "not-observed" };
200
+ if (event.type === "done") event.message.responseModelIdObservation = observation;
201
+ else if (event.type === "error") event.error.responseModelIdObservation = observation;
202
+ annotated.push(event as AssistantMessageEvent);
203
+ }
204
+ annotated.end();
205
+ } catch (err) {
206
+ const message = err instanceof Error ? err.message : String(err);
207
+ throw new Error(message);
208
+ }
209
+ })();
210
+ return annotated;
211
+ }
212
+
86
213
  function samplingParamsFromProfile(profile: SamplingProfile): Record<string, unknown> {
87
214
  return {
88
215
  ...(profile.topP !== undefined ? { top_p: profile.topP } : {}),
@@ -735,17 +862,16 @@ export const openAICompletionsApiProvider: EngineApiProvider<"openai-completions
735
862
  stripSentinelsFromStream(
736
863
  stripNeverReasoningFromStream(
737
864
  filterGemmaChannelStream(
738
- withLocalResidency(
739
- model,
740
- {
741
- ...(options?.apiKey !== undefined ? { apiKey: options.apiKey } : {}),
742
- ...(options?.signal !== undefined ? { signal: options.signal } : {}),
743
- },
744
- (requestModel) =>
745
- piOpenAICompletions.stream(
746
- requestModel,
747
- effectiveContext,
748
- withRemainingContextBudget(model, effectiveContext, withSamplers),
865
+ withResponseModelIdCapture(
866
+ withRemainingContextBudget(model, effectiveContext, withSamplers),
867
+ (capturedOptions) =>
868
+ withLocalResidency(
869
+ model,
870
+ {
871
+ ...(options?.apiKey !== undefined ? { apiKey: options.apiKey } : {}),
872
+ ...(options?.signal !== undefined ? { signal: options.signal } : {}),
873
+ },
874
+ (requestModel) => piOpenAICompletions.stream(requestModel, effectiveContext, capturedOptions),
749
875
  ),
750
876
  ),
751
877
  resolved.family === "gemma-4",
@@ -768,17 +894,16 @@ export const openAICompletionsApiProvider: EngineApiProvider<"openai-completions
768
894
  stripSentinelsFromStream(
769
895
  stripNeverReasoningFromStream(
770
896
  filterGemmaChannelStream(
771
- withLocalResidency(
772
- model,
773
- {
774
- ...(options?.apiKey !== undefined ? { apiKey: options.apiKey } : {}),
775
- ...(options?.signal !== undefined ? { signal: options.signal } : {}),
776
- },
777
- (requestModel) =>
778
- piOpenAICompletions.streamSimple(
779
- requestModel,
780
- effectiveContext,
781
- withRemainingContextBudget(model, effectiveContext, withSamplers),
897
+ withResponseModelIdCapture(
898
+ withRemainingContextBudget(model, effectiveContext, withSamplers),
899
+ (capturedOptions) =>
900
+ withLocalResidency(
901
+ model,
902
+ {
903
+ ...(options?.apiKey !== undefined ? { apiKey: options.apiKey } : {}),
904
+ ...(options?.signal !== undefined ? { signal: options.signal } : {}),
905
+ },
906
+ (requestModel) => piOpenAICompletions.streamSimple(requestModel, effectiveContext, capturedOptions),
782
907
  ),
783
908
  ),
784
909
  resolved.family === "gemma-4",
@@ -413,9 +413,15 @@ function permissionResultForDecision(
413
413
  return result;
414
414
  }
415
415
 
416
- function decideToolUse(input: PermissionGateInput, toolName: string, toolInput: unknown): ClaudeToolPermissionDecision {
416
+ function decideToolUse(
417
+ input: PermissionGateInput,
418
+ toolCallId: string | undefined,
419
+ toolName: string,
420
+ toolInput: unknown,
421
+ ): ClaudeToolPermissionDecision {
417
422
  return emitClaudeToolPermissionDecision({
418
423
  toolName,
424
+ ...(toolCallId !== undefined ? { toolCallId } : {}),
419
425
  input: coerceToolInput(toolInput),
420
426
  safety: input.safety,
421
427
  cwd: input.cwd,
@@ -447,7 +453,7 @@ function decideToolUseOnce(
447
453
  toolInput: unknown,
448
454
  ): ClaudeToolPermissionDecision {
449
455
  return decideClaudeSdkToolUseOnce(input.handledToolDecisions, toolUseID, () =>
450
- decideToolUse(input, toolName, toolInput),
456
+ decideToolUse(input, toolUseID, toolName, toolInput),
451
457
  );
452
458
  }
453
459
 
@@ -8,6 +8,7 @@ import {
8
8
  DEFAULT_AUTONOMY_LEVEL,
9
9
  mapAutonomy,
10
10
  } from "../../domains/safety/autonomy.js";
11
+ import { describeCallAction } from "../../domains/safety/call-target.js";
11
12
  import type { SafetyContract, SafetyDecision } from "../../domains/safety/contract.js";
12
13
  import type { RejectionMessage } from "../../domains/safety/rejection-feedback.js";
13
14
  import type { ToolFinishEvent, ToolStartEvent } from "../../tools/agent-tools.js";
@@ -68,6 +69,8 @@ export interface EvaluateClaudeToolPermissionInput {
68
69
  export interface EmitClaudeToolPermissionInput extends EvaluateClaudeToolPermissionInput {
69
70
  emit(event: ClioWorkerEvent): void;
70
71
  onPermission?: "deny" | "fail";
72
+ /** SDK tool-use id shared by the start and finish telemetry events. */
73
+ toolCallId?: string;
71
74
  }
72
75
 
73
76
  function isRecord(value: unknown): value is Record<string, unknown> {
@@ -324,9 +327,11 @@ function emitToolFinish(
324
327
  outcome: ToolFinishEvent["outcome"],
325
328
  reason: string,
326
329
  reasonCode?: string,
330
+ toolCallId?: string,
327
331
  ): void {
328
332
  const event: ToolFinishEvent = {
329
333
  tool: mapped.clioToolName,
334
+ ...(toolCallId !== undefined ? { toolCallId } : {}),
330
335
  posture: "operating",
331
336
  durationMs: Math.round(performance.now() - startedAtClock),
332
337
  outcome,
@@ -350,10 +355,16 @@ export function emitClaudeToolPermissionDecision(input: EmitClaudeToolPermission
350
355
  const startedAt = Date.now();
351
356
  const startedAtClock = performance.now();
352
357
  const decision = evaluateClaudeToolPermission(input);
358
+ const toolCallId = input.toolCallId?.trim() ? input.toolCallId : undefined;
359
+ // Mapped args are this side's own translation of the subprocess call, so the
360
+ // descriptor is composed from them here rather than downstream of the seam.
361
+ const action = describeCallAction(decision.mapped.clioToolName, decision.mapped.args);
353
362
  const start: ToolStartEvent = {
354
363
  tool: decision.mapped.clioToolName,
364
+ ...(toolCallId !== undefined ? { toolCallId } : {}),
355
365
  posture: "operating",
356
366
  startedAt,
367
+ ...(action !== null ? { action } : {}),
357
368
  };
358
369
  input.emit({ type: "clio_tool_start", payload: start });
359
370
  if (decision.kind === "allow") {
@@ -365,6 +376,7 @@ export function emitClaudeToolPermissionDecision(input: EmitClaudeToolPermission
365
376
  "ok",
366
377
  decision.reason,
367
378
  decision.reasonCode,
379
+ toolCallId,
368
380
  );
369
381
  return decision;
370
382
  }
@@ -391,6 +403,7 @@ export function emitClaudeToolPermissionDecision(input: EmitClaudeToolPermission
391
403
  "blocked",
392
404
  decision.reason,
393
405
  decision.reasonCode,
406
+ toolCallId,
394
407
  );
395
408
  return decision;
396
409
  }
@@ -458,12 +458,14 @@ export interface CreateLoopGuardRegistrationOptions {
458
458
  export interface LoopGuardRegistration extends MiddlewareHookRegistration {
459
459
  /** Read-only attempt counter for tests and telemetry. */
460
460
  callCount(): number;
461
+ /** One-way worker phase growth admitted before launch for a result-contract revision. */
462
+ extendWorkerToolCallPhase(phase: { toolCalls: number; readReserve: number }): boolean;
461
463
  }
462
464
 
463
465
  export function createLoopGuardRegistration(options: CreateLoopGuardRegistrationOptions): LoopGuardRegistration {
464
466
  const budget = options.turnBlockBudget ?? INTERACTIVE_LOOP_BLOCK_BUDGET;
465
467
  const cap = options.toolCallCap;
466
- const softLimit =
468
+ let softLimit =
467
469
  options.toolCallSoftLimit !== undefined &&
468
470
  Number.isSafeInteger(options.toolCallSoftLimit) &&
469
471
  options.toolCallSoftLimit > 0 &&
@@ -472,8 +474,8 @@ export function createLoopGuardRegistration(options: CreateLoopGuardRegistration
472
474
  : undefined;
473
475
  const turnBudget = options.turnToolCallBudget;
474
476
  const synthesisLockout = options.turnSynthesisLockout === true;
475
- const softReadReserve = options.toolCallSoftReadReserve ?? 0;
476
- const softReadReserveThreshold =
477
+ let softReadReserve = options.toolCallSoftReadReserve ?? 0;
478
+ let softReadReserveThreshold =
477
479
  softLimit !== undefined && softReadReserve > 0 && softLimit > softReadReserve ? softLimit - softReadReserve : null;
478
480
  const deliveryTools = [...new Set(options.deliveryTools ?? [])];
479
481
  const reserveAdmits = (tool: string | undefined): boolean => isReserveAdmittedTool(tool, deliveryTools);
@@ -850,6 +852,28 @@ export function createLoopGuardRegistration(options: CreateLoopGuardRegistration
850
852
  "blocks repeated tool calls, enforces tool-call caps, and annotates substantial identical read results across distinct arguments",
851
853
  hooks: ["before_tool", "after_tool"],
852
854
  callCount: () => count,
855
+ extendWorkerToolCallPhase(phase): boolean {
856
+ if (
857
+ softLimit === undefined ||
858
+ !Number.isSafeInteger(phase.toolCalls) ||
859
+ !Number.isSafeInteger(phase.readReserve) ||
860
+ phase.toolCalls <= 0 ||
861
+ phase.readReserve < 0 ||
862
+ phase.readReserve >= phase.toolCalls ||
863
+ (cap !== undefined && phase.toolCalls > cap) ||
864
+ phase.toolCalls < softLimit ||
865
+ (phase.toolCalls === softLimit && phase.readReserve <= softReadReserve)
866
+ ) {
867
+ return false;
868
+ }
869
+ softLimit = phase.toolCalls;
870
+ softReadReserve = phase.readReserve;
871
+ softReadReserveThreshold = softReadReserve > 0 && softLimit > softReadReserve ? softLimit - softReadReserve : null;
872
+ softReadReserveEntered = false;
873
+ softLockout = null;
874
+ reserveDenials = { denials: 0 };
875
+ return true;
876
+ },
853
877
  evaluate(input): ReadonlyArray<MiddlewareEffect> {
854
878
  if (input.hook === "after_tool") {
855
879
  recordSuccessfulResult(input);
@@ -57,13 +57,19 @@ export interface ClioSessionMeta {
57
57
  platform: string;
58
58
  nodeVersion: string;
59
59
  /**
60
- * Version 3 on every new session. Readers reject earlier versions and
61
- * sessions that omit this field.
60
+ * Version 4 on every new session. Readers reject earlier versions, sessions
61
+ * that omit this field, and versions from the future.
62
62
  */
63
63
  sessionFormatVersion?: number;
64
64
  }
65
65
 
66
- export const CURRENT_SESSION_FORMAT_VERSION = 3;
66
+ /**
67
+ * Version 4 adds the working-set ledger kinds (`contextEviction`,
68
+ * `contextRecall`). A version-3 reader would parse those entries as unknown
69
+ * kinds and replay a session as if nothing had been evicted, so the bump is
70
+ * what makes an older Clio refuse the file instead of misreading it.
71
+ */
72
+ export const CURRENT_SESSION_FORMAT_VERSION = 4;
67
73
 
68
74
  export interface ClioSessionJsonlHeader {
69
75
  type: "session";
@@ -53,9 +53,10 @@ export interface ClioPermissionEscalatedEvent {
53
53
  tool: string;
54
54
  summary: string;
55
55
  /**
56
- * Sanitized one-line preview of the call's object (command, path, or
57
- * compact args), so the operator's approval overlay can show what the
58
- * call will touch. The args themselves never cross the stdout seam.
56
+ * Sanitized one-line preview of the call's allowlisted object fields, so
57
+ * the operator's approval overlay can show what the call will touch.
58
+ * Unlisted fields carry only type-and-size summaries, and the args
59
+ * themselves never cross the stdout seam.
59
60
  */
60
61
  target?: string;
61
62
  axis?: string;
@@ -443,6 +443,42 @@ export function startWorkerRun(input: WorkerRunInput, emit: WorkerEventEmit): Wo
443
443
  // loop guard counts synthesis-lock noncompliance by model round, preventing
444
444
  // one wide parallel batch from consuming the entire denial backstop.
445
445
  let workerModelRound = 0;
446
+ const loopGuardRegistration = createLoopGuardRegistration({
447
+ safety,
448
+ toolCallCap: workerBudget.hardCap,
449
+ toolCallSoftLimit: workerBudget.toolCalls,
450
+ // A worker's blocks all land in one run-long bucket, so the bound on
451
+ // them is a statement about this run's length, not about a turn.
452
+ turnBlockBudget: workerLoopBlockBudget(workerBudget.revision?.toolCalls ?? workerBudget.toolCalls),
453
+ toolCallSoftReadReserve: readReserve,
454
+ ...(deliveryTools.length > 0 ? { deliveryTools } : {}),
455
+ turnSynthesisLockout: workerBudget.synthesis,
456
+ // Once locked, the next model round is forced text-only at the
457
+ // request level. The lockout directive alone relies on model compliance.
458
+ onSynthesisLockout: () => {
459
+ if (workerBudget.synthesis) synthesisToolLock = true;
460
+ },
461
+ ...(!workerBudget.synthesis
462
+ ? {
463
+ onSoftLimitFinalCallAdmitted: (toolCallId: string | undefined) => {
464
+ if (workerBoundFailure === null) {
465
+ workerBoundFailure = `worker agent budget reached (${workerBudget.toolCalls}); synthesis is disabled`;
466
+ }
467
+ // Native agent tool calls carry a stable provider id. The wildcard
468
+ // keeps even an invariant violation on the post-result path.
469
+ stopAfterToolResultCallId = toolCallId ?? STOP_AFTER_ANY_TOOL_RESULT;
470
+ },
471
+ }
472
+ : {}),
473
+ // Requiring read is correct only when reading is the whole reserve.
474
+ ...(readReserve > 0 && deliveryTools.length === 0
475
+ ? {
476
+ onSoftReadReserve: () => {
477
+ middlewareToolChoice.apply([{ kind: "require_tool", toolName: ToolNames.Read }]);
478
+ },
479
+ }
480
+ : {}),
481
+ });
446
482
  const registry = createWorkerToolRegistry(
447
483
  input.middlewareSnapshot,
448
484
  safety,
@@ -461,52 +497,7 @@ export function startWorkerRun(input: WorkerRunInput, emit: WorkerEventEmit): Wo
461
497
  // and has no persistence sink. It can still absorb worker-local
462
498
  // protect_path effects from snapshot rules for the rest of this run.
463
499
  [
464
- createLoopGuardRegistration({
465
- safety,
466
- toolCallCap: workerBudget.hardCap,
467
- toolCallSoftLimit: workerBudget.toolCalls,
468
- // A worker's blocks all land in one run-long bucket, so the bound on
469
- // them is a statement about this run's length, not about a turn.
470
- turnBlockBudget: workerLoopBlockBudget(workerBudget.toolCalls),
471
- toolCallSoftReadReserve: readReserve,
472
- ...(deliveryTools.length > 0 ? { deliveryTools } : {}),
473
- turnSynthesisLockout: workerBudget.synthesis,
474
- // Once locked, the next model round is forced text-only at the
475
- // request level (the tool surface is removed in onPayload below):
476
- // the lockout directive alone relies on model compliance, and
477
- // measured local models kept calling tools until the backstop
478
- // aborted the run, or answered the forced round with tool-call
479
- // markup that the loop guard then removed.
480
- onSynthesisLockout: () => {
481
- if (workerBudget.synthesis) {
482
- synthesisToolLock = true;
483
- }
484
- },
485
- ...(!workerBudget.synthesis
486
- ? {
487
- onSoftLimitFinalCallAdmitted: (toolCallId: string | undefined) => {
488
- if (workerBoundFailure === null) {
489
- workerBoundFailure = `worker agent budget reached (${workerBudget.toolCalls}); synthesis is disabled`;
490
- }
491
- // Native agent tool calls carry a stable provider id. The wildcard
492
- // keeps even an invariant violation on the post-result path
493
- // instead of reintroducing a before_tool abort.
494
- stopAfterToolResultCallId = toolCallId ?? STOP_AFTER_ANY_TOOL_RESULT;
495
- },
496
- }
497
- : {}),
498
- // Forcing the next round to `read` is only correct when reading is
499
- // the whole reserve. An agent with delivery tools must be able to
500
- // write in its own reserve window, so it gets the steering directive
501
- // without the request-level lock.
502
- ...(readReserve > 0 && deliveryTools.length === 0
503
- ? {
504
- onSoftReadReserve: () => {
505
- middlewareToolChoice.apply([{ kind: "require_tool", toolName: ToolNames.Read }]);
506
- },
507
- }
508
- : {}),
509
- }),
500
+ loopGuardRegistration,
510
501
  createProtectedArtifactsRegistration({
511
502
  ...(input.protectedArtifactState !== undefined
512
503
  ? { initialState: { artifacts: [...input.protectedArtifactState.artifacts] } }
@@ -519,6 +510,7 @@ export function startWorkerRun(input: WorkerRunInput, emit: WorkerEventEmit): Wo
519
510
  );
520
511
  const contractCwd = input.cwd ?? process.cwd();
521
512
  let resultContractRepairsQueued = 0;
513
+ let resultContractRevisionActive = false;
522
514
  /** Read tool call id -> what was asked for, pending that call's result. */
523
515
  const pendingReadCitations = new Map<string, { path: string; offset: number | null; tail: boolean }>();
524
516
  const observedReadRanges = new Map<string, Array<readonly [number, number]>>();
@@ -702,10 +694,17 @@ export function startWorkerRun(input: WorkerRunInput, emit: WorkerEventEmit): Wo
702
694
  if (violation !== null) {
703
695
  if (resultContractRepairsQueued < RESULT_CONTRACT_REPAIR_LIMIT) {
704
696
  resultContractRepairsQueued += 1;
705
- // A repair round is terminal by construction, so enforce the
706
- // directive's own claim that tool use is over at the request
707
- // level instead of trusting the model to honor it.
708
- synthesisToolLock = true;
697
+ if (!resultContractRevisionActive && workerBudget.revision !== undefined) {
698
+ resultContractRevisionActive = loopGuardRegistration.extendWorkerToolCallPhase(workerBudget.revision);
699
+ if (resultContractRevisionActive) {
700
+ synthesisToolLock = false;
701
+ middlewareToolChoice.reset();
702
+ stopAfterToolResultCallId = null;
703
+ }
704
+ }
705
+ const revisionToolsAvailable = resultContractRevisionActive && !synthesisToolLock;
706
+ // A repair without preauthorized growth remains terminal and text-only.
707
+ if (!revisionToolsAvailable) synthesisToolLock = true;
709
708
  // Delivered as a paired tool exchange, never a user turn. Templates
710
709
  // that key history rendering off the last `user` message re-render
711
710
  // every earlier assistant turn when one is appended mid-run, which
@@ -714,7 +713,13 @@ export function startWorkerRun(input: WorkerRunInput, emit: WorkerEventEmit): Wo
714
713
  // bytes as a tool result (#55). The synthetic assistant call gives
715
714
  // the result a real tool_call_id for strict endpoints (#62).
716
715
  const repair = resultContractRepairMessages(
717
- { contract, reason: violation, attempt: resultContractRepairsQueued, anchors: observedReadAnchors() },
716
+ {
717
+ contract,
718
+ reason: violation,
719
+ attempt: resultContractRepairsQueued,
720
+ anchors: observedReadAnchors(),
721
+ ...(revisionToolsAvailable ? { toolsAvailable: true } : {}),
722
+ },
718
723
  { provider: model.provider, api: model.api, model: model.id },
719
724
  );
720
725
  for (const message of repair) agent.followUp(message as unknown as AgentMessage);
@@ -915,9 +920,9 @@ export function startWorkerRun(input: WorkerRunInput, emit: WorkerEventEmit): Wo
915
920
  const timer = setTimeout(() => resolveEscalation(requestId, "deny", "timeout"), escalationConfig.timeoutMs);
916
921
  activeEscalation = { requestId, tool: call.tool, actionClass, callKey, timer };
917
922
  // The operator decides on this exact call, so the escalation carries a
918
- // sanitized preview of its object; the args stay inside the worker. The
919
- // cap bounds the NDJSON line, and the overlay truncates further.
920
- const target = describeCallTarget(call.args).slice(0, 200);
923
+ // sanitized allowlisted preview of its object. Unlisted fields cross only
924
+ // as type-and-size summaries, and the args stay inside the worker.
925
+ const target = describeCallTarget(call.tool, call.args);
921
926
  emit({
922
927
  type: "clio_permission_escalated",
923
928
  payload: {
@@ -161,8 +161,8 @@ import {
161
161
  import { openSession, readSessionTailTurns, sessionCurrentPath, sessionPaths } from "../engine/session.js";
162
162
  import type { EngineModel } from "../engine/types.js";
163
163
  import { createChatLoop } from "../interactive/chat-loop.js";
164
- import { buildReplayAgentMessagesFromTurns } from "../interactive/chat-renderer.js";
165
164
  import { type RunIo, startInteractive } from "../interactive/index.js";
165
+ import { buildModelReplayAgentMessagesFromTurns } from "../interactive/model-session-replay.js";
166
166
  import type { BootOptions } from "./boot-options.js";
167
167
 
168
168
  export type { BootOptions, HeadlessSamplingOverrides } from "./boot-options.js";
@@ -183,6 +183,7 @@ import { isGitRepository, recoverCleanupReadyCompeteGroups } from "../tools/comp
183
183
  import { createDispatchBackgroundRegistry } from "../tools/dispatch-background.js";
184
184
  import { createFileMutationObserver, createSkillActivationObserver } from "../tools/observers.js";
185
185
  import { createRegistry } from "../tools/registry.js";
186
+ import { sweepExpiredToolOffloads } from "../tools/result-shaping.js";
186
187
 
187
188
  export interface BootResult {
188
189
  exitCode: number;
@@ -726,6 +727,7 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
726
727
  termination.installSignalHandlers();
727
728
 
728
729
  ensureClioState();
730
+ sweepExpiredToolOffloads();
729
731
  timer.mark("install check");
730
732
 
731
733
  // A hard-killed coordinator cannot run domain drains. Reconcile its compete
@@ -1236,7 +1238,16 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
1236
1238
  // one object the dispatch tool and the keypress both hold.
1237
1239
  const dispatchBackground = createDispatchBackgroundRegistry();
1238
1240
  registerAllTools(toolRegistry, {
1239
- ...(session ? { session } : {}),
1241
+ ...(session
1242
+ ? {
1243
+ session,
1244
+ readSessionEntries: () => {
1245
+ const meta = session.current();
1246
+ return meta ? readSessionEntriesForCompact(meta.id) : [];
1247
+ },
1248
+ onContextRecalled: (payload) => bus.emit(BusChannels.ContextRecalled, payload),
1249
+ }
1250
+ : {}),
1240
1251
  taskBoard,
1241
1252
  userTasks,
1242
1253
  dispatch,
@@ -1544,8 +1555,13 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
1544
1555
  getMemorySection: () => {
1545
1556
  try {
1546
1557
  const records = loadMemoryRecordsSync(clioDataDir());
1558
+ const settings = getCurrentSettings();
1559
+ const targetId = session?.current()?.target ?? settings.orchestrator?.target;
1560
+ const runtimeId = targetId ? providers.getTarget(targetId)?.runtime : undefined;
1547
1561
  return buildMemoryPromptSection(records, {
1562
+ scopes: ["global", "repo", "runtime"],
1548
1563
  activeRepository: canonicalMemoryRepositoryIdentity(process.cwd()),
1564
+ activeRuntime: runtimeId === undefined ? null : { kind: "runtime", key: runtimeId },
1549
1565
  }).section;
1550
1566
  } catch {
1551
1567
  return "";
@@ -1553,7 +1569,17 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
1553
1569
  },
1554
1570
  getTaskMemoryHandoffSource: () => {
1555
1571
  ensureTaskMemorySession();
1556
- return renderTaskMemoryHandoffSource(taskMemoryBank.snapshot());
1572
+ const meta = session?.current();
1573
+ if (!meta) throw new Error("task memory handoff requires an active session");
1574
+ const settings = getCurrentSettings();
1575
+ const targetId = meta.target ?? settings.orchestrator?.target;
1576
+ const runtimeId = targetId ? providers.getTarget(targetId)?.runtime : undefined;
1577
+ return renderTaskMemoryHandoffSource(taskMemoryBank.snapshot(), {
1578
+ sessionId: meta.id,
1579
+ evidenceRefs: [`session-${meta.id}`],
1580
+ runtimeIds: runtimeId === undefined ? [] : [runtimeId],
1581
+ agentIds: [],
1582
+ });
1557
1583
  },
1558
1584
  registerDeferredReminderSink: (sink) => {
1559
1585
  deferredMemoryReminderSink = sink;
@@ -1612,7 +1638,10 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
1612
1638
  // parent onto.
1613
1639
  chat.resetForSession(
1614
1640
  leafTurnId,
1615
- buildReplayAgentMessagesFromTurns(readCurrentSessionEntries(), leafTurnId ? { activeLeafTurnId: leafTurnId } : {}),
1641
+ buildModelReplayAgentMessagesFromTurns(
1642
+ readCurrentSessionEntries(),
1643
+ leafTurnId ? { activeLeafTurnId: leafTurnId } : {},
1644
+ ),
1616
1645
  );
1617
1646
  } catch (err) {
1618
1647
  chat.resetForSession(leafTurnId);
@@ -1664,7 +1693,7 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
1664
1693
  ? {
1665
1694
  readSessionEntries: readSessionEntriesForCompact,
1666
1695
  buildReplayMessages: (entries: ReadonlyArray<SessionEntry>, leafTurnId: string | null) =>
1667
- buildReplayAgentMessagesFromTurns(entries, leafTurnId === null ? {} : { activeLeafTurnId: leafTurnId }),
1696
+ buildModelReplayAgentMessagesFromTurns(entries, leafTurnId === null ? {} : { activeLeafTurnId: leafTurnId }),
1668
1697
  }
1669
1698
  : {}),
1670
1699
  providers,