@iowarp/clio-coder 0.5.0 → 0.5.2

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 (312) hide show
  1. package/.claude-plugin/marketplace.json +4 -4
  2. package/CHANGELOG.md +35 -0
  3. package/CONTRIBUTING.md +9 -0
  4. package/README.md +12 -9
  5. package/ROADMAP.md +30 -5
  6. package/dist/{acp-KS7ARGV6.js → acp-F6PGOFZ4.js} +2 -2
  7. package/dist/{agents-YOGJ2YEJ.js → agents-ZHP4R6EX.js} +22 -21
  8. package/dist/assets/codewiki.json +1 -1
  9. package/dist/{auth-CEDCO6O6.js → auth-N75U2H3Z.js} +4 -4
  10. package/dist/{chunk-D3KWFOUT.js → chunk-23UIXH6Z.js} +3 -3
  11. package/dist/{chunk-ZR5YA6ZF.js → chunk-36XVK5P5.js} +5 -5
  12. package/dist/{chunk-AVSR2HKI.js → chunk-3DZ4LPLS.js} +6 -6
  13. package/dist/{chunk-LYJJ6546.js → chunk-3GSPCWFX.js} +12 -12
  14. package/dist/{chunk-CRHPCPZP.js → chunk-4KC4CHKJ.js} +2 -2
  15. package/dist/{chunk-4VDHLCF5.js → chunk-4WJBWKMI.js} +2 -2
  16. package/dist/{chunk-KBUTSJSR.js → chunk-4XBYHXGK.js} +2 -2
  17. package/dist/{chunk-6W75D37F.js → chunk-53P775GE.js} +6 -3
  18. package/dist/{chunk-XZWSP67B.js → chunk-5APYAXCB.js} +2 -2
  19. package/dist/{chunk-D65E25JG.js → chunk-67HPYNTJ.js} +2 -2
  20. package/dist/{chunk-5JQR5B4D.js → chunk-6KV4KCQZ.js} +4 -4
  21. package/dist/{chunk-V4XJWDHR.js → chunk-6UKFRTXI.js} +2 -1
  22. package/dist/{chunk-NDPF2N2L.js → chunk-7DW7CJW2.js} +2 -2
  23. package/dist/{chunk-5KQEUIWM.js → chunk-7LUEQODV.js} +4 -4
  24. package/dist/{chunk-LNOXCGN2.js → chunk-7UFDIOBT.js} +3 -3
  25. package/dist/{chunk-DXSETN72.js → chunk-7WUGNN43.js} +2 -2
  26. package/dist/{chunk-V35KZDUZ.js → chunk-AA3GRZWS.js} +2 -2
  27. package/dist/{chunk-X4CEFOSG.js → chunk-ADO3OQIC.js} +4 -4
  28. package/dist/{chunk-UX2L5CUD.js → chunk-AI36V3ON.js} +3 -3
  29. package/dist/{chunk-B7GY6QER.js → chunk-B26OUMJW.js} +2 -2
  30. package/dist/{chunk-LAR7DX5E.js → chunk-B5TASTCK.js} +153 -62
  31. package/dist/{chunk-TGWZXJ7L.js → chunk-BEMQK5Q7.js} +92 -84
  32. package/dist/{chunk-RNDCROSG.js → chunk-CNXU7TR2.js} +3 -3
  33. package/dist/{chunk-3JLTRPN7.js → chunk-CVMUXT64.js} +35 -66
  34. package/dist/{chunk-733ZBT5P.js → chunk-E74MJF6G.js} +4 -4
  35. package/dist/{chunk-T2MDZES3.js → chunk-EBMTBE76.js} +3 -3
  36. package/dist/{chunk-OZ76TLGU.js → chunk-EEITU7S6.js} +2 -2
  37. package/dist/{chunk-GRHOXDKB.js → chunk-ELDJWJKM.js} +97 -14
  38. package/dist/{chunk-GCQFN7YI.js → chunk-EMYEQLWO.js} +2 -2
  39. package/dist/{chunk-LTWH2ACR.js → chunk-EQPFEDFV.js} +222 -127
  40. package/dist/{chunk-TDGYTWM6.js → chunk-F4I3H56H.js} +2 -2
  41. package/dist/chunk-F6KCJO3U.js +523 -0
  42. package/dist/{chunk-FGSQHUCK.js → chunk-F6VKWTTY.js} +5 -5
  43. package/dist/{chunk-UJD5MPP6.js → chunk-FLNXQQ5B.js} +2 -2
  44. package/dist/{chunk-EX57VVWW.js → chunk-FOSRJ2VZ.js} +4 -4
  45. package/dist/{chunk-ZARZ4POR.js → chunk-G5ZDQQE3.js} +3 -3
  46. package/dist/{chunk-HQVN7G4F.js → chunk-G62JBPXN.js} +7 -7
  47. package/dist/{chunk-ZJPORZTC.js → chunk-GIAI7A2K.js} +2 -2
  48. package/dist/chunk-GPTR6OTU.js +2551 -0
  49. package/dist/{chunk-EZD2BXGP.js → chunk-GUFQPTQK.js} +2 -2
  50. package/dist/{chunk-OPD7GL6F.js → chunk-H6FQVNZC.js} +8 -7
  51. package/dist/chunk-H7REJ7L5.js +80 -0
  52. package/dist/{chunk-EYTLV3W3.js → chunk-HTWTORF7.js} +2 -2
  53. package/dist/{chunk-DF6PI6GN.js → chunk-I6UKQPGS.js} +3 -3
  54. package/dist/{chunk-ISHUS7HC.js → chunk-JRW236JT.js} +3 -3
  55. package/dist/{chunk-DE2LU267.js → chunk-KKAJGT3B.js} +3 -2
  56. package/dist/{chunk-OV62D6KO.js → chunk-KQMYL5GR.js} +3 -3
  57. package/dist/{chunk-QDUUOZ3S.js → chunk-KWMFC7FN.js} +3 -3
  58. package/dist/{chunk-R7FGRZNF.js → chunk-LXYHCCIZ.js} +9 -9
  59. package/dist/{chunk-NFSK2VNU.js → chunk-LZMTVVN4.js} +2 -2
  60. package/dist/{chunk-62UK7AGW.js → chunk-M5PMRZSR.js} +397 -44
  61. package/dist/chunk-MMZMM6SW.js +131 -0
  62. package/dist/{chunk-FFYTC2KQ.js → chunk-MNABNEBX.js} +2 -2
  63. package/dist/{chunk-XMDBTYCH.js → chunk-N3CWOGVF.js} +3 -3
  64. package/dist/{chunk-VYRQHORQ.js → chunk-NMQSO6Z6.js} +4 -3
  65. package/dist/{chunk-R2UUNSNQ.js → chunk-O3ZAF7UY.js} +18 -5
  66. package/dist/{chunk-4SYHGVDE.js → chunk-OQNCLZI4.js} +4 -4
  67. package/dist/{chunk-EG4ARXPC.js → chunk-OX6QTA4N.js} +3 -3
  68. package/dist/{chunk-P4GNZPXO.js → chunk-OYUS3UZP.js} +2 -2
  69. package/dist/{chunk-OQ2EOAHA.js → chunk-PF36KOGI.js} +291 -112
  70. package/dist/{chunk-55RMDDFM.js → chunk-PGQG7ZMW.js} +2 -2
  71. package/dist/{chunk-CBAQPTDU.js → chunk-PIU6BXXW.js} +2 -2
  72. package/dist/{chunk-EKV4UBCM.js → chunk-PZBQUJ2F.js} +2 -2
  73. package/dist/{chunk-7RET77VB.js → chunk-Q2HKY32Y.js} +2 -2
  74. package/dist/{chunk-U6YTUVOX.js → chunk-Q4RMNWMZ.js} +4 -4
  75. package/dist/chunk-Q6FRR3AQ.js +48 -0
  76. package/dist/{chunk-6RJMYYIE.js → chunk-QJSIAJBY.js} +3 -3
  77. package/dist/{chunk-DOUISWEE.js → chunk-QOJ4ERMU.js} +5 -5
  78. package/dist/{chunk-IFPDAD7R.js → chunk-RWSRKXLT.js} +3 -3
  79. package/dist/{chunk-T33IYTZM.js → chunk-SAASVMIY.js} +3 -3
  80. package/dist/{chunk-2DTIWSAW.js → chunk-SEQDH6JC.js} +3 -3
  81. package/dist/{chunk-BRCOSE7O.js → chunk-SZUO5BSE.js} +2 -2
  82. package/dist/{chunk-HOEDA42N.js → chunk-T7HZ7KJF.js} +2 -2
  83. package/dist/{chunk-OJXOA6YU.js → chunk-TL6LNERH.js} +2 -2
  84. package/dist/{chunk-UCINIRHW.js → chunk-TPCZXWLT.js} +3 -3
  85. package/dist/{chunk-ZUKUCZYZ.js → chunk-TQ2KTH4A.js} +2 -2
  86. package/dist/{chunk-4WOOTKFD.js → chunk-U3FETNQB.js} +341 -20
  87. package/dist/{chunk-5B3RTYAK.js → chunk-UGDSB4AN.js} +2 -2
  88. package/dist/{chunk-VKHLUZNO.js → chunk-UZ7YBL43.js} +3 -3
  89. package/dist/{chunk-QNUQ7K7D.js → chunk-VKMSEO7Y.js} +4 -4
  90. package/dist/{chunk-IN7DGBVS.js → chunk-VLX5VZ35.js} +5 -5
  91. package/dist/{chunk-SJGS3GDI.js → chunk-WAGBMMNX.js} +5 -5
  92. package/dist/{chunk-BFOSV5EZ.js → chunk-WX2YCH7F.js} +2 -2
  93. package/dist/{chunk-3LT34CAM.js → chunk-XBIGUILU.js} +32 -17
  94. package/dist/{chunk-XDHUDE5K.js → chunk-XKYBFRWR.js} +5 -5
  95. package/dist/{chunk-U5QU5ZOD.js → chunk-XYPWFSU5.js} +3 -3
  96. package/dist/{chunk-YBUECLAF.js → chunk-YEJQDODA.js} +2 -2
  97. package/dist/{chunk-YQ6XEFVK.js → chunk-Z74OGONW.js} +39 -7
  98. package/dist/{chunk-I4ELN5BX.js → chunk-ZTNOEJRI.js} +2 -2
  99. package/dist/cli/index.js +26 -26
  100. package/dist/{clio-K2PBVCEM.js → clio-4IKFE25F.js} +2 -2
  101. package/dist/{clio-context-tools-GR4OHFSO.js → clio-context-tools-OHJXYX4R.js} +25 -23
  102. package/dist/{code-nav-4X3OI6DK.js → code-nav-XPM3MY5I.js} +7 -7
  103. package/dist/{config-Q5J7UBAB.js → config-343YS25T.js} +43 -41
  104. package/dist/{config-graph-P3555574.js → config-graph-WI263KDX.js} +43 -41
  105. package/dist/{configure-KIJ32ZO6.js → configure-GZDG4HER.js} +21 -21
  106. package/dist/{context-LFBQOSXT.js → context-4OFO7N7Y.js} +12 -12
  107. package/dist/{context-WBH3KSVR.js → context-JLK2RLOG.js} +42 -40
  108. package/dist/{context-NNLBES2Y.js → context-O6DIWCF6.js} +23 -21
  109. package/dist/{context-clear-YOHMQ6SQ.js → context-clear-MR7KOGQI.js} +42 -40
  110. package/dist/{context-working-set-MRAEVDNV.js → context-working-set-YZAD3KWO.js} +14 -13
  111. package/dist/{data-tool-JJPBGMQJ.js → data-tool-EKD7DFHG.js} +7 -7
  112. package/dist/{detail-RHJOTGN4.js → detail-YZPHFEVJ.js} +43 -41
  113. package/dist/{dispatch-runner-NC7PYNIO.js → dispatch-runner-VKVXVTWJ.js} +44 -42
  114. package/dist/{doctor-S5KZ2GTN.js → doctor-53AVQXAO.js} +17 -16
  115. package/dist/{doctor-deep-2EJQSHIZ.js → doctor-deep-BLRB2NCG.js} +5 -5
  116. package/dist/{eval-3D6M36N7.js → eval-P3BQSLQE.js} +21 -20
  117. package/dist/{evidence-XNVPPLVK.js → evidence-6LLI6SZH.js} +44 -42
  118. package/dist/{evidence-6IYN52ET.js → evidence-HTN3JLUO.js} +42 -40
  119. package/dist/{evidence-JVECPI3F.js → evidence-NJRE3R5H.js} +42 -40
  120. package/dist/{evolve-TLPUDJDM.js → evolve-WWZHJVJP.js} +42 -40
  121. package/dist/{fleet-WKVBF5SZ.js → fleet-GGZ4BVJE.js} +64 -62
  122. package/dist/{fleet-6RJWIOEO.js → fleet-QG3IIWWM.js} +44 -42
  123. package/dist/{fleet-commands-L6WFT7KZ.js → fleet-commands-J2ZIKISQ.js} +7 -7
  124. package/dist/{fleet-decisions-BTQMWU5A.js → fleet-decisions-Y2Q7375Q.js} +8 -8
  125. package/dist/{fleet-graph-553DZ67Q.js → fleet-graph-TCBCCCBP.js} +11 -11
  126. package/dist/{fleet-inspect-236FZPRB.js → fleet-inspect-KOLKBC56.js} +44 -42
  127. package/dist/{fleet-preflight-NZQHABWO.js → fleet-preflight-44UNCC4Y.js} +27 -25
  128. package/dist/{fleet-validate-7NLAE26R.js → fleet-validate-K2FXRZVA.js} +13 -13
  129. package/dist/{fleet-verify-E3RO6A5W.js → fleet-verify-AS444WDW.js} +42 -40
  130. package/dist/{fleet-view-KMZQY7YJ.js → fleet-view-X4JU4J6O.js} +43 -41
  131. package/dist/gui/ops-worker.js +9 -9
  132. package/dist/gui/reads-worker.js +9 -8
  133. package/dist/{init-DV5B6HQG.js → init-VSOWFHVL.js} +54 -52
  134. package/dist/{interactive-OXGYZYAS.js → interactive-JRCBENLZ.js} +1733 -577
  135. package/dist/{interop-J7UOBOSF.js → interop-JR6BCY7R.js} +10 -10
  136. package/dist/{inventory-OEGIL6PY.js → inventory-3JUGLVJI.js} +43 -41
  137. package/dist/{library-ROZAMUIL.js → library-7BOH3Y7N.js} +15 -15
  138. package/dist/{library-B3UPGY4V.js → library-7PFONXUI.js} +9 -9
  139. package/dist/{library-MXQA5DLT.js → library-TUYZMFIA.js} +7 -7
  140. package/dist/{library-import-2LTWOVBD.js → library-import-TJVXKLDK.js} +10 -10
  141. package/dist/{library-inventory-LJ3XHLWO.js → library-inventory-MD3AYTP4.js} +9 -9
  142. package/dist/{library-validation-DARHHUNW.js → library-validation-ZDLITEA3.js} +7 -7
  143. package/dist/{mcp-2GSPYQ54.js → mcp-EFCWUPUR.js} +3 -3
  144. package/dist/{memory-J3STUUIK.js → memory-Q2YVOKK5.js} +42 -40
  145. package/dist/{models-Y3GMFJ2V.js → models-M7BOCW7F.js} +17 -16
  146. package/dist/{monitor-LHNH577J.js → monitor-2HD3MC67.js} +47 -45
  147. package/dist/{orchestrator-DTAPXULA.js → orchestrator-OUW3ZN6Y.js} +1894 -226
  148. package/dist/{panes-A743ONZ5.js → panes-TTPPSYCL.js} +3 -3
  149. package/dist/{preload-OJBMAIXF.js → preload-A47C2NUF.js} +42 -40
  150. package/dist/{providers-CMETPZNS.js → providers-T43W7EL4.js} +4 -4
  151. package/dist/{resources-FY6XYFN2.js → resources-TP6X2V6W.js} +20 -11
  152. package/dist/{run-H64MEVTA.js → run-LDSBPMCS.js} +61 -59
  153. package/dist/{share-CAFMMQLM.js → share-URHKKMHR.js} +9 -9
  154. package/dist/{skills-ERKQUC7L.js → skills-E477MOOO.js} +13 -11
  155. package/dist/{skills-eval-CQ23MYQK.js → skills-eval-AIBR2L7H.js} +583 -93
  156. package/dist/{skills-inventory-TVUYJDVN.js → skills-inventory-HLMKY3R5.js} +13 -11
  157. package/dist/{slash-commands-ROG3KIFJ.js → slash-commands-LIIZKA5Y.js} +28 -26
  158. package/dist/{startup-background-KDWCQMWT.js → startup-background-JR2KDK6R.js} +43 -41
  159. package/dist/{steer-KKYXJSH4.js → steer-D7RTAPCM.js} +3 -3
  160. package/dist/{system-ZDW44ZGX.js → system-X2P5N4IY.js} +14 -13
  161. package/dist/{targets-AJWA6E7Z.js → targets-VFFAKUFM.js} +25 -24
  162. package/dist/{tasks-6L2JJRC5.js → tasks-ERAWZA22.js} +6 -6
  163. package/dist/{terminal-lease-YFUSDPAD.js → terminal-lease-V2N2BIKQ.js} +2 -2
  164. package/dist/{trace-LA5MVR4Y.js → trace-MA5BT7XY.js} +3 -3
  165. package/dist/{usage-XSILDHKF.js → usage-WYFY2L6M.js} +50 -48
  166. package/dist/{verifiers-ECA5GIHL.js → verifiers-TYIW2XKF.js} +7 -7
  167. package/dist/{verify-R6QQCCEB.js → verify-242UFYTP.js} +6 -6
  168. package/dist/{web-fetch-PVZNMBOB.js → web-fetch-LRAGYU6Y.js} +3 -3
  169. package/dist/{wiki-generate-RP22WP5K.js → wiki-generate-OFKOX2T6.js} +53 -51
  170. package/dist/worker/entry.js +31 -29
  171. package/docs/architecture/architecture.md +1 -0
  172. package/docs/architecture/context-engine.md +4 -2
  173. package/docs/architecture/observability.md +6 -4
  174. package/docs/architecture/prompt-envelope-and-tools.md +1 -1
  175. package/docs/architecture/session-lifecycle.md +1 -1
  176. package/docs/architecture/tui-design.md +12 -9
  177. package/docs/gui/parity/02-slash-and-surfaces.md +1 -1
  178. package/docs/guide/commands-and-modes.md +91 -5
  179. package/docs/guide/configuration-and-targets.md +2 -2
  180. package/docs/guide/context-continuity.md +49 -0
  181. package/docs/guide/proactive-memory.md +6 -3
  182. package/library/registry.yaml +292 -32
  183. package/library/skills/README.md +27 -2
  184. package/library/skills/context/context-handoff/SKILL.md +10 -4
  185. package/library/skills/meta/clio-coder-dev/SKILL.md +92 -88
  186. package/library/skills/meta/clio-coder-dev/evals.md +51 -46
  187. package/library/skills/meta/clio-coder-dev/plugin.json +2 -2
  188. package/library/skills/meta/clio-coder-dev/references/change-map.md +49 -0
  189. package/library/skills/meta/clio-coder-test/SKILL.md +89 -142
  190. package/library/skills/meta/clio-coder-test/evals.md +58 -62
  191. package/library/skills/meta/clio-coder-test/plugin.json +2 -2
  192. package/library/skills/meta/clio-coder-test/references/harness.md +3 -3
  193. package/library/skills/meta/clio-coder-test/references/lifecycle-validation.md +30 -0
  194. package/library/skills/meta/clio-coder-test/references/test-map.md +60 -89
  195. package/library/skills/registry.yaml +5 -5
  196. package/library/skills/skill-marketplace.json +12 -14
  197. package/package.json +1 -1
  198. package/src/cli/skills-eval.ts +1064 -54
  199. package/src/cli/usage.ts +2 -2
  200. package/src/core/clio-repo.ts +3 -0
  201. package/src/core/tool-names.ts +1 -0
  202. package/src/domains/context/budget/inspection.ts +9 -0
  203. package/src/domains/context/budget/live-view.ts +418 -0
  204. package/src/domains/context/budget/pressure.ts +393 -0
  205. package/src/domains/context/budget/request-fit.ts +15 -0
  206. package/src/domains/evidence/build.ts +24 -0
  207. package/src/domains/gateway/mcp/client.ts +1 -1
  208. package/src/domains/memory/commit-state.ts +160 -0
  209. package/src/domains/memory/operations.ts +18 -10
  210. package/src/domains/memory/prompt-cache.ts +95 -0
  211. package/src/domains/memory/prompt-section.ts +50 -7
  212. package/src/domains/memory/relevance.ts +93 -0
  213. package/src/domains/memory/restoration.ts +78 -0
  214. package/src/domains/memory/store.ts +39 -1
  215. package/src/domains/middleware/memory-intervention.ts +116 -24
  216. package/src/domains/observability/background-memory-usage.ts +1 -1
  217. package/src/domains/observability/cost.ts +3 -3
  218. package/src/domains/observability/extension.ts +1 -1
  219. package/src/domains/observability/metrics.ts +1 -1
  220. package/src/domains/prompts/compiler.ts +1 -1
  221. package/src/domains/prompts/extension.ts +38 -1
  222. package/src/domains/quota/anthropic-max-provider.ts +134 -0
  223. package/src/domains/quota/anthropic-usage.ts +220 -0
  224. package/src/domains/quota/antigravity-provider.ts +339 -0
  225. package/src/domains/quota/cache.ts +87 -0
  226. package/src/domains/quota/claude-code-provider.ts +169 -0
  227. package/src/domains/quota/codex-provider.ts +237 -0
  228. package/src/domains/quota/presentation.ts +240 -0
  229. package/src/domains/quota/registry.ts +23 -0
  230. package/src/domains/quota/service.ts +93 -0
  231. package/src/domains/quota/summary-feed.ts +86 -0
  232. package/src/domains/quota/types.ts +84 -0
  233. package/src/domains/resources/index.ts +11 -0
  234. package/src/domains/resources/skills/catalog-view.ts +571 -0
  235. package/src/domains/resources/skills/lexical-match.ts +136 -0
  236. package/src/domains/resources/skills/loader.ts +38 -0
  237. package/src/domains/resources/skills/promotion.ts +1 -55
  238. package/src/domains/resources/skills/provenance-pin.ts +50 -20
  239. package/src/domains/safety/action-classifier.ts +1 -0
  240. package/src/domains/session/compaction/branch-summary.ts +4 -1
  241. package/src/domains/session/compaction/compact.ts +18 -2
  242. package/src/domains/session/compaction/cut-point.ts +20 -1
  243. package/src/domains/session/compaction/tokens.ts +48 -3
  244. package/src/domains/session/context-accounting.ts +8 -1
  245. package/src/domains/session/continuity/carry.ts +59 -0
  246. package/src/domains/session/continuity/contract.ts +592 -0
  247. package/src/domains/session/continuity/evidence.ts +290 -0
  248. package/src/domains/session/continuity/fold.ts +1075 -0
  249. package/src/domains/session/continuity/note.ts +79 -0
  250. package/src/domains/session/continuity/operator-request.ts +104 -0
  251. package/src/domains/session/continuity/persistence.ts +408 -0
  252. package/src/domains/session/continuity/ports.ts +231 -0
  253. package/src/domains/session/continuity/projection.ts +538 -0
  254. package/src/domains/session/continuity/validate.ts +352 -0
  255. package/src/domains/session/entries.ts +50 -3
  256. package/src/domains/session/index.ts +48 -0
  257. package/src/domains/session/migrations/index.ts +7 -3
  258. package/src/domains/session/tree/fork.ts +15 -1
  259. package/src/domains/session/usage.ts +2 -2
  260. package/src/engine/acp/commands.ts +1 -1
  261. package/src/engine/agent.ts +133 -9
  262. package/src/engine/session.ts +13 -6
  263. package/src/entry/orchestrator.ts +116 -33
  264. package/src/interactive/chat-loop-messages.ts +2 -2
  265. package/src/interactive/chat-loop.ts +219 -63
  266. package/src/interactive/chat-renderer.ts +99 -8
  267. package/src/interactive/context-overlay.ts +24 -3
  268. package/src/interactive/continuity-controller.ts +526 -0
  269. package/src/interactive/dispatch-board.ts +21 -3
  270. package/src/interactive/footer/dashboard.ts +25 -1
  271. package/src/interactive/footer/key-hints.ts +2 -2
  272. package/src/interactive/footer/pages.ts +70 -27
  273. package/src/interactive/footer/widgets.ts +32 -7
  274. package/src/interactive/footer-panel.ts +1 -1
  275. package/src/interactive/interactive-application.ts +7 -2
  276. package/src/interactive/interactive-input-runtime.ts +2 -2
  277. package/src/interactive/interactive-presentation.ts +15 -0
  278. package/src/interactive/interactive-slash-runtime.ts +45 -8
  279. package/src/interactive/interactive-tickers.ts +7 -1
  280. package/src/interactive/model-session-replay.ts +130 -3
  281. package/src/interactive/output-reserve.ts +35 -0
  282. package/src/interactive/overlay-general-openers.ts +12 -8
  283. package/src/interactive/overlay-key-routing.ts +6 -9
  284. package/src/interactive/overlay-lifecycle.ts +10 -6
  285. package/src/interactive/overlay-session-lifecycle.ts +21 -14
  286. package/src/interactive/quota-view.ts +229 -0
  287. package/src/interactive/session-last-turn.ts +1 -1
  288. package/src/interactive/session-transcript.ts +2 -1
  289. package/src/interactive/session-usage-reseed.ts +2 -2
  290. package/src/interactive/side-question.ts +2 -2
  291. package/src/interactive/slash-commands.ts +28 -9
  292. package/src/interactive/turn-context.ts +686 -63
  293. package/src/interactive/turn-middleware.ts +39 -6
  294. package/src/interactive/turn-persistence.ts +15 -2
  295. package/src/interactive/turn-prewarm.ts +1 -1
  296. package/src/interactive/turn-runtime.ts +48 -5
  297. package/src/interactive/{cost-overlay.ts → usage-overlay.ts} +121 -33
  298. package/src/interactive/welcome-dashboard.ts +26 -3
  299. package/src/tools/agent-tools.ts +3 -1
  300. package/src/tools/bootstrap.ts +6 -0
  301. package/src/tools/builtin-tool-catalog.ts +9 -0
  302. package/src/tools/context/index.ts +148 -113
  303. package/src/tools/context/surface.ts +11 -7
  304. package/src/tools/core-bootstrap.ts +4 -0
  305. package/src/tools/observation.ts +8 -2
  306. package/src/tools/policy.ts +3 -0
  307. package/src/tools/self-compact.ts +31 -0
  308. package/src/tools/surface.ts +1 -0
  309. package/src/tools/tasks.ts +1 -1
  310. package/dist/chunk-CTFPFW3H.js +0 -44
  311. package/dist/chunk-EOJPDNUP.js +0 -1325
  312. package/dist/chunk-I4Y4WKZR.js +0 -386
@@ -1,7 +1,14 @@
1
+ import type { LiveBudgetView } from "../domains/context/budget/live-view.js";
1
2
  import type { WorkerContextSnapshot } from "../domains/context/worker/contract.js";
2
3
  import { captureWorkerContext } from "../domains/context/worker/snapshot.js";
3
- import { replaceEngineMessages } from "../engine/agent.js";
4
+ import type { MemoryPromptRequest } from "../domains/memory/prompt-cache.js";
5
+ import type { MemoryInterventionRegistration } from "../domains/middleware/memory-intervention.js";
6
+ import { estimateAgentMessageTokens } from "../domains/session/context-accounting.js";
7
+ import { createContinuityPersistencePorts } from "../domains/session/continuity/ports.js";
8
+ import { continuityReplayText, resolveContinuityProjection } from "../domains/session/continuity/projection.js";
9
+ import { continueEngineWithoutInput, replaceEngineMessages } from "../engine/agent.js";
4
10
  import { isLockedSynthesisFallbackOnly, lockedSynthesisRepromptMessages } from "../engine/loop-guard.js";
11
+ import { ContinuityController } from "./continuity-controller.js";
5
12
  /**
6
13
  * The chat loop: one turn's state machine.
7
14
  *
@@ -31,6 +38,8 @@ import {
31
38
  } from "../core/skill-activation.js";
32
39
  import { snapshotTurnConstraints, type TurnConstraints } from "../core/turn-constraints.js";
33
40
  import { clioStateDir } from "../core/xdg.js";
41
+ import type { BudgetInspection } from "../domains/context/budget/inspection.js";
42
+ import { requestFits } from "../domains/context/budget/request-fit.js";
34
43
  import {
35
44
  createMiddlewareToolChoiceControl,
36
45
  type MiddlewareContract,
@@ -52,8 +61,7 @@ import {
52
61
  import { type AutonomyLevel, modelMayActivateSkills } from "../domains/safety/autonomy.js";
53
62
  import type { ProtectedArtifactState } from "../domains/safety/protected-artifacts.js";
54
63
  import type { CompactInput, CompactResult } from "../domains/session/compaction/compact.js";
55
- import type { ContextSnapshot, ContextUsageSnapshot } from "../domains/session/context-accounting.js";
56
- import { ceilChars, snapshotInputTokens } from "../domains/session/context-accounting.js";
64
+ import type { ContextSnapshot } from "../domains/session/context-accounting.js";
57
65
  import type { ContextLedger } from "../domains/session/context-ledger.js";
58
66
  import type { SessionContract } from "../domains/session/contract.js";
59
67
  import {
@@ -67,7 +75,6 @@ import { protectedArtifactStateFromSessionEntries } from "../domains/session/pro
67
75
  import { isRetryableErrorMessage, type RetrySettings } from "../domains/session/retry.js";
68
76
  import { filterEntriesToActivePath } from "../domains/session/tree/active-path.js";
69
77
  import { createEngineAgent } from "../engine/agent.js";
70
- import { resolveReservedOutputTokens } from "../engine/apis/output-budget.js";
71
78
  import { cwdHash } from "../engine/session.js";
72
79
  import type { AgentEvent, AgentMessage, ImageContent, Usage } from "../engine/types.js";
73
80
  import { resolveSessionTools } from "../tools/agent-tools.js";
@@ -90,7 +97,7 @@ import type { ApprovalRequestView } from "./permission-overlay.js";
90
97
  import type { runPrewarmRound } from "./prewarm.js";
91
98
  import { runSideQuestion, type SideQuestionResult, sideQuestionUsage } from "./side-question.js";
92
99
  import type { AgentStatusEvent } from "./status/types.js";
93
- import { createTurnContext } from "./turn-context.js";
100
+ import { createTurnContext, type LiveContextUsage } from "./turn-context.js";
94
101
  import { createTurnMiddleware } from "./turn-middleware.js";
95
102
  import { createTurnPersistence } from "./turn-persistence.js";
96
103
  import { createTurnPrewarm, type PrewarmOutcome, subscribePrewarmToCompaction } from "./turn-prewarm.js";
@@ -338,7 +345,7 @@ export interface ChatLoop {
338
345
  turnPreparation(): { phase: TurnPreparationPhase; since: number };
339
346
  /** Fires on every preparation transition, including back to `idle`. */
340
347
  onTurnPreparation(handler: (phase: TurnPreparationPhase) => void): () => void;
341
- contextUsage(): ContextUsageSnapshot;
348
+ contextUsage(): LiveContextUsage;
342
349
  /**
343
350
  * Categorized context-window ledger for the `/context` overlay: where every
344
351
  * occupied token lives (system prompt, tools, agents, skills, memory,
@@ -346,6 +353,21 @@ export interface ChatLoop {
346
353
  * estimate with the current turn's prompt segment manifest.
347
354
  */
348
355
  contextLedger(): ContextLedger;
356
+ /**
357
+ * The published live budget for the next request: one immutable view with
358
+ * the effective window, the structural accounting, the real output
359
+ * reservation, headroom, pressure, and an opaque revision every consumer
360
+ * quotes. A pure read of what the producer last published; it never rescans
361
+ * the conversation, calls a model, persists anything, or triggers reduction.
362
+ */
363
+ liveBudget(): LiveBudgetView;
364
+ inspectLiveBudget(): BudgetInspection;
365
+ /**
366
+ * Republish the live budget before making a decision from it, for a consumer
367
+ * that may be observing results appended since the last publication. The
368
+ * only thing it mutates is the accounting cache.
369
+ */
370
+ refreshLiveBudget(): LiveBudgetView;
349
371
  /**
350
372
  * Force-run the compaction flow for the current session, swap the agent's
351
373
  * in-memory `state.messages` for a single bridge message carrying the
@@ -356,13 +378,15 @@ export interface ChatLoop {
356
378
  * notice so the `/context compact` handler does not have to mirror the logic.
357
379
  */
358
380
  compact(instructions?: string): Promise<void>;
381
+ requestSelfCompact(note: unknown, toolCallId: string, signal?: AbortSignal): Promise<string>;
382
+ recoverHandoff(handoffId: string, action: "reduce" | "deliver"): Promise<void>;
359
383
  /**
360
384
  * `/btw`: answer one side question against the session's active target,
361
385
  * model, and compiled message history without starting a turn.
362
386
  *
363
387
  * Nothing this produces reaches the session ledger, the transcript, the
364
388
  * context ledger, or the footer token counters; the message history is read,
365
- * never mutated. The round's provider usage is still reported to `/cost`,
389
+ * never mutated. The round's provider usage is still reported to `/usage`,
366
390
  * labeled as a side question, because money was spent. Refused outright
367
391
  * while a turn is in flight rather than queued.
368
392
  */
@@ -404,6 +428,7 @@ export interface ChatLoop {
404
428
  }
405
429
 
406
430
  export interface CreateChatLoopDeps {
431
+ memoryCommitBridge?: MemoryInterventionRegistration | undefined;
407
432
  interactiveGuidance?: boolean;
408
433
  getSettings: () => Readonly<ClioSettings>;
409
434
  /**
@@ -470,7 +495,15 @@ export interface CreateChatLoopDeps {
470
495
  autoCompact?: (
471
496
  instructions?: string,
472
497
  trigger?: CompactionTrigger,
473
- budget?: Pick<CompactInput, "keepRecentTokens" | "preserveUserTurnId" | "skillContextState" | "signal">,
498
+ budget?: Pick<
499
+ CompactInput,
500
+ | "keepRecentTokens"
501
+ | "preserveUserTurnId"
502
+ | "skillContextState"
503
+ | "signal"
504
+ | "beforeSummaryCall"
505
+ | "checkpointForSummary"
506
+ >,
474
507
  ) => Promise<CompactResult | null>;
475
508
  /** Optional observability sink for orchestrator chat token usage. */
476
509
  observability?: ObservabilityContract;
@@ -519,7 +552,7 @@ export interface CreateChatLoopDeps {
519
552
  * compiler injects via the memory dynamic fragment. Optional so unit
520
553
  * tests omit it when memory is irrelevant.
521
554
  */
522
- getMemorySection?: () => string;
555
+ getMemorySection?: (request: MemoryPromptRequest) => string;
523
556
  getReadySkillCount?: () => number;
524
557
  /** Structured, redacted task-bank export supplied only to an explicit context-handoff skill request. */
525
558
  getTaskMemoryHandoffSource?: () => string;
@@ -528,7 +561,7 @@ export interface CreateChatLoopDeps {
528
561
  * observers produce after their turn boundary closed. Called once during
529
562
  * composition; the loop owns the buffer the reminder lands in.
530
563
  */
531
- registerDeferredReminderSink?: (sink: (message: string) => void) => void;
564
+ registerDeferredReminderSink?: (sink: (message: string, isCurrent?: () => boolean) => void) => void;
532
565
  /**
533
566
  * The same seam for findings that are for the operator rather than the model.
534
567
  * The watchdog uses it: its run settles after the turn it reviewed, and its
@@ -780,6 +813,7 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
780
813
  });
781
814
 
782
815
  const middleware = createTurnMiddleware({
816
+ memoryContentGuard: deps.memoryCommitBridge?.isContentCurrent,
783
817
  state,
784
818
  middleware: deps.middleware,
785
819
  session: deps.session,
@@ -789,7 +823,9 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
789
823
  });
790
824
 
791
825
  try {
792
- deps.registerDeferredReminderSink?.((message) => middleware.injectDeferredReminder(message));
826
+ deps.registerDeferredReminderSink?.((message, isCurrent) =>
827
+ middleware.injectDeferredReminder(message, "advisory", isCurrent),
828
+ );
793
829
  deps.registerDeferredNoticeSink?.((text) => middleware.emitDeferredNotice(text));
794
830
  } catch {
795
831
  // A background observer losing its delivery path must not stop the loop
@@ -809,11 +845,126 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
809
845
  readSessionEntries: deps.readSessionEntries,
810
846
  autoCompact: deps.autoCompact,
811
847
  getMemorySection: deps.getMemorySection,
848
+ memoryCommitBridge: deps.memoryCommitBridge,
812
849
  getReadySkillCount: deps.getReadySkillCount,
850
+ getPendingHandoff: () => {
851
+ const sessionId = deps.session?.current()?.id;
852
+ if (!sessionId) return null;
853
+ const fold = resolveContinuityProjection({
854
+ entries: filterEntriesToActivePath(deps.readSessionEntries?.() ?? [], state.lastTurnId ?? undefined),
855
+ sessionId,
856
+ }).current;
857
+ return fold?.identity && fold.phase !== "acknowledged"
858
+ ? {
859
+ id: fold.identity.handoffId,
860
+ preparedAt: fold.policy ? new Date(fold.policy.preparedAtMs).toISOString() : null,
861
+ sourceRevision: fold.identity.sourceRevision,
862
+ }
863
+ : null;
864
+ },
865
+ getLastOutcome: () => {
866
+ const sessionId = deps.session?.current()?.id;
867
+ if (!sessionId) return null;
868
+ const fold = resolveContinuityProjection({
869
+ entries: filterEntriesToActivePath(deps.readSessionEntries?.() ?? [], state.lastTurnId ?? undefined),
870
+ sessionId,
871
+ }).current;
872
+ return fold?.identity ? { outcome: fold.phase, at: null, detail: fold.commit?.outcome ?? null } : null;
873
+ },
813
874
  middleware,
814
875
  emitNotice,
815
876
  });
816
877
 
878
+ let continuityReplayInstalled = false;
879
+ const continuity = new ContinuityController({
880
+ captureOrigin: () => {
881
+ continuityReplayInstalled = false;
882
+ const session = deps.session;
883
+ const meta = session?.current();
884
+ const runtime = state.runtime;
885
+ const leaf = state.lastTurnId;
886
+ const operator = state.activeUserTurnId;
887
+ if (!session || !meta || !runtime || !leaf || !operator || context.inspectLiveBudget().status !== "available") {
888
+ throw new Error("Self-compaction requires a native runtime and a persisted operator turn.");
889
+ }
890
+ const revision = context.navigationRevision();
891
+ const route = `${deps.getSettings().chat.target}/${deps.getSettings().chat.model}`;
892
+ return {
893
+ sessionId: meta.id,
894
+ leafTurnId: leaf,
895
+ initiatingTurnId: operator,
896
+ sourceRevision: context.refreshLiveBudget().revision,
897
+ ports: createContinuityPersistencePorts({
898
+ session,
899
+ origin: {
900
+ sessionId: meta.id,
901
+ cwdHash: meta.cwdHash,
902
+ stillCurrent: () =>
903
+ context.navigationRevision() === revision &&
904
+ state.runtime === runtime &&
905
+ state.activeUserTurnId === operator &&
906
+ `${deps.getSettings().chat.target}/${deps.getSettings().chat.model}` === route,
907
+ },
908
+ }),
909
+ };
910
+ },
911
+ entries: () => filterEntriesToActivePath(deps.readSessionEntries?.() ?? [], state.lastTurnId ?? undefined),
912
+ leaf: () => state.lastTurnId,
913
+ admitNote: (accepted) => {
914
+ const view = context.refreshLiveBudget();
915
+ if (!view.breakdown || view.historical) return false;
916
+ const messages = state.runtime?.agent.state.messages ?? [];
917
+ const operator = [...messages].reverse().find((message) => message.role === "user");
918
+ const note = continuityReplayText({
919
+ note: accepted.note,
920
+ handoffId: "pending",
921
+ commitId: "pending",
922
+ originSessionId: view.sessionId ?? "",
923
+ phase: "prepared",
924
+ authority: "execution",
925
+ });
926
+ // Include the complete skill messages and tool receipts already in the
927
+ // protected suffix. The final guard still prices the actual replay.
928
+ const protectedSkills = messages
929
+ .filter((message) => message.role === "toolResult" && message.toolName === "context")
930
+ .reduce((sum, message) => sum + estimateAgentMessageTokens(message), 0);
931
+ const floor =
932
+ view.breakdown.systemPromptTokens +
933
+ view.breakdown.toolSchemaTokens +
934
+ protectedSkills +
935
+ (operator ? estimateAgentMessageTokens(operator) : 0) +
936
+ estimateAgentMessageTokens({ role: "assistant", content: [{ type: "text", text: note }] }) +
937
+ 512;
938
+ return requestFits(floor, view.outputReserveTokens, view.effectiveWindow);
939
+ },
940
+ fits: () => {
941
+ const view = context.refreshLiveBudget();
942
+ return requestFits(view.inputTokens, view.outputReserveTokens, view.effectiveWindow);
943
+ },
944
+ inputTokens: () => context.refreshLiveBudget().inputTokens ?? 0,
945
+ reduce: async (hooks, signal) => {
946
+ if (!state.runtime) throw new Error("Native runtime was detached.");
947
+ continuityReplayInstalled = await context.runAutoCompact(
948
+ state.runtime,
949
+ true,
950
+ undefined,
951
+ "overflow",
952
+ undefined,
953
+ undefined,
954
+ signal,
955
+ hooks,
956
+ );
957
+ },
958
+ installReplay: () => {
959
+ if (state.runtime && !continuityReplayInstalled) {
960
+ context.refreshAgentMessagesFromSession(state.runtime);
961
+ continuityReplayInstalled = true;
962
+ }
963
+ },
964
+ onCommit: (commitId, outcome) => context.notifyMemoryCommit(commitId, "continuity", outcome),
965
+ notice: emitNotice,
966
+ });
967
+
817
968
  const persistence = createTurnPersistence({
818
969
  state,
819
970
  session: deps.session,
@@ -851,6 +1002,8 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
851
1002
  knownTargets: deps.knownTargets,
852
1003
  observability: deps.observability,
853
1004
  createAgent,
1005
+ continuity,
1006
+ hasQueuedSteering: () => queues.queuedMessages().steer.length > 0,
854
1007
  middlewareToolChoice,
855
1008
  persistence,
856
1009
  context,
@@ -967,12 +1120,12 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
967
1120
  deps.recordOutOfTurnUsageRow ?? ((row: OutOfTurnUsageRow): void => appendOutOfTurnUsageRow(clioStateDir(), row));
968
1121
 
969
1122
  /**
970
- * Report an out-of-turn round's provider usage. Money was spent, so `/cost`
1123
+ * Report an out-of-turn round's provider usage. Money was spent, so `/usage`
971
1124
  * says so under its own label; turn persistence, the working-set ledger,
972
1125
  * compaction inputs, and the footer counters never see it.
973
1126
  *
974
1127
  * The same call is also appended to the out-of-turn usage store under the
975
- * state dir. That store exists because `/cost` only knows what this process
1128
+ * state dir. That store exists because `/usage` only knows what this process
976
1129
  * spent: the round appends nothing to the session JSONL by design, so an
977
1130
  * archive reader such as `clio-coder usage report` had no record of the
978
1131
  * spend at all once the process exited. The session ledger stays untouched.
@@ -1174,6 +1327,11 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1174
1327
  const pendingSkillRequests =
1175
1328
  state.currentTurnConstraints?.skills === "disabled" ? [] : (options.pendingSkillRequests ?? []);
1176
1329
  context.addWorkingContextPaths(options.workingContextPaths ?? []);
1330
+ context.prepareMemoryTurn(agentRuntime, {
1331
+ taskText: text,
1332
+ continuation: options.requestContinuation === true,
1333
+ images,
1334
+ });
1177
1335
  // A skill the operator activated narrows the tools for the workflow
1178
1336
  // it started, and that workflow outlives the turn it began in. A
1179
1337
  // fresh /skill this turn replaces the armed surface; otherwise the
@@ -1205,7 +1363,7 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1205
1363
  // the user message: persisted in the ledger, no hidden prompt
1206
1364
  // machinery.
1207
1365
  middleware.fireTurnStart(agentRuntime, text, pendingSkillRequests.length, options.requestContinuation === true);
1208
- const reminderBlock = middleware.flushPendingReminders();
1366
+ const reminderProjection = middleware.takePendingReminderProjection();
1209
1367
  // Pending skill requests are plain visible text in the user message
1210
1368
  // itself: persisted in the ledger, no hidden prompt machinery.
1211
1369
  const skillPreamble = pendingSkillRequestPreamble(pendingSkillRequests);
@@ -1218,9 +1376,9 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1218
1376
  // the explicitly requested skill turn.
1219
1377
  }
1220
1378
  }
1221
- const submittedText = [reminderBlock, skillPreamble, taskMemoryHandoffSource, text]
1222
- .filter((part) => part.length > 0)
1223
- .join("\n\n");
1379
+ const composeSubmittedText = () =>
1380
+ [reminderProjection(), skillPreamble, taskMemoryHandoffSource, text].filter((part) => part.length > 0).join("\n\n");
1381
+ let submittedText = composeSubmittedText();
1224
1382
 
1225
1383
  // 2. Pre-submit auto-compaction trigger
1226
1384
  const forceNow = process.env.CLIO_CODER_FORCE_COMPACT === "1";
@@ -1236,6 +1394,7 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1236
1394
 
1237
1395
  // 3. Ensure the session prompt (compiles only on explicit events)
1238
1396
  const compiledPrompt = await context.ensureSessionPrompt(agentRuntime);
1397
+ submittedText = composeSubmittedText();
1239
1398
 
1240
1399
  // 4. Preflight overflow check, before the user turn is committed.
1241
1400
  // A blocked request must not leave a dangling user entry that the
@@ -1252,63 +1411,27 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1252
1411
  toolSignature,
1253
1412
  });
1254
1413
 
1255
- const effectiveWindow = agentRuntime.runtimeResolution.contextWindowDetails.effectiveContextWindow;
1256
- const outputForInput = (inputTokens: number): number =>
1257
- resolveReservedOutputTokens(agentRuntime.runtimeResolution.capabilityDecisions.maxTokens, {
1258
- api: agentRuntime.agent.state.model?.api ?? "",
1259
- contextWindow: effectiveWindow,
1260
- inputTokens,
1261
- });
1262
- const pendingInputTokens = ceilChars(submittedText.length);
1263
1414
  let turnSnapshot = captureTurnSnapshot("pending");
1264
- // The snapshot prices the prompt at chars/4. When the provider has
1265
- // already attested a larger prompt for this conversation, that figure
1266
- // is what the request will actually cost, so the overflow guard reads
1267
- // the higher of the two (issue #227). Both already carry the pending
1268
- // user text and the tool schema estimate.
1269
- const budgetedPromptTokens = (snapshot: ContextSnapshot): number =>
1270
- Math.max(
1271
- snapshotInputTokens(snapshot) + pendingInputTokens,
1272
- context.liveContextEstimate(agentRuntime, submittedText).tokens,
1273
- );
1274
- const inputEstimate = budgetedPromptTokens(turnSnapshot);
1275
- const reservedOutput = outputForInput(inputEstimate);
1276
- const totalEstimate = inputEstimate + reservedOutput;
1277
-
1278
- if (effectiveWindow > 0 && totalEstimate > effectiveWindow) {
1279
- emitNotice(
1280
- `[Clio Coder] Estimated request size ${totalEstimate} tokens (input ${totalEstimate - reservedOutput} + output budget ${reservedOutput}) exceeds the effective context window of ${effectiveWindow} tokens. Running compaction before sending...`,
1281
- );
1415
+ let admission = context.refreshLiveBudget(submittedText);
1416
+ if (!requestFits(admission.inputTokens, admission.outputReserveTokens, admission.effectiveWindow)) {
1282
1417
  setTurnPreparation("compacting");
1283
- let compactionFailure: string | undefined;
1284
- const compacted = await context
1418
+ let failure: string | undefined;
1419
+ await context
1285
1420
  .runAutoCompact(agentRuntime, true, undefined, "overflow", submittedText, pendingSkillPolicy)
1286
1421
  .catch((error: unknown) => {
1287
- compactionFailure = error instanceof Error ? error.message : String(error);
1288
- return false;
1422
+ failure = error instanceof Error ? error.message : String(error);
1289
1423
  })
1290
1424
  .finally(endPreparationCompaction);
1291
- if (!compacted) {
1425
+ submittedText = composeSubmittedText();
1426
+ admission = context.refreshLiveBudget(submittedText);
1427
+ if (!requestFits(admission.inputTokens, admission.outputReserveTokens, admission.effectiveWindow)) {
1292
1428
  emitAdmissionNotice(
1293
- `[Clio Coder] Request exceeds the context window and compaction could not reclaim enough space.${compactionFailure ? ` ${compactionFailure}.` : ""} Trim the prompt or reduce active tools.`,
1429
+ `[Clio Coder] Request exceeds the available context window (input ${admission.inputTokens ?? "unknown"} + output ${admission.outputReserveTokens ?? "unknown"}, window ${admission.effectiveWindow ?? "unknown"}).${failure ? ` ${failure}` : ""} Trim the prompt or reduce active tools.`,
1294
1430
  "context-window-exceeded",
1295
1431
  );
1296
1432
  return;
1297
1433
  }
1298
- context.refreshAgentMessagesFromSession(agentRuntime);
1299
1434
  turnSnapshot = captureTurnSnapshot("pending");
1300
- const postInputEstimate = budgetedPromptTokens(turnSnapshot);
1301
- const postTotalEstimate = postInputEstimate + outputForInput(postInputEstimate);
1302
- if (postTotalEstimate > effectiveWindow) {
1303
- emitAdmissionNotice(
1304
- `[Clio Coder] Request still exceeds the effective window after compaction (${postTotalEstimate} > ${effectiveWindow}). Request blocked.`,
1305
- "context-window-exceeded",
1306
- );
1307
- return;
1308
- }
1309
- emitNotice(
1310
- `[Clio Coder] Context budget check passed post-compaction (${postTotalEstimate} <= ${effectiveWindow}). Proceeding.`,
1311
- );
1312
1435
  }
1313
1436
 
1314
1437
  // 5. Append the user turn, then stamp and persist the snapshot.
@@ -1322,6 +1445,8 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1322
1445
  text,
1323
1446
  options.display?.text,
1324
1447
  );
1448
+ context.installMemoryRestoration(agentRuntime, submittedText);
1449
+ context.commitMemoryTurn(agentRuntime);
1325
1450
  // An interrupt was submitted while a run was active, so no caller drew
1326
1451
  // it in the transcript; render it here, after the cancel notice and the
1327
1452
  // cancelled run's leftovers, which is the order the ledger has.
@@ -1509,6 +1634,7 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1509
1634
  },
1510
1635
 
1511
1636
  cancel(options?: ChatCancelOptions): void {
1637
+ continuity.cancel();
1512
1638
  const wasStreaming = state.streaming;
1513
1639
  context.cancelCompaction();
1514
1640
  recovery.cancelRetryCountdown();
@@ -1581,10 +1707,15 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1581
1707
  contextUsage: () => context.contextUsage(),
1582
1708
  currentTurnConstraints: () => state.currentTurnConstraints,
1583
1709
  contextLedger: () => context.contextLedger(),
1710
+ liveBudget: () => context.liveBudget(),
1711
+ inspectLiveBudget: () => context.inspectLiveBudget(),
1712
+ refreshLiveBudget: () => context.refreshLiveBudget(),
1584
1713
  whenSettled: () => activeSubmit,
1585
1714
  whenPrewarmSettled: () => prewarm.settled(),
1586
1715
 
1587
1716
  resetForSession(leafTurnId: string | null, replayMessages?: ReadonlyArray<AgentMessage>): void {
1717
+ continuity.cancel();
1718
+ void continuity.pause();
1588
1719
  state.currentTurnConstraints = undefined;
1589
1720
  if (state.runtime) {
1590
1721
  state.runtime.agent.abort();
@@ -1597,7 +1728,10 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1597
1728
  middlewareToolChoice.reset();
1598
1729
  state.lastTurnId = leafTurnId;
1599
1730
  state.lastRunSnapshot = null;
1600
- context.resetForSession();
1731
+ // The incoming leaf is the advisory branch identity for the new session.
1732
+ // Ordinary appends advance `state.lastTurnId` from here without re-arming
1733
+ // an advisory; arriving here at all is the navigation that does.
1734
+ context.resetForSession(leafTurnId);
1601
1735
  // The resumed ledger renders before the first new turn (issue #189),
1602
1736
  // and the window it renders against should be the probed one rather
1603
1737
  // than the catalog's, so the live capability probe the first submit
@@ -1704,6 +1838,28 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
1704
1838
  return result.aborted ? { status: "aborted", text: result.text } : { status: "answered", text: result.text };
1705
1839
  },
1706
1840
 
1841
+ requestSelfCompact: (note, toolCallId, signal) => continuity.request(note, toolCallId, signal),
1842
+ async recoverHandoff(handoffId, action) {
1843
+ if (state.streaming || state.turnPreparation !== "idle")
1844
+ throw new Error("Wait for the current turn to settle before recovery.");
1845
+ const runtime = turnRuntime.ensureRuntime();
1846
+ if (!runtime) throw new Error("No native runtime is configured.");
1847
+ state.activeInterruptReason = null;
1848
+ if (!state.activeUserTurnId) {
1849
+ const operator = [...filterEntriesToActivePath(deps.readSessionEntries?.() ?? [], state.lastTurnId ?? undefined)]
1850
+ .reverse()
1851
+ .find((entry) => entry.kind === "message" && entry.role === "user");
1852
+ state.activeUserTurnId = operator?.turnId ?? null;
1853
+ }
1854
+ state.streaming = true;
1855
+ try {
1856
+ await continuity.recover(handoffId, action);
1857
+ await continueEngineWithoutInput(runtime.agent);
1858
+ } finally {
1859
+ state.streaming = false;
1860
+ await continuity.pause();
1861
+ }
1862
+ },
1707
1863
  async compact(instructions?: string): Promise<void> {
1708
1864
  // Session check runs BEFORE orchestrator-configuration so a fresh
1709
1865
  // TUI with nothing configured still reports the actionable "no
@@ -365,6 +365,48 @@ export interface RehydrateChatPanelOptions {
365
365
  * every failure, so a session whose receipts are gone still replays.
366
366
  */
367
367
  readWorkerReceipt?: WorkerReceiptReader;
368
+ /**
369
+ * Already-rendered continuity blocks, in order, from
370
+ * `continuityReplayBlocks`. Each is emitted verbatim exactly once and never
371
+ * routed through `appendContextMessage`, whose trim and replay cap would
372
+ * crop an accepted note; §4 forbids trimming, normalizing or regenerating
373
+ * accepted text. Resolving which blocks these are needs the session and fork
374
+ * facts a renderer does not have, so the caller supplies the finished text
375
+ * and this module only places it.
376
+ */
377
+ continuityBlocks?: ReadonlyArray<string>;
378
+ }
379
+
380
+ /**
381
+ * The active-path slice a replay works from, **before** the compaction cut.
382
+ *
383
+ * Exported because the continuity fold needs exactly this array: evidence for a
384
+ * transaction routinely sits older than the cut, and `selectReplayEntries`
385
+ * would both drop it and renumber every surviving position. A caller that
386
+ * resolves a projection folds this, then renders, so the two agree on one
387
+ * ordered input.
388
+ */
389
+ export function activeEntriesBeforeCompactionCut(
390
+ turns: ReadonlyArray<SessionEntry>,
391
+ options: RehydrateChatPanelOptions = {},
392
+ /**
393
+ * Whether to apply `uptoTurnId`'s positional truncation.
394
+ *
395
+ * Display replay always does. Continuity does not, unless the selection is a
396
+ * genuine historical cut, because `uptoTurnId` carries two different meanings
397
+ * through one option: a live `/tree` switch passes it to stop the transcript
398
+ * at the selected message, and a historical fork passes it to describe a
399
+ * moment that really had no later records. Truncating for the first case
400
+ * physically discards a pause, a delivery or an acknowledgement anchored
401
+ * after the selected message, and a fold cannot restore a record it was never
402
+ * shown: leaving a `historical` flag false does not undo a cut already made.
403
+ * The branch selection honors the leaf either way; only the positional cut
404
+ * is conditional.
405
+ */
406
+ truncateAtSelection = true,
407
+ ): SessionEntry[] {
408
+ const active = filterEntriesToActivePath(turns, options.activeLeafTurnId ?? options.uptoTurnId);
409
+ return truncateAtSelection ? truncateAtTurn(active, options.uptoTurnId) : active;
368
410
  }
369
411
 
370
412
  function extractTurnText(payload: unknown): string {
@@ -1105,15 +1147,23 @@ export function buildReplayAgentMessagesFromTurns(
1105
1147
  ): AgentMessage[] {
1106
1148
  const out = createOrderedReplaySink();
1107
1149
  const seenToolCalls = new Set<string>();
1108
- const activeEntries = truncateAtTurn(
1109
- filterEntriesToActivePath(turns, options.activeLeafTurnId ?? options.uptoTurnId),
1110
- options.uptoTurnId,
1111
- );
1150
+ const activeEntries = activeEntriesBeforeCompactionCut(turns, options);
1112
1151
  const skillState = latestSkillContextState(activeEntries);
1113
- const evidence = truncateAtTurn(
1114
- filterEntriesToActivePath(options.skillContextEntries ?? turns, options.activeLeafTurnId ?? options.uptoTurnId),
1115
- options.uptoTurnId,
1116
- );
1152
+ // One emission point for the continuity blocks, tracked by a flag rather than
1153
+ // by counting occurrences: the note has to appear exactly once whether the
1154
+ // replay window holds a carrying summary, an eviction-only commit, or
1155
+ // neither. `selectReplayEntries` keeps at most one compaction summary, so
1156
+ // the in-loop emission below cannot fire twice.
1157
+ let continuityEmitted = false;
1158
+ const emitContinuity = (timestamp: string): void => {
1159
+ if (continuityEmitted) return;
1160
+ continuityEmitted = true;
1161
+ // Verbatim. The accepted note is agent-authored handoff text and is
1162
+ // carried as data; it is never presented as an operator turn and never
1163
+ // passes through the trimming/truncating context helper.
1164
+ for (const block of options.continuityBlocks ?? []) out.push(makeTextMessage("user", block, timestamp));
1165
+ };
1166
+ const evidence = activeEntriesBeforeCompactionCut(options.skillContextEntries ?? turns, options);
1117
1167
  const verified = captureSkillContext(evidence, skillState);
1118
1168
  for (const entry of selectReplayEntries(turns, options)) {
1119
1169
  switch (entry.kind) {
@@ -1181,6 +1231,10 @@ export function buildReplayAgentMessagesFromTurns(
1181
1231
  for (const block of skill.content) out.push(makeTextMessage("user", block.text, entry.timestamp));
1182
1232
  }
1183
1233
  }
1234
+ // The reduction boundary is where the handoff happened, so the note
1235
+ // sits directly after the summary that replaced the history it
1236
+ // describes, whether or not this summary is the one that carried it.
1237
+ emitContinuity(entry.timestamp);
1184
1238
  break;
1185
1239
  case "skillActivation":
1186
1240
  if (entry.activation.runId !== undefined || (skillState && !skillState.activationRefs.includes(entry.turnId)))
@@ -1213,9 +1267,20 @@ export function buildReplayAgentMessagesFromTurns(
1213
1267
  // entries themselves never become messages.
1214
1268
  case "contextEviction":
1215
1269
  case "contextRecall":
1270
+ // Continuity records are bookkeeping. The chain and the commit payload
1271
+ // never become model messages; the one thing that reaches the model is
1272
+ // the accepted note, projected once through `continuityBlocks`. Handling
1273
+ // each commit here instead would emit a copy per record and would still
1274
+ // miss a commit that sits before the cut.
1275
+ case "handoffTransaction":
1276
+ case "continuityCommit":
1216
1277
  break;
1217
1278
  }
1218
1279
  }
1280
+ // No compaction summary in the replay window: an eviction-only or
1281
+ // continuity-only outcome still has a note to carry, so it lands at the end
1282
+ // of the replayed history rather than being dropped.
1283
+ emitContinuity(activeEntries[activeEntries.length - 1]?.timestamp ?? new Date(0).toISOString());
1219
1284
  out.flush();
1220
1285
  return out.messages;
1221
1286
  }
@@ -1412,6 +1477,21 @@ export function rehydrateChatPanelFromTurns(
1412
1477
  chatPanel.applyWorkerState(state);
1413
1478
  break;
1414
1479
  }
1480
+ // One bounded provenance line each. The transcript says a handoff
1481
+ // happened and where it stands; the note itself is rendered once,
1482
+ // below, under its own label, rather than once per record.
1483
+ case "handoffTransaction":
1484
+ appendReplayLine(
1485
+ chatPanel,
1486
+ `[continuity] ${entry.event.phase} handoff=${entry.identity.handoffId} seq=${entry.transition.sequence} attempt=${entry.transition.attempt}`,
1487
+ );
1488
+ break;
1489
+ case "continuityCommit":
1490
+ appendReplayLine(
1491
+ chatPanel,
1492
+ `[continuity] commit handoff=${entry.continuity.identity.handoffId} outcome=${entry.continuity.commit.outcome} tokens=${entry.continuity.commit.tokensBefore}->${entry.continuity.commit.tokensAfter}`,
1493
+ );
1494
+ break;
1415
1495
  case "label":
1416
1496
  case "taskLedger":
1417
1497
  case "decisionLedger":
@@ -1420,6 +1500,17 @@ export function rehydrateChatPanelFromTurns(
1420
1500
  break;
1421
1501
  }
1422
1502
  }
1503
+ // The transcript shows the note as what it is: assistant-authored handoff
1504
+ // text, labelled, never rendered as an operator turn.
1505
+ //
1506
+ // This is a presentation view, not the durable text. The panel wraps to its
1507
+ // width and strips terminal control sequences, so a rendered line is not
1508
+ // byte-identical to the note; §12 permits an export transformation exactly
1509
+ // when it is labelled, and the exact bytes stay in the ledger and in the
1510
+ // model replay the same blocks feed.
1511
+ for (const block of options.continuityBlocks ?? []) {
1512
+ for (const line of block.split("\n")) appendReplayLine(chatPanel, line);
1513
+ }
1423
1514
  for (const pendingId of pendingToolIds) {
1424
1515
  chatPanel.applyEvent({
1425
1516
  type: "tool_execution_end",