@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
package/src/tools/bash.ts CHANGED
@@ -3,87 +3,124 @@ import { BASH_HARD_CAP_BYTES, combineBashOutput, runBashCommand } from "../core/
3
3
  import { resolveSafeCwd } from "../core/safe-exec.js";
4
4
  import { ToolNames } from "../core/tool-names.js";
5
5
  import { expandPath } from "./path-utils.js";
6
- import type { ToolInvokeOptions, ToolResult, ToolResultDetails, ToolSpec } from "./registry.js";
7
- import { writeToolOffload } from "./result-shaping.js";
8
- import { DEFAULT_MAX_LINES, formatSize, truncateTail } from "./truncate.js";
6
+ import { toolPresentationPolicy } from "./presentation.js";
7
+ import type { ToolResult, ToolResultDetails, ToolSpec } from "./registry.js";
8
+ import {
9
+ deterministicDiagnosticSummary,
10
+ type NormalizedToolResultDisposition,
11
+ type ToolResultContextDisposition,
12
+ type ToolResultDisposition,
13
+ } from "./result-disposition.js";
14
+ import { DEFAULT_MAX_LINES, truncateTail } from "./truncate.js";
15
+ import { byteLength } from "./truncate-utf8.js";
9
16
 
10
- // What the model sees inline. The tail is where the failing assertion, compiler
11
- // error, and exit summary live, so we keep the LAST lines/bytes. The full
12
- // output is spilled to an offload file before truncating, so nothing is lost.
17
+ // The registry's bounded model projection and operator presentation share this
18
+ // cap. Both are tail-biased for Bash because diagnostics usually land last.
13
19
  const BASH_DISPLAY_MAX_BYTES = 16 * 1024;
14
- // Slack reserved so the continuation notice appended after truncation still
15
- // fits under the registry's per-tool bash budget without a second (head-first)
16
- // re-truncation cutting the tail we just preserved.
17
- const BASH_TAIL_NOTE_RESERVE = 512;
20
+ export type BashOutputPolicy = "full" | "bounded" | "summary" | "metadata-only";
18
21
 
19
- interface ShapedBashOutput {
20
- text: string;
21
- details?: ToolResultDetails;
22
+ const BASH_OUTPUT_POLICIES = new Set<BashOutputPolicy>(["full", "bounded", "summary", "metadata-only"]);
23
+
24
+ function bashContextDisposition(policy: BashOutputPolicy): ToolResultContextDisposition & { maxBytes: number } {
25
+ if (policy === "full") {
26
+ return { mode: "full", maxBytes: BASH_DISPLAY_MAX_BYTES, downgradeExcerpt: "tail" };
27
+ }
28
+ if (policy === "summary") {
29
+ return {
30
+ mode: "summary",
31
+ maxBytes: BASH_DISPLAY_MAX_BYTES,
32
+ strategy: "diagnostic",
33
+ redact: true,
34
+ };
35
+ }
36
+ if (policy === "metadata-only") return { mode: "metadata-only", maxBytes: BASH_DISPLAY_MAX_BYTES };
37
+ return { mode: "bounded", maxBytes: BASH_DISPLAY_MAX_BYTES, excerpt: "tail" };
38
+ }
39
+
40
+ /** Canonical default attached by the builtin catalog and reused by direct registries. */
41
+ export const BASH_DEFAULT_RESULT_DISPOSITION: ToolResultDisposition = {
42
+ presentation: {
43
+ ...toolPresentationPolicy(ToolNames.Bash, undefined),
44
+ maxBytes: BASH_DISPLAY_MAX_BYTES,
45
+ overflow: "tail",
46
+ },
47
+ context: bashContextDisposition("bounded"),
48
+ };
49
+
50
+ /** Pure, idempotent normalization used by registry admission and direct calls. */
51
+ export function normalizeBashArguments(args: Record<string, unknown>): Record<string, unknown> {
52
+ return args.output_policy === undefined ? { ...args, output_policy: "bounded" } : args;
53
+ }
54
+
55
+ function bashOutputPolicy(args: Record<string, unknown>): BashOutputPolicy | null {
56
+ const value = args.output_policy;
57
+ return typeof value === "string" && BASH_OUTPUT_POLICIES.has(value as BashOutputPolicy)
58
+ ? (value as BashOutputPolicy)
59
+ : null;
60
+ }
61
+
62
+ function resolveBashResultDisposition(
63
+ args: Record<string, unknown>,
64
+ declared: ToolResultDisposition | undefined = BASH_DEFAULT_RESULT_DISPOSITION,
65
+ ): ToolResultDisposition {
66
+ const policy = bashOutputPolicy(args) ?? "bounded";
67
+ const base = declared ?? BASH_DEFAULT_RESULT_DISPOSITION;
68
+ return { presentation: base.presentation, context: bashContextDisposition(policy) };
69
+ }
70
+
71
+ function normalizedBashDisposition(policy: BashOutputPolicy): NormalizedToolResultDisposition {
72
+ return {
73
+ presentation: { ...BASH_DEFAULT_RESULT_DISPOSITION.presentation, maxBytes: BASH_DISPLAY_MAX_BYTES },
74
+ context: bashContextDisposition(policy),
75
+ };
22
76
  }
23
77
 
24
78
  /** Bound a cumulative live snapshot without writing an offload file per tick. */
25
- function shapeBashProgress(rawOutput: string): ToolResult {
26
- const truncation = truncateTail(rawOutput, {
27
- maxLines: DEFAULT_MAX_LINES,
28
- maxBytes: BASH_DISPLAY_MAX_BYTES - BASH_TAIL_NOTE_RESERVE,
29
- });
30
- const bytes = Buffer.byteLength(rawOutput, "utf8");
79
+ function shapeBashProgress(rawOutput: string, outputBytes: number, policy: BashOutputPolicy): ToolResult {
80
+ const disposition = normalizedBashDisposition(policy);
81
+ let output: string;
82
+ if (policy === "metadata-only") {
83
+ output = `bash: running; captured ${outputBytes} bytes; stdout/stderr omitted by metadata-only policy`;
84
+ } else if (policy === "summary") {
85
+ output = deterministicDiagnosticSummary(rawOutput, BASH_DISPLAY_MAX_BYTES, true).text;
86
+ } else {
87
+ output = truncateTail(rawOutput, {
88
+ maxLines: DEFAULT_MAX_LINES,
89
+ maxBytes: disposition.context.maxBytes,
90
+ }).content;
91
+ }
31
92
  return {
32
93
  kind: "ok",
33
- output: truncation.content,
94
+ output,
34
95
  details: {
35
96
  resultSize: {
36
- bytes,
37
- shownBytes: truncation.outputBytes,
97
+ bytes: outputBytes,
98
+ shownBytes: byteLength(output),
38
99
  maxBytes: BASH_DISPLAY_MAX_BYTES,
39
- truncated: truncation.truncated,
40
- policy: "tail",
100
+ truncated: byteLength(rawOutput) > byteLength(output),
101
+ policy,
41
102
  },
42
103
  },
43
104
  };
44
105
  }
45
106
 
46
- // Tail-truncate the combined output for display, spilling the full output to a
47
- // scratch file first when it overflows the display cap. Setting
48
- // `details.resultSize.offloadPath` tells the registry result-shaper to leave
49
- // this already-shaped (tail-biased) output alone instead of re-truncating it
50
- // head-first.
51
- function shapeBashOutput(
107
+ function bashResultDetails(
108
+ result: Awaited<ReturnType<typeof runBashCommand>>,
52
109
  rawOutput: string,
53
- context: Pick<ToolInvokeOptions, "sessionId" | "toolCallId"> | undefined,
54
- ): ShapedBashOutput {
55
- const truncation = truncateTail(rawOutput, {
56
- maxLines: DEFAULT_MAX_LINES,
57
- maxBytes: BASH_DISPLAY_MAX_BYTES - BASH_TAIL_NOTE_RESERVE,
58
- });
59
- if (!truncation.truncated) return { text: rawOutput };
60
-
61
- const totalBytes = Buffer.byteLength(rawOutput, "utf8");
62
- const offloadPath = writeToolOffload(rawOutput, context, BASH_HARD_CAP_BYTES);
63
- const startLine = truncation.totalLines - truncation.outputLines + 1;
64
- const scope = truncation.lastLinePartial
65
- ? `last ${formatSize(truncation.outputBytes)} of line ${truncation.totalLines} (line is large)`
66
- : `lines ${startLine}-${truncation.totalLines} of ${truncation.totalLines}`;
67
- const location = offloadPath !== null ? ` Full output saved to ${offloadPath}; read it with offset/limit.` : "";
68
- const note = `[Output tail-truncated: showing ${scope} (${formatSize(BASH_DISPLAY_MAX_BYTES)} display limit).${location}]`;
69
- const details: ToolResultDetails = {
70
- resultSize: {
71
- bytes: totalBytes,
72
- shownBytes: truncation.outputBytes,
73
- maxBytes: BASH_DISPLAY_MAX_BYTES,
74
- truncated: true,
75
- policy: "tail",
76
- followUpHint: "Read the full-output offload file with offset/limit, or re-run a narrower command.",
77
- ...(offloadPath !== null ? { offloadPath } : {}),
78
- },
110
+ outcome: "success" | "nonzero" | "timeout" | "abort" | "output-cap",
111
+ ): ToolResultDetails {
112
+ return {
113
+ outcome,
114
+ exitCode: result.exitCode,
115
+ signal: result.signal,
116
+ timedOut: result.timedOut,
117
+ aborted: result.aborted,
118
+ outputCapped: result.outputCapped,
119
+ outputBytes: result.outputBytes,
120
+ retainedBytes: byteLength(rawOutput),
121
+ stdoutBytes: byteLength(result.stdout),
122
+ stderrBytes: byteLength(result.stderr),
79
123
  };
80
- return { text: `${truncation.content}\n\n${note}`, details };
81
- }
82
-
83
- function withDetails(base: ToolResult, details: ToolResultDetails | undefined): ToolResult {
84
- if (details === undefined) return base;
85
- if (base.kind === "ok") return { ...base, details };
86
- return { ...base, details };
87
124
  }
88
125
 
89
126
  // A bash command whose first word is a plain file/dir observer has a
@@ -120,7 +157,7 @@ function observeToolsNudge(command: string, sessionId: string | undefined): stri
120
157
  export const bashTool: ToolSpec = {
121
158
  name: ToolNames.Bash,
122
159
  description:
123
- "Execute a bash command and return stdout and stderr. Runs inside the session workspace. Default timeout 300000 ms.",
160
+ "Execute a bash command inside the session workspace. output_policy controls model context: bounded keeps the diagnostic tail and is the default; summary keeps deterministic redacted diagnostics; metadata-only keeps facts and retrieval; full is admitted only within the result budget.",
124
161
  parameters: Type.Object({
125
162
  command: Type.String({ description: "Bash command to execute." }),
126
163
  cwd: Type.Optional(
@@ -130,13 +167,30 @@ export const bashTool: ToolSpec = {
130
167
  }),
131
168
  ),
132
169
  timeout_ms: Type.Optional(Type.Number({ description: "Timeout in milliseconds." })),
170
+ output_policy: Type.Optional(
171
+ Type.Union([Type.Literal("full"), Type.Literal("bounded"), Type.Literal("summary"), Type.Literal("metadata-only")], {
172
+ description:
173
+ "Model-context disposition. Omit for bounded tail output. Use summary for noisy runs, metadata-only when only status and retrieval matter, and full only for known-small output.",
174
+ }),
175
+ ),
133
176
  }),
134
177
  baseActionClass: "execute",
135
178
  executionMode: "sequential",
179
+ prepareArguments: normalizeBashArguments,
180
+ resolveResultDisposition: resolveBashResultDisposition,
136
181
  async run(args, options): Promise<ToolResult> {
182
+ args = normalizeBashArguments(args);
137
183
  if (typeof args.command !== "string" || args.command.length === 0) {
138
184
  return { kind: "error", message: "bash: missing command argument" };
139
185
  }
186
+ const outputPolicy = bashOutputPolicy(args);
187
+ if (outputPolicy === null) {
188
+ return {
189
+ kind: "error",
190
+ message: "bash: output_policy must be one of full, bounded, summary, or metadata-only (omit it for bounded)",
191
+ details: { outcome: "invalid-arguments", exitCode: null },
192
+ };
193
+ }
140
194
  const cwdArg = typeof args.cwd === "string" && args.cwd.length > 0 ? args.cwd : undefined;
141
195
  // Pin the child's cwd inside the session workspace in the tool itself.
142
196
  // The safety net blocks escaping cwd arguments at admission; this is the
@@ -161,41 +215,49 @@ export const bashTool: ToolSpec = {
161
215
  };
162
216
  if (options?.onUpdate !== undefined) {
163
217
  runOptions.onUpdate = (progress) => {
164
- options.onUpdate?.(shapeBashProgress(combineBashOutput(progress)));
218
+ options.onUpdate?.(shapeBashProgress(combineBashOutput(progress), progress.outputBytes, outputPolicy));
165
219
  };
166
220
  }
167
221
  const result = await runBashCommand(args.command, runOptions);
168
222
  const { error, aborted, timedOut, outputCapped } = result;
223
+ const rawOutput = combineBashOutput(result);
224
+ const output = rawOutput.trim();
169
225
  if (aborted) {
170
- return { kind: "error", message: "bash: command aborted" };
226
+ const status = "bash: command aborted";
227
+ return {
228
+ kind: "error",
229
+ message: output.length > 0 ? `${output}\n\n${status}` : status,
230
+ details: bashResultDetails(result, rawOutput, "abort"),
231
+ };
171
232
  }
172
- const shaped = shapeBashOutput(combineBashOutput(result), options);
173
- const output = shaped.text.trim();
174
233
  if (timedOut) {
175
234
  const status = `bash: command timed out after ${timeout}ms`;
176
- return withDetails(
177
- { kind: "error", message: output.length > 0 ? `${output}\n\n${status}` : status },
178
- shaped.details,
179
- );
235
+ return {
236
+ kind: "error",
237
+ message: output.length > 0 ? `${output}\n\n${status}` : status,
238
+ details: bashResultDetails(result, rawOutput, "timeout"),
239
+ };
180
240
  }
181
241
  if (outputCapped) {
182
242
  const status = `bash: command output exceeded ${BASH_HARD_CAP_BYTES} bytes and was stopped`;
183
- return withDetails(
184
- { kind: "error", message: output.length > 0 ? `${output}\n\n${status}` : status },
185
- shaped.details,
186
- );
243
+ return {
244
+ kind: "error",
245
+ message: output.length > 0 ? `${output}\n\n${status}` : status,
246
+ details: bashResultDetails(result, rawOutput, "output-cap"),
247
+ };
187
248
  }
188
249
  if (error) {
189
250
  const code = typeof error.code === "number" ? error.code : (error as { code?: string }).code;
190
251
  const status = `bash: command failed (exit ${code ?? "?"})`;
191
252
  const message = output.length > 0 ? `${output}\n\n${status}` : `${status}: ${error.message}`;
192
- return withDetails({ kind: "error", message }, shaped.details);
253
+ return { kind: "error", message, details: bashResultDetails(result, rawOutput, "nonzero") };
193
254
  }
194
- const body = shaped.text.length > 0 ? shaped.text : "(no output)";
195
- return withDetails(
196
- { kind: "ok", output: `${body}${observeToolsNudge(args.command, options?.sessionId)}` },
197
- shaped.details,
198
- );
255
+ const body = rawOutput.length > 0 ? rawOutput : "(no output)";
256
+ return {
257
+ kind: "ok",
258
+ output: `${body}${observeToolsNudge(args.command, options?.sessionId)}`,
259
+ details: bashResultDetails(result, rawOutput, "success"),
260
+ };
199
261
  } catch (err) {
200
262
  const msg = err instanceof Error ? err.message : String(err);
201
263
  return { kind: "error", message: `bash: ${msg}` };
@@ -1,4 +1,6 @@
1
+ import { BASH_HARD_CAP_BYTES } from "../core/bash-exec.js";
1
2
  import { type ToolName, ToolNames } from "../core/tool-names.js";
3
+ import { BASH_DEFAULT_RESULT_DISPOSITION } from "./bash.js";
2
4
  import { OBSERVATION_POLICY_SLACK_BYTES, OBSERVE_SELF_CAPS } from "./observation.js";
3
5
  import { toolPresentationPolicy } from "./presentation.js";
4
6
  import { readMaxBytes } from "./read.js";
@@ -93,7 +95,7 @@ const TOOL_METADATA: Readonly<Record<string, ToolMetadata>> = {
93
95
  ),
94
96
  costLatency: "local_fast",
95
97
  promptHint:
96
- 'Call context with scope="skills" to list installed and marketplace skills; when one matches the task, or the operator names a skill or asks how one works, suggest the operator run /skill <name> (a marketplace skill is offered for install) and never load it uninvited. When the user message carries a skill request, first load that skill via context (scope="skills", name=<skill>) before doing anything else.',
98
+ 'Call context with scope="skills" to list installed and marketplace skills; when one matches the task, or the operator names a skill or asks how one works, suggest the operator run /skill <name> (a marketplace skill is offered for install) and never load it uninvited. When the user message carries a skill request, first load that skill via context (scope="skills", name=<skill>) before doing anything else. When an [evicted ...] marker names content you need, recall it with context(scope="recall", ref=...); re-read the file only when the marker says it changed.',
97
99
  },
98
100
  [ToolNames.CredentialPresent]: {
99
101
  objective: "Check whether a credential key is present without returning its value.",
@@ -121,15 +123,19 @@ const TOOL_METADATA: Readonly<Record<string, ToolMetadata>> = {
121
123
  resultSizePolicy: exactMutationPolicy,
122
124
  costLatency: "local_fast",
123
125
  },
124
- // EXECUTE: the safe-exec spine (bash offloads its own overflow).
126
+ // EXECUTE: the safe-exec spine (canonical shaping offloads Bash overflow).
125
127
  [ToolNames.Bash]: {
126
128
  objective: "Execute an explicit shell command when narrower tools are insufficient.",
127
129
  uiLabel: "Shell",
128
130
  retrySafety: "unknown",
129
- resultSizePolicy: summaryPolicy(
130
- "Use a narrower command or a dedicated verification/read/search tool to inspect omitted output.",
131
- ),
131
+ resultSizePolicy: {
132
+ ...summaryPolicy("Use a narrower command or a dedicated verification/read/search tool to inspect omitted output."),
133
+ offloadMaxBytes: BASH_HARD_CAP_BYTES,
134
+ },
135
+ resultDisposition: BASH_DEFAULT_RESULT_DISPOSITION,
132
136
  costLatency: "local_slow",
137
+ promptHint:
138
+ "Bash output_policy defaults to bounded diagnostic tail. Use summary for noisy commands, metadata-only when only outcome and retrieval matter, and full only when output is known to fit the bounded result budget.",
133
139
  },
134
140
  [ToolNames.Git]: {
135
141
  objective: "Read-only git inspection: status, diff, or log.",
@@ -1,7 +1,15 @@
1
1
  import { type Dirent, readdirSync } from "node:fs";
2
2
  import path from "node:path";
3
+ import type { ContextRecalledPayload } from "../../core/bus-events.js";
3
4
  import { SKILL_SUGGESTION_ANCHOR } from "../../core/skill-activation.js";
4
5
  import { ToolNames } from "../../core/tool-names.js";
6
+ import { foldWorkingSet } from "../../domains/context/working-set/fold.js";
7
+ import {
8
+ buildRecallFields,
9
+ recallErrorMessage,
10
+ recallParentTurnId,
11
+ resolveRecall,
12
+ } from "../../domains/context/working-set/recall.js";
5
13
  import {
6
14
  checkSkillDrift,
7
15
  discoverMarketplaceSkills,
@@ -11,6 +19,8 @@ import {
11
19
  modelVisibleSkills,
12
20
  type Skill,
13
21
  } from "../../domains/resources/index.js";
22
+ import type { SessionEntryInput } from "../../domains/session/contract.js";
23
+ import type { SessionEntry } from "../../domains/session/entries.js";
14
24
  import type { WorkspaceSnapshot } from "../../domains/session/workspace/index.js";
15
25
  import {
16
26
  finalizeObservation,
@@ -30,7 +40,8 @@ import { contextToolSurface } from "./surface.js";
30
40
  * workspace snapshot, scope=docs retrieves cited sections from Clio's bundled
31
41
  * documentation, scope=skills lists available skills or loads a requested
32
42
  * skill body (the skill-activation and pending-request contracts are
33
- * unchanged from the absorbed read_skill tool).
43
+ * unchanged from the absorbed read_skill tool), scope=recall readmits an
44
+ * evicted tool-result body by ref and records the `contextRecall` entry.
34
45
  */
35
46
 
36
47
  const DEFAULT_TREE_ENTRIES = 50;
@@ -42,6 +53,17 @@ export interface ContextWorkspaceDeps {
42
53
  saveSnapshot(snapshot: WorkspaceSnapshot): void;
43
54
  }
44
55
 
56
+ /** Ledger access for scope=recall: read the full ledger, fold it at the live leaf, append the recall record. */
57
+ export interface ContextSessionDeps {
58
+ hasSession(): boolean;
59
+ readEntries(): ReadonlyArray<SessionEntry>;
60
+ /** The live append point (`/tree` pin or tree leaf); undefined lets the fold infer it. */
61
+ activeLeafTurnId(): string | undefined;
62
+ appendEntry(entry: SessionEntryInput): SessionEntry;
63
+ /** Called after the recall entry is recorded; the orchestrator publishes it as BusChannels.ContextRecalled. */
64
+ onRecalled?: (payload: ContextRecalledPayload) => void;
65
+ }
66
+
45
67
  export interface ContextToolDeps {
46
68
  getCwd?: () => string;
47
69
  getSkillLoaderOptions?: () => Pick<
@@ -50,6 +72,8 @@ export interface ContextToolDeps {
50
72
  >;
51
73
  /** Absent in worker registries without a session; scope=workspace errors cleanly. */
52
74
  workspace?: ContextWorkspaceDeps;
75
+ /** Absent in worker registries without a session; scope=recall errors cleanly. */
76
+ session?: ContextSessionDeps;
53
77
  /**
54
78
  * Whether scope=skills may list marketplace entries beside installed
55
79
  * skills. Worker registries set false: a worker can neither install a
@@ -144,7 +168,7 @@ function policyIsRecipeBound(policy: { requests: ReadonlyArray<{ source: string
144
168
  // load it (only the operator activates), so its move is suggest-and-wait.
145
169
  const NO_PENDING_SKILL_DENIAL =
146
170
  "context: no pending skill request is active this turn; only the operator can activate a skill, so do not retry this load. " +
147
- `If a listed skill matches the task, open your reply with the line \`${SKILL_SUGGESTION_ANCHOR}\` and wait for the operator to run it; otherwise continue without skills.`;
171
+ `If a listed skill matches the task, open your reply with the line \`${SKILL_SUGGESTION_ANCHOR}\` and continue the task without it; only the operator can run it. Otherwise continue without skills.`;
148
172
 
149
173
  function pendingSkillPolicyError(name: string, options: ToolInvokeOptions | undefined): string | null {
150
174
  const policy = options?.pendingSkillPolicy;
@@ -263,7 +287,7 @@ function renderSkillsList(
263
287
  // template where they skip conditional prose in the header.
264
288
  lines.push(
265
289
  "",
266
- `If one skill above matches the current task, begin your reply with the line \`${SKILL_SUGGESTION_ANCHOR}\` (a comma-separated sequence, in order, when several compose) and wait for the operator to run it. If none match, do not mention skills.`,
290
+ `If one skill above matches the current task, begin your reply with the line \`${SKILL_SUGGESTION_ANCHOR}\` (a comma-separated sequence, in order, when several compose), then continue the task in the same turn without it; only the operator can run it. If none match, do not mention skills.`,
267
291
  );
268
292
  return lines.join("\n");
269
293
  }
@@ -482,13 +506,90 @@ function runSkillsScope(
482
506
  });
483
507
  }
484
508
 
509
+ /**
510
+ * scope=recall: the body goes back through the observation envelope like any
511
+ * OBSERVE result, so the per-turn pool and the self cap still apply; an
512
+ * oversize body is offloaded by the envelope and the notice carries the
513
+ * pointer. A body whose original result was itself offloaded already ends in
514
+ * that tool's own `full: <path>` pointer, which is what the model gets back;
515
+ * the file is never inlined. The `contextRecall` entry is appended before the
516
+ * result returns; it is the churn record, not an un-eviction, so the marker
517
+ * and the prefix cache stay where they are.
518
+ */
519
+ function runRecallScope(
520
+ deps: ContextToolDeps,
521
+ args: Record<string, unknown>,
522
+ reservation: ObservationReservation,
523
+ options: ToolInvokeOptions | undefined,
524
+ ): ToolResult {
525
+ const session = deps.session;
526
+ if (!session?.hasSession()) {
527
+ return { kind: "error", message: "context: recall scope requires a bound session; none is active here" };
528
+ }
529
+ const ref = typeof args.ref === "string" ? args.ref.trim() : "";
530
+ if (ref.length === 0) {
531
+ return {
532
+ kind: "error",
533
+ message: "context: recall scope requires ref=<turnId>, the ref named in the [evicted ...] marker",
534
+ };
535
+ }
536
+ const entries = session.readEntries();
537
+ const leaf = session.activeLeafTurnId();
538
+ const view = foldWorkingSet(entries, leaf);
539
+ const resolved = resolveRecall(entries, view, ref, leaf);
540
+ if (!resolved.ok) return { kind: "error", message: `context: ${recallErrorMessage(resolved.error, entries, view)}` };
541
+ const { result } = resolved;
542
+ const fields = buildRecallFields(result, {
543
+ trigger: "tool",
544
+ ...(options?.toolCallId ? { toolCallId: options.toolCallId } : {}),
545
+ });
546
+ // The recall record parents onto the live leaf so the fold sees it on
547
+ // this branch and only this branch.
548
+ const parentTurnId = recallParentTurnId(entries, leaf);
549
+ let recorded: SessionEntry;
550
+ try {
551
+ recorded = session.appendEntry({ ...fields, parentTurnId });
552
+ } catch (err) {
553
+ return {
554
+ kind: "error",
555
+ message: `context: recall of ${result.ref.entry} could not be recorded: ${err instanceof Error ? err.message : String(err)}`,
556
+ };
557
+ }
558
+ session.onRecalled?.({ ref: result.ref.entry, trigger: "tool", tokensReadmitted: result.tokens, at: Date.now() });
559
+ const evictedState = view.evicted.get(result.ref.entry);
560
+ const truncation = truncateHead(result.body, {
561
+ maxBytes: reservation.callCapBytes,
562
+ maxLines: Number.MAX_SAFE_INTEGER,
563
+ });
564
+ return finalizeObservation({
565
+ tool: ToolNames.Context,
566
+ unit: "results",
567
+ output: truncation.content,
568
+ ...(truncation.truncated ? { fullOutput: result.body } : {}),
569
+ shownCount: 1,
570
+ totalCount: 1,
571
+ truncated: truncation.truncated,
572
+ details: {
573
+ recall: {
574
+ ref: result.ref.entry,
575
+ tokensReadmitted: result.tokens,
576
+ recallTurnId: recorded.turnId,
577
+ ...(evictedState ? { reason: evictedState.reason, evictedAtTurnId: evictedState.evictedAtTurnId } : {}),
578
+ ...(result.offloadPath !== undefined ? { offloadPath: result.offloadPath } : {}),
579
+ },
580
+ },
581
+ reservation,
582
+ ...(options ? { options } : {}),
583
+ });
584
+ }
585
+
485
586
  export function createContextTool(deps: ContextToolDeps = {}): ToolSpec {
486
587
  return {
487
588
  ...contextToolSurface,
488
589
  async run(args, options): Promise<ToolResult> {
489
590
  const scope = typeof args.scope === "string" ? args.scope : "";
490
- if (scope !== "workspace" && scope !== "docs" && scope !== "skills") {
491
- return { kind: "error", message: `context: scope must be workspace, docs, or skills; got '${scope}'` };
591
+ if (scope !== "workspace" && scope !== "docs" && scope !== "skills" && scope !== "recall") {
592
+ return { kind: "error", message: `context: scope must be workspace, docs, skills, or recall; got '${scope}'` };
492
593
  }
493
594
  const selfCap =
494
595
  scope === "docs"
@@ -508,6 +609,7 @@ export function createContextTool(deps: ContextToolDeps = {}): ToolSpec {
508
609
  }
509
610
  if (scope === "workspace") return runWorkspaceScope(deps, reservation, options);
510
611
  if (scope === "docs") return runDocsScope(args, reservation, options);
612
+ if (scope === "recall") return runRecallScope(deps, args, reservation, options);
511
613
  return runSkillsScope(deps, args, reservation, options);
512
614
  },
513
615
  };
@@ -6,12 +6,13 @@ import type { ToolSurface } from "../lazy-tool.js";
6
6
  export const contextToolSurface = {
7
7
  name: ToolNames.Context,
8
8
  description:
9
- "Environment context: scope=workspace returns the git/project snapshot, scope=docs searches Clio's bundled documentation (omit query to list the corpus), scope=skills lists installed and marketplace skills or loads an installed one by name. For repository code and the repo's generated wiki use code_nav (mode=wiki).",
9
+ "Environment context: scope=workspace returns the git/project snapshot, scope=docs searches Clio's bundled documentation (omit query to list the corpus), scope=skills lists installed and marketplace skills or loads an installed one by name, scope=recall returns the exact body of an evicted tool result by ref (the turnId named in an [evicted ...] marker). For repository code and the repo's generated wiki use code_nav (mode=wiki).",
10
10
  parameters: Type.Object({
11
- scope: StringEnum(["workspace", "docs", "skills"], { description: "Context source." }),
11
+ scope: StringEnum(["workspace", "docs", "skills", "recall"], { description: "Context source." }),
12
12
  query: Type.Optional(Type.String({ description: "scope=docs: question or terms; omit to list the corpus." })),
13
13
  name: Type.Optional(Type.String({ description: "scope=skills: skill name to load; omit to list." })),
14
14
  limit: Type.Optional(Type.Number({ description: "scope=docs: max sections (default 5, max 12)." })),
15
+ ref: Type.Optional(Type.String({ description: "scope=recall: ref of the evicted item, as named in its marker." })),
15
16
  include_tree: Type.Optional(Type.Boolean({ description: "scope=skills: list files under the skill base_dir." })),
16
17
  }),
17
18
  baseActionClass: "read",
@@ -1,5 +1,7 @@
1
+ import type { ContextRecalledPayload } from "../core/bus-events.js";
1
2
  import type { LoadSkillsInput } from "../domains/resources/index.js";
2
3
  import type { SessionContract } from "../domains/session/contract.js";
4
+ import type { SessionEntry } from "../domains/session/entries.js";
3
5
  import { createTaskBoardStore, type TaskBoardStore } from "../domains/session/task-board.js";
4
6
  import type { UserTasksStore } from "../domains/user-tasks/store.js";
5
7
  import type { AgentLedgerPort } from "../worker/protocol.js";
@@ -28,6 +30,10 @@ import { writeTool } from "./write.js";
28
30
 
29
31
  export interface CoreToolBootstrapDeps {
30
32
  session?: SessionContract;
33
+ /** Full ledger of the current session; context(scope=recall) folds it. Absent in worker registries. */
34
+ readSessionEntries?: () => ReadonlyArray<SessionEntry>;
35
+ /** Publishes a successful context(scope=recall) on the bus; absent where no bus is wired. */
36
+ onContextRecalled?: (payload: ContextRecalledPayload) => void;
31
37
  askUser?: AskUserHandler;
32
38
  taskBoard?: TaskBoardStore;
33
39
  userTasks?: UserTasksStore;
@@ -108,6 +114,7 @@ export function registerCoreTools(registry: ToolRegistry, deps: CoreToolBootstra
108
114
  ...builtin(credentialPresentTool, { path: "src/tools/credential-present.ts", scope: "core" }),
109
115
  });
110
116
  const session = deps.session;
117
+ const readSessionEntries = deps.readSessionEntries;
111
118
  registry.register({
112
119
  ...builtin(
113
120
  lazyTool(contextToolSurface, async () => {
@@ -116,6 +123,20 @@ export function registerCoreTools(registry: ToolRegistry, deps: CoreToolBootstra
116
123
  const { probeWorkspace } = await import("../domains/session/workspace/index.js");
117
124
  return createContextTool({
118
125
  ...skillToolDeps,
126
+ ...(readSessionEntries
127
+ ? {
128
+ session: {
129
+ hasSession: () => session.current() !== null,
130
+ readEntries: readSessionEntries,
131
+ activeLeafTurnId: () => {
132
+ const meta = session.current();
133
+ return meta ? (session.tree(meta.id).leafId ?? undefined) : undefined;
134
+ },
135
+ appendEntry: (entry) => session.appendEntry(entry),
136
+ ...(deps.onContextRecalled ? { onRecalled: deps.onContextRecalled } : {}),
137
+ },
138
+ }
139
+ : {}),
119
140
  workspace: {
120
141
  hasSession: () => session.current() !== null,
121
142
  getSnapshot: () => session.current()?.workspace ?? null,
@@ -2,6 +2,7 @@
2
2
 
3
3
  import type { AgentAutomationAuthority } from "../domains/agents/spec.js";
4
4
  import { type AgentTaskType, classifyAgentTask } from "../domains/dispatch/agent-candidates.js";
5
+ import { cloneDispatchBudgetRequest } from "../domains/dispatch/budget-envelope.js";
5
6
  import type { DispatchRequest } from "../domains/dispatch/contract.js";
6
7
  import { type AgentRoleFactsResolver, requestExecutionRole } from "../domains/dispatch/execution-role.js";
7
8
  import { parseRoutingIntent } from "../domains/dispatch/routing-intent.js";
@@ -171,6 +172,13 @@ function dispatchRequestFromArgs(
171
172
  }
172
173
  request.thinkingLevel = thinkingLevel as JobThinkingLevel;
173
174
  }
175
+ if (args.budget !== undefined) {
176
+ try {
177
+ request.budget = cloneDispatchBudgetRequest(args.budget);
178
+ } catch (error) {
179
+ return { ok: false, message: error instanceof Error ? error.message : String(error) };
180
+ }
181
+ }
174
182
  return { ok: true, request };
175
183
  }
176
184
 
@@ -1,6 +1,25 @@
1
+ import { responseModelIdObservationFromRecord } from "../core/response-model-id.js";
1
2
  import { durableAssistantTextFromEvent } from "../domains/dispatch/event-pump.js";
3
+ import type { RunReceipt } from "../domains/dispatch/types.js";
2
4
 
3
5
  /** Return the durable final assistant text carried by a worker event. */
4
6
  export function assistantTextFromEvent(event: unknown): string {
5
7
  return durableAssistantTextFromEvent(event).trim();
6
8
  }
9
+
10
+ /** Human projection of the response model-id observations carried by a receipt. */
11
+ export function receiptResponseModelIdObservationLabel(receipt: Pick<RunReceipt, "upstreamResponses">): string | null {
12
+ const responses = receipt.upstreamResponses ?? [];
13
+ if (responses.length === 0) return null;
14
+ const labels = responses.map((response) => {
15
+ const observation = responseModelIdObservationFromRecord(
16
+ response as unknown as Record<string, unknown>,
17
+ "legacy-difference-only",
18
+ );
19
+ if (observation.state === "reported") return `reported:${observation.reportedModelId}`;
20
+ if (observation.state === "not-reported") return "not-reported";
21
+ if (observation.state === "not-observed") return "not-observed";
22
+ return `legacy-difference-only:${observation.differingModelId ?? "none"}`;
23
+ });
24
+ return `response_model_id_observation=${labels.join(",")}`;
25
+ }