@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
@@ -10,15 +10,25 @@ import {
10
10
  BusChannels,
11
11
  type ContextActivityStatus,
12
12
  type ContextPrunedPayload,
13
+ type ContextRecalledPayload,
13
14
  type ContextWarningPayload,
14
15
  } from "../core/bus-events.js";
15
16
  import type { ClioSettings } from "../core/config.js";
16
17
  import type { SafeEventBus } from "../core/event-bus.js";
17
18
  import type { ToolName } from "../core/tool-names.js";
19
+ import { buildEvictionFields, planEviction } from "../domains/context/working-set/engine.js";
20
+ import { foldWorkingSet } from "../domains/context/working-set/fold.js";
21
+ import { resolveWorkingSetPolicy } from "../domains/context/working-set/policies/index.js";
22
+ import { selectVisibleEntries } from "../domains/context/working-set/visible.js";
18
23
  import type { ObservabilityContract } from "../domains/observability/contract.js";
19
24
  import type { CompiledSessionPrompt, SessionPromptInputs } from "../domains/prompts/compiler.js";
20
25
  import type { PromptsContract } from "../domains/prompts/contract.js";
21
- import type { ProvidersContract } from "../domains/providers/index.js";
26
+ import {
27
+ type ContextWindowDetails,
28
+ type ContextWindowSource,
29
+ type ProvidersContract,
30
+ resolveRuntimeTarget,
31
+ } from "../domains/providers/index.js";
22
32
  import type { LocalModelQuirks } from "../domains/providers/types/local-model-quirks.js";
23
33
  import {
24
34
  AutoCompactionTrigger,
@@ -27,6 +37,7 @@ import {
27
37
  } from "../domains/session/compaction/auto.js";
28
38
  import type { CompactResult } from "../domains/session/compaction/compact.js";
29
39
  import { maskStaleObservations } from "../domains/session/compaction/mask-observations.js";
40
+ import { estimateTokens } from "../domains/session/compaction/tokens.js";
30
41
  import {
31
42
  appendContextSnapshot,
32
43
  type CaptureContextSnapshotInput,
@@ -57,7 +68,7 @@ import {
57
68
  sumRunUsage,
58
69
  toolNamesFromAgentState,
59
70
  } from "./chat-loop-messages.js";
60
- import { buildReplayAgentMessagesFromTurns } from "./chat-renderer.js";
71
+ import { buildModelReplayAgentMessagesFromTurns } from "./model-session-replay.js";
61
72
  import { renderCompactionSummaryLine } from "./renderers/compaction-summary.js";
62
73
  import type { TurnMiddleware } from "./turn-middleware.js";
63
74
  import type { AgentRuntime, ChatTurnState } from "./turn-state.js";
@@ -73,6 +84,8 @@ export interface TurnContextDeps {
73
84
  bus?: SafeEventBus | undefined;
74
85
  readSessionEntries?: (() => ReadonlyArray<SessionEntry>) | undefined;
75
86
  autoCompact?: ((instructions?: string, trigger?: CompactionTrigger) => Promise<CompactResult | null>) | undefined;
87
+ /** Test seam for the eviction planner; production uses `planEviction` from the working-set engine. */
88
+ planEviction?: typeof planEviction;
76
89
  getMemorySection?: (() => string) | undefined;
77
90
  middleware: TurnMiddleware;
78
91
  emitNotice: (text: string) => void;
@@ -159,6 +172,11 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
159
172
  let sessionPromptKey: string | null = null;
160
173
  const sessionWorkingContextPaths = new Set<string>();
161
174
  let pendingPromptLogEntry: SessionPromptCompileRecord | null = null;
175
+ // A post-tool guard can run after every tool result in one model turn. Once
176
+ // both automatic stages report that they have nothing to do, remember that
177
+ // stable user-turn id so the remaining results do not repeat the same plan
178
+ // and summary probes. A new submitted user turn gets a new id naturally.
179
+ let emptyAutoCompactTurnId: string | null = null;
162
180
 
163
181
  // Cache-disturbance honesty (T3.3). Dispatch traffic and history
164
182
  // compaction invalidate a single-slot local backend's prefix cache.
@@ -167,20 +185,41 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
167
185
  // its first assistant entry, and shows one dim notice.
168
186
  const pendingColdReasons = new Set<string>();
169
187
  let runExpectedColdReasons: string[] = [];
170
- let stampColdReasonsPending = false;
188
+ let nextAssistantColdReasons: string[] = [];
189
+ // A working-set eviction changes the prefix itself, so it cools every
190
+ // tier's cache, not only a single-slot local one. Dispatch and compaction
191
+ // disturbances keep the local-native gate below.
192
+ const TIER_INDEPENDENT_COLD_REASONS: ReadonlySet<string> = new Set(["working_set_evict"]);
193
+ const stampsOnTier = (reason: string, runtimeId: string | undefined): boolean =>
194
+ TIER_INDEPENDENT_COLD_REASONS.has(reason) ||
195
+ (runtimeId !== undefined && deps.providers.getRuntime(runtimeId)?.tier === "local-native");
196
+ const noteColdReason = (reason: string): void => {
197
+ if (!state.streaming) {
198
+ pendingColdReasons.add(reason);
199
+ return;
200
+ }
201
+ if (!stampsOnTier(reason, state.runtime?.runtimeId)) return;
202
+ if (!runExpectedColdReasons.includes(reason)) runExpectedColdReasons.push(reason);
203
+ if (nextAssistantColdReasons.includes(reason)) return;
204
+ nextAssistantColdReasons.push(reason);
205
+ deps.emitNotice(`[context engine] backend prefix cache likely cold this turn: ${reason}`);
206
+ };
171
207
  const unsubscribeColdReasonSources = [
172
208
  ...[BusChannels.DispatchStarted, BusChannels.DispatchCompleted, BusChannels.DispatchFailed].map(
173
209
  (channel) =>
174
210
  deps.bus?.on(channel, () => {
175
- pendingColdReasons.add("dispatch");
211
+ noteColdReason("dispatch");
176
212
  }) ?? null,
177
213
  ),
178
214
  ...[BusChannels.CompactionBegin, BusChannels.CompactionEnd].map(
179
215
  (channel) =>
180
216
  deps.bus?.on(channel, () => {
181
- pendingColdReasons.add("compaction");
217
+ noteColdReason("compaction");
182
218
  }) ?? null,
183
219
  ),
220
+ deps.bus?.on(BusChannels.ContextRecalled, (payload: ContextRecalledPayload) => {
221
+ middleware.fireCompactionHook("working_set_recall", payload.trigger);
222
+ }) ?? null,
184
223
  ];
185
224
 
186
225
  /**
@@ -275,6 +314,75 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
275
314
  return ceilChars(snapshot.pendingUserInput.length);
276
315
  };
277
316
 
317
+ /**
318
+ * The window the next turn will resolve to, read the way ensureRuntime
319
+ * reads it but without building an agent. Before the first turn of a
320
+ * process, /resume included, state.runtime is null, and the ledger and the
321
+ * footer meter said `context window unknown · 0 tokens` until a turn ran
322
+ * (issue #189). Null when no target is configured or it does not resolve,
323
+ * and the overlay's "unknown" is then true.
324
+ */
325
+ const resolveWindowWithoutRuntime = (): ContextWindowDetails | null => {
326
+ const settings = deps.getSettings();
327
+ const targetId = settings.orchestrator?.target?.trim();
328
+ const wireModelId = settings.orchestrator?.model?.trim();
329
+ if (!targetId || !wireModelId) return null;
330
+ const resolved = resolveRuntimeTarget(deps.providers, {
331
+ targetId,
332
+ wireModelId,
333
+ requestedThinkingLevel: settings.orchestrator?.thinkingLevel ?? "off",
334
+ use: "orchestrator",
335
+ requireTools: false,
336
+ requireOutputBudget: true,
337
+ });
338
+ return resolved.ok ? resolved.target.contextWindowDetails : null;
339
+ };
340
+
341
+ const CONTEXT_WINDOW_SOURCES: ReadonlySet<string> = new Set<ContextWindowSource>([
342
+ "catalog",
343
+ "probe",
344
+ "loaded",
345
+ "target-override",
346
+ "model-hint",
347
+ "descriptor-default",
348
+ "unknown",
349
+ ]);
350
+
351
+ /** The snapshot stores its source as a plain string; only a known label is worth repeating. */
352
+ const snapshotWindowSource = (snapshot: ContextSnapshot): ContextWindowSource | null =>
353
+ CONTEXT_WINDOW_SOURCES.has(snapshot.contextWindowSource)
354
+ ? (snapshot.contextWindowSource as ContextWindowSource)
355
+ : null;
356
+
357
+ /**
358
+ * Window facts while no runtime exists: the live resolution first, then the
359
+ * resumed snapshot's recorded window, which is what the previous process
360
+ * measured the same messages against.
361
+ */
362
+ const windowWithoutRuntime = (): {
363
+ contextWindow: number;
364
+ contextWindowSource: ContextWindowSource | null;
365
+ contextWindowSlots: ContextWindowDetails["contextWindowSlots"];
366
+ } => {
367
+ const details = resolveWindowWithoutRuntime();
368
+ if (details) {
369
+ return {
370
+ contextWindow: details.effectiveContextWindow,
371
+ contextWindowSource: details.contextWindowSource,
372
+ contextWindowSlots: details.contextWindowSlots,
373
+ };
374
+ }
375
+ const snapshot = currentContextSnapshot;
376
+ if (snapshot && snapshot.effectiveContextWindow > 0) {
377
+ return {
378
+ contextWindow: snapshot.effectiveContextWindow,
379
+ contextWindowSource: snapshotWindowSource(snapshot),
380
+ contextWindowSlots: null,
381
+ };
382
+ }
383
+ return { contextWindow: 0, contextWindowSource: null, contextWindowSlots: null };
384
+ };
385
+
278
386
  const liveContextEstimate = (agentRuntime: AgentRuntime, pendingUserText?: string): LiveContextEstimate => {
279
387
  const contextWindow = agentRuntime.runtimeResolution.contextWindowDetails.effectiveContextWindow;
280
388
  const estimateInput = {
@@ -292,7 +400,7 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
292
400
 
293
401
  const refreshAgentMessagesFromSession = (agentRuntime: AgentRuntime): ReadonlyArray<SessionEntry> => {
294
402
  const refreshedEntries = deps.readSessionEntries?.() ?? [];
295
- agentRuntime.agent.state.messages = buildReplayAgentMessagesFromTurns(refreshedEntries, {
403
+ agentRuntime.agent.state.messages = buildModelReplayAgentMessagesFromTurns(refreshedEntries, {
296
404
  ...(state.lastTurnId ? { activeLeafTurnId: state.lastTurnId } : {}),
297
405
  });
298
406
  state.replayedContextMessages = [];
@@ -339,12 +447,12 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
339
447
 
340
448
  /**
341
449
  * Two-mechanism context protection. When pressure crosses the single
342
- * threshold, first mask the bodies of tool observations older than
343
- * `excludeLastTurns` (cheap, no LLM call). If pressure stays above the
344
- * threshold, delegate to the pi-style LLM compaction path: append a
345
- * compaction summary entry, then replay from the session view.
450
+ * threshold, first apply a non-destructive working-set eviction. If pressure
451
+ * stays above the threshold, delegate to the pi-style LLM compaction path:
452
+ * append a compaction summary entry, then replay from the session view. The
453
+ * destructive observation mask remains only as a one-release escape hatch.
346
454
  *
347
- * `force = true` skips the pressure check and the mask pre-stage and runs
455
+ * `force = true` skips the pressure check and every pre-stage and runs
348
456
  * the LLM summary directly. Used for `/context compact`, CLIO_CODER_FORCE_COMPACT=1,
349
457
  * and overflow recovery.
350
458
  */
@@ -355,11 +463,17 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
355
463
  triggerOverride?: CompactionTrigger,
356
464
  pendingUserText?: string,
357
465
  ): Promise<boolean> => {
358
- if (!deps.autoCompact || !deps.readSessionEntries) return false;
466
+ if (!deps.readSessionEntries) return false;
467
+ const activeAutoTurnId = force ? null : state.activeUserTurnId;
468
+ if (activeAutoTurnId && emptyAutoCompactTurnId === activeAutoTurnId) return false;
359
469
  const settings = deps.getSettings();
360
470
  const cfg = settings.compaction;
361
471
  const autoEnabled = cfg?.auto !== false;
362
472
  if (!force && !autoEnabled) return false;
473
+ let preSummaryStageActed = false;
474
+ const rememberEmptyAutomaticAttempt = (): void => {
475
+ if (!force && !preSummaryStageActed && activeAutoTurnId) emptyAutoCompactTurnId = activeAutoTurnId;
476
+ };
363
477
 
364
478
  const compactionThreshold = cfg?.threshold ?? DEFAULT_COMPACTION_THRESHOLD;
365
479
  const trigger: CompactionTrigger = triggerOverride ?? (force ? "force" : "auto");
@@ -369,91 +483,198 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
369
483
  const verdict = shouldCompact(estimate.tokens, compactionThreshold, estimate.contextWindow);
370
484
  if (!verdict.shouldCompact) return false;
371
485
 
372
- // Mechanism B pre-stage: mask stale observations before paying for
373
- // an LLM summary. History rewrites here invalidate the backend
374
- // prefix cache; the "compaction" expectedColdReasons stamp from the
375
- // CompactionBegin/End subscription explains the next cold turn.
486
+ // One-release compatibility escape hatch. This is the destructive
487
+ // pre-stage that working-set eviction replaces; keep it byte-for-byte
488
+ // reachable only when explicitly requested.
376
489
  if (deps.session?.current()) {
377
490
  const beforeSnapshotId = currentContextSnapshot?.snapshotId ?? null;
378
- let masked: ReturnType<typeof maskStaleObservations>;
379
- try {
380
- masked = maskStaleObservations(deps.readSessionEntries() ?? [], cfg?.excludeLastTurns ?? 6);
381
- } catch (error) {
382
- middleware.fireCompactionHook("mask_observations", trigger, estimate.tokens);
383
- deps.bus?.emit(BusChannels.CompactionBegin, { trigger, at: Date.now() });
384
- emitCompactionActivity("started", "compacting context (mask stage)");
385
- emitCompactionActivity("failed", compactionFailureMessage(error));
386
- deps.bus?.emit(BusChannels.CompactionEnd, { trigger, at: Date.now() });
387
- throw error;
388
- }
389
- if (masked.changed) {
390
- middleware.fireCompactionHook("mask_observations", trigger, estimate.tokens);
391
- deps.bus?.emit(BusChannels.CompactionBegin, { trigger, at: Date.now() });
392
- emitCompactionActivity("started", "compacting context (mask stage)");
393
- deps.session.replaceEntries(masked.entries);
394
- refreshAgentMessagesFromSession(agentRuntime);
395
- deps.bus?.emit(BusChannels.CompactionEnd, { trigger, at: Date.now() });
396
-
397
- const postMaskSnapshot = captureRuntimeContextSnapshot(
398
- agentRuntime,
399
- state.activeUserTurnId || "compaction",
400
- compactionThreshold,
401
- );
402
- currentContextSnapshot = postMaskSnapshot;
403
- persistContextSnapshot(postMaskSnapshot);
404
-
405
- const tokensAfterMask = snapshotInputTokens(postMaskSnapshot);
406
- lastCompactionEvent = {
407
- stage: "mask_observations",
408
- tokensBefore: estimate.tokens,
409
- tokensAfter: tokensAfterMask,
410
- trigger,
411
- };
412
- deps.bus?.emit(BusChannels.ContextPruned, {
413
- stage: "mask_observations",
414
- pressure: verdict.pressure,
415
- tokensBefore: estimate.tokens,
416
- tokensAfter: tokensAfterMask,
417
- maskedObservations: masked.maskedObservations,
418
- maskedThinkingBlocks: masked.maskedThinkingBlocks,
419
- maskedThinkingChars: masked.maskedThinkingChars,
420
- trigger,
421
- snapshotIdBefore: beforeSnapshotId,
422
- snapshotIdAfter: postMaskSnapshot.snapshotId,
423
- at: Date.now(),
424
- } satisfies ContextPrunedPayload);
425
- emitCompactionActivity("completed", `${masked.maskedObservations} observations masked`);
426
- const thinkingNote =
427
- masked.maskedThinkingBlocks > 0
428
- ? `, ${masked.maskedThinkingBlocks} thinking blocks dropped (~${masked.maskedThinkingChars} chars)`
429
- : "";
430
- deps.emitNotice(
431
- `[context engine] mask_observations: ${masked.maskedObservations} observations masked${thinkingNote}; ~${estimate.tokens} tokens -> ~${tokensAfterMask} tokens`,
432
- );
491
+ if (process.env.CLIO_CODER_LEGACY_MASK === "1") {
492
+ let masked: ReturnType<typeof maskStaleObservations>;
493
+ try {
494
+ masked = maskStaleObservations(deps.readSessionEntries() ?? [], cfg?.excludeLastTurns ?? 6);
495
+ } catch (error) {
496
+ middleware.fireCompactionHook("mask_observations", trigger, estimate.tokens);
497
+ deps.bus?.emit(BusChannels.CompactionBegin, { trigger, at: Date.now() });
498
+ emitCompactionActivity("started", "compacting context (mask stage)");
499
+ emitCompactionActivity("failed", compactionFailureMessage(error));
500
+ deps.bus?.emit(BusChannels.CompactionEnd, { trigger, at: Date.now() });
501
+ throw error;
502
+ }
503
+ if (masked.changed) {
504
+ preSummaryStageActed = true;
505
+ middleware.fireCompactionHook("mask_observations", trigger, estimate.tokens);
506
+ deps.bus?.emit(BusChannels.CompactionBegin, { trigger, at: Date.now() });
507
+ emitCompactionActivity("started", "compacting context (mask stage)");
508
+ deps.session.replaceEntries(masked.entries);
509
+ refreshAgentMessagesFromSession(agentRuntime);
510
+ deps.bus?.emit(BusChannels.CompactionEnd, { trigger, at: Date.now() });
511
+
512
+ const postMaskSnapshot = captureRuntimeContextSnapshot(
513
+ agentRuntime,
514
+ state.activeUserTurnId || "compaction",
515
+ compactionThreshold,
516
+ );
517
+ currentContextSnapshot = postMaskSnapshot;
518
+ persistContextSnapshot(postMaskSnapshot);
433
519
 
434
- const after = liveContextEstimate(agentRuntime, pendingUserText);
435
- if (!shouldCompact(after.tokens, compactionThreshold, after.contextWindow).shouldCompact) return true;
520
+ const tokensAfterMask = snapshotInputTokens(postMaskSnapshot);
521
+ lastCompactionEvent = {
522
+ stage: "mask_observations",
523
+ tokensBefore: estimate.tokens,
524
+ tokensAfter: tokensAfterMask,
525
+ trigger,
526
+ };
527
+ deps.bus?.emit(BusChannels.ContextPruned, {
528
+ stage: "mask_observations",
529
+ pressure: verdict.pressure,
530
+ tokensBefore: estimate.tokens,
531
+ tokensAfter: tokensAfterMask,
532
+ maskedObservations: masked.maskedObservations,
533
+ maskedThinkingBlocks: masked.maskedThinkingBlocks,
534
+ maskedThinkingChars: masked.maskedThinkingChars,
535
+ trigger,
536
+ snapshotIdBefore: beforeSnapshotId,
537
+ snapshotIdAfter: postMaskSnapshot.snapshotId,
538
+ at: Date.now(),
539
+ } satisfies ContextPrunedPayload);
540
+ emitCompactionActivity("completed", `${masked.maskedObservations} observations masked`);
541
+ const thinkingNote =
542
+ masked.maskedThinkingBlocks > 0
543
+ ? `, ${masked.maskedThinkingBlocks} thinking blocks dropped (~${masked.maskedThinkingChars} chars)`
544
+ : "";
545
+ deps.emitNotice(
546
+ `[context engine] mask_observations: ${masked.maskedObservations} observations masked${thinkingNote}; ~${estimate.tokens} tokens -> ~${tokensAfterMask} tokens`,
547
+ );
548
+
549
+ const after = liveContextEstimate(agentRuntime, pendingUserText);
550
+ if (!shouldCompact(after.tokens, compactionThreshold, after.contextWindow).shouldCompact) return true;
551
+ }
552
+ } else if (settings.context.workingSet.enabled) {
553
+ let planned: ReturnType<typeof planEviction>;
554
+ try {
555
+ const entries = deps.readSessionEntries() ?? [];
556
+ const view = foldWorkingSet(entries, state.lastTurnId ?? undefined);
557
+ const policy = resolveWorkingSetPolicy(settings.context.workingSet.policy);
558
+ planned = (deps.planEviction ?? planEviction)(policy, {
559
+ entries: selectVisibleEntries(entries, state.lastTurnId ?? undefined),
560
+ view,
561
+ cwd: deps.session.current()?.cwd ?? null,
562
+ settings: settings.context.workingSet,
563
+ pressure: {
564
+ tokens: estimate.tokens,
565
+ contextWindow: estimate.contextWindow,
566
+ threshold: compactionThreshold,
567
+ target: settings.context.workingSet.target,
568
+ },
569
+ estimateTokens,
570
+ });
571
+ } catch (error) {
572
+ middleware.fireCompactionHook("working_set_evict", "pressure", estimate.tokens);
573
+ emitCompactionActivity("started", "compacting context (working-set eviction)");
574
+ emitCompactionActivity("failed", compactionFailureMessage(error));
575
+ throw error;
576
+ }
577
+ if (planned) {
578
+ preSummaryStageActed = true;
579
+ middleware.fireCompactionHook("working_set_evict", "pressure", estimate.tokens);
580
+ emitCompactionActivity("started", "compacting context (working-set eviction)");
581
+ try {
582
+ deps.session.appendEntry({
583
+ ...buildEvictionFields(planned, {
584
+ trigger: "pressure",
585
+ pressureBefore: verdict.pressure,
586
+ snapshotIdBefore: beforeSnapshotId,
587
+ }),
588
+ // appendEntry does not infer this anchor; the interactive
589
+ // cursor is the leaf the next message will extend.
590
+ parentTurnId: state.lastTurnId,
591
+ });
592
+ noteColdReason("working_set_evict");
593
+ refreshAgentMessagesFromSession(agentRuntime);
594
+
595
+ const postEvictionSnapshot = captureRuntimeContextSnapshot(
596
+ agentRuntime,
597
+ state.activeUserTurnId || "compaction",
598
+ compactionThreshold,
599
+ );
600
+ currentContextSnapshot = postEvictionSnapshot;
601
+ persistContextSnapshot(postEvictionSnapshot);
602
+
603
+ // Every surface that describes this event (the notice, the toast
604
+ // ContextPruned feeds, the overlay's last-compaction line, the
605
+ // ledger entry) quotes the plan: the same chars/4 pricing over the
606
+ // same visible slice. The live estimate prices the agent message
607
+ // list and differs by the tool schemas and replay text; it stays
608
+ // what the meter and the re-check below read, not what the event
609
+ // reports about itself.
610
+ lastCompactionEvent = {
611
+ stage: "working_set",
612
+ tokensBefore: planned.tokensBefore,
613
+ tokensAfter: planned.tokensAfter,
614
+ trigger,
615
+ };
616
+ deps.bus?.emit(BusChannels.ContextPruned, {
617
+ stage: "working_set",
618
+ pressure: verdict.pressure,
619
+ tokensBefore: planned.tokensBefore,
620
+ tokensAfter: planned.tokensAfter,
621
+ trigger,
622
+ snapshotIdBefore: beforeSnapshotId,
623
+ snapshotIdAfter: postEvictionSnapshot.snapshotId,
624
+ policyId: planned.policyId,
625
+ evictedItems: planned.items.length,
626
+ at: Date.now(),
627
+ } satisfies ContextPrunedPayload);
628
+ const itemsWord = planned.items.length === 1 ? "item" : "items";
629
+ emitCompactionActivity("completed", `${planned.items.length} working-set ${itemsWord} evicted`);
630
+ deps.emitNotice(
631
+ `[context engine] working set: ${planned.items.length} ${itemsWord} evicted by ${planned.policyId}; ~${planned.tokensBefore} -> ~${planned.tokensAfter} tokens, recall by ref with context(scope="recall")`,
632
+ );
633
+
634
+ const after = liveContextEstimate(agentRuntime, pendingUserText);
635
+ if (!shouldCompact(after.tokens, compactionThreshold, after.contextWindow).shouldCompact) return true;
636
+ } catch (error) {
637
+ emitCompactionActivity("failed", compactionFailureMessage(error));
638
+ throw error;
639
+ }
640
+ }
436
641
  }
437
642
  }
438
643
  }
439
644
 
440
- middleware.fireCompactionHook("llm_summary", trigger);
441
- deps.bus?.emit(BusChannels.CompactionBegin, { trigger, at: Date.now() });
442
- emitCompactionActivity("started", "compacting context (summary)");
645
+ if (!deps.autoCompact) {
646
+ rememberEmptyAutomaticAttempt();
647
+ return false;
648
+ }
649
+ let summaryLifecycleStarted = false;
650
+ const startSummaryLifecycle = (): void => {
651
+ middleware.fireCompactionHook("llm_summary", trigger);
652
+ deps.bus?.emit(BusChannels.CompactionBegin, { trigger, at: Date.now() });
653
+ emitCompactionActivity("started", "compacting context (summary)");
654
+ summaryLifecycleStarted = true;
655
+ };
656
+ if (force) startSummaryLifecycle();
443
657
  let result: CompactResult | null = null;
444
658
  const beforeSnapshotId = currentContextSnapshot?.snapshotId ?? null;
445
659
  try {
446
660
  result = await compactionTrigger.fire(() => (deps.autoCompact ?? (async () => null))(instructions, trigger));
447
661
  } catch (error) {
662
+ if (!summaryLifecycleStarted) startSummaryLifecycle();
448
663
  emitCompactionActivity("failed", compactionFailureMessage(error));
449
- throw error;
450
- } finally {
451
664
  deps.bus?.emit(BusChannels.CompactionEnd, { trigger, at: Date.now() });
665
+ throw error;
452
666
  }
453
667
  if (!result || result.summary.length === 0) {
454
- emitCompactionActivity("completed", "nothing to compact");
668
+ if (summaryLifecycleStarted) {
669
+ deps.bus?.emit(BusChannels.CompactionEnd, { trigger, at: Date.now() });
670
+ emitCompactionActivity("completed", "nothing to compact");
671
+ } else {
672
+ rememberEmptyAutomaticAttempt();
673
+ }
455
674
  return false;
456
675
  }
676
+ if (!summaryLifecycleStarted) startSummaryLifecycle();
677
+ deps.bus?.emit(BusChannels.CompactionEnd, { trigger, at: Date.now() });
457
678
 
458
679
  // The summarization call spends tokens on the same target the turn would
459
680
  // have. The ledger entry carries its usage for a later reseed; this is the
@@ -699,8 +920,9 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
699
920
  },
700
921
 
701
922
  contextUsage(): ContextUsageSnapshot {
702
- if (!state.runtime) return contextUsageSnapshot(null, 0);
703
- const effectiveWindow = state.runtime.runtimeResolution.contextWindowDetails.effectiveContextWindow;
923
+ const effectiveWindow = state.runtime
924
+ ? state.runtime.runtimeResolution.contextWindowDetails.effectiveContextWindow
925
+ : windowWithoutRuntime().contextWindow;
704
926
  if (!currentContextSnapshot) {
705
927
  return contextUsageSnapshot(null, effectiveWindow);
706
928
  }
@@ -720,28 +942,27 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
720
942
  const settings = deps.getSettings();
721
943
  const compactionThreshold = settings.compaction?.threshold ?? null;
722
944
  const compactionAuto = settings.compaction?.auto !== false;
723
- if (!state.runtime) {
724
- return buildContextLedger({
725
- provider: settings.orchestrator?.target ?? null,
726
- model: settings.orchestrator?.model ?? null,
727
- contextWindow: 0,
728
- compactionThreshold,
729
- compactionAuto,
730
- });
731
- }
732
- const windowDetails = state.runtime.runtimeResolution.contextWindowDetails;
733
- const effectiveWindow = windowDetails.effectiveContextWindow;
734
-
735
- const provider = state.runtime.targetId ?? settings.orchestrator?.target ?? null;
736
- const model = state.runtime.wireModelId ?? settings.orchestrator?.model ?? null;
945
+ // Without a runtime (before the first turn of this process, /resume
946
+ // included) the window comes from the live resolution or the resumed
947
+ // snapshot, and the token facts from the snapshot: the resumed
948
+ // messages are the ones it measured.
949
+ const window = state.runtime
950
+ ? {
951
+ contextWindow: state.runtime.runtimeResolution.contextWindowDetails.effectiveContextWindow,
952
+ contextWindowSource: state.runtime.runtimeResolution.contextWindowDetails.contextWindowSource,
953
+ contextWindowSlots: state.runtime.runtimeResolution.contextWindowDetails.contextWindowSlots,
954
+ }
955
+ : windowWithoutRuntime();
956
+ const provider = state.runtime?.targetId ?? settings.orchestrator?.target ?? null;
957
+ const model = state.runtime?.wireModelId ?? settings.orchestrator?.model ?? null;
958
+ const liveToolCount = state.runtime?.agent.state.tools.length ?? 0;
737
959
 
738
960
  if (!currentContextSnapshot) {
739
961
  return buildContextLedger({
740
962
  provider,
741
963
  model,
742
- contextWindow: effectiveWindow,
743
- contextWindowSource: windowDetails.contextWindowSource,
744
- toolCount: state.runtime.agent.state.tools.length,
964
+ ...window,
965
+ toolCount: liveToolCount,
745
966
  compactionThreshold,
746
967
  compactionAuto,
747
968
  promptCache: lastPromptCache,
@@ -756,15 +977,14 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
756
977
  return buildContextLedger({
757
978
  provider,
758
979
  model,
759
- contextWindow: effectiveWindow,
760
- contextWindowSource: windowDetails.contextWindowSource,
980
+ ...window,
761
981
  compactionThreshold,
762
982
  compactionAuto,
763
983
  systemPromptTokens: currentContextSnapshot.categories.system,
764
984
  toolSchemaTokens: currentContextSnapshot.categories.tools,
765
985
  // Persisted snapshots strip the captured schemas; fall back to
766
986
  // the live agent state after a session resume.
767
- toolCount: currentContextSnapshot.activeToolSchemas?.length ?? state.runtime.agent.state.tools.length,
987
+ toolCount: currentContextSnapshot.activeToolSchemas?.length ?? liveToolCount,
768
988
  messageTokens: currentContextSnapshot.categories.messages,
769
989
  agentsTokens: currentContextSnapshot.categories.agents,
770
990
  skillsTokens: currentContextSnapshot.categories.skills,
@@ -790,16 +1010,17 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
790
1010
  consumeExpectedColdReasons(runtimeId: string): void {
791
1011
  // Cache-disturbance honesty (T3.3): consume disturbances since
792
1012
  // the last settled run. Only single-slot local backends lose their
793
- // prefix cache to interleaved work, so only local-native targets
794
- // stamp reasons and notify; other tiers just clear the set.
1013
+ // prefix cache to interleaved work, so dispatch and compaction
1014
+ // stamp only on local-native targets; a working-set eviction moved
1015
+ // the prefix itself and stamps on every tier (see stampsOnTier).
795
1016
  runExpectedColdReasons = [];
796
- stampColdReasonsPending = false;
1017
+ nextAssistantColdReasons = [];
797
1018
  if (pendingColdReasons.size > 0) {
798
- const reasons = [...pendingColdReasons];
1019
+ const reasons = [...pendingColdReasons].filter((reason) => stampsOnTier(reason, runtimeId));
799
1020
  pendingColdReasons.clear();
800
- if (deps.providers.getRuntime(runtimeId)?.tier === "local-native") {
1021
+ if (reasons.length > 0) {
801
1022
  runExpectedColdReasons = reasons;
802
- stampColdReasonsPending = true;
1023
+ nextAssistantColdReasons = reasons;
803
1024
  deps.emitNotice(`[context engine] backend prefix cache likely cold this turn: ${reasons.join(", ")}`);
804
1025
  }
805
1026
  }
@@ -818,9 +1039,9 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
818
1039
  cacheWrite,
819
1040
  backendVerdict: backendCacheVerdict(input, cacheRead),
820
1041
  };
821
- if (stampColdReasonsPending && runExpectedColdReasons.length > 0) {
822
- promptCache.expectedColdReasons = [...runExpectedColdReasons];
823
- stampColdReasonsPending = false;
1042
+ if (nextAssistantColdReasons.length > 0) {
1043
+ promptCache.expectedColdReasons = [...nextAssistantColdReasons];
1044
+ nextAssistantColdReasons = [];
824
1045
  }
825
1046
  return promptCache;
826
1047
  },
@@ -834,6 +1055,7 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
834
1055
  cacheWriteTokens: cacheSummary.cacheRead > 0 || cacheSummary.cacheWrite > 0 ? cacheSummary.cacheWrite : null,
835
1056
  uncachedInputTokens: cacheSummary.input,
836
1057
  backendVerdict: runFirstCallVerdict,
1058
+ ...(runExpectedColdReasons.length > 0 ? { expectedColdReasons: [...runExpectedColdReasons] } : {}),
837
1059
  };
838
1060
  }
839
1061
  },
@@ -844,6 +1066,7 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
844
1066
  sessionPromptKey = null;
845
1067
  sessionWorkingContextPaths.clear();
846
1068
  pendingPromptLogEntry = null;
1069
+ emptyAutoCompactTurnId = null;
847
1070
  const session = deps.session?.current();
848
1071
  currentContextSnapshot = session ? getLatestContextSnapshot(session) : null;
849
1072
  },
@@ -15,7 +15,7 @@ import {
15
15
  type MiddlewareToolChoiceControl,
16
16
  } from "../domains/middleware/index.js";
17
17
  import type { SessionContract } from "../domains/session/contract.js";
18
- import type { CompactionTrigger } from "../domains/session/entries.js";
18
+ import type { CompactionTrigger, EvictionTrigger, RecallTrigger } from "../domains/session/entries.js";
19
19
  import type { AgentMessage } from "../engine/types.js";
20
20
  import { extractText, hasStructuredToolCall, toolNamesFromAgentState } from "./chat-loop-messages.js";
21
21
  import type { AgentRuntime, ChatTurnState } from "./turn-state.js";
@@ -42,8 +42,8 @@ export interface TurnMiddleware {
42
42
  terminalToolResult?: { toolCallId: string; toolName: string },
43
43
  ): Promise<void>;
44
44
  fireCompactionHook(
45
- stage: "mask_observations" | "llm_summary",
46
- trigger: CompactionTrigger,
45
+ stage: "mask_observations" | "working_set_evict" | "working_set_recall" | "llm_summary",
46
+ trigger: CompactionTrigger | EvictionTrigger | RecallTrigger,
47
47
  tokensBefore?: number,
48
48
  ): void;
49
49
  flushPendingReminders(): string;
@@ -205,6 +205,7 @@ export function createTurnMiddleware(deps: TurnMiddlewareDeps): TurnMiddleware {
205
205
  runtimeTier: agentRuntime.runtimeResolution.runtimeTier ?? "",
206
206
  activeToolNames: toolNamesFromAgentState(agentRuntime.agent.state.tools).join(","),
207
207
  turnToolCalls: state.turnToolCalls,
208
+ turnToolNames: state.turnToolNames.join(","),
208
209
  sharedWorkerNote: state.turnSharedWorkerNote,
209
210
  };
210
211
  if (terminalToolResult !== undefined) {
@@ -240,9 +241,9 @@ export function createTurnMiddleware(deps: TurnMiddlewareDeps): TurnMiddleware {
240
241
  },
241
242
 
242
243
  /**
243
- * Observe-only lifecycle point fired before each compaction stage, at the
244
- * existing CompactionBegin emit sites. Consumers record telemetry or state
245
- * ahead of context loss; returned effects are discarded by design.
244
+ * Observe-only lifecycle point fired before each compaction or working-set
245
+ * stage. Consumers record telemetry or state around projected context
246
+ * changes; returned effects are discarded by design.
246
247
  */
247
248
  fireCompactionHook(stage, trigger, tokensBefore): void {
248
249
  if (!deps.middleware) return;