@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
@@ -3,30 +3,30 @@ import {
3
3
  buildEvictionFields,
4
4
  planEviction,
5
5
  resolveWorkingSetPolicy
6
- } from "./chunk-2DTIWSAW.js";
6
+ } from "./chunk-SEQDH6JC.js";
7
7
  import {
8
8
  projectHeadlessJsonEvent,
9
9
  setupSteerChannel
10
- } from "./chunk-CBAQPTDU.js";
10
+ } from "./chunk-PIU6BXXW.js";
11
11
  import {
12
12
  ShareDomainModule
13
- } from "./chunk-IFPDAD7R.js";
13
+ } from "./chunk-RWSRKXLT.js";
14
14
  import {
15
15
  modelBootstrapGenerate,
16
16
  resolveBootstrapRoute
17
- } from "./chunk-LYJJ6546.js";
17
+ } from "./chunk-3GSPCWFX.js";
18
18
  import {
19
19
  SchedulingDomainModule,
20
20
  createPromptsDomainModule,
21
21
  renderAgentCatalogSectionsFromSpecs
22
- } from "./chunk-3JLTRPN7.js";
22
+ } from "./chunk-CVMUXT64.js";
23
23
  import {
24
24
  recordFailedCompactionCalls
25
- } from "./chunk-D3KWFOUT.js";
25
+ } from "./chunk-23UIXH6Z.js";
26
26
  import {
27
27
  LifecycleDomainModule,
28
28
  describeUpgradeNotice
29
- } from "./chunk-4VDHLCF5.js";
29
+ } from "./chunk-4WJBWKMI.js";
30
30
  import "./chunk-HOYZUKG7.js";
31
31
  import "./chunk-QHSBROEW.js";
32
32
  import {
@@ -44,12 +44,12 @@ import {
44
44
  } from "./chunk-DR3TAEUA.js";
45
45
  import {
46
46
  steerToolSurface
47
- } from "./chunk-GCQFN7YI.js";
47
+ } from "./chunk-EMYEQLWO.js";
48
48
  import {
49
49
  panesToolSurface
50
- } from "./chunk-ZUKUCZYZ.js";
50
+ } from "./chunk-TQ2KTH4A.js";
51
51
  import "./chunk-NASYVWDB.js";
52
- import "./chunk-IN7DGBVS.js";
52
+ import "./chunk-VLX5VZ35.js";
53
53
  import {
54
54
  ensureClioState,
55
55
  takeUpgradeNotice
@@ -61,11 +61,12 @@ import {
61
61
  prewarmPromptTokens,
62
62
  recordStartupDiagnostic,
63
63
  runPrewarmRound
64
- } from "./chunk-BFOSV5EZ.js";
64
+ } from "./chunk-WX2YCH7F.js";
65
65
  import {
66
66
  HANDOFF_RESPONSE_SCHEMA,
67
67
  buildModelReplayAgentMessagesFromTurns,
68
68
  compact,
69
+ continuityContextFromSession,
69
70
  createAssistantGenerationTiming,
70
71
  hasAssistantGenerationDelta,
71
72
  rememberRecentModel,
@@ -73,12 +74,12 @@ import {
73
74
  renderEvictionSkipLine,
74
75
  subscribeLoopGuardStop,
75
76
  watchdogBlockersNotice
76
- } from "./chunk-62UK7AGW.js";
77
+ } from "./chunk-M5PMRZSR.js";
77
78
  import {
78
79
  DEFAULT_KEEP_RECENT_TOKENS,
79
80
  calculateContextTokens,
80
81
  estimateTokens
81
- } from "./chunk-YQ6XEFVK.js";
82
+ } from "./chunk-Z74OGONW.js";
82
83
  import {
83
84
  assistantSessionPayload,
84
85
  attachedToolSchemasFromState,
@@ -110,12 +111,12 @@ import {
110
111
  toolNamesFromAgentState,
111
112
  toolResultSummary,
112
113
  toolSignatureFromState
113
- } from "./chunk-ZR5YA6ZF.js";
114
+ } from "./chunk-36XVK5P5.js";
114
115
  import {
115
116
  PROMPT_MANIFEST_VERSION,
116
117
  appendPromptCompileRecord,
117
118
  readPromptCompileRecords
118
- } from "./chunk-ZJPORZTC.js";
119
+ } from "./chunk-GIAI7A2K.js";
119
120
  import {
120
121
  detectPlatformKeybindingWarnings,
121
122
  detectTerminalKeySupport,
@@ -125,7 +126,7 @@ import {
125
126
  } from "./chunk-3O5JWP4L.js";
126
127
  import {
127
128
  InteropDomainModule
128
- } from "./chunk-T2MDZES3.js";
129
+ } from "./chunk-EBMTBE76.js";
129
130
  import "./chunk-4A6YRGV4.js";
130
131
  import {
131
132
  BUILTIN_SLASH_COMMANDS,
@@ -133,7 +134,7 @@ import {
133
134
  dispatchSlashCommand,
134
135
  isWorkerShareNote,
135
136
  parseSlashCommand
136
- } from "./chunk-3LT34CAM.js";
137
+ } from "./chunk-XBIGUILU.js";
137
138
  import {
138
139
  bootstrapInputFromInitOptions
139
140
  } from "./chunk-DGC6X6XR.js";
@@ -141,32 +142,32 @@ import "./chunk-RFCKPS2V.js";
141
142
  import "./chunk-QUIJZSDW.js";
142
143
  import {
143
144
  AgentsDomainModule
144
- } from "./chunk-UCINIRHW.js";
145
- import "./chunk-OJXOA6YU.js";
145
+ } from "./chunk-TPCZXWLT.js";
146
+ import "./chunk-TL6LNERH.js";
146
147
  import "./chunk-GGPF6LLE.js";
147
148
  import {
148
149
  createUserTasksStore
149
- } from "./chunk-ISHUS7HC.js";
150
+ } from "./chunk-JRW236JT.js";
150
151
  import {
151
152
  compileScoutTransition
152
- } from "./chunk-DF6PI6GN.js";
153
+ } from "./chunk-I6UKQPGS.js";
153
154
  import {
154
155
  monitorToolSurface
155
- } from "./chunk-B7GY6QER.js";
156
+ } from "./chunk-B26OUMJW.js";
156
157
  import {
157
158
  ObservabilityDomainModule,
158
159
  recordBackgroundMemoryStep
159
- } from "./chunk-HQVN7G4F.js";
160
+ } from "./chunk-G62JBPXN.js";
160
161
  import {
161
162
  appendOutOfTurnUsageRow
162
- } from "./chunk-V35KZDUZ.js";
163
- import "./chunk-HOEDA42N.js";
164
- import "./chunk-EG4ARXPC.js";
163
+ } from "./chunk-AA3GRZWS.js";
164
+ import "./chunk-T7HZ7KJF.js";
165
+ import "./chunk-OX6QTA4N.js";
165
166
  import {
166
167
  foldWorkingSet,
167
168
  isTurnStart,
168
169
  selectVisibleEntries
169
- } from "./chunk-EZD2BXGP.js";
170
+ } from "./chunk-GUFQPTQK.js";
170
171
  import {
171
172
  applyOverrides,
172
173
  applyRoutingPatch,
@@ -184,7 +185,7 @@ import {
184
185
  import "./chunk-LJRBX7QR.js";
185
186
  import {
186
187
  createResourcesDomainModule
187
- } from "./chunk-4WOOTKFD.js";
188
+ } from "./chunk-U3FETNQB.js";
188
189
  import "./chunk-Y3JU5RPK.js";
189
190
  import "./chunk-6OWUM2NC.js";
190
191
  import {
@@ -209,6 +210,7 @@ import {
209
210
  ACP_SESSION_META_KEY,
210
211
  ACP_TOOL_PROGRESS_META_KEY,
211
212
  ACP_USAGE_META_KEY,
213
+ MEMORY_RELEVANCE_VERSION,
212
214
  buildMemoryPromptSection,
213
215
  canonicalMemoryRepositoryIdentity,
214
216
  createContextDomainModule,
@@ -217,11 +219,11 @@ import {
217
219
  createRunReceiptQuality,
218
220
  createTaskMemoryTrace,
219
221
  endpointCapacityUsage,
220
- loadMemoryRecordsSync,
221
222
  newRunId,
222
223
  openLedger,
223
224
  proposeInjectedTaskMemory,
224
225
  protectedResidencyModels,
226
+ readMemoryStoreSnapshot,
225
227
  readTaskMemorySpendSummary,
226
228
  renderDispatchReviewerTask,
227
229
  renderTaskMemoryHandoffSource,
@@ -231,36 +233,42 @@ import {
231
233
  taskMemoryBankSize,
232
234
  taskMemoryHandoffSeedOffer,
233
235
  taskMemoryTracePath
234
- } from "./chunk-LTWH2ACR.js";
236
+ } from "./chunk-EQPFEDFV.js";
235
237
  import "./chunk-PECBKCDK.js";
236
238
  import {
237
239
  readGateDecisionArtifacts,
238
240
  readPendingGateDecisions
239
- } from "./chunk-OV62D6KO.js";
240
- import "./chunk-I4ELN5BX.js";
241
+ } from "./chunk-KQMYL5GR.js";
242
+ import "./chunk-ZTNOEJRI.js";
241
243
  import {
242
244
  isGitRepository,
243
245
  recoverCleanupReadyCompeteGroups
244
- } from "./chunk-DXSETN72.js";
246
+ } from "./chunk-7WUGNN43.js";
245
247
  import {
246
248
  requireAgentSteps
247
- } from "./chunk-NFSK2VNU.js";
248
- import "./chunk-OZ76TLGU.js";
249
+ } from "./chunk-LZMTVVN4.js";
250
+ import "./chunk-EEITU7S6.js";
249
251
  import {
250
252
  SessionDomainModule,
251
253
  addResponseModelIdObservationCounts,
252
254
  attributedModelId,
255
+ continuityProjectionTokens,
256
+ continuityReplayText,
257
+ createContinuityPersistencePorts,
253
258
  emptyResponseModelIdObservationCounts,
254
259
  protectedArtifactEntryFromArtifact,
255
- protectedArtifactStateFromSessionEntries
256
- } from "./chunk-EOJPDNUP.js";
260
+ protectedArtifactStateFromSessionEntries,
261
+ resolveContinuityEvidence,
262
+ resolveContinuityProjection,
263
+ validateContinuityNote
264
+ } from "./chunk-GPTR6OTU.js";
257
265
  import "./chunk-BOBCCDJ6.js";
258
266
  import "./chunk-E6IPKW2E.js";
259
267
  import {
260
268
  SafetyDomainModule,
261
269
  parseRigorOverride,
262
270
  resolveRigor
263
- } from "./chunk-XMDBTYCH.js";
271
+ } from "./chunk-N3CWOGVF.js";
264
272
  import {
265
273
  configureRunEventJournal
266
274
  } from "./chunk-UQIVHSE4.js";
@@ -277,16 +285,16 @@ import "./chunk-ZU4JPCPR.js";
277
285
  import {
278
286
  activeDecisionRefs,
279
287
  createDecisionBoardStore
280
- } from "./chunk-U5QU5ZOD.js";
288
+ } from "./chunk-XYPWFSU5.js";
281
289
  import "./chunk-DNHVRBH4.js";
282
290
  import {
283
291
  filterEntriesToActivePath
284
292
  } from "./chunk-O5F25NIE.js";
285
- import "./chunk-U6YTUVOX.js";
293
+ import "./chunk-Q4RMNWMZ.js";
286
294
  import "./chunk-SD7SHNDW.js";
287
295
  import {
288
296
  discoverDeclaredChecks
289
- } from "./chunk-7RET77VB.js";
297
+ } from "./chunk-Q2HKY32Y.js";
290
298
  import "./chunk-YQ3GFEUY.js";
291
299
  import "./chunk-WTQGRBHV.js";
292
300
  import "./chunk-GLIQT7SU.js";
@@ -311,17 +319,17 @@ import "./chunk-GM4TY6VN.js";
311
319
  import "./chunk-XISLDHEL.js";
312
320
  import {
313
321
  collectSessionEntries
314
- } from "./chunk-RNDCROSG.js";
315
- import "./chunk-5KQEUIWM.js";
316
- import "./chunk-5JQR5B4D.js";
317
- import "./chunk-ZARZ4POR.js";
318
- import "./chunk-UX2L5CUD.js";
322
+ } from "./chunk-CNXU7TR2.js";
323
+ import "./chunk-7LUEQODV.js";
324
+ import "./chunk-6KV4KCQZ.js";
325
+ import "./chunk-G5ZDQQE3.js";
326
+ import "./chunk-AI36V3ON.js";
319
327
  import {
320
328
  discoverMarketplaceSkills,
321
329
  installSkill,
322
330
  installedSkillNames,
323
331
  modelVisibleSkills
324
- } from "./chunk-LAR7DX5E.js";
332
+ } from "./chunk-B5TASTCK.js";
325
333
  import "./chunk-DNCE23JJ.js";
326
334
  import "./chunk-CDQ2UC6W.js";
327
335
  import "./chunk-5LP5FNNR.js";
@@ -336,6 +344,7 @@ import {
336
344
  import "./chunk-LAVUQX3B.js";
337
345
  import {
338
346
  INTERACTIVE_LOOP_BLOCK_BUDGET,
347
+ continueEngineWithoutInput,
339
348
  createEngineAgent,
340
349
  createLoopGuardRegistration,
341
350
  createProtectedArtifactsRegistration,
@@ -350,19 +359,19 @@ import {
350
359
  replaceEngineMessages,
351
360
  sanitizeLockedSynthesisMessage,
352
361
  setEngineSystemPrompt
353
- } from "./chunk-GRHOXDKB.js";
362
+ } from "./chunk-ELDJWJKM.js";
354
363
  import "./chunk-ZNUZ3EXD.js";
355
364
  import {
356
365
  captureWorkerContext
357
- } from "./chunk-R7FGRZNF.js";
366
+ } from "./chunk-LXYHCCIZ.js";
358
367
  import {
359
368
  DEFAULT_RECENT_ENTRY_LIMIT,
360
369
  assessFinishContract,
361
370
  recentEntries
362
- } from "./chunk-DOUISWEE.js";
371
+ } from "./chunk-QOJ4ERMU.js";
363
372
  import {
364
373
  verifyReceiptIntegrity
365
- } from "./chunk-VKHLUZNO.js";
374
+ } from "./chunk-UZ7YBL43.js";
366
375
  import {
367
376
  COUNCIL_JUDGE_PROMPT,
368
377
  COUNCIL_MAX_MEMBERS,
@@ -428,14 +437,14 @@ import {
428
437
  watchdogBriefing,
429
438
  withResolvedDispatchPlan,
430
439
  writeMiddlewareDiagnosticToStderr
431
- } from "./chunk-OQ2EOAHA.js";
440
+ } from "./chunk-PF36KOGI.js";
432
441
  import {
433
442
  createTaskBoardStore
434
- } from "./chunk-NDPF2N2L.js";
443
+ } from "./chunk-7DW7CJW2.js";
435
444
  import "./chunk-7QXCO3AF.js";
436
445
  import {
437
446
  defaultRoutingIntent
438
- } from "./chunk-T33IYTZM.js";
447
+ } from "./chunk-SAASVMIY.js";
439
448
  import "./chunk-OH3YYA3B.js";
440
449
  import "./chunk-VESL6DQY.js";
441
450
  import "./chunk-FD3IAHQM.js";
@@ -444,40 +453,41 @@ import {
444
453
  } from "./chunk-XNIZBOFV.js";
445
454
  import {
446
455
  TOOL_PROFILE_NAMES
447
- } from "./chunk-XZWSP67B.js";
456
+ } from "./chunk-5APYAXCB.js";
448
457
  import {
449
458
  agentRoleFactsResolver,
450
459
  gateDeciderAgentId,
451
460
  requestExecutionRole
452
- } from "./chunk-BRCOSE7O.js";
461
+ } from "./chunk-SZUO5BSE.js";
453
462
  import {
454
463
  DEFAULT_KILL_GRACE_MS,
455
464
  DEFAULT_TEARDOWN_BOUND_MS
456
- } from "./chunk-55RMDDFM.js";
465
+ } from "./chunk-PGQG7ZMW.js";
457
466
  import {
458
467
  narrowDispatchIntentToReadOnly,
459
468
  normalizeDispatchIntent
460
469
  } from "./chunk-L4N3DWNW.js";
461
- import "./chunk-TDGYTWM6.js";
462
- import "./chunk-CTFPFW3H.js";
463
- import "./chunk-DE2LU267.js";
470
+ import "./chunk-F4I3H56H.js";
471
+ import "./chunk-Q6FRR3AQ.js";
472
+ import "./chunk-KKAJGT3B.js";
473
+ import "./chunk-H7REJ7L5.js";
464
474
  import "./chunk-YYLCPDCC.js";
465
475
  import "./chunk-EFSEIZE6.js";
466
- import "./chunk-KBUTSJSR.js";
476
+ import "./chunk-4XBYHXGK.js";
467
477
  import {
468
478
  expandInlineFileReferencesAsync
469
- } from "./chunk-YBUECLAF.js";
470
- import "./chunk-CRHPCPZP.js";
471
- import "./chunk-FFYTC2KQ.js";
472
- import "./chunk-UJD5MPP6.js";
479
+ } from "./chunk-YEJQDODA.js";
480
+ import "./chunk-4KC4CHKJ.js";
481
+ import "./chunk-MNABNEBX.js";
482
+ import "./chunk-FLNXQQ5B.js";
473
483
  import {
474
484
  escapeRegExp
475
485
  } from "./chunk-DZLR4UEH.js";
476
486
  import {
477
487
  sweepExpiredToolOffloads,
478
488
  toolResultPresentationText
479
- } from "./chunk-OPD7GL6F.js";
480
- import "./chunk-733ZBT5P.js";
489
+ } from "./chunk-H6FQVNZC.js";
490
+ import "./chunk-E74MJF6G.js";
481
491
  import "./chunk-GHXGMFQ5.js";
482
492
  import "./chunk-KECNXMDE.js";
483
493
  import "./chunk-WXA2CFJO.js";
@@ -493,6 +503,7 @@ import {
493
503
  ProvidersDomainModule,
494
504
  applyModelCapabilityPatch,
495
505
  declareRuntimeNoticeProducer,
506
+ estimateInputTokensFromContext,
496
507
  firstRuntimeResolutionError,
497
508
  gatewayRoutingObservationFromRecord,
498
509
  gatewayRoutingObservationLabel,
@@ -516,7 +527,7 @@ import {
516
527
  setGlobalDefaultMaxOutputTokens,
517
528
  setProtectedModelsProvider,
518
529
  withRunOverrides
519
- } from "./chunk-EYTLV3W3.js";
530
+ } from "./chunk-HTWTORF7.js";
520
531
  import "./chunk-MCJKEGHO.js";
521
532
  import "./chunk-DVTBERFY.js";
522
533
  import {
@@ -544,7 +555,7 @@ import {
544
555
  reconcileSnapshot,
545
556
  shouldCompact,
546
557
  snapshotInputTokens
547
- } from "./chunk-6W75D37F.js";
558
+ } from "./chunk-53P775GE.js";
548
559
  import {
549
560
  VALID_THINKING_LEVELS,
550
561
  registerBuiltinRuntimes
@@ -577,21 +588,30 @@ import {
577
588
  incompleteInstallationAdvice
578
589
  } from "./chunk-HMN2C6YL.js";
579
590
  import {
591
+ HANDOFF_MAX_FLUSH_RETRIES,
592
+ HANDOFF_MAX_WINDOW_MS,
593
+ HANDOFF_POLICY_LIMITS,
594
+ HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE,
595
+ HANDOFF_SCHEMA_VERSION,
580
596
  SKILL_CONTEXT_STATE,
581
- armedSkillSurface,
597
+ canonicalJson,
598
+ isContinuityCheckpointPayload,
582
599
  isSessionEntry,
583
- isSkillActivation,
584
600
  latestSkillContextState,
585
- mainSkillContextState,
586
- skillActivationFromToolDetails,
587
- skillSurfaceLabels,
588
- skillSurfaceNames,
589
- withModelSkillActivation
590
- } from "./chunk-I4Y4WKZR.js";
601
+ mainSkillContextState
602
+ } from "./chunk-F6KCJO3U.js";
591
603
  import {
592
604
  DEFAULT_AUTONOMY_LEVEL,
593
605
  modelMayActivateSkills
594
606
  } from "./chunk-UZVYEF57.js";
607
+ import {
608
+ armedSkillSurface,
609
+ isSkillActivation,
610
+ skillActivationFromToolDetails,
611
+ skillSurfaceLabels,
612
+ skillSurfaceNames,
613
+ withModelSkillActivation
614
+ } from "./chunk-MMZMM6SW.js";
595
615
  import "./chunk-O3VV6IVK.js";
596
616
  import {
597
617
  atomicWrite,
@@ -600,20 +620,20 @@ import {
600
620
  readSessionTailTurns,
601
621
  sessionCurrentPath,
602
622
  sessionPaths
603
- } from "./chunk-EKV4UBCM.js";
623
+ } from "./chunk-PZBQUJ2F.js";
604
624
  import "./chunk-UHW5V7HC.js";
605
625
  import {
606
626
  RESULT_SUMMARY_MAX_BYTES_CEILING,
607
627
  parseScoutResult,
608
628
  parseVerifierResult,
609
629
  resultContractSourceId
610
- } from "./chunk-LNOXCGN2.js";
630
+ } from "./chunk-7UFDIOBT.js";
611
631
  import "./chunk-BJH6WFIB.js";
612
632
  import "./chunk-5EQO5NWP.js";
613
- import "./chunk-VYRQHORQ.js";
633
+ import "./chunk-NMQSO6Z6.js";
614
634
  import {
615
635
  toolMutationPaths
616
- } from "./chunk-P4GNZPXO.js";
636
+ } from "./chunk-OYUS3UZP.js";
617
637
  import "./chunk-R2P2U3ZS.js";
618
638
  import "./chunk-NTTS3O53.js";
619
639
  import "./chunk-C2CPOIH2.js";
@@ -625,7 +645,7 @@ import {
625
645
  } from "./chunk-M4KFUXS7.js";
626
646
  import {
627
647
  ToolNames
628
- } from "./chunk-V4XJWDHR.js";
648
+ } from "./chunk-6UKFRTXI.js";
629
649
  import {
630
650
  readSettings,
631
651
  updateSettings
@@ -700,7 +720,7 @@ import {
700
720
 
701
721
  // src/entry/orchestrator.ts
702
722
  init_esm_shims();
703
- import { randomUUID as randomUUID2 } from "node:crypto";
723
+ import { randomUUID as randomUUID3 } from "node:crypto";
704
724
  import { readFileSync as readFileSync3 } from "node:fs";
705
725
  import { join as join3 } from "node:path";
706
726
 
@@ -1386,6 +1406,65 @@ function stableJson(value) {
1386
1406
  return JSON.stringify(value);
1387
1407
  }
1388
1408
 
1409
+ // src/domains/memory/prompt-cache.ts
1410
+ init_esm_shims();
1411
+ import { createHash } from "node:crypto";
1412
+ function createMemoryPromptReader(options) {
1413
+ const selection = { ...options.selection, scopes: [...options.selection?.scopes ?? ["global", "repo", "runtime"]] };
1414
+ const experimentalRelevance = options.experimentalRelevance === true;
1415
+ const readStore = options.readStore ?? readMemoryStoreSnapshot;
1416
+ let frame = null;
1417
+ let selected = null;
1418
+ return (request) => {
1419
+ const dataDir = options.getDataDir();
1420
+ const activeRepository = canonicalMemoryRepositoryIdentity(request.cwd);
1421
+ const authority = JSON.stringify([
1422
+ dataDir,
1423
+ request.sessionAuthority,
1424
+ request.cwd,
1425
+ activeRepository,
1426
+ request.targetId,
1427
+ request.runtimeId,
1428
+ request.modelId
1429
+ ]);
1430
+ const frameKey = JSON.stringify([request.turnId, authority]);
1431
+ if (request.turnId !== null && frame?.key === frameKey) return frame.section;
1432
+ let section = "";
1433
+ try {
1434
+ const snapshot = readStore(dataDir);
1435
+ const relevance = {
1436
+ taskText: request.taskText,
1437
+ activePaths: [...request.activePaths],
1438
+ activeSymbols: [...request.activeSymbols ?? []]
1439
+ };
1440
+ const key = createHash("sha256").update(
1441
+ JSON.stringify([
1442
+ snapshot.revision,
1443
+ authority,
1444
+ selection,
1445
+ relevance,
1446
+ experimentalRelevance,
1447
+ MEMORY_RELEVANCE_VERSION
1448
+ ])
1449
+ ).digest("hex");
1450
+ if (selected?.key === key) section = selected.section;
1451
+ else {
1452
+ section = buildMemoryPromptSection(snapshot.records, {
1453
+ ...selection,
1454
+ activeRepository,
1455
+ activeRuntime: { kind: "runtime", key: request.runtimeId },
1456
+ ...experimentalRelevance ? { relevance } : {}
1457
+ }).section;
1458
+ selected = Object.freeze({ key, section });
1459
+ }
1460
+ } catch {
1461
+ selected = null;
1462
+ }
1463
+ frame = request.turnId === null ? null : Object.freeze({ key: frameKey, section });
1464
+ return section;
1465
+ };
1466
+ }
1467
+
1389
1468
  // src/domains/middleware/demo-guidance.ts
1390
1469
  init_esm_shims();
1391
1470
  function createDemoGuidanceRegistration(enabled) {
@@ -1498,13 +1577,29 @@ function recordDecision(options, turnId, assessment, rigor) {
1498
1577
  }
1499
1578
  }
1500
1579
 
1580
+ // src/domains/session/continuity/carry.ts
1581
+ init_esm_shims();
1582
+ function continuityPayloadFromFold(fold) {
1583
+ if (!fold.validated) return null;
1584
+ if (!fold.identity || !fold.accepted || !fold.policy || !fold.commit || !fold.state) return null;
1585
+ const payload = {
1586
+ schemaVersion: HANDOFF_SCHEMA_VERSION,
1587
+ identity: fold.identity,
1588
+ accepted: fold.accepted,
1589
+ policy: fold.policy,
1590
+ commit: fold.commit,
1591
+ state: fold.state
1592
+ };
1593
+ return isContinuityCheckpointPayload(payload) ? payload : null;
1594
+ }
1595
+
1501
1596
  // src/domains/session/protected-artifact-journal.ts
1502
1597
  init_esm_shims();
1503
- import { createHash, randomBytes } from "node:crypto";
1598
+ import { createHash as createHash2, randomBytes } from "node:crypto";
1504
1599
  import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from "node:fs";
1505
1600
  import { join } from "node:path";
1506
1601
  function sessionKey(sessionId) {
1507
- return createHash("sha256").update(sessionId, "utf8").digest("hex");
1602
+ return createHash2("sha256").update(sessionId, "utf8").digest("hex");
1508
1603
  }
1509
1604
  function journalDirectory(sessionId) {
1510
1605
  return join(clioStateDir(), "protected-artifact-pending", sessionKey(sessionId));
@@ -1537,7 +1632,7 @@ function recordPayload(record2, legacyNaming = false) {
1537
1632
  };
1538
1633
  }
1539
1634
  function digest(record2, legacyNaming = false) {
1540
- return createHash("sha256").update(JSON.stringify(recordPayload(record2, legacyNaming)), "utf8").digest("hex");
1635
+ return createHash2("sha256").update(JSON.stringify(recordPayload(record2, legacyNaming)), "utf8").digest("hex");
1541
1636
  }
1542
1637
  function withoutIntegrity(record2) {
1543
1638
  return {
@@ -2001,7 +2096,7 @@ function headlessContext(host, notice, push) {
2001
2096
  listAgents: () => unreachable("listAgents"),
2002
2097
  listDelegationAgents: () => unreachable("listDelegationAgents"),
2003
2098
  shutdown: () => unreachable("shutdown"),
2004
- openCost: () => unreachable("openCost"),
2099
+ openUsage: () => unreachable("openUsage"),
2005
2100
  openSideQuestion: () => unreachable("openSideQuestion"),
2006
2101
  startHandoff: () => unreachable("startHandoff"),
2007
2102
  openContextView: () => unreachable("openContextView"),
@@ -4216,8 +4311,689 @@ function promptResponse(stopReason, active) {
4216
4311
 
4217
4312
  // src/interactive/chat-loop.ts
4218
4313
  init_esm_shims();
4314
+
4315
+ // src/interactive/continuity-controller.ts
4316
+ init_esm_shims();
4317
+ import { randomUUID as randomUUID2 } from "node:crypto";
4318
+
4319
+ // src/domains/session/continuity/persistence.ts
4320
+ init_esm_shims();
4321
+ function continuityRecordDigest(entry) {
4322
+ return canonicalJson(entry);
4323
+ }
4324
+ function blocked(reason, accepted, barrierAttempts, anomalies) {
4325
+ return { status: "uncertain", reason, accepted: [...accepted], barrierAttempts, anomalies };
4326
+ }
4327
+ function readback(entry, ports) {
4328
+ try {
4329
+ return ports.readExact(entry);
4330
+ } catch (error) {
4331
+ return { status: "unresolved", detail: error instanceof Error ? error.message : String(error) };
4332
+ }
4333
+ }
4334
+ function reconcileContinuityEntry(entry, ports) {
4335
+ const result = readback(entry, ports);
4336
+ if (result.status === "matching") return { status: "accepted" };
4337
+ if (result.status === "conflicting") {
4338
+ return { status: "conflicting", ...result.detail === void 0 ? {} : { detail: result.detail } };
4339
+ }
4340
+ if (result.status === "unresolved") {
4341
+ return { status: "unresolved", ...result.detail === void 0 ? {} : { detail: result.detail } };
4342
+ }
4343
+ return { status: "absent" };
4344
+ }
4345
+ function invalidSchedule(retry) {
4346
+ if (!Number.isSafeInteger(retry.limit) || retry.limit < 0 || retry.limit > HANDOFF_MAX_FLUSH_RETRIES) {
4347
+ return `retry limit ${retry.limit} is not an integer in 0..${HANDOFF_MAX_FLUSH_RETRIES}`;
4348
+ }
4349
+ if (!Number.isFinite(retry.deadlineAtMs)) return "retry deadline is not finite";
4350
+ const now = retry.now();
4351
+ if (!Number.isFinite(now)) return "retry clock is not finite";
4352
+ return null;
4353
+ }
4354
+ function createClockGuard(retry) {
4355
+ let observed = Number.NEGATIVE_INFINITY;
4356
+ let broken = false;
4357
+ return {
4358
+ read() {
4359
+ if (broken) return null;
4360
+ const now = retry.now();
4361
+ if (!Number.isFinite(now) || now < observed) {
4362
+ broken = true;
4363
+ return null;
4364
+ }
4365
+ observed = now;
4366
+ return now;
4367
+ }
4368
+ };
4369
+ }
4370
+ async function runBarrier(request, ports, retry, clock) {
4371
+ const flushing = request.barrier.kind === "flush";
4372
+ const port = flushing ? ports.flushAppends : ports.checkpoint;
4373
+ if (!port) {
4374
+ return {
4375
+ ok: false,
4376
+ attempts: 0,
4377
+ reason: "unsupported_barrier_port",
4378
+ detail: `${request.barrier.kind} port is not available`
4379
+ };
4380
+ }
4381
+ let attempts = 0;
4382
+ let detail = "";
4383
+ for (let round = 0; round <= retry.limit; round += 1) {
4384
+ let before = clock.read();
4385
+ if (round > 0) {
4386
+ if (before === null || before >= retry.deadlineAtMs) break;
4387
+ try {
4388
+ await retry.wait(round);
4389
+ } catch (error) {
4390
+ return {
4391
+ ok: false,
4392
+ attempts,
4393
+ reason: "barrier_failed",
4394
+ detail: `retry wait rejected: ${error instanceof Error ? error.message : String(error)}`
4395
+ };
4396
+ }
4397
+ before = clock.read();
4398
+ if (before === null) {
4399
+ return { ok: false, attempts, reason: "invalid_retry_schedule", detail: "retry clock became unusable" };
4400
+ }
4401
+ if (ports.isStateRemoved()) return { ok: false, attempts, reason: "state_removed", detail: "state removed" };
4402
+ if (!ports.isOriginCurrent()) return { ok: false, attempts, reason: "origin_changed", detail: "origin changed" };
4403
+ if (before >= retry.deadlineAtMs) break;
4404
+ }
4405
+ attempts += 1;
4406
+ try {
4407
+ if (request.barrier.kind === "flush") {
4408
+ port();
4409
+ } else {
4410
+ await port(request.barrier.reason);
4411
+ }
4412
+ const after = clock.read();
4413
+ return {
4414
+ ok: true,
4415
+ attempts,
4416
+ confirmedWithinDeadline: before !== null && after !== null && after < retry.deadlineAtMs
4417
+ };
4418
+ } catch (error) {
4419
+ detail = error instanceof Error ? error.message : String(error);
4420
+ }
4421
+ }
4422
+ return { ok: false, attempts, reason: "barrier_failed", detail: detail || "deadline reached before the barrier ran" };
4423
+ }
4424
+ async function persistContinuityGroup(request, ports, retry) {
4425
+ const anomalies = [];
4426
+ const accepted = [];
4427
+ const wanted = /* @__PURE__ */ new Map();
4428
+ for (const entry of request.entries) {
4429
+ const digest2 = continuityRecordDigest(entry);
4430
+ const previous = wanted.get(entry.turnId);
4431
+ if (previous !== void 0 && previous !== digest2) {
4432
+ anomalies.push({
4433
+ kind: "duplicate_conflict",
4434
+ entryId: entry.turnId,
4435
+ detail: "the group contains two different records under one entry id"
4436
+ });
4437
+ return blocked("duplicate_group_entry", accepted, 0, anomalies);
4438
+ }
4439
+ wanted.set(entry.turnId, digest2);
4440
+ }
4441
+ const acceptedIds = /* @__PURE__ */ new Set();
4442
+ for (const progress of request.alreadyAccepted ?? []) {
4443
+ const expected = wanted.get(progress.entryId);
4444
+ if (expected === void 0 || expected !== progress.payloadDigest) {
4445
+ anomalies.push({
4446
+ kind: "duplicate_conflict",
4447
+ entryId: progress.entryId,
4448
+ detail: "reported progress does not match this group's record for that id"
4449
+ });
4450
+ return blocked("progress_mismatch", [], 0, anomalies);
4451
+ }
4452
+ if (acceptedIds.has(progress.entryId)) continue;
4453
+ acceptedIds.add(progress.entryId);
4454
+ accepted.push({ entryId: progress.entryId, payloadDigest: progress.payloadDigest });
4455
+ }
4456
+ const scheduleProblem = invalidSchedule(retry);
4457
+ if (scheduleProblem) {
4458
+ anomalies.push({ kind: "malformed_record", entryId: null, detail: scheduleProblem });
4459
+ return blocked("invalid_retry_schedule", accepted, 0, anomalies);
4460
+ }
4461
+ const clock = createClockGuard(retry);
4462
+ const remaining = () => request.entries.filter((entry) => !acceptedIds.has(entry.turnId));
4463
+ const startedAt = clock.read();
4464
+ if (startedAt === null) {
4465
+ anomalies.push({ kind: "malformed_record", entryId: null, detail: "the retry clock is not usable" });
4466
+ return blocked("invalid_retry_schedule", accepted, 0, anomalies);
4467
+ }
4468
+ if (startedAt >= retry.deadlineAtMs && remaining().length > 0) {
4469
+ anomalies.push({
4470
+ kind: "malformed_record",
4471
+ entryId: null,
4472
+ detail: "the deadline passed before the group was written"
4473
+ });
4474
+ return blocked("deadline_expired", accepted, 0, anomalies);
4475
+ }
4476
+ if (ports.isStateRemoved()) return blocked("state_removed", accepted, 0, anomalies);
4477
+ if (!ports.isOriginCurrent()) return blocked("origin_changed", accepted, 0, anomalies);
4478
+ const writeBlocked = () => {
4479
+ const now = clock.read();
4480
+ if (now === null) return "invalid_retry_schedule";
4481
+ if (now >= retry.deadlineAtMs) return "deadline_expired";
4482
+ if (ports.isStateRemoved()) return "state_removed";
4483
+ if (!ports.isOriginCurrent()) return "origin_changed";
4484
+ return null;
4485
+ };
4486
+ for (const entry of request.entries) {
4487
+ if (acceptedIds.has(entry.turnId)) continue;
4488
+ const guard = writeBlocked();
4489
+ if (guard) {
4490
+ anomalies.push({ kind: "malformed_record", entryId: entry.turnId, detail: `write refused: ${guard}` });
4491
+ return blocked(guard, accepted, 0, anomalies);
4492
+ }
4493
+ const digest2 = continuityRecordDigest(entry);
4494
+ const accept = () => {
4495
+ accepted.push({ entryId: entry.turnId, payloadDigest: digest2 });
4496
+ acceptedIds.add(entry.turnId);
4497
+ };
4498
+ try {
4499
+ ports.append(entry);
4500
+ accept();
4501
+ continue;
4502
+ } catch (error) {
4503
+ anomalies.push({
4504
+ kind: "malformed_record",
4505
+ entryId: entry.turnId,
4506
+ detail: `append threw: ${error instanceof Error ? error.message : String(error)}`
4507
+ });
4508
+ }
4509
+ const reconciled = reconcileContinuityEntry(entry, ports);
4510
+ if (reconciled.status === "accepted") {
4511
+ accept();
4512
+ continue;
4513
+ }
4514
+ if (reconciled.status === "conflicting") {
4515
+ anomalies.push({
4516
+ kind: "duplicate_conflict",
4517
+ entryId: entry.turnId,
4518
+ detail: reconciled.detail ?? "stored record has this id and different payload"
4519
+ });
4520
+ return blocked("append_conflicting", accepted, 0, anomalies);
4521
+ }
4522
+ if (reconciled.status === "unresolved") {
4523
+ return blocked("append_unresolved", accepted, 0, anomalies);
4524
+ }
4525
+ const reguard = writeBlocked();
4526
+ if (reguard) {
4527
+ anomalies.push({ kind: "malformed_record", entryId: entry.turnId, detail: `reappend refused: ${reguard}` });
4528
+ return blocked(reguard, accepted, 0, anomalies);
4529
+ }
4530
+ try {
4531
+ ports.append(entry);
4532
+ accept();
4533
+ } catch (error) {
4534
+ const second = reconcileContinuityEntry(entry, ports);
4535
+ if (second.status === "accepted") {
4536
+ accept();
4537
+ continue;
4538
+ }
4539
+ anomalies.push({
4540
+ kind: "malformed_record",
4541
+ entryId: entry.turnId,
4542
+ detail: `reappend threw: ${error instanceof Error ? error.message : String(error)}`
4543
+ });
4544
+ return blocked(
4545
+ second.status === "absent" ? "append_absent_after_retry" : "append_unresolved",
4546
+ accepted,
4547
+ 0,
4548
+ anomalies
4549
+ );
4550
+ }
4551
+ }
4552
+ if (ports.isStateRemoved()) return blocked("state_removed", accepted, 0, anomalies);
4553
+ if (!ports.isOriginCurrent()) return blocked("origin_changed", accepted, 0, anomalies);
4554
+ const barrier = await runBarrier(request, ports, retry, clock);
4555
+ if (!barrier.ok) {
4556
+ anomalies.push({
4557
+ kind: "malformed_record",
4558
+ entryId: null,
4559
+ detail: `${request.barrier.kind} barrier did not complete: ${barrier.detail}`
4560
+ });
4561
+ return blocked(barrier.reason, accepted, barrier.attempts, anomalies);
4562
+ }
4563
+ if (ports.isStateRemoved()) return blocked("state_removed", accepted, barrier.attempts, anomalies);
4564
+ if (!ports.isOriginCurrent()) return blocked("origin_changed", accepted, barrier.attempts, anomalies);
4565
+ return {
4566
+ status: "durable",
4567
+ accepted,
4568
+ barrierAttempts: barrier.attempts,
4569
+ confirmedWithinDeadline: barrier.confirmedWithinDeadline
4570
+ };
4571
+ }
4572
+
4573
+ // src/interactive/continuity-controller.ts
4574
+ var ContinuityController = class {
4575
+ constructor(deps) {
4576
+ this.deps = deps;
4577
+ this.now = deps.now ?? Date.now;
4578
+ this.id = deps.id ?? randomUUID2;
4579
+ }
4580
+ deps;
4581
+ live = null;
4582
+ busy = false;
4583
+ now;
4584
+ id;
4585
+ armDeadline(live) {
4586
+ live.deadlineTimer = setTimeout(
4587
+ () => live.abort.abort(new Error("Handoff automatic deadline expired.")),
4588
+ Math.max(1, live.deadline - this.now())
4589
+ );
4590
+ live.deadlineTimer.unref?.();
4591
+ }
4592
+ fold(live = this.live) {
4593
+ if (!live?.origin.ports.isOriginCurrent() || live.origin.ports.isStateRemoved()) return null;
4594
+ return resolveContinuityProjection({
4595
+ entries: this.deps.entries(),
4596
+ sessionId: live.origin.sessionId,
4597
+ nowMs: this.now()
4598
+ }).current;
4599
+ }
4600
+ check(live) {
4601
+ live.abort.signal.throwIfAborted();
4602
+ if (this.live !== live || !live.origin.ports.isOriginCurrent()) throw new Error("Handoff ownership changed.");
4603
+ if (live.uncertain) throw new Error("Handoff persistence is unresolved; inspect before recovery.");
4604
+ if (this.now() >= live.deadline) throw new Error("Handoff automatic deadline expired.");
4605
+ }
4606
+ async persist(live, entries, checkpoint = false, closing = false) {
4607
+ const result = await persistContinuityGroup(
4608
+ {
4609
+ entries: entries.map((entry) => ({ ...entry })),
4610
+ barrier: checkpoint ? { kind: "checkpoint", reason: "context-handoff" } : { kind: "flush" }
4611
+ },
4612
+ live.origin.ports,
4613
+ {
4614
+ limit: live.policy.flushRetryLimit,
4615
+ deadlineAtMs: closing ? this.now() + 5e3 : live.deadline,
4616
+ now: this.now,
4617
+ wait: async () => {
4618
+ }
4619
+ }
4620
+ );
4621
+ if (result.status !== "durable" || !result.confirmedWithinDeadline) {
4622
+ live.uncertain = true;
4623
+ throw new Error(
4624
+ `Handoff durability unresolved: ${result.status === "durable" ? "deadline expired" : result.reason}`
4625
+ );
4626
+ }
4627
+ }
4628
+ async transition(live, event, closing = false) {
4629
+ const fold = this.fold(live);
4630
+ if (!fold?.validated || fold.identity?.handoffId !== live.identity.handoffId || !fold.state)
4631
+ throw new Error("Handoff head cannot be validated.");
4632
+ const entryId = this.id();
4633
+ const entry = {
4634
+ kind: "handoffTransaction",
4635
+ schemaVersion: 1,
4636
+ turnId: entryId,
4637
+ parentTurnId: live.identity.branchAnchorTurnId,
4638
+ timestamp: new Date(this.now()).toISOString(),
4639
+ identity: live.identity,
4640
+ transition: {
4641
+ entryId,
4642
+ prevEntryId: fold.state.transition.entryId,
4643
+ sequence: fold.state.transition.sequence + 1,
4644
+ attempt: fold.attemptsSpent + (event.phase === "reducing" ? 1 : 0)
4645
+ },
4646
+ event
4647
+ };
4648
+ await this.persist(live, [entry], false, closing);
4649
+ if (!this.fold(live)?.validated) throw new Error("Persisted handoff transition failed validation.");
4650
+ }
4651
+ async request(note, toolCallId, signal) {
4652
+ const validation = validateContinuityNote(note);
4653
+ if (!validation.ok) throw new Error(`Invalid handoff note: ${validation.reason}`);
4654
+ if (!toolCallId || this.busy || this.live)
4655
+ throw new Error("A context handoff is already pending or no tool identity was supplied.");
4656
+ signal?.throwIfAborted();
4657
+ const origin = this.deps.captureOrigin();
4658
+ if (origin.ports.isStateRemoved() || !origin.ports.isOriginCurrent())
4659
+ throw new Error("The originating session is unavailable.");
4660
+ if (!this.deps.admitNote(validation.accepted))
4661
+ throw new Error("Exact handoff note and protected context exceed the replay budget.");
4662
+ const prior = resolveContinuityProjection({
4663
+ entries: this.deps.entries(),
4664
+ sessionId: origin.sessionId,
4665
+ nowMs: this.now()
4666
+ }).current;
4667
+ if (prior && prior.phase !== "absent" && prior.phase !== "acknowledged")
4668
+ throw new Error("An earlier handoff requires operator recovery.");
4669
+ const preparedAtMs = this.now();
4670
+ const policy = {
4671
+ ...HANDOFF_POLICY_LIMITS,
4672
+ preparedAtMs,
4673
+ automaticDeadlineAtMs: preparedAtMs + HANDOFF_MAX_WINDOW_MS
4674
+ };
4675
+ const identity = {
4676
+ handoffId: this.id(),
4677
+ preparedEntryId: this.id(),
4678
+ commitId: this.id(),
4679
+ commitEntryId: this.id(),
4680
+ originSessionId: origin.sessionId,
4681
+ branchAnchorTurnId: origin.leafTurnId,
4682
+ initiatingTurnId: origin.initiatingTurnId,
4683
+ toolCallId,
4684
+ sourceRevision: origin.sourceRevision
4685
+ };
4686
+ const live = {
4687
+ origin,
4688
+ identity,
4689
+ accepted: validation.accepted,
4690
+ policy,
4691
+ deadline: policy.automaticDeadlineAtMs,
4692
+ abort: new AbortController(),
4693
+ uncertain: false
4694
+ };
4695
+ this.live = live;
4696
+ this.armDeadline(live);
4697
+ const entry = {
4698
+ kind: "handoffTransaction",
4699
+ schemaVersion: 1,
4700
+ turnId: identity.preparedEntryId,
4701
+ parentTurnId: identity.branchAnchorTurnId,
4702
+ timestamp: new Date(preparedAtMs).toISOString(),
4703
+ identity,
4704
+ transition: { entryId: identity.preparedEntryId, prevEntryId: null, sequence: 0, attempt: 0 },
4705
+ event: { phase: "prepared", accepted: live.accepted, policy }
4706
+ };
4707
+ try {
4708
+ await this.persist(live, [entry]);
4709
+ signal?.throwIfAborted();
4710
+ this.check(live);
4711
+ return `Context handoff ${identity.handoffId} prepared. The exact note is saved. Reduction and continuation follow after this tool receipt is persisted.`;
4712
+ } catch (error) {
4713
+ await this.pause();
4714
+ throw error;
4715
+ }
4716
+ }
4717
+ readyPayload(live, outcome, before, after, ref) {
4718
+ this.check(live);
4719
+ const fold = this.fold(live);
4720
+ if (!fold?.validated || fold.phase !== "reducing" || !fold.state)
4721
+ throw new Error("No durable reduction attempt owns this checkpoint.");
4722
+ const transition = {
4723
+ entryId: live.identity.commitEntryId,
4724
+ prevEntryId: fold.state.transition.entryId,
4725
+ sequence: fold.state.transition.sequence + 1,
4726
+ attempt: fold.attemptsSpent
4727
+ };
4728
+ return {
4729
+ schemaVersion: 1,
4730
+ identity: live.identity,
4731
+ accepted: live.accepted,
4732
+ policy: live.policy,
4733
+ commit: {
4734
+ outcome,
4735
+ entry: {
4736
+ turnId: live.identity.commitEntryId,
4737
+ parentTurnId: live.identity.branchAnchorTurnId,
4738
+ timestamp: new Date(this.now()).toISOString()
4739
+ },
4740
+ transition,
4741
+ tokensBefore: before,
4742
+ tokensAfter: after,
4743
+ ...outcome === "summarized" ? { summaryRef: ref ?? "" } : outcome === "evicted" ? { evictionRef: ref ?? "" } : {}
4744
+ },
4745
+ state: { transition, event: { phase: "ready" }, activeResume: fold.state.activeResume, delivery: null }
4746
+ };
4747
+ }
4748
+ /** Called only at the settled engine boundary, after awaited result persistence. */
4749
+ async settle(signal, recovery = false) {
4750
+ const live = this.live;
4751
+ if (!live || this.busy || live.deliveryId) return false;
4752
+ this.busy = true;
4753
+ const onAbort = () => live.abort.abort(signal?.reason);
4754
+ signal?.addEventListener("abort", onAbort, { once: true });
4755
+ try {
4756
+ if (signal?.aborted) onAbort();
4757
+ this.check(live);
4758
+ const fold = this.fold(live);
4759
+ if (!fold?.validated) throw new Error("Handoff evidence is unresolved.");
4760
+ if (!recovery) {
4761
+ const entries = this.deps.entries();
4762
+ const preparedIndex = entries.findIndex((entry) => entry.turnId === live.identity.preparedEntryId);
4763
+ const receipt = preparedIndex >= 0 && entries.slice(preparedIndex + 1).some((entry) => {
4764
+ if (entry.kind !== "message" || entry.role !== "tool_result") return false;
4765
+ const payload = entry.payload;
4766
+ return entry.parentTurnId === live.origin.leafTurnId && payload.toolCallId === live.identity.toolCallId && payload.toolName === "self_compact" && payload.isError === false;
4767
+ });
4768
+ if (!receipt) throw new Error("Matching successful handoff receipt has not been persisted.");
4769
+ }
4770
+ await this.persist(live, []);
4771
+ this.check(live);
4772
+ if (!fold.commit) {
4773
+ if (fold.attemptsSpent >= live.policy.maxAttempts) throw new Error("Handoff reduction attempts exhausted.");
4774
+ const resumeRef2 = fold.state?.activeResume?.entryId;
4775
+ await this.transition(live, { phase: "reducing", ...resumeRef2 ? { resumeRef: resumeRef2 } : {} });
4776
+ const before = this.deps.inputTokens();
4777
+ const priorIds = new Set(this.deps.entries().map((entry) => entry.turnId));
4778
+ let payload;
4779
+ let calls = 0;
4780
+ await this.deps.reduce(
4781
+ {
4782
+ beforeSummaryCall: () => {
4783
+ this.check(live);
4784
+ if (++calls > live.policy.maxSummaryCallsPerAttempt)
4785
+ throw new Error("Handoff summary invocation limit reached.");
4786
+ },
4787
+ checkpointForSummary: (ref, tokensBefore, tokensAfter) => {
4788
+ payload = this.readyPayload(live, "summarized", tokensBefore, tokensAfter, ref);
4789
+ return payload;
4790
+ }
4791
+ },
4792
+ live.abort.signal
4793
+ );
4794
+ this.check(live);
4795
+ if (!payload) {
4796
+ this.deps.installReplay();
4797
+ if (!this.deps.fits()) throw new Error("Context remains unsafe after reduction; the exact note is retained.");
4798
+ const eviction = [...this.deps.entries()].reverse().find((entry) => entry.kind === "contextEviction" && !priorIds.has(entry.turnId));
4799
+ payload = this.readyPayload(
4800
+ live,
4801
+ eviction ? "evicted" : "continuity_only",
4802
+ before,
4803
+ this.deps.inputTokens(),
4804
+ eviction?.turnId
4805
+ );
4806
+ }
4807
+ await this.persist(
4808
+ live,
4809
+ [
4810
+ {
4811
+ kind: "continuityCommit",
4812
+ ...payload.commit.entry,
4813
+ continuity: { ...payload, state: { ...payload.state, activeResume: null } }
4814
+ }
4815
+ ],
4816
+ true
4817
+ );
4818
+ this.check(live);
4819
+ if (!this.fold(live)?.validated) throw new Error("Handoff checkpoint cannot be validated.");
4820
+ this.deps.onCommit?.(live.identity.commitId, payload.commit.outcome);
4821
+ }
4822
+ this.deps.installReplay();
4823
+ if (!this.deps.fits()) throw new Error("Compacted continuation exceeds the request budget.");
4824
+ this.check(live);
4825
+ const continuationTurnId = this.deps.leaf();
4826
+ if (!continuationTurnId) throw new Error("Continuation has no persisted message anchor.");
4827
+ const deliveryId = this.id();
4828
+ const deliveryFold = this.fold(live);
4829
+ const resumeRef = deliveryFold?.phase === "resumed" ? deliveryFold.state?.activeResume?.entryId : void 0;
4830
+ await this.transition(live, {
4831
+ phase: "delivered",
4832
+ deliveryId,
4833
+ continuationTurnId,
4834
+ ...resumeRef ? { resumeRef } : {}
4835
+ });
4836
+ this.check(live);
4837
+ live.deliveryId = deliveryId;
4838
+ return true;
4839
+ } catch (error) {
4840
+ await this.pause();
4841
+ throw error;
4842
+ } finally {
4843
+ signal?.removeEventListener("abort", onAbort);
4844
+ this.busy = false;
4845
+ }
4846
+ }
4847
+ /** Synchronous final provider boundary, including steering drained after prepare. */
4848
+ admission() {
4849
+ const live = this.live;
4850
+ if (!live) return { block: false };
4851
+ try {
4852
+ this.check(live);
4853
+ if (!live.deliveryId) throw new Error("Context handoff has not durably reached delivery.");
4854
+ return { block: false, correlationId: live.deliveryId };
4855
+ } catch (error) {
4856
+ return { block: true, reason: error instanceof Error ? error.message : String(error) };
4857
+ }
4858
+ }
4859
+ async response(entryId, correlationId) {
4860
+ const live = this.live;
4861
+ if (!live || !entryId || !correlationId || correlationId !== live.deliveryId || live.abort.signal.aborted || !live.origin.ports.isOriginCurrent())
4862
+ return;
4863
+ this.check(live);
4864
+ const evidence = resolveContinuityEvidence({
4865
+ entries: this.deps.entries(),
4866
+ unreadableRecords: 0
4867
+ }).terminalResponses.find((row) => row.entryId === entryId);
4868
+ if (evidence?.status !== "success") return;
4869
+ await this.transition(live, { phase: "acknowledged", deliveryId: correlationId, terminalResponseEntryId: entryId });
4870
+ if (live.deadlineTimer) clearTimeout(live.deadlineTimer);
4871
+ this.live = null;
4872
+ }
4873
+ cancel() {
4874
+ this.live?.abort.abort(new Error("Operator cancelled the context handoff."));
4875
+ }
4876
+ async pause() {
4877
+ const live = this.live;
4878
+ if (!live) return;
4879
+ live.abort.abort();
4880
+ if (live.deadlineTimer) clearTimeout(live.deadlineTimer);
4881
+ try {
4882
+ const fold = this.fold(live);
4883
+ if (!live.uncertain && fold?.validated && ["prepared", "reducing", "ready", "delivered", "resumed"].includes(fold.phase)) {
4884
+ await this.transition(
4885
+ live,
4886
+ { phase: "paused", reason: live.deliveryId ? "delivery_uncertain" : "operator_cancelled" },
4887
+ true
4888
+ );
4889
+ }
4890
+ } catch (error) {
4891
+ this.deps.notice(`Handoff ${live.identity.handoffId}: ${error instanceof Error ? error.message : String(error)}`);
4892
+ } finally {
4893
+ if (this.live === live) this.live = null;
4894
+ }
4895
+ }
4896
+ /** Host-only operator action; never reachable through tool arguments. */
4897
+ async recover(handoffId, action) {
4898
+ if (this.live || this.busy) throw new Error("A handoff is already active.");
4899
+ const origin = this.deps.captureOrigin();
4900
+ let fold = resolveContinuityProjection({
4901
+ entries: this.deps.entries(),
4902
+ sessionId: origin.sessionId,
4903
+ nowMs: this.now()
4904
+ }).current;
4905
+ if (!fold?.validated || fold.identity?.handoffId !== handoffId || !fold.accepted || !fold.policy || !fold.state)
4906
+ throw new Error("No validated handoff matches that identity on this branch.");
4907
+ if (["prepared", "reducing", "ready", "delivered", "resumed"].includes(fold.phase)) {
4908
+ this.live = {
4909
+ origin,
4910
+ identity: fold.identity,
4911
+ accepted: fold.accepted,
4912
+ policy: fold.policy,
4913
+ deadline: this.now() + HANDOFF_MAX_WINDOW_MS,
4914
+ abort: new AbortController(),
4915
+ uncertain: false
4916
+ };
4917
+ if (fold.missingCommit) {
4918
+ const expected = { ...fold.missingCommit.entry };
4919
+ const readback2 = origin.ports.readExact(expected);
4920
+ if (readback2.status !== "absent" && readback2.status !== "matching") {
4921
+ this.live = null;
4922
+ throw new Error("Commit reconstruction is unresolved.");
4923
+ }
4924
+ await this.persist(this.live, readback2.status === "absent" ? [expected] : [], true);
4925
+ }
4926
+ await this.pause();
4927
+ fold = resolveContinuityProjection({
4928
+ entries: this.deps.entries(),
4929
+ sessionId: origin.sessionId,
4930
+ nowMs: this.now()
4931
+ }).current;
4932
+ if (!fold?.validated || !fold.identity || !fold.accepted || !fold.policy || !fold.state)
4933
+ throw new Error("Interrupted handoff could not be paused durably.");
4934
+ }
4935
+ if (!["paused", "failed"].includes(fold.phase))
4936
+ throw new Error("Only a paused or failed handoff accepts operator recovery.");
4937
+ if (action === "reduce" ? fold.commit !== null || fold.attemptsSpent >= fold.policy.maxAttempts : fold.commit === null)
4938
+ throw new Error("That recovery action is unavailable for this handoff.");
4939
+ const now = this.now();
4940
+ const live = {
4941
+ origin,
4942
+ identity: fold.identity,
4943
+ accepted: fold.accepted,
4944
+ policy: fold.policy,
4945
+ deadline: now + HANDOFF_MAX_WINDOW_MS,
4946
+ abort: new AbortController(),
4947
+ uncertain: false
4948
+ };
4949
+ this.live = live;
4950
+ this.armDeadline(live);
4951
+ const request = {
4952
+ kind: "custom",
4953
+ customType: HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE,
4954
+ turnId: this.id(),
4955
+ parentTurnId: origin.leafTurnId,
4956
+ timestamp: new Date(now).toISOString(),
4957
+ display: false,
4958
+ data: {
4959
+ version: 1,
4960
+ requestKind: "handoff_recovery",
4961
+ handoffId,
4962
+ action,
4963
+ sessionId: origin.sessionId,
4964
+ branchAnchorTurnId: fold.identity.branchAnchorTurnId,
4965
+ selectedLeafTurnId: origin.leafTurnId,
4966
+ pausedOrFailedEntryId: fold.state.transition.entryId
4967
+ }
4968
+ };
4969
+ try {
4970
+ await this.persist(live, [request]);
4971
+ await this.transition(live, {
4972
+ phase: "resumed",
4973
+ authority: {
4974
+ operatorRequestEntryId: request.turnId,
4975
+ pausedOrFailedEntryId: fold.state.transition.entryId,
4976
+ action,
4977
+ automaticDeadlineAtMs: live.deadline
4978
+ }
4979
+ });
4980
+ await this.settle(void 0, true);
4981
+ } catch (error) {
4982
+ await this.pause();
4983
+ throw error;
4984
+ }
4985
+ }
4986
+ };
4987
+
4988
+ // src/interactive/chat-loop.ts
4219
4989
  import { AsyncLocalStorage } from "node:async_hooks";
4220
4990
 
4991
+ // src/domains/context/budget/request-fit.ts
4992
+ init_esm_shims();
4993
+ function requestFits(input, output, window) {
4994
+ return input !== null && output !== null && window !== null && Number.isFinite(input) && input >= 0 && Number.isFinite(output) && output >= 0 && Number.isFinite(window) && window > 0 && input + output <= window;
4995
+ }
4996
+
4221
4997
  // src/domains/session/retry.ts
4222
4998
  init_esm_shims();
4223
4999
  import { performance as performance2 } from "node:perf_hooks";
@@ -4467,7 +5243,323 @@ Extract the handoff record for that goal now.`
4467
5243
 
4468
5244
  // src/interactive/turn-context.ts
4469
5245
  init_esm_shims();
4470
- import { createHash as createHash2 } from "node:crypto";
5246
+ import { createHash as createHash4 } from "node:crypto";
5247
+
5248
+ // src/domains/context/budget/live-view.ts
5249
+ init_esm_shims();
5250
+ import { createHash as createHash3 } from "node:crypto";
5251
+
5252
+ // src/domains/context/budget/pressure.ts
5253
+ init_esm_shims();
5254
+ var DEFAULT_REDUCE_THRESHOLD = 0.8;
5255
+ var DEFAULT_WORKING_SET_TARGET = 0.6;
5256
+ var NOTICE_PROPORTION = 0.65 / 0.8;
5257
+ var PREPARE_PROPORTION = 0.72 / 0.8;
5258
+ var ADVISORY_RESET_BAND = 0.05;
5259
+ var INITIAL_ADVISORY_MEMORY = Object.freeze({
5260
+ announcedPhase: "normal",
5261
+ advisoryKey: null,
5262
+ noCutKey: null
5263
+ });
5264
+ var ADVISORY_RANK = { normal: 0, notice: 1, prepare: 2 };
5265
+ function isPositiveFinite(value) {
5266
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
5267
+ }
5268
+ function isNonNegativeFinite(value) {
5269
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
5270
+ }
5271
+ function resolvePressurePolicy(input = {}) {
5272
+ const reduce = input.reduceThreshold ?? DEFAULT_REDUCE_THRESHOLD;
5273
+ const target = input.workingSetTarget ?? DEFAULT_WORKING_SET_TARGET;
5274
+ if (!Number.isFinite(reduce) || reduce <= 0 || reduce > 1) {
5275
+ return { ok: false, reason: "reduce-threshold-out-of-range" };
5276
+ }
5277
+ if (!Number.isFinite(target) || target <= 0 || target >= reduce) {
5278
+ return { ok: false, reason: "target-out-of-range" };
5279
+ }
5280
+ const hasNotice = input.noticeThreshold !== void 0;
5281
+ const hasPrepare = input.prepareThreshold !== void 0;
5282
+ if (hasNotice !== hasPrepare) return { ok: false, reason: "advisory-thresholds-incomplete" };
5283
+ const notice = hasNotice ? input.noticeThreshold : reduce * NOTICE_PROPORTION;
5284
+ const prepare = hasPrepare ? input.prepareThreshold : reduce * PREPARE_PROPORTION;
5285
+ if (!Number.isFinite(notice) || !Number.isFinite(prepare)) {
5286
+ return { ok: false, reason: "advisory-thresholds-out-of-order" };
5287
+ }
5288
+ if (!(notice > 0 && notice < prepare && prepare < reduce)) {
5289
+ return { ok: false, reason: "advisory-thresholds-out-of-order" };
5290
+ }
5291
+ return {
5292
+ ok: true,
5293
+ policy: Object.freeze({ notice, prepare, reduce, target, derived: !hasNotice })
5294
+ };
5295
+ }
5296
+ function advisoryIdentity(facts, policy) {
5297
+ const identity = facts.contextIdentity ?? {};
5298
+ return JSON.stringify([
5299
+ "advisory/1",
5300
+ facts.revision.sessionId,
5301
+ facts.revision.branchAnchorTurnId,
5302
+ Number.isFinite(facts.effectiveContextWindow) ? facts.effectiveContextWindow : null,
5303
+ policy.notice,
5304
+ policy.prepare,
5305
+ policy.reduce,
5306
+ policy.target,
5307
+ identity.epoch ?? null,
5308
+ identity.targetId ?? null,
5309
+ identity.modelId ?? null,
5310
+ identity.promptFingerprint ?? null,
5311
+ identity.toolSignature ?? null
5312
+ ]);
5313
+ }
5314
+ function requestIdentity(advisoryKey, facts) {
5315
+ return JSON.stringify(["request/1", advisoryKey, facts.revision.revisionId]);
5316
+ }
5317
+ function unmeasuredDecision(facts, policy, prior, reason) {
5318
+ return Object.freeze({
5319
+ phase: "normal",
5320
+ pressure: null,
5321
+ headroomTokens: null,
5322
+ measured: false,
5323
+ inputSource: facts.inputSource,
5324
+ completeInputTokens: null,
5325
+ admission: Object.freeze({ result: "unknown", reason, requiredTokens: null }),
5326
+ advisory: Object.freeze({
5327
+ emit: false,
5328
+ phase: "normal",
5329
+ suppressedBy: "measurement-unknown"
5330
+ }),
5331
+ // An unmeasured turn carries prior memory through untouched. Advancing
5332
+ // the advisory key here would consume the re-arm owed to a target, model,
5333
+ // window, or policy change first seen without a usable reading.
5334
+ memory: Object.freeze({ ...prior }),
5335
+ policy
5336
+ });
5337
+ }
5338
+ function evaluatePressure(facts, policy, prior = INITIAL_ADVISORY_MEMORY) {
5339
+ if (!isPositiveFinite(facts.effectiveContextWindow)) {
5340
+ return unmeasuredDecision(facts, policy, prior, "window-unknown");
5341
+ }
5342
+ if (facts.inputTokens === null || !Number.isFinite(facts.inputTokens) || facts.inputTokens < 0) {
5343
+ return unmeasuredDecision(facts, policy, prior, "input-unknown");
5344
+ }
5345
+ const overhead = isNonNegativeFinite(facts.pendingRequestOverheadTokens) ? facts.pendingRequestOverheadTokens : null;
5346
+ const reserve = isNonNegativeFinite(facts.outputReserveTokens) ? facts.outputReserveTokens : null;
5347
+ if (overhead === null || reserve === null) {
5348
+ return unmeasuredDecision(facts, policy, prior, "input-unknown");
5349
+ }
5350
+ const window = facts.effectiveContextWindow;
5351
+ const completeInput = facts.inputTokens + overhead;
5352
+ const required = completeInput + reserve;
5353
+ const pressure = completeInput / window;
5354
+ const headroom = window - required;
5355
+ const advisoryKey = advisoryIdentity(facts, policy);
5356
+ const requestKey = requestIdentity(advisoryKey, facts);
5357
+ const noCutNow = facts.reduction === "no-useful-cut";
5358
+ const noCutCarried = prior.noCutKey !== null && prior.noCutKey === requestKey;
5359
+ const suppressedByNoCut = noCutNow || noCutCarried;
5360
+ let phase;
5361
+ let admission;
5362
+ if (required > window) {
5363
+ phase = "recover";
5364
+ admission = {
5365
+ result: "unsafe",
5366
+ reason: suppressedByNoCut ? "exceeds-window-no-material" : "exceeds-window",
5367
+ requiredTokens: required
5368
+ };
5369
+ } else if (pressure >= policy.reduce) {
5370
+ phase = "reduce";
5371
+ admission = {
5372
+ result: suppressedByNoCut ? "admit" : "reduce-first",
5373
+ reason: suppressedByNoCut ? "fits" : "pressure-at-reduce-threshold",
5374
+ requiredTokens: required
5375
+ };
5376
+ } else {
5377
+ phase = pressure >= policy.prepare ? "prepare" : pressure >= policy.notice ? "notice" : "normal";
5378
+ admission = { result: "admit", reason: "fits", requiredTokens: required };
5379
+ }
5380
+ const advisoryPhase = phase === "notice" || phase === "prepare" ? phase : "normal";
5381
+ const rearmed = prior.advisoryKey !== null && prior.advisoryKey !== advisoryKey;
5382
+ const resetLevel = policy.notice * (1 - ADVISORY_RESET_BAND);
5383
+ const fellBelowReset = pressure < resetLevel;
5384
+ const announcedBefore = rearmed || fellBelowReset ? "normal" : prior.announcedPhase;
5385
+ const actionWouldBeRequested = advisoryPhase !== "normal" || phase === "reduce" || phase === "recover";
5386
+ let emit = false;
5387
+ let suppressedBy = null;
5388
+ if (suppressedByNoCut && actionWouldBeRequested) {
5389
+ suppressedBy = "no-useful-cut-unchanged-revision";
5390
+ } else if (advisoryPhase === "normal") {
5391
+ suppressedBy = "not-crossed";
5392
+ } else if (ADVISORY_RANK[advisoryPhase] > ADVISORY_RANK[announcedBefore]) {
5393
+ emit = true;
5394
+ } else {
5395
+ suppressedBy = "already-announced";
5396
+ }
5397
+ const nextAnnounced = emit ? advisoryPhase : announcedBefore;
5398
+ const nextNoCutKey = noCutNow || noCutCarried ? requestKey : null;
5399
+ return Object.freeze({
5400
+ phase,
5401
+ pressure,
5402
+ headroomTokens: headroom,
5403
+ measured: true,
5404
+ inputSource: facts.inputSource,
5405
+ completeInputTokens: completeInput,
5406
+ admission: Object.freeze(admission),
5407
+ advisory: Object.freeze({ emit, phase: advisoryPhase, suppressedBy }),
5408
+ memory: Object.freeze({ announcedPhase: nextAnnounced, advisoryKey, noCutKey: nextNoCutKey }),
5409
+ policy
5410
+ });
5411
+ }
5412
+
5413
+ // src/domains/context/budget/live-view.ts
5414
+ function resolveLiveBudgetPolicy(reduceThreshold, workingSetTarget) {
5415
+ const configured = resolvePressurePolicy({
5416
+ ...typeof reduceThreshold === "number" ? { reduceThreshold } : {},
5417
+ ...typeof workingSetTarget === "number" ? { workingSetTarget } : {}
5418
+ });
5419
+ if (configured.ok) return { policy: configured.policy, rejection: null };
5420
+ const fallback = resolvePressurePolicy({
5421
+ reduceThreshold: DEFAULT_REDUCE_THRESHOLD,
5422
+ workingSetTarget: DEFAULT_WORKING_SET_TARGET
5423
+ });
5424
+ if (!fallback.ok) throw new Error(`default pressure policy is unresolvable: ${fallback.reason}`);
5425
+ return { policy: fallback.policy, rejection: configured.reason };
5426
+ }
5427
+ function materialFingerprint(input) {
5428
+ return createHash3("sha256").update(
5429
+ JSON.stringify([
5430
+ "live-budget/1",
5431
+ input.sessionId,
5432
+ input.branchAnchorTurnId,
5433
+ input.navigationEpoch,
5434
+ input.targetId,
5435
+ input.runtimeId,
5436
+ input.modelId,
5437
+ input.modelApi,
5438
+ input.effectiveWindow,
5439
+ input.windowSource,
5440
+ input.estimatedInputTokens,
5441
+ input.anchoredInputTokens,
5442
+ input.inputTokens,
5443
+ input.inputSource,
5444
+ input.historical,
5445
+ input.breakdown,
5446
+ input.breakdownSource,
5447
+ input.outputReserveTokens,
5448
+ input.thresholdReserveTokens,
5449
+ input.policy.notice,
5450
+ input.policy.prepare,
5451
+ input.policy.reduce,
5452
+ input.policy.target,
5453
+ input.policyRejection,
5454
+ input.reduction,
5455
+ input.reductionBasisKey,
5456
+ input.promptFingerprint,
5457
+ input.toolSignature,
5458
+ input.lastReduction,
5459
+ input.pendingHandoff,
5460
+ input.lastOutcome
5461
+ ])
5462
+ ).digest("hex");
5463
+ }
5464
+ function pressureFacts(input) {
5465
+ return {
5466
+ revision: {
5467
+ sessionId: input.sessionId ?? "",
5468
+ branchAnchorTurnId: input.branchAnchorTurnId,
5469
+ // The policy reuses a no-cut refusal only for this exact identity, so
5470
+ // it has to be the post-accounting one: a recalibrated anchor or a
5471
+ // different resolved output cap is a request the reducer never saw.
5472
+ revisionId: input.reductionBasisKey
5473
+ },
5474
+ inputTokens: input.inputTokens,
5475
+ inputSource: input.inputSource === "anchored-plus-estimated-tail" ? "trusted" : "estimated",
5476
+ effectiveContextWindow: input.effectiveWindow,
5477
+ outputReserveTokens: input.outputReserveTokens ?? Number.NaN,
5478
+ // The live estimate already prices pending text, tool schemas, and every
5479
+ // injected block, so there is nothing left for the caller to add once.
5480
+ pendingRequestOverheadTokens: 0,
5481
+ reduction: input.reduction,
5482
+ contextIdentity: {
5483
+ targetId: input.targetId,
5484
+ modelId: input.modelId,
5485
+ promptFingerprint: input.promptFingerprint,
5486
+ toolSignature: input.toolSignature,
5487
+ epoch: input.navigationEpoch
5488
+ }
5489
+ };
5490
+ }
5491
+ function frozenCopy(value) {
5492
+ return value === null ? null : Object.freeze({ ...value });
5493
+ }
5494
+ function createLiveBudgetProducer() {
5495
+ let view = null;
5496
+ let fingerprint = null;
5497
+ let sequence = 0;
5498
+ let memory = INITIAL_ADVISORY_MEMORY;
5499
+ let decision = null;
5500
+ return {
5501
+ publish(input) {
5502
+ const next = materialFingerprint(input);
5503
+ const transitioned = next !== fingerprint;
5504
+ if (transitioned) {
5505
+ fingerprint = next;
5506
+ sequence += 1;
5507
+ }
5508
+ const revision = `lb-${sequence}-${next.slice(0, 16)}`;
5509
+ if (transitioned || decision === null) {
5510
+ decision = evaluatePressure(pressureFacts(input), input.policy, memory);
5511
+ memory = decision.memory;
5512
+ }
5513
+ view = Object.freeze({
5514
+ revision,
5515
+ capturedSnapshotId: input.capturedSnapshotId,
5516
+ sessionId: input.sessionId,
5517
+ branchAnchorTurnId: input.branchAnchorTurnId,
5518
+ activeLeafTurnId: input.activeLeafTurnId,
5519
+ activeUserTurnId: input.activeUserTurnId,
5520
+ targetId: input.targetId,
5521
+ runtimeId: input.runtimeId,
5522
+ modelId: input.modelId,
5523
+ modelApi: input.modelApi,
5524
+ effectiveWindow: input.effectiveWindow,
5525
+ windowSource: input.windowSource,
5526
+ estimatedInputTokens: input.estimatedInputTokens,
5527
+ anchoredInputTokens: input.anchoredInputTokens,
5528
+ inputTokens: input.inputTokens,
5529
+ inputSource: input.inputSource,
5530
+ historical: input.historical,
5531
+ breakdown: frozenCopy(input.breakdown),
5532
+ breakdownSource: input.breakdownSource,
5533
+ outputReserveTokens: input.outputReserveTokens,
5534
+ thresholdReserveTokens: input.thresholdReserveTokens,
5535
+ headroomTokens: decision.headroomTokens,
5536
+ pressure: decision.pressure,
5537
+ phase: decision.phase,
5538
+ admission: decision.admission,
5539
+ advisory: decision.advisory,
5540
+ policy: Object.freeze({ ...input.policy }),
5541
+ policyRejection: input.policyRejection,
5542
+ reduction: input.reduction,
5543
+ // Copied and frozen at this boundary. An injected reader owns the
5544
+ // object it returns and may reuse or mutate it; a published view is
5545
+ // the thing a revision names, and it does not change underneath one.
5546
+ lastReduction: frozenCopy(input.lastReduction),
5547
+ pendingHandoff: frozenCopy(input.pendingHandoff),
5548
+ lastOutcome: frozenCopy(input.lastOutcome)
5549
+ });
5550
+ return view;
5551
+ },
5552
+ current() {
5553
+ return view;
5554
+ },
5555
+ reset() {
5556
+ view = null;
5557
+ fingerprint = null;
5558
+ decision = null;
5559
+ memory = INITIAL_ADVISORY_MEMORY;
5560
+ }
5561
+ };
5562
+ }
4471
5563
 
4472
5564
  // src/domains/session/compaction/mask-observations.ts
4473
5565
  init_esm_shims();
@@ -4663,6 +5755,16 @@ function maskStaleObservations(entries, excludeLastTurns) {
4663
5755
  };
4664
5756
  }
4665
5757
 
5758
+ // src/interactive/output-reserve.ts
5759
+ init_esm_shims();
5760
+ function resolveTurnOutputReserve(agentRuntime, inputTokens) {
5761
+ return resolveReservedOutputTokens(agentRuntime.runtimeResolution.capabilityDecisions?.maxTokens, {
5762
+ api: agentRuntime.agent.state.model?.api ?? "",
5763
+ contextWindow: agentRuntime.runtimeResolution.contextWindowDetails.effectiveContextWindow,
5764
+ inputTokens
5765
+ });
5766
+ }
5767
+
4666
5768
  // src/interactive/turn-context.ts
4667
5769
  function evictionSkipMessage(visibleEntries, workingSet, policyId) {
4668
5770
  const turns = visibleEntries.filter(isTurnStart).length;
@@ -4673,6 +5775,9 @@ function evictionSkipMessage(visibleEntries, workingSet, policyId) {
4673
5775
  policyId
4674
5776
  });
4675
5777
  }
5778
+ function messageListDigest(messages) {
5779
+ return createHash4("sha256").update(JSON.stringify(messages)).digest("hex");
5780
+ }
4676
5781
  function createTurnContext(deps) {
4677
5782
  const { state, middleware } = deps;
4678
5783
  const compactionTrigger = new AutoCompactionTrigger();
@@ -4689,8 +5794,16 @@ function createTurnContext(deps) {
4689
5794
  let resumedPromptHashRead = false;
4690
5795
  let resumedPromptHash = null;
4691
5796
  const sessionWorkingContextPaths = /* @__PURE__ */ new Set();
5797
+ let memoryTurnSequence = 0;
5798
+ let memoryAuthorityEpoch = 0;
5799
+ let memoryTurn = null;
5800
+ const memoryOrigin = (runtime) => JSON.stringify([process.cwd(), runtime.targetId, runtime.runtimeId, runtime.wireModelId]);
4692
5801
  let pendingPromptLogEntry = null;
4693
5802
  let emptyAutoCompactContextKey = null;
5803
+ const budgetProducer = createLiveBudgetProducer();
5804
+ let branchAnchorTurnId = null;
5805
+ let navigationEpoch = 0;
5806
+ let noUsefulCutBasisKey = null;
4694
5807
  const pendingColdReasons = /* @__PURE__ */ new Set();
4695
5808
  let runExpectedColdReasons = [];
4696
5809
  let nextAssistantColdReasons = [];
@@ -4808,7 +5921,7 @@ function createTurnContext(deps) {
4808
5921
  resumedPromptHash = records[records.length - 1]?.systemPromptHash ?? null;
4809
5922
  return resumedPromptHash;
4810
5923
  };
4811
- const resolveWindowWithoutRuntime = () => {
5924
+ const resolveWindowWithoutRuntime = (allowLedgerRead = true) => {
4812
5925
  const settings = deps.getSettings();
4813
5926
  const targetId = settings.chat?.target?.trim();
4814
5927
  const wireModelId = settings.chat?.model?.trim();
@@ -4820,7 +5933,7 @@ function createTurnContext(deps) {
4820
5933
  use: "orchestrator",
4821
5934
  requireTools: false,
4822
5935
  requireOutputBudget: true,
4823
- knownLoadedContextWindow: rememberedLoadedContextWindow(targetId, wireModelId)
5936
+ knownLoadedContextWindow: allowLedgerRead ? rememberedLoadedContextWindow(targetId, wireModelId) : currentContextSnapshot?.contextWindowSource === "loaded" && currentContextSnapshot.providerId === targetId && currentContextSnapshot.modelId === wireModelId ? currentContextSnapshot.effectiveContextWindow : null
4824
5937
  });
4825
5938
  return resolved.ok ? resolved.target.contextWindowDetails : null;
4826
5939
  };
@@ -4834,8 +5947,8 @@ function createTurnContext(deps) {
4834
5947
  "unknown"
4835
5948
  ]);
4836
5949
  const snapshotWindowSource = (snapshot) => CONTEXT_WINDOW_SOURCES.has(snapshot.contextWindowSource) ? snapshot.contextWindowSource : null;
4837
- const windowWithoutRuntime = () => {
4838
- const details = resolveWindowWithoutRuntime();
5950
+ const windowWithoutRuntime = (allowLedgerRead = true) => {
5951
+ const details = resolveWindowWithoutRuntime(allowLedgerRead);
4839
5952
  if (details) {
4840
5953
  return {
4841
5954
  contextWindow: details.effectiveContextWindow,
@@ -4861,7 +5974,7 @@ function createTurnContext(deps) {
4861
5974
  const anchor = reconciledAnchor;
4862
5975
  if (!anchor) return null;
4863
5976
  const messages = agentRuntime.agent.state.messages.filter((message) => message.role !== "system");
4864
- if (anchor.runtime !== agentRuntime || anchor.model !== agentRuntime.agent.state.model || anchor.modelKey !== anchorModelKey(agentRuntime) || anchor.targetId !== agentRuntime.targetId || anchor.runtimeId !== agentRuntime.runtimeId || anchor.wireModelId !== agentRuntime.wireModelId || anchor.anchoredMessages.length > messages.length || anchor.anchoredMessages.some((message, index) => message !== messages[index])) {
5977
+ if (anchor.runtime !== agentRuntime || anchor.model !== agentRuntime.agent.state.model || anchor.modelKey !== anchorModelKey(agentRuntime) || anchor.targetId !== agentRuntime.targetId || anchor.runtimeId !== agentRuntime.runtimeId || anchor.wireModelId !== agentRuntime.wireModelId || anchor.anchoredMessages.length > messages.length || anchor.anchoredMessages.some((message, index) => message !== messages[index]) || messageListDigest(messages.slice(0, anchor.anchoredMessages.length)) !== anchor.contentDigest) {
4865
5978
  reconciledAnchor = null;
4866
5979
  return null;
4867
5980
  }
@@ -4873,13 +5986,14 @@ function createTurnContext(deps) {
4873
5986
  }
4874
5987
  return anchor.tokens + tail;
4875
5988
  };
5989
+ let pendingUserImages = [];
4876
5990
  const liveContextEstimate = (agentRuntime, pendingUserText) => {
4877
5991
  const contextWindow = agentRuntime.runtimeResolution.contextWindowDetails.effectiveContextWindow;
4878
5992
  const estimateInput = {
4879
5993
  systemPrompt: agentRuntime.agent.state.systemPrompt,
4880
5994
  messages: agentRuntime.agent.state.messages.filter((message) => message.role !== "system"),
4881
5995
  tools: agentRuntime.agent.state.tools,
4882
- ...pendingUserText !== void 0 ? { pendingUserText } : {}
5996
+ ...pendingUserText !== void 0 ? { pendingUserText, pendingUserImages } : {}
4883
5997
  };
4884
5998
  const breakdown = estimateAgentContextBreakdown(estimateInput);
4885
5999
  const historyTokens = reconciledHistoryTokens(agentRuntime);
@@ -4897,12 +6011,177 @@ function createTurnContext(deps) {
4897
6011
  breakdown
4898
6012
  };
4899
6013
  };
6014
+ const sha256Json = (value) => createHash4("sha256").update(JSON.stringify(value)).digest("hex");
6015
+ const liveBudgetContentFingerprint = (agentRuntime, pendingUserText, promptFingerprint, toolSignature) => {
6016
+ const settings = deps.getSettings();
6017
+ const eligibility = {
6018
+ compaction: settings.context.compaction ?? null,
6019
+ workingSet: settings.context.workingSet ?? null,
6020
+ maxOutputTokens: settings.chat?.maxOutputTokens ?? null
6021
+ };
6022
+ if (!agentRuntime) {
6023
+ const snapshot = currentContextSnapshot;
6024
+ return sha256Json([
6025
+ "historical/1",
6026
+ settings.chat?.target ?? null,
6027
+ settings.chat?.model ?? null,
6028
+ snapshot?.snapshotId ?? null,
6029
+ snapshot ? snapshotInputTokens(snapshot) : null,
6030
+ snapshot?.sources.total ?? null,
6031
+ windowWithoutRuntime(false).contextWindow,
6032
+ promptFingerprint,
6033
+ toolSignature,
6034
+ eligibility
6035
+ ]);
6036
+ }
6037
+ const details = agentRuntime.runtimeResolution.contextWindowDetails;
6038
+ return sha256Json([
6039
+ "live/1",
6040
+ agentRuntime.targetId,
6041
+ agentRuntime.runtimeId,
6042
+ agentRuntime.wireModelId,
6043
+ agentRuntime.agent.state.model?.api ?? null,
6044
+ details.effectiveContextWindow,
6045
+ details.contextWindowSource,
6046
+ promptFingerprint,
6047
+ toolSignature,
6048
+ // Same serialization the anchor validates its attested prefix with, so
6049
+ // the two cannot disagree about whether the conversation changed.
6050
+ messageListDigest(agentRuntime.agent.state.messages.filter((message) => message.role !== "system")),
6051
+ pendingUserText ?? null,
6052
+ pendingUserText === void 0 ? null : pendingUserImages,
6053
+ eligibility
6054
+ ]);
6055
+ };
6056
+ const capturedBreakdown = (snapshot, pendingTokens) => ({
6057
+ systemPromptTokens: snapshot.categories.system,
6058
+ messageTokens: snapshot.categories.messages + (snapshot.categories.toolResults ?? 0),
6059
+ pendingUserTokens: pendingTokens,
6060
+ toolSchemaTokens: snapshot.categories.tools
6061
+ });
6062
+ const optionalProjection = (read) => {
6063
+ if (!read) return null;
6064
+ try {
6065
+ return read();
6066
+ } catch {
6067
+ return null;
6068
+ }
6069
+ };
6070
+ const liveRuntimeAccounting = (agentRuntime, pendingUserText) => {
6071
+ const estimate = liveContextEstimate(agentRuntime, pendingUserText);
6072
+ const details = agentRuntime.runtimeResolution.contextWindowDetails;
6073
+ return {
6074
+ targetId: agentRuntime.targetId,
6075
+ runtimeId: agentRuntime.runtimeId,
6076
+ modelId: agentRuntime.wireModelId,
6077
+ modelApi: agentRuntime.agent.state.model?.api ?? null,
6078
+ effectiveWindow: details.effectiveContextWindow > 0 ? details.effectiveContextWindow : null,
6079
+ windowSource: details.contextWindowSource,
6080
+ estimatedInputTokens: estimate.estimatedTokens,
6081
+ anchoredInputTokens: estimate.reconciledTokens,
6082
+ inputTokens: estimate.tokens,
6083
+ inputSource: estimate.reconciledTokens === null ? "estimated" : "anchored-plus-estimated-tail",
6084
+ historical: false,
6085
+ breakdown: { ...estimate.breakdown },
6086
+ breakdownSource: "live",
6087
+ outputReserveTokens: resolveTurnOutputReserve(agentRuntime, estimate.tokens)
6088
+ };
6089
+ };
6090
+ const historicalAccounting = (snapshot, settings) => {
6091
+ const sameRoute = !snapshot || snapshot.providerId === settings.chat?.target && snapshot.modelId === settings.chat?.model;
6092
+ const window = snapshot && !sameRoute ? {
6093
+ contextWindow: snapshot.effectiveContextWindow,
6094
+ contextWindowSource: snapshotWindowSource(snapshot)
6095
+ } : windowWithoutRuntime(false);
6096
+ const pendingTokens = pendingUserInputTokens();
6097
+ return {
6098
+ targetId: snapshot?.providerId ?? settings.chat?.target ?? null,
6099
+ runtimeId: snapshot?.runtimeId ?? null,
6100
+ modelId: snapshot?.modelId ?? settings.chat?.model ?? null,
6101
+ modelApi: null,
6102
+ effectiveWindow: window.contextWindow > 0 ? window.contextWindow : null,
6103
+ windowSource: window.contextWindowSource,
6104
+ estimatedInputTokens: snapshot?.estimatedTokens ?? null,
6105
+ anchoredInputTokens: snapshot?.reconciledTokens ?? null,
6106
+ inputTokens: snapshot ? snapshotInputTokens(snapshot) + pendingTokens : null,
6107
+ inputSource: snapshot ? "historical" : "unknown",
6108
+ historical: true,
6109
+ breakdown: snapshot ? capturedBreakdown(snapshot, pendingTokens) : null,
6110
+ breakdownSource: snapshot ? "captured" : null,
6111
+ // No resolved route means no honest output reservation, and an unpriced
6112
+ // reserve is unknown rather than zero.
6113
+ outputReserveTokens: null
6114
+ };
6115
+ };
6116
+ const liveBudgetInput = (pendingUserText) => {
6117
+ const settings = deps.getSettings();
6118
+ const agentRuntime = state.runtime;
6119
+ const snapshot = currentContextSnapshot;
6120
+ const promptFingerprint = agentRuntime ? sha256Json(agentRuntime.agent.state.systemPrompt ?? null) : snapshot?.promptHash ?? null;
6121
+ const toolSignature = agentRuntime ? toolSignatureFromState(agentRuntime.agent.state.tools) : snapshot?.toolSignature ?? null;
6122
+ const contentFingerprint = liveBudgetContentFingerprint(
6123
+ agentRuntime,
6124
+ pendingUserText,
6125
+ promptFingerprint,
6126
+ toolSignature
6127
+ );
6128
+ const policyResolution = resolveLiveBudgetPolicy(
6129
+ settings.context.compaction?.threshold,
6130
+ settings.context.workingSet?.target
6131
+ );
6132
+ const accounting = agentRuntime ? liveRuntimeAccounting(agentRuntime, pendingUserText) : historicalAccounting(snapshot, settings);
6133
+ const reductionBasisKey = sha256Json([
6134
+ "reduction-basis/1",
6135
+ contentFingerprint,
6136
+ accounting.effectiveWindow,
6137
+ accounting.estimatedInputTokens,
6138
+ accounting.anchoredInputTokens,
6139
+ accounting.inputTokens,
6140
+ accounting.inputSource,
6141
+ accounting.outputReserveTokens,
6142
+ // The resolved cap can move without moving the reservation, because a
6143
+ // route clamp may be the binding constraint. Both belong in the key.
6144
+ agentRuntime?.runtimeResolution.capabilityDecisions?.maxTokens ?? null,
6145
+ snapshot?.categories.reserve ?? null,
6146
+ policyResolution.policy.reduce,
6147
+ policyResolution.policy.target
6148
+ ]);
6149
+ return {
6150
+ ...accounting,
6151
+ sessionId: deps.session?.current()?.id ?? snapshot?.sessionId ?? null,
6152
+ branchAnchorTurnId,
6153
+ activeLeafTurnId: state.lastTurnId,
6154
+ activeUserTurnId: state.activeUserTurnId,
6155
+ capturedSnapshotId: snapshot?.snapshotId ?? null,
6156
+ // Compaction headroom recorded by the capture. Carried under its own
6157
+ // name so nothing can mistake it for the output reserve.
6158
+ thresholdReserveTokens: snapshot?.categories.reserve ?? null,
6159
+ policy: policyResolution.policy,
6160
+ policyRejection: policyResolution.rejection,
6161
+ // Reduction has not been asked about unless an automatic attempt for
6162
+ // this exact basis already came back empty.
6163
+ reduction: noUsefulCutBasisKey !== null && noUsefulCutBasisKey === reductionBasisKey ? "no-useful-cut" : "unknown",
6164
+ reductionBasisKey,
6165
+ promptFingerprint,
6166
+ toolSignature,
6167
+ navigationEpoch,
6168
+ lastReduction: lastCompactionEvent ? { ...lastCompactionEvent } : null,
6169
+ pendingHandoff: optionalProjection(deps.getPendingHandoff),
6170
+ lastOutcome: optionalProjection(deps.getLastOutcome)
6171
+ };
6172
+ };
6173
+ const refreshLiveBudget = (pendingUserText) => budgetProducer.publish(liveBudgetInput(pendingUserText));
4900
6174
  const carryReconciledAnchorThroughProjection = (agentRuntime, historyTokens, tokensRemoved) => {
4901
6175
  if (!reconciledAnchor || historyTokens === null) return;
6176
+ const anchoredMessages = [...agentRuntime.agent.state.messages.filter((message) => message.role !== "system")];
4902
6177
  reconciledAnchor = {
4903
6178
  ...reconciledAnchor,
4904
6179
  tokens: Math.max(0, historyTokens - Math.max(0, tokensRemoved)),
4905
- anchoredMessages: [...agentRuntime.agent.state.messages.filter((message) => message.role !== "system")]
6180
+ anchoredMessages,
6181
+ // Re-anchoring on the projected list is the point of this carry, so its
6182
+ // content identity is retaken here. Keeping the old digest would fail
6183
+ // the next validation and discard the attestation this preserves.
6184
+ contentDigest: messageListDigest(anchoredMessages)
4906
6185
  };
4907
6186
  };
4908
6187
  const refreshAgentMessagesFromSession = (agentRuntime) => {
@@ -4910,7 +6189,11 @@ function createTurnContext(deps) {
4910
6189
  replaceEngineMessages(
4911
6190
  agentRuntime.agent,
4912
6191
  buildModelReplayAgentMessagesFromTurns(refreshedEntries, {
4913
- ...state.lastTurnId ? { activeLeafTurnId: state.lastTurnId } : {}
6192
+ ...state.lastTurnId ? { activeLeafTurnId: state.lastTurnId } : {},
6193
+ // The post-compaction rebuild is where an accepted note has to
6194
+ // survive: wiring continuity only into /fork would lose it on the
6195
+ // very refresh the reduction triggers.
6196
+ continuity: continuityContextFromSession(deps.session)
4914
6197
  })
4915
6198
  );
4916
6199
  state.replayedContextMessages = [];
@@ -4948,9 +6231,11 @@ function createTurnContext(deps) {
4948
6231
  usage.targetId === void 0 && usage.modelId === void 0 ? agentRuntime.runtimeResolution.costProvenance : usage.cost.total > 0 ? "estimated" : "unknown"
4949
6232
  );
4950
6233
  };
4951
- const runAutoCompact = async (agentRuntime, force, instructions, triggerOverride, pendingUserText, pendingSkillPolicy, signal) => {
6234
+ const performAutoCompact = async (agentRuntime, force, instructions, triggerOverride, pendingUserText, pendingSkillPolicy, signal, handoff) => {
4952
6235
  if (!deps.readSessionEntries) return false;
4953
- const useRequestBudget = !force || triggerOverride === "overflow";
6236
+ const originSession = deps.session?.current()?.id;
6237
+ const originNavigation = navigationEpoch;
6238
+ const useRequestBudget = !force || triggerOverride === "overflow" || handoff !== void 0;
4954
6239
  const activeAutoTurnId = useRequestBudget ? state.activeUserTurnId : null;
4955
6240
  const skillContextState = mainSkillContextState(
4956
6241
  filterEntriesToActivePath(deps.readSessionEntries(), state.lastTurnId ?? void 0),
@@ -4961,10 +6246,11 @@ function createTurnContext(deps) {
4961
6246
  const autoEnabled = cfg?.auto !== false;
4962
6247
  if (!force && !autoEnabled) return false;
4963
6248
  const compactionThreshold = cfg?.threshold ?? DEFAULT_COMPACTION_THRESHOLD;
4964
- const pressureEstimate = force ? null : liveContextEstimate(agentRuntime, pendingUserText);
4965
- if (pressureEstimate && !shouldCompact(pressureEstimate.tokens, compactionThreshold, pressureEstimate.contextWindow).shouldCompact)
6249
+ const requiredFit = triggerOverride === "overflow";
6250
+ const pressureEstimate = force && !requiredFit ? null : liveContextEstimate(agentRuntime, pendingUserText);
6251
+ if (pressureEstimate && !requiredFit && !shouldCompact(pressureEstimate.tokens, compactionThreshold, pressureEstimate.contextWindow).shouldCompact)
4966
6252
  return false;
4967
- const attemptKey = !force && activeAutoTurnId ? createHash2("sha256").update(
6253
+ const attemptKey = !force && activeAutoTurnId ? createHash4("sha256").update(
4968
6254
  JSON.stringify({
4969
6255
  turn: activeAutoTurnId,
4970
6256
  leaf: state.lastTurnId,
@@ -4988,7 +6274,9 @@ function createTurnContext(deps) {
4988
6274
  let preSummaryStageActed = false;
4989
6275
  let evictionSkipNotice = null;
4990
6276
  const rememberEmptyAutomaticAttempt = () => {
4991
- if (!force && !preSummaryStageActed && attemptKey) emptyAutoCompactContextKey = attemptKey;
6277
+ if (force || preSummaryStageActed || !attemptKey) return;
6278
+ emptyAutoCompactContextKey = attemptKey;
6279
+ noUsefulCutBasisKey = liveBudgetInput(pendingUserText).reductionBasisKey;
4992
6280
  };
4993
6281
  const trigger = triggerOverride ?? (force ? "force" : "auto");
4994
6282
  if (pressureEstimate) {
@@ -4996,7 +6284,7 @@ function createTurnContext(deps) {
4996
6284
  const verdict = shouldCompact(estimate.tokens, compactionThreshold, estimate.contextWindow);
4997
6285
  if (deps.session?.current()) {
4998
6286
  const beforeSnapshotId = currentContextSnapshot?.snapshotId ?? null;
4999
- if (process.env.CLIO_CODER_LEGACY_MASK === "1") {
6287
+ if (process.env.CLIO_CODER_LEGACY_MASK === "1" && !handoff) {
5000
6288
  let masked;
5001
6289
  try {
5002
6290
  masked = maskStaleObservations(deps.readSessionEntries() ?? [], 6);
@@ -5049,8 +6337,10 @@ function createTurnContext(deps) {
5049
6337
  deps.emitNotice(
5050
6338
  `[context engine] mask_observations: ${masked.maskedObservations} observations masked${thinkingNote}; ~${estimate.tokens} tokens -> ~${tokensAfterMask} tokens`
5051
6339
  );
6340
+ refreshLiveBudget(pendingUserText);
5052
6341
  const after = liveContextEstimate(agentRuntime, pendingUserText);
5053
- if (!shouldCompact(after.tokens, compactionThreshold, after.contextWindow).shouldCompact) return true;
6342
+ if (requiredFit ? requestFits(after.tokens, resolveTurnOutputReserve(agentRuntime, after.tokens), after.contextWindow) : !shouldCompact(after.tokens, compactionThreshold, after.contextWindow).shouldCompact)
6343
+ return true;
5054
6344
  }
5055
6345
  } else if (settings.context.workingSet.enabled) {
5056
6346
  let planned;
@@ -5070,8 +6360,14 @@ function createTurnContext(deps) {
5070
6360
  pressure: {
5071
6361
  tokens: estimate.tokens,
5072
6362
  contextWindow: estimate.contextWindow,
5073
- threshold: compactionThreshold,
5074
- target: settings.context.workingSet.target
6363
+ threshold: requiredFit ? Math.min(
6364
+ compactionThreshold,
6365
+ Math.max(0, 1 - resolveTurnOutputReserve(agentRuntime, estimate.tokens) / estimate.contextWindow)
6366
+ ) : compactionThreshold,
6367
+ target: requiredFit ? Math.min(
6368
+ settings.context.workingSet.target,
6369
+ Math.max(0, 1 - resolveTurnOutputReserve(agentRuntime, estimate.tokens) / estimate.contextWindow)
6370
+ ) : settings.context.workingSet.target
5075
6371
  },
5076
6372
  estimateTokens
5077
6373
  });
@@ -5087,7 +6383,7 @@ function createTurnContext(deps) {
5087
6383
  emitCompactionActivity("started", "compacting context (working-set eviction)");
5088
6384
  try {
5089
6385
  const historyTokens = reconciledHistoryTokens(agentRuntime);
5090
- deps.session.appendEntry({
6386
+ const evictionEntry = deps.session.appendEntry({
5091
6387
  ...buildEvictionFields(planned, {
5092
6388
  trigger: "pressure",
5093
6389
  pressureBefore: verdict.pressure,
@@ -5097,6 +6393,10 @@ function createTurnContext(deps) {
5097
6393
  // cursor is the leaf the next message will extend.
5098
6394
  parentTurnId: state.lastTurnId
5099
6395
  });
6396
+ if (deps.session.flushAppends) {
6397
+ deps.session.flushAppends();
6398
+ if (!handoff) notifyMemoryCommit(evictionEntry.turnId, "summary", "evicted");
6399
+ }
5100
6400
  noteColdReason("working_set_evict");
5101
6401
  refreshAgentMessagesFromSession(agentRuntime);
5102
6402
  carryReconciledAnchorThroughProjection(agentRuntime, historyTokens, planned.tokensBefore - planned.tokensAfter);
@@ -5130,8 +6430,10 @@ function createTurnContext(deps) {
5130
6430
  deps.emitNotice(
5131
6431
  `[context engine] working set: ${planned.items.length} ${itemsWord} evicted by ${planned.policyId}; ~${planned.tokensBefore} -> ~${planned.tokensAfter} tokens, recall by ref with context(scope="recall")`
5132
6432
  );
6433
+ refreshLiveBudget(pendingUserText);
5133
6434
  const after = liveContextEstimate(agentRuntime, pendingUserText);
5134
- if (!shouldCompact(after.tokens, compactionThreshold, after.contextWindow).shouldCompact) return true;
6435
+ if (requiredFit ? requestFits(after.tokens, resolveTurnOutputReserve(agentRuntime, after.tokens), after.contextWindow) : !shouldCompact(after.tokens, compactionThreshold, after.contextWindow).shouldCompact)
6436
+ return true;
5135
6437
  } catch (error) {
5136
6438
  emitCompactionActivity("failed", compactionFailureMessage(error));
5137
6439
  throw error;
@@ -5173,14 +6475,7 @@ function createTurnContext(deps) {
5173
6475
  let budget = skillContextState !== void 0 ? { skillContextState } : void 0;
5174
6476
  if (useRequestBudget) {
5175
6477
  const estimate = liveContextEstimate(agentRuntime, pendingUserText);
5176
- const output = Math.min(
5177
- resolveReservedOutputTokens(agentRuntime.agent.state.model?.maxTokens, {
5178
- api: agentRuntime.agent.state.model?.api ?? "",
5179
- contextWindow: estimate.contextWindow,
5180
- inputTokens: estimate.tokens
5181
- }),
5182
- settings.chat.maxOutputTokens > 0 ? settings.chat.maxOutputTokens : Number.POSITIVE_INFINITY
5183
- );
6478
+ const output = resolveTurnOutputReserve(agentRuntime, estimate.tokens);
5184
6479
  const inputTarget = Math.min(estimate.contextWindow * compactionThreshold, estimate.contextWindow - output);
5185
6480
  const staticTokens = estimate.breakdown.systemPromptTokens + estimate.breakdown.toolSchemaTokens;
5186
6481
  const calibration = Math.max(
@@ -5203,6 +6498,7 @@ function createTurnContext(deps) {
5203
6498
  try {
5204
6499
  return await deps.autoCompact?.(instructions, trigger, {
5205
6500
  ...budget,
6501
+ ...handoff,
5206
6502
  signal: summarySignal
5207
6503
  }) ?? null;
5208
6504
  } finally {
@@ -5228,6 +6524,14 @@ function createTurnContext(deps) {
5228
6524
  if (!summaryLifecycleStarted) startSummaryLifecycle();
5229
6525
  deps.bus?.emit(BusChannels.CompactionEnd, { trigger, at: Date.now() });
5230
6526
  recordCompactionUsage(agentRuntime, result);
6527
+ if (deps.memoryCommitBridge && !handoff && deps.session?.flushAppends) {
6528
+ deps.session.flushAppends();
6529
+ await deps.session.checkpoint("context-summary");
6530
+ if (originSession !== deps.session.current()?.id || originNavigation !== navigationEpoch || signal?.aborted)
6531
+ throw new Error("Context ownership changed during checkpoint.");
6532
+ const summary = [...deps.readSessionEntries()].reverse().find((entry) => entry.kind === "compactionSummary");
6533
+ if (summary) notifyMemoryCommit(summary.turnId, "summary", "summarized");
6534
+ }
5231
6535
  refreshAgentMessagesFromSession(agentRuntime);
5232
6536
  reconciledAnchor = null;
5233
6537
  const postCompactSnapshot = captureRuntimeContextSnapshot(
@@ -5265,6 +6569,67 @@ function createTurnContext(deps) {
5265
6569
  isSplitTurn: result.isSplitTurn
5266
6570
  })
5267
6571
  );
6572
+ refreshLiveBudget(pendingUserText);
6573
+ return true;
6574
+ };
6575
+ let reductionInFlight = null;
6576
+ const runAutoCompact = (...args) => {
6577
+ if (reductionInFlight) {
6578
+ if (args[7]) return Promise.reject(new Error("Another context reduction is already in progress."));
6579
+ return reductionInFlight;
6580
+ }
6581
+ const operation = performAutoCompact(...args);
6582
+ reductionInFlight = operation;
6583
+ void operation.finally(() => {
6584
+ if (reductionInFlight === operation) reductionInFlight = null;
6585
+ }).catch(() => {
6586
+ });
6587
+ return operation;
6588
+ };
6589
+ const bindMemoryScope = () => {
6590
+ const sessionId = deps.session?.current()?.id;
6591
+ if (!sessionId) return null;
6592
+ const scope = { sessionId, branchAnchorTurnId };
6593
+ deps.memoryCommitBridge?.bindCommitScope(scope);
6594
+ return scope;
6595
+ };
6596
+ const notifyMemoryCommit = (commitId, kind, outcome) => {
6597
+ const scope = bindMemoryScope();
6598
+ if (scope) deps.memoryCommitBridge?.notifyContextCommitted({ ...scope, commitId, kind, outcome });
6599
+ };
6600
+ const installMemoryRestoration = (runtime, pendingText) => {
6601
+ const bridge = deps.memoryCommitBridge;
6602
+ if (!bridge || !bindMemoryScope()) return false;
6603
+ const entries = filterEntriesToActivePath(deps.readSessionEntries?.() ?? [], state.lastTurnId ?? void 0);
6604
+ const latest = [...entries].reverse().find((entry) => entry.kind === "continuityCommit" || entry.kind === "compactionSummary");
6605
+ const currentState = latest?.kind === "continuityCommit" ? { kind: "handoff", text: latest.continuity.accepted.note } : latest?.kind === "compactionSummary" ? { kind: "summary", text: latest.summary } : null;
6606
+ const view = refreshLiveBudget(pendingText);
6607
+ const available = Math.max(
6608
+ 0,
6609
+ (view.effectiveWindow ?? 0) - (view.outputReserveTokens ?? 0) - (view.inputTokens ?? 0) - 64
6610
+ );
6611
+ const offer = bridge.prepareRestoration(currentState, available);
6612
+ if (!offer) return false;
6613
+ if (!offer.message) {
6614
+ bridge.acknowledgeRestoration(offer);
6615
+ return false;
6616
+ }
6617
+ const message = {
6618
+ role: "user",
6619
+ content: `<system-reminder>
6620
+ ${offer.message}
6621
+ </system-reminder>`,
6622
+ timestamp: Date.now()
6623
+ };
6624
+ const cost = estimateAgentMessageTokens(message);
6625
+ if (!requestFits((view.inputTokens ?? Infinity) + cost, view.outputReserveTokens, view.effectiveWindow)) return false;
6626
+ const prior = [...runtime.agent.state.messages];
6627
+ replaceEngineMessages(runtime.agent, [...prior, message]);
6628
+ if (!bridge.acknowledgeRestoration(offer)) {
6629
+ replaceEngineMessages(runtime.agent, prior);
6630
+ return false;
6631
+ }
6632
+ refreshLiveBudget(pendingText);
5268
6633
  return true;
5269
6634
  };
5270
6635
  const toolResultTail = (agentRuntime) => {
@@ -5281,28 +6646,73 @@ function createTurnContext(deps) {
5281
6646
  thinkingLevel: agentRuntime.agent.state.thinkingLevel
5282
6647
  });
5283
6648
  return {
6649
+ notifyMemoryCommit,
6650
+ installMemoryRestoration,
6651
+ prepareMemoryTurn(runtime, input) {
6652
+ bindMemoryScope();
6653
+ pendingUserImages = [...input.images ?? []];
6654
+ if (input.continuation && memoryTurn !== null) return;
6655
+ const sessionId = deps.session?.current()?.id ?? null;
6656
+ memoryTurnSequence += 1;
6657
+ memoryTurn = {
6658
+ id: String(memoryTurnSequence),
6659
+ sessionId,
6660
+ sessionAuthority: JSON.stringify([memoryAuthorityEpoch, sessionId ?? `pending:${memoryTurnSequence}`]),
6661
+ origin: memoryOrigin(runtime),
6662
+ taskText: input.taskText,
6663
+ activePaths: Object.freeze([...sessionWorkingContextPaths])
6664
+ };
6665
+ },
6666
+ commitMemoryTurn(runtime) {
6667
+ pendingUserImages = [];
6668
+ if (memoryTurn?.sessionId === null && memoryTurn.origin === memoryOrigin(runtime)) {
6669
+ memoryTurn.sessionId = deps.session?.current()?.id ?? null;
6670
+ }
6671
+ },
5284
6672
  captureRuntimeContextSnapshot,
5285
6673
  persistContextSnapshot,
5286
6674
  liveContextEstimate,
6675
+ refreshLiveBudget,
5287
6676
  rememberedLoadedContextWindow,
5288
6677
  refreshAgentMessagesFromSession,
5289
6678
  runAutoCompact,
6679
+ navigationRevision: () => navigationEpoch,
5290
6680
  cancelCompaction: () => compactionController?.abort(),
6681
+ liveBudget: () => budgetProducer.current() ?? refreshLiveBudget(),
6682
+ inspectLiveBudget() {
6683
+ const targetId = deps.getSettings().chat?.target;
6684
+ const target = !state.runtime && targetId ? deps.providers.getTarget(targetId) : null;
6685
+ const runtime = state.runtime?.runtimeResolution.runtime ?? (target ? deps.providers.getRuntime(target.runtime) : null);
6686
+ if (runtime && (!isOrchestratorEligibleRuntime(runtime) || runtime.externalAgentLoop))
6687
+ return {
6688
+ status: "unsupported",
6689
+ reason: "The external agent owns its context; native request accounting is unavailable."
6690
+ };
6691
+ if (!runtime)
6692
+ return {
6693
+ status: "unavailable",
6694
+ reason: "No native runtime descriptor is resolved. Inspection does not initialize a runtime."
6695
+ };
6696
+ return { status: "available", capability: "native", view: refreshLiveBudget() };
6697
+ },
5291
6698
  setCurrentSnapshot(snapshot) {
5292
6699
  currentContextSnapshot = snapshot;
6700
+ refreshLiveBudget(snapshot.pendingUserInput);
5293
6701
  },
5294
6702
  flushReconciledSnapshot() {
5295
- if (!snapshotPersistPending || !currentContextSnapshot) return;
5296
- persistContextSnapshot(currentContextSnapshot);
6703
+ if (snapshotPersistPending && currentContextSnapshot) persistContextSnapshot(currentContextSnapshot);
6704
+ refreshLiveBudget();
5297
6705
  },
5298
6706
  reconcileUsage(usage) {
5299
6707
  const promptTokens = (usage.input || 0) + (usage.cacheRead || 0) + (usage.cacheWrite || 0);
5300
6708
  const runtime = state.runtime;
5301
6709
  if (promptTokens > 0 && runtime) {
5302
6710
  const breakdown = estimateAgentContextBreakdown({ ...runtime.agent.state, messages: [] });
6711
+ const anchoredMessages = [...runtime.agent.state.messages.filter((message) => message.role !== "system")];
5303
6712
  reconciledAnchor = {
5304
6713
  tokens: promptTokens + (usage.output || 0),
5305
- anchoredMessages: [...runtime.agent.state.messages.filter((message) => message.role !== "system")],
6714
+ anchoredMessages,
6715
+ contentDigest: messageListDigest(anchoredMessages),
5306
6716
  runtime,
5307
6717
  model: runtime.agent.state.model,
5308
6718
  modelKey: anchorModelKey(runtime),
@@ -5313,7 +6723,10 @@ function createTurnContext(deps) {
5313
6723
  toolSchemaTokens: breakdown.toolSchemaTokens
5314
6724
  };
5315
6725
  }
5316
- if (!currentContextSnapshot) return;
6726
+ if (!currentContextSnapshot) {
6727
+ refreshLiveBudget();
6728
+ return;
6729
+ }
5317
6730
  if (runtime) {
5318
6731
  const messages = runtime.agent.state.messages.filter((message) => message.role !== "system");
5319
6732
  const promptMessages = messages.at(-1)?.role === "assistant" ? messages.slice(0, -1) : messages;
@@ -5331,6 +6744,7 @@ function createTurnContext(deps) {
5331
6744
  }
5332
6745
  currentContextSnapshot = reconcileSnapshot(currentContextSnapshot, usage);
5333
6746
  snapshotPersistPending = true;
6747
+ refreshLiveBudget();
5334
6748
  },
5335
6749
  promptSideTokens() {
5336
6750
  return currentContextSnapshot ? snapshotInputTokens(currentContextSnapshot) : 0;
@@ -5375,7 +6789,20 @@ function createTurnContext(deps) {
5375
6789
  };
5376
6790
  if (deps.getMemorySection) {
5377
6791
  try {
5378
- const memorySection = deps.getMemorySection();
6792
+ if (memoryTurn !== null && memoryTurn.sessionId !== (sessionId || null)) {
6793
+ memoryAuthorityEpoch += 1;
6794
+ memoryTurn = null;
6795
+ }
6796
+ const memorySection = deps.getMemorySection({
6797
+ turnId: memoryTurn?.id ?? null,
6798
+ sessionAuthority: memoryTurn?.sessionAuthority ?? JSON.stringify([memoryAuthorityEpoch, sessionId]),
6799
+ cwd,
6800
+ targetId: agentRuntime.targetId,
6801
+ runtimeId: agentRuntime.runtimeId,
6802
+ modelId: agentRuntime.wireModelId,
6803
+ taskText: memoryTurn?.taskText ?? "",
6804
+ activePaths: memoryTurn?.activePaths ?? []
6805
+ });
5379
6806
  if (memorySection.length > 0) sessionInputs.memorySection = memorySection;
5380
6807
  } catch (err) {
5381
6808
  deps.emitNotice(
@@ -5481,15 +6908,25 @@ function createTurnContext(deps) {
5481
6908
  async postToolContinuationGuard(agentRuntime, signal, contextChanged = false) {
5482
6909
  if (signal?.aborted || !contextChanged && !toolResultTail(agentRuntime)) return void 0;
5483
6910
  const reminder = contextChanged ? agentRuntime.agent.state.messages.at(-1) : void 0;
6911
+ const beforeView = refreshLiveBudget();
5484
6912
  const before = liveContextEstimate(agentRuntime);
5485
- if (before.contextWindow <= 0 || before.tokens <= 0) return void 0;
6913
+ if (before.contextWindow <= 0) throw new Error("Context window is unavailable; continuation refused.");
5486
6914
  const settings = deps.getSettings();
5487
6915
  const threshold = settings.context.compaction?.threshold ?? DEFAULT_COMPACTION_THRESHOLD;
5488
6916
  const verdict = shouldCompact(before.tokens, threshold, before.contextWindow);
5489
6917
  let compacted = false;
5490
- if (verdict.shouldCompact) {
6918
+ const mustFit = !requestFits(beforeView.inputTokens, beforeView.outputReserveTokens, beforeView.effectiveWindow);
6919
+ if (mustFit || verdict.shouldCompact) {
5491
6920
  try {
5492
- compacted = await runAutoCompact(agentRuntime, false, void 0, "auto", void 0, void 0, signal);
6921
+ compacted = await runAutoCompact(
6922
+ agentRuntime,
6923
+ mustFit,
6924
+ void 0,
6925
+ mustFit ? "overflow" : "auto",
6926
+ void 0,
6927
+ void 0,
6928
+ signal
6929
+ );
5493
6930
  } catch (err) {
5494
6931
  throw new Error(
5495
6932
  `[Clio Coder] post-tool context guard could not compact before continuation: ${err instanceof Error ? err.message : String(err)}`
@@ -5499,28 +6936,23 @@ function createTurnContext(deps) {
5499
6936
  if (reminder && !agentRuntime.agent.state.messages.includes(reminder)) {
5500
6937
  agentRuntime.agent.state.messages.push(reminder);
5501
6938
  }
5502
- const after = liveContextEstimate(agentRuntime);
5503
- if (after.tokens >= after.contextWindow) {
6939
+ const afterView = refreshLiveBudget();
6940
+ if (!requestFits(afterView.inputTokens, afterView.outputReserveTokens, afterView.effectiveWindow)) {
5504
6941
  throw new Error(
5505
- `[Clio Coder] post-tool context guard stopped continuation before provider call: estimated ${after.tokens} tokens exceeds reported context window ${after.contextWindow}. Use /context compact, narrower reads, or a follow-up turn with smaller observations.`
6942
+ `[Clio Coder] post-tool context guard stopped continuation before provider call: estimated input ${afterView.inputTokens} plus reserved output ${afterView.outputReserveTokens} tokens does not fit context window ${afterView.effectiveWindow}. Use /context compact, narrower reads, or a follow-up turn with smaller observations.`
5506
6943
  );
5507
6944
  }
5508
6945
  return compacted ? continuationContextUpdate(agentRuntime) : void 0;
5509
6946
  },
5510
6947
  contextUsage() {
5511
- const effectiveWindow = state.runtime ? state.runtime.runtimeResolution.contextWindowDetails.effectiveContextWindow : windowWithoutRuntime().contextWindow;
5512
- if (!currentContextSnapshot) {
5513
- return contextUsageSnapshot(null, effectiveWindow);
5514
- }
5515
- const pendingTokens = pendingUserInputTokens();
5516
- const totalUsed = snapshotInputTokens(currentContextSnapshot) + pendingTokens + liveStreamingOutputTokens();
5517
- const breakdown = {
5518
- systemPromptTokens: currentContextSnapshot.categories.system,
5519
- messageTokens: currentContextSnapshot.categories.messages + (currentContextSnapshot.categories.toolResults ?? 0),
5520
- pendingUserTokens: pendingTokens,
5521
- toolSchemaTokens: currentContextSnapshot.categories.tools
6948
+ const view = budgetProducer.current() ?? refreshLiveBudget();
6949
+ return {
6950
+ ...contextUsageSnapshot(view.inputTokens, view.effectiveWindow, view.breakdown ?? void 0),
6951
+ revision: view.revision,
6952
+ inputSource: view.inputSource,
6953
+ historical: view.historical,
6954
+ breakdownSource: view.breakdownSource
5522
6955
  };
5523
- return contextUsageSnapshot(totalUsed > 0 ? totalUsed : null, effectiveWindow, breakdown);
5524
6956
  },
5525
6957
  contextLedger() {
5526
6958
  const settings = deps.getSettings();
@@ -5530,7 +6962,7 @@ function createTurnContext(deps) {
5530
6962
  contextWindow: state.runtime.runtimeResolution.contextWindowDetails.effectiveContextWindow,
5531
6963
  contextWindowSource: state.runtime.runtimeResolution.contextWindowDetails.contextWindowSource,
5532
6964
  contextWindowSlots: state.runtime.runtimeResolution.contextWindowDetails.contextWindowSlots
5533
- } : windowWithoutRuntime();
6965
+ } : windowWithoutRuntime(false);
5534
6966
  const provider = state.runtime?.targetId ?? settings.chat?.target ?? null;
5535
6967
  const model = state.runtime?.wireModelId ?? settings.chat?.model ?? null;
5536
6968
  const liveToolCount = state.runtime?.agent.state.tools.length ?? 0;
@@ -5642,7 +7074,9 @@ function createTurnContext(deps) {
5642
7074
  };
5643
7075
  }
5644
7076
  },
5645
- resetForSession() {
7077
+ resetForSession(incomingBranchAnchorTurnId = null) {
7078
+ memoryTurn = null;
7079
+ memoryAuthorityEpoch += 1;
5646
7080
  compactionController?.abort();
5647
7081
  if (sessionPromptHash !== null) noteColdReason("prompt_recompiled");
5648
7082
  lastPromptCache = null;
@@ -5655,7 +7089,12 @@ function createTurnContext(deps) {
5655
7089
  sessionWorkingContextPaths.clear();
5656
7090
  pendingPromptLogEntry = null;
5657
7091
  emptyAutoCompactContextKey = null;
7092
+ noUsefulCutBasisKey = null;
5658
7093
  reconciledAnchor = null;
7094
+ branchAnchorTurnId = incomingBranchAnchorTurnId;
7095
+ navigationEpoch += 1;
7096
+ bindMemoryScope();
7097
+ budgetProducer.reset();
5659
7098
  const session = deps.session?.current();
5660
7099
  currentContextSnapshot = session ? getLatestContextSnapshot(session) : null;
5661
7100
  },
@@ -5671,9 +7110,17 @@ init_esm_shims();
5671
7110
  function createTurnMiddleware(deps) {
5672
7111
  const { state, middlewareToolChoice } = deps;
5673
7112
  const pendingReminders = [];
5674
- const bufferReminder = (message, severity, deferred = false) => {
7113
+ const dropStale = () => {
7114
+ for (let index = pendingReminders.length - 1; index >= 0; index--) {
7115
+ if (pendingReminders[index]?.isCurrent?.() === false) pendingReminders.splice(index, 1);
7116
+ }
7117
+ };
7118
+ const bufferReminder = (message, severity, deferred = false, isCurrent) => {
7119
+ dropStale();
7120
+ isCurrent ??= message.startsWith("Memory:") ? deps.memoryContentGuard?.() : void 0;
7121
+ if (isCurrent?.() === false) return;
5675
7122
  if (pendingReminders.some((entry) => entry.message === message && entry.severity === severity)) return;
5676
- pendingReminders.push({ message, severity, deferred });
7123
+ pendingReminders.push({ message, severity, deferred, isCurrent });
5677
7124
  };
5678
7125
  const runMiddlewareTurnHook = (input) => {
5679
7126
  if (!deps.middleware) return [];
@@ -5752,6 +7199,7 @@ function createTurnMiddleware(deps) {
5752
7199
  []
5753
7200
  );
5754
7201
  if (!current()) return false;
7202
+ dropStale();
5755
7203
  const ready = pendingReminders.filter((entry) => entry.deferred);
5756
7204
  if (ready.length === 0) return false;
5757
7205
  const content = `<system-reminder>
@@ -5860,7 +7308,19 @@ ${[...new Set(ready.map((entry) => entry.message))].join("\n\n")}
5860
7308
  metadata
5861
7309
  });
5862
7310
  },
7311
+ takePendingReminderProjection() {
7312
+ const entries = pendingReminders.splice(0);
7313
+ return () => {
7314
+ const messages = [
7315
+ ...new Set(entries.filter((entry) => entry.isCurrent?.() !== false).map((entry) => entry.message))
7316
+ ];
7317
+ return messages.length ? `<system-reminder>
7318
+ ${messages.join("\n\n")}
7319
+ </system-reminder>` : "";
7320
+ };
7321
+ },
5863
7322
  flushPendingReminders() {
7323
+ dropStale();
5864
7324
  if (pendingReminders.length === 0) return "";
5865
7325
  const messages = [...new Set(pendingReminders.map((entry) => entry.message))];
5866
7326
  pendingReminders.length = 0;
@@ -5871,12 +7331,14 @@ ${messages.join("\n\n")}
5871
7331
  clearPendingReminders() {
5872
7332
  pendingReminders.length = 0;
5873
7333
  },
5874
- injectDeferredReminder(message, severity = "advisory") {
7334
+ injectDeferredReminder(message, severity = "advisory", isCurrent) {
7335
+ dropStale();
7336
+ if (isCurrent?.() === false) return;
5875
7337
  const text = message.trim();
5876
7338
  if (text.length === 0) return;
5877
7339
  const level = severity === "hard-block" ? "advisory" : severity;
5878
7340
  if (pendingReminders.some((entry) => entry.message === text)) return;
5879
- bufferReminder(text, level, true);
7341
+ bufferReminder(text, level, true, isCurrent);
5880
7342
  appendMiddlewareReminderEntry(text, level);
5881
7343
  deps.emitNotice(text);
5882
7344
  },
@@ -5979,7 +7441,7 @@ function createTurnPersistence(deps) {
5979
7441
  costUsd: (total.costUsd ?? 0) + summary.costUsd
5980
7442
  };
5981
7443
  };
5982
- const appendAssistantTurn = (message, timing) => {
7444
+ const appendAssistantTurn = (message, timing, deliveryId) => {
5983
7445
  if (message?.role !== "assistant") return;
5984
7446
  if (persistedAssistantMessages.has(message)) return;
5985
7447
  if (state.activeInterruptReason !== null && isEmptyAbortedAssistantMessage(message)) return;
@@ -5987,6 +7449,10 @@ function createTurnPersistence(deps) {
5987
7449
  const failure2 = terminalFailureFromAssistantMessage(message);
5988
7450
  const payload = assistantSessionPayload(message, failure2);
5989
7451
  if (timing) payload.timing = timing;
7452
+ if (deliveryId) {
7453
+ payload.continuityDeliveryId = deliveryId;
7454
+ payload.stopReason = message.stopReason;
7455
+ }
5990
7456
  const usage = message.usage;
5991
7457
  if (usage && typeof usage === "object") {
5992
7458
  const backendTimings = message.backendTimings;
@@ -6023,6 +7489,7 @@ function createTurnPersistence(deps) {
6023
7489
  tokens: traceUsage?.totalTokens ?? null
6024
7490
  });
6025
7491
  if (!turnContinues) finishTracedTurn(failure2 ? "fail" : "success", failure2?.errorMessage ?? null);
7492
+ return turn.id;
6026
7493
  };
6027
7494
  return {
6028
7495
  wasPersisted(message) {
@@ -6196,7 +7663,7 @@ function createTurnPersistence(deps) {
6196
7663
 
6197
7664
  // src/interactive/turn-prewarm.ts
6198
7665
  init_esm_shims();
6199
- import { createHash as createHash3 } from "node:crypto";
7666
+ import { createHash as createHash5 } from "node:crypto";
6200
7667
 
6201
7668
  // src/interactive/prewarm.ts
6202
7669
  init_esm_shims();
@@ -6322,7 +7789,7 @@ function createTurnPrewarm(deps) {
6322
7789
  if (Date.now() - started > 3e4) return { ran: false, reason: "expired" };
6323
7790
  if (deployment.endpoint && (endpointCapacityUsage()[deployment.endpoint] ?? 0) > 0)
6324
7791
  return { ran: false, reason: "dispatch-active" };
6325
- const prefix = createHash3("sha256").update(
7792
+ const prefix = createHash5("sha256").update(
6326
7793
  JSON.stringify({
6327
7794
  model: runtime.agent.state.model,
6328
7795
  system: runtime.agent.state.systemPrompt,
@@ -7139,6 +8606,33 @@ function createTurnRuntime(deps) {
7139
8606
  },
7140
8607
  maxRetryDelayMs: deps.retrySettings().maxDelayMs,
7141
8608
  ...gatewaySessionId ? { sessionId: gatewaySessionId } : {},
8609
+ beforeToolBatch: ({ assistantMessage }) => {
8610
+ const calls = assistantMessage.content.filter((block) => block.type === "toolCall");
8611
+ if (calls.some((call) => call.name === "self_compact") && calls.length !== 1) {
8612
+ return { reason: "self_compact must be the only call in its batch; no sibling was executed." };
8613
+ }
8614
+ return void 0;
8615
+ },
8616
+ beforeStreamRequest: ({ context: request }) => {
8617
+ setGlobalDefaultMaxOutputTokens(deps.getSettings().chat.maxOutputTokens);
8618
+ if (state.activeInterruptReason !== null || state.runtime !== localRuntime) {
8619
+ return { block: true, reason: "Request ownership changed before invocation." };
8620
+ }
8621
+ if (deps.hasQueuedSteering?.() && deps.continuity?.admission().block === false) {
8622
+ const handoff = deps.continuity.admission();
8623
+ if (!handoff.block && handoff.correlationId)
8624
+ return { block: true, reason: "Operator steering arrived during handoff preparation; handoff paused." };
8625
+ }
8626
+ const view = context.refreshLiveBudget();
8627
+ const resolved = resolvedRequestContext(request);
8628
+ const actual = state.synthesisToolLock ? { ...resolved, systemPrompt: lockedSynthesisSystemPrompt(resolved.systemPrompt ?? "") } : request;
8629
+ const input = Math.max(estimateInputTokensFromContext(actual), view.inputTokens ?? 0);
8630
+ const output = resolveTurnOutputReserve(localRuntime, input);
8631
+ return requestFits(input, output, view.effectiveWindow) ? deps.continuity?.admission() ?? { block: false } : {
8632
+ block: true,
8633
+ reason: `Context window exceeded: input ${input} + output ${output}, window ${view.effectiveWindow ?? "unknown"}.`
8634
+ };
8635
+ },
7142
8636
  onStreamInvocation: () => {
7143
8637
  setGlobalDefaultMaxOutputTokens(deps.getSettings().chat.maxOutputTokens);
7144
8638
  apiCallStartedAt = performance3.now();
@@ -7218,14 +8712,17 @@ function createTurnRuntime(deps) {
7218
8712
  );
7219
8713
  stallTimer.unref?.();
7220
8714
  };
7221
- handle.agent.prepareNextTurn = async (signal) => {
8715
+ handle.agent.prepareNextTurnWithContext = async (_completed, signal) => {
7222
8716
  stallSuspendDepth += 1;
7223
8717
  try {
7224
8718
  if (signal?.aborted || state.activeInterruptReason !== null) return void 0;
7225
- const contextChanged = await middleware.prepareToolContinuation(localRuntime, signal);
8719
+ if (deps.hasQueuedSteering?.()) await deps.continuity?.pause();
8720
+ const handoffChanged = await deps.continuity?.settle(signal) ?? false;
8721
+ const contextChanged = await middleware.prepareToolContinuation(localRuntime, signal) || handoffChanged;
7226
8722
  const update = await context.postToolContinuationGuard(localRuntime, signal, contextChanged);
8723
+ const restored = context.installMemoryRestoration(localRuntime);
7227
8724
  if (signal?.aborted || state.activeInterruptReason !== null) return void 0;
7228
- return update ?? (contextChanged ? {
8725
+ return (!restored ? update : void 0) ?? (contextChanged || restored ? {
7229
8726
  context: {
7230
8727
  messages: [...localRuntime.agent.state.messages],
7231
8728
  tools: [...localRuntime.agent.state.tools]
@@ -7434,7 +8931,9 @@ function createTurnRuntime(deps) {
7434
8931
  ttftMs: apiCallFirstDeltaAt !== null ? Math.round(Math.max(0, apiCallFirstDeltaAt - apiCallStartedAt)) : null,
7435
8932
  apiMs: Math.round(Math.max(0, eventClock - apiCallStartedAt))
7436
8933
  } : null;
7437
- persistence.appendAssistantTurn(enrichedEvent.message, timing);
8934
+ const correlationId = event.type === "message_end" ? handle.requestCorrelationId(event.message) : void 0;
8935
+ const persistedId = persistence.appendAssistantTurn(enrichedEvent.message, timing, correlationId);
8936
+ await deps.continuity?.response(persistedId, correlationId);
7438
8937
  if (isAssistant) apiCallStartedAt = null;
7439
8938
  const usage = enrichedEvent.message.usage;
7440
8939
  if (isAssistant && usage && typeof usage === "object" && runFirstCallVerdict === null) {
@@ -7455,6 +8954,7 @@ function createTurnRuntime(deps) {
7455
8954
  pendingTerminalToolResult = enrichedEvent.result?.terminate === true ? { toolCallId: enrichedEvent.toolCallId, toolName: enrichedEvent.toolName } : null;
7456
8955
  }
7457
8956
  if (enrichedEvent.type === "agent_end") {
8957
+ await deps.continuity?.pause();
7458
8958
  clearStallTimer();
7459
8959
  const terminal = pendingTerminalToolResult;
7460
8960
  pendingTerminalToolResult = null;
@@ -7633,6 +9133,7 @@ function createChatLoop(deps) {
7633
9133
  submit: (text, options) => submitTracked(text, options)
7634
9134
  });
7635
9135
  const middleware = createTurnMiddleware({
9136
+ memoryContentGuard: deps.memoryCommitBridge?.isContentCurrent,
7636
9137
  state,
7637
9138
  middleware: deps.middleware,
7638
9139
  session: deps.session,
@@ -7641,7 +9142,9 @@ function createChatLoop(deps) {
7641
9142
  emitFooterNotice
7642
9143
  });
7643
9144
  try {
7644
- deps.registerDeferredReminderSink?.((message) => middleware.injectDeferredReminder(message));
9145
+ deps.registerDeferredReminderSink?.(
9146
+ (message, isCurrent) => middleware.injectDeferredReminder(message, "advisory", isCurrent)
9147
+ );
7645
9148
  deps.registerDeferredNoticeSink?.((text) => middleware.emitDeferredNotice(text));
7646
9149
  } catch {
7647
9150
  }
@@ -7658,10 +9161,108 @@ function createChatLoop(deps) {
7658
9161
  readSessionEntries: deps.readSessionEntries,
7659
9162
  autoCompact: deps.autoCompact,
7660
9163
  getMemorySection: deps.getMemorySection,
9164
+ memoryCommitBridge: deps.memoryCommitBridge,
7661
9165
  getReadySkillCount: deps.getReadySkillCount,
9166
+ getPendingHandoff: () => {
9167
+ const sessionId = deps.session?.current()?.id;
9168
+ if (!sessionId) return null;
9169
+ const fold = resolveContinuityProjection({
9170
+ entries: filterEntriesToActivePath(deps.readSessionEntries?.() ?? [], state.lastTurnId ?? void 0),
9171
+ sessionId
9172
+ }).current;
9173
+ return fold?.identity && fold.phase !== "acknowledged" ? {
9174
+ id: fold.identity.handoffId,
9175
+ preparedAt: fold.policy ? new Date(fold.policy.preparedAtMs).toISOString() : null,
9176
+ sourceRevision: fold.identity.sourceRevision
9177
+ } : null;
9178
+ },
9179
+ getLastOutcome: () => {
9180
+ const sessionId = deps.session?.current()?.id;
9181
+ if (!sessionId) return null;
9182
+ const fold = resolveContinuityProjection({
9183
+ entries: filterEntriesToActivePath(deps.readSessionEntries?.() ?? [], state.lastTurnId ?? void 0),
9184
+ sessionId
9185
+ }).current;
9186
+ return fold?.identity ? { outcome: fold.phase, at: null, detail: fold.commit?.outcome ?? null } : null;
9187
+ },
7662
9188
  middleware,
7663
9189
  emitNotice
7664
9190
  });
9191
+ let continuityReplayInstalled = false;
9192
+ const continuity = new ContinuityController({
9193
+ captureOrigin: () => {
9194
+ continuityReplayInstalled = false;
9195
+ const session = deps.session;
9196
+ const meta = session?.current();
9197
+ const runtime = state.runtime;
9198
+ const leaf = state.lastTurnId;
9199
+ const operator = state.activeUserTurnId;
9200
+ if (!session || !meta || !runtime || !leaf || !operator || context.inspectLiveBudget().status !== "available") {
9201
+ throw new Error("Self-compaction requires a native runtime and a persisted operator turn.");
9202
+ }
9203
+ const revision = context.navigationRevision();
9204
+ const route = `${deps.getSettings().chat.target}/${deps.getSettings().chat.model}`;
9205
+ return {
9206
+ sessionId: meta.id,
9207
+ leafTurnId: leaf,
9208
+ initiatingTurnId: operator,
9209
+ sourceRevision: context.refreshLiveBudget().revision,
9210
+ ports: createContinuityPersistencePorts({
9211
+ session,
9212
+ origin: {
9213
+ sessionId: meta.id,
9214
+ cwdHash: meta.cwdHash,
9215
+ stillCurrent: () => context.navigationRevision() === revision && state.runtime === runtime && state.activeUserTurnId === operator && `${deps.getSettings().chat.target}/${deps.getSettings().chat.model}` === route
9216
+ }
9217
+ })
9218
+ };
9219
+ },
9220
+ entries: () => filterEntriesToActivePath(deps.readSessionEntries?.() ?? [], state.lastTurnId ?? void 0),
9221
+ leaf: () => state.lastTurnId,
9222
+ admitNote: (accepted) => {
9223
+ const view = context.refreshLiveBudget();
9224
+ if (!view.breakdown || view.historical) return false;
9225
+ const messages = state.runtime?.agent.state.messages ?? [];
9226
+ const operator = [...messages].reverse().find((message) => message.role === "user");
9227
+ const note = continuityReplayText({
9228
+ note: accepted.note,
9229
+ handoffId: "pending",
9230
+ commitId: "pending",
9231
+ originSessionId: view.sessionId ?? "",
9232
+ phase: "prepared",
9233
+ authority: "execution"
9234
+ });
9235
+ const protectedSkills = messages.filter((message) => message.role === "toolResult" && message.toolName === "context").reduce((sum, message) => sum + estimateAgentMessageTokens(message), 0);
9236
+ const floor = view.breakdown.systemPromptTokens + view.breakdown.toolSchemaTokens + protectedSkills + (operator ? estimateAgentMessageTokens(operator) : 0) + estimateAgentMessageTokens({ role: "assistant", content: [{ type: "text", text: note }] }) + 512;
9237
+ return requestFits(floor, view.outputReserveTokens, view.effectiveWindow);
9238
+ },
9239
+ fits: () => {
9240
+ const view = context.refreshLiveBudget();
9241
+ return requestFits(view.inputTokens, view.outputReserveTokens, view.effectiveWindow);
9242
+ },
9243
+ inputTokens: () => context.refreshLiveBudget().inputTokens ?? 0,
9244
+ reduce: async (hooks, signal) => {
9245
+ if (!state.runtime) throw new Error("Native runtime was detached.");
9246
+ continuityReplayInstalled = await context.runAutoCompact(
9247
+ state.runtime,
9248
+ true,
9249
+ void 0,
9250
+ "overflow",
9251
+ void 0,
9252
+ void 0,
9253
+ signal,
9254
+ hooks
9255
+ );
9256
+ },
9257
+ installReplay: () => {
9258
+ if (state.runtime && !continuityReplayInstalled) {
9259
+ context.refreshAgentMessagesFromSession(state.runtime);
9260
+ continuityReplayInstalled = true;
9261
+ }
9262
+ },
9263
+ onCommit: (commitId, outcome) => context.notifyMemoryCommit(commitId, "continuity", outcome),
9264
+ notice: emitNotice
9265
+ });
7665
9266
  const persistence = createTurnPersistence({
7666
9267
  state,
7667
9268
  session: deps.session,
@@ -7697,6 +9298,8 @@ function createChatLoop(deps) {
7697
9298
  knownTargets: deps.knownTargets,
7698
9299
  observability: deps.observability,
7699
9300
  createAgent,
9301
+ continuity,
9302
+ hasQueuedSteering: () => queues.queuedMessages().steer.length > 0,
7700
9303
  middlewareToolChoice,
7701
9304
  persistence,
7702
9305
  context,
@@ -7919,6 +9522,11 @@ function createChatLoop(deps) {
7919
9522
  const images = options.images && options.images.length > 0 ? [...options.images] : void 0;
7920
9523
  const pendingSkillRequests = state.currentTurnConstraints?.skills === "disabled" ? [] : options.pendingSkillRequests ?? [];
7921
9524
  context.addWorkingContextPaths(options.workingContextPaths ?? []);
9525
+ context.prepareMemoryTurn(agentRuntime, {
9526
+ taskText: text,
9527
+ continuation: options.requestContinuation === true,
9528
+ images
9529
+ });
7922
9530
  const pendingSkillPolicy = withModelSkillActivation(
7923
9531
  createPendingSkillToolPolicy(pendingSkillRequests) ?? state.activeSkillSurface,
7924
9532
  state.currentTurnConstraints?.skills !== "disabled" && modelMayActivateSkills(deps.getAutonomy?.() ?? deps.getSettings().safety.autonomy)
@@ -7933,7 +9541,7 @@ function createChatLoop(deps) {
7933
9541
  const toolSignature = toolSignatureFromState(agentRuntime.agent.state.tools);
7934
9542
  const askUserPolicy = createAskUserToolPolicy(agentRuntime.agent.state.tools);
7935
9543
  middleware.fireTurnStart(agentRuntime, text, pendingSkillRequests.length, options.requestContinuation === true);
7936
- const reminderBlock = middleware.flushPendingReminders();
9544
+ const reminderProjection = middleware.takePendingReminderProjection();
7937
9545
  const skillPreamble = pendingSkillRequestPreamble(pendingSkillRequests);
7938
9546
  let taskMemoryHandoffSource = "";
7939
9547
  if (pendingSkillRequests.some((request) => request.name.trim() === "context-handoff")) {
@@ -7942,7 +9550,8 @@ function createChatLoop(deps) {
7942
9550
  } catch {
7943
9551
  }
7944
9552
  }
7945
- const submittedText = [reminderBlock, skillPreamble, taskMemoryHandoffSource, text].filter((part) => part.length > 0).join("\n\n");
9553
+ const composeSubmittedText = () => [reminderProjection(), skillPreamble, taskMemoryHandoffSource, text].filter((part) => part.length > 0).join("\n\n");
9554
+ let submittedText = composeSubmittedText();
7946
9555
  const forceNow = process.env.CLIO_CODER_FORCE_COMPACT === "1";
7947
9556
  try {
7948
9557
  setTurnPreparation("compacting");
@@ -7954,6 +9563,7 @@ function createChatLoop(deps) {
7954
9563
  endPreparationCompaction();
7955
9564
  }
7956
9565
  const compiledPrompt = await context.ensureSessionPrompt(agentRuntime);
9566
+ submittedText = composeSubmittedText();
7957
9567
  const compactionThreshold = deps.getSettings().context.compaction?.threshold ?? null;
7958
9568
  const captureTurnSnapshot = (turnId) => context.captureRuntimeContextSnapshot(agentRuntime, turnId, compactionThreshold, {
7959
9569
  promptSegments: compiledPrompt ? compiledPrompt.sections.map((s) => ({ id: s.id, tokenEstimate: s.tokenEstimate })) : void 0,
@@ -7962,52 +9572,24 @@ function createChatLoop(deps) {
7962
9572
  promptHash: compiledPrompt?.systemPromptHash,
7963
9573
  toolSignature
7964
9574
  });
7965
- const effectiveWindow = agentRuntime.runtimeResolution.contextWindowDetails.effectiveContextWindow;
7966
- const outputForInput = (inputTokens) => resolveReservedOutputTokens(agentRuntime.runtimeResolution.capabilityDecisions.maxTokens, {
7967
- api: agentRuntime.agent.state.model?.api ?? "",
7968
- contextWindow: effectiveWindow,
7969
- inputTokens
7970
- });
7971
- const pendingInputTokens = ceilChars(submittedText.length);
7972
9575
  let turnSnapshot = captureTurnSnapshot("pending");
7973
- const budgetedPromptTokens = (snapshot) => Math.max(
7974
- snapshotInputTokens(snapshot) + pendingInputTokens,
7975
- context.liveContextEstimate(agentRuntime, submittedText).tokens
7976
- );
7977
- const inputEstimate = budgetedPromptTokens(turnSnapshot);
7978
- const reservedOutput = outputForInput(inputEstimate);
7979
- const totalEstimate = inputEstimate + reservedOutput;
7980
- if (effectiveWindow > 0 && totalEstimate > effectiveWindow) {
7981
- emitNotice(
7982
- `[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...`
7983
- );
9576
+ let admission = context.refreshLiveBudget(submittedText);
9577
+ if (!requestFits(admission.inputTokens, admission.outputReserveTokens, admission.effectiveWindow)) {
7984
9578
  setTurnPreparation("compacting");
7985
- let compactionFailure;
7986
- const compacted = await context.runAutoCompact(agentRuntime, true, void 0, "overflow", submittedText, pendingSkillPolicy).catch((error) => {
7987
- compactionFailure = error instanceof Error ? error.message : String(error);
7988
- return false;
9579
+ let failure2;
9580
+ await context.runAutoCompact(agentRuntime, true, void 0, "overflow", submittedText, pendingSkillPolicy).catch((error) => {
9581
+ failure2 = error instanceof Error ? error.message : String(error);
7989
9582
  }).finally(endPreparationCompaction);
7990
- if (!compacted) {
9583
+ submittedText = composeSubmittedText();
9584
+ admission = context.refreshLiveBudget(submittedText);
9585
+ if (!requestFits(admission.inputTokens, admission.outputReserveTokens, admission.effectiveWindow)) {
7991
9586
  emitAdmissionNotice(
7992
- `[Clio Coder] Request exceeds the context window and compaction could not reclaim enough space.${compactionFailure ? ` ${compactionFailure}.` : ""} Trim the prompt or reduce active tools.`,
9587
+ `[Clio Coder] Request exceeds the available context window (input ${admission.inputTokens ?? "unknown"} + output ${admission.outputReserveTokens ?? "unknown"}, window ${admission.effectiveWindow ?? "unknown"}).${failure2 ? ` ${failure2}` : ""} Trim the prompt or reduce active tools.`,
7993
9588
  "context-window-exceeded"
7994
9589
  );
7995
9590
  return;
7996
9591
  }
7997
- context.refreshAgentMessagesFromSession(agentRuntime);
7998
9592
  turnSnapshot = captureTurnSnapshot("pending");
7999
- const postInputEstimate = budgetedPromptTokens(turnSnapshot);
8000
- const postTotalEstimate = postInputEstimate + outputForInput(postInputEstimate);
8001
- if (postTotalEstimate > effectiveWindow) {
8002
- emitAdmissionNotice(
8003
- `[Clio Coder] Request still exceeds the effective window after compaction (${postTotalEstimate} > ${effectiveWindow}). Request blocked.`,
8004
- "context-window-exceeded"
8005
- );
8006
- return;
8007
- }
8008
- emitNotice(
8009
- `[Clio Coder] Context budget check passed post-compaction (${postTotalEstimate} <= ${effectiveWindow}). Proceeding.`
8010
- );
8011
9593
  }
8012
9594
  const userTurnId = persistence.appendSubmittedUserTurn(
8013
9595
  agentRuntime,
@@ -8017,6 +9599,8 @@ function createChatLoop(deps) {
8017
9599
  text,
8018
9600
  options.display?.text
8019
9601
  );
9602
+ context.installMemoryRestoration(agentRuntime, submittedText);
9603
+ context.commitMemoryTurn(agentRuntime);
8020
9604
  if (interrupted)
8021
9605
  emit({
8022
9606
  type: "queued_user_turn",
@@ -8164,6 +9748,7 @@ function createChatLoop(deps) {
8164
9748
  }
8165
9749
  },
8166
9750
  cancel(options) {
9751
+ continuity.cancel();
8167
9752
  const wasStreaming = state.streaming;
8168
9753
  context.cancelCompaction();
8169
9754
  recovery.cancelRetryCountdown();
@@ -8220,9 +9805,14 @@ function createChatLoop(deps) {
8220
9805
  contextUsage: () => context.contextUsage(),
8221
9806
  currentTurnConstraints: () => state.currentTurnConstraints,
8222
9807
  contextLedger: () => context.contextLedger(),
9808
+ liveBudget: () => context.liveBudget(),
9809
+ inspectLiveBudget: () => context.inspectLiveBudget(),
9810
+ refreshLiveBudget: () => context.refreshLiveBudget(),
8223
9811
  whenSettled: () => activeSubmit,
8224
9812
  whenPrewarmSettled: () => prewarm.settled(),
8225
9813
  resetForSession(leafTurnId, replayMessages) {
9814
+ continuity.cancel();
9815
+ void continuity.pause();
8226
9816
  state.currentTurnConstraints = void 0;
8227
9817
  if (state.runtime) {
8228
9818
  state.runtime.agent.abort();
@@ -8235,7 +9825,7 @@ function createChatLoop(deps) {
8235
9825
  middlewareToolChoice.reset();
8236
9826
  state.lastTurnId = leafTurnId;
8237
9827
  state.lastRunSnapshot = null;
8238
- context.resetForSession();
9828
+ context.resetForSession(leafTurnId);
8239
9829
  void turnRuntime.ensureLiveCapabilitiesForSelectedModel().catch(() => {
8240
9830
  });
8241
9831
  state.replayedContextMessages = replayMessages ? [...replayMessages] : [];
@@ -8327,6 +9917,26 @@ function createChatLoop(deps) {
8327
9917
  recordOutOfTurnUsage(prepared.runtime, result.usage, "handoff");
8328
9918
  return result.aborted ? { status: "aborted", text: result.text } : { status: "answered", text: result.text };
8329
9919
  },
9920
+ requestSelfCompact: (note, toolCallId, signal) => continuity.request(note, toolCallId, signal),
9921
+ async recoverHandoff(handoffId, action) {
9922
+ if (state.streaming || state.turnPreparation !== "idle")
9923
+ throw new Error("Wait for the current turn to settle before recovery.");
9924
+ const runtime = turnRuntime.ensureRuntime();
9925
+ if (!runtime) throw new Error("No native runtime is configured.");
9926
+ state.activeInterruptReason = null;
9927
+ if (!state.activeUserTurnId) {
9928
+ const operator = [...filterEntriesToActivePath(deps.readSessionEntries?.() ?? [], state.lastTurnId ?? void 0)].reverse().find((entry) => entry.kind === "message" && entry.role === "user");
9929
+ state.activeUserTurnId = operator?.turnId ?? null;
9930
+ }
9931
+ state.streaming = true;
9932
+ try {
9933
+ await continuity.recover(handoffId, action);
9934
+ await continueEngineWithoutInput(runtime.agent);
9935
+ } finally {
9936
+ state.streaming = false;
9937
+ await continuity.pause();
9938
+ }
9939
+ },
8330
9940
  async compact(instructions) {
8331
9941
  if (!deps.session?.current()) {
8332
9942
  emitNotice("[/context compact] no current session to compact; start one with /new or /resume first");
@@ -10001,7 +11611,7 @@ function createDispatchTool(inputDeps, surfaceOptions = {}) {
10001
11611
  const admission = createDispatchAdmissionController(deps);
10002
11612
  let runnerPromise = null;
10003
11613
  const loadRunner = () => {
10004
- runnerPromise ??= (surfaceOptions.loadRunner ?? (() => import("./dispatch-runner-NC7PYNIO.js")))();
11614
+ runnerPromise ??= (surfaceOptions.loadRunner ?? (() => import("./dispatch-runner-VKVXVTWJ.js")))();
10005
11615
  return runnerPromise;
10006
11616
  };
10007
11617
  return {
@@ -10038,6 +11648,32 @@ function createDispatchTool(inputDeps, surfaceOptions = {}) {
10038
11648
  };
10039
11649
  }
10040
11650
 
11651
+ // src/tools/self-compact.ts
11652
+ init_esm_shims();
11653
+ function createSelfCompactTool(request) {
11654
+ return {
11655
+ name: ToolNames.SelfCompact,
11656
+ description: "Save an exact handoff note and compact context before continuing this task. Call alone, with no sibling tools. Include current objective, decisions, evidence, paths, and next steps. A pending receipt is not proof of completion.",
11657
+ parameters: typebox_exports.Object({
11658
+ note_to_self: typebox_exports.String({
11659
+ maxLength: 8192,
11660
+ description: "Assistant-authored handoff text; nonblank, at most 8192 UTF-8 bytes. Preserved exactly."
11661
+ })
11662
+ }),
11663
+ baseActionClass: "read",
11664
+ executionMode: "sequential",
11665
+ async run(args, options) {
11666
+ try {
11667
+ const note = args.note_to_self;
11668
+ const output = await request(note, options?.toolCallId ?? "", options?.signal);
11669
+ return { kind: "ok", output };
11670
+ } catch (error) {
11671
+ return { kind: "error", message: error instanceof Error ? error.message : String(error) };
11672
+ }
11673
+ }
11674
+ };
11675
+ }
11676
+
10041
11677
  // src/tools/bootstrap.ts
10042
11678
  function registerAllTools(registry, deps = {}) {
10043
11679
  const cwd = deps.session?.current()?.cwd ?? process.cwd();
@@ -10047,6 +11683,10 @@ function registerAllTools(registry, deps = {}) {
10047
11683
  ...coreDeps,
10048
11684
  ...mcpCapabilities ? { mcpCapabilities } : {}
10049
11685
  });
11686
+ if (deps.requestSelfCompact)
11687
+ registry.register(
11688
+ builtin(createSelfCompactTool(deps.requestSelfCompact), { path: "src/tools/self-compact.ts", scope: "core" })
11689
+ );
10050
11690
  if (deps.dispatch) {
10051
11691
  const dispatch = deps.dispatch;
10052
11692
  const dispatchRunEvents = createDispatchRunEventRegistry({ journal: null });
@@ -10076,7 +11716,7 @@ function registerAllTools(registry, deps = {}) {
10076
11716
  ...builtin(
10077
11717
  lazyTool(
10078
11718
  monitorToolSurface,
10079
- async () => (await import("./monitor-LHNH577J.js")).createMonitorTool({ dispatch, runEvents: dispatchRunEvents })
11719
+ async () => (await import("./monitor-2HD3MC67.js")).createMonitorTool({ dispatch, runEvents: dispatchRunEvents })
10080
11720
  ),
10081
11721
  {
10082
11722
  path: "src/tools/monitor.ts",
@@ -10086,7 +11726,7 @@ function registerAllTools(registry, deps = {}) {
10086
11726
  });
10087
11727
  registry.register({
10088
11728
  ...builtin(
10089
- lazyTool(steerToolSurface, async () => (await import("./steer-KKYXJSH4.js")).createSteerTool({ dispatch })),
11729
+ lazyTool(steerToolSurface, async () => (await import("./steer-D7RTAPCM.js")).createSteerTool({ dispatch })),
10090
11730
  {
10091
11731
  path: "src/tools/steer.ts",
10092
11732
  scope: "core"
@@ -10098,7 +11738,7 @@ function registerAllTools(registry, deps = {}) {
10098
11738
  const panes = deps.panes;
10099
11739
  registry.register({
10100
11740
  ...builtin(
10101
- lazyTool(panesToolSurface, async () => (await import("./panes-A743ONZ5.js")).createPanesTool({ panes })),
11741
+ lazyTool(panesToolSurface, async () => (await import("./panes-TTPPSYCL.js")).createPanesTool({ panes })),
10102
11742
  { path: "src/tools/panes.ts", scope: "core" }
10103
11743
  )
10104
11744
  });
@@ -10598,6 +12238,15 @@ async function runCompactionFlow(session, settings, providers, instructions, tri
10598
12238
  const isOriginCurrent = () => session.current()?.id === meta.id && (session.tree(meta.id).leafId ?? void 0) === activeLeafTurnId;
10599
12239
  const entries = filterEntriesToActivePath(readSessionEntriesForCompact(meta.id), activeLeafTurnId);
10600
12240
  if (entries.length === 0) return null;
12241
+ const continuity = resolveContinuityProjection({
12242
+ entries,
12243
+ sessionId: meta.id,
12244
+ ...meta.parentSessionId && meta.parentTurnId ? { fork: { parentSessionId: meta.parentSessionId, parentTurnId: meta.parentTurnId } } : {}
12245
+ });
12246
+ const continuityNote = {
12247
+ tokens: continuityProjectionTokens(continuity),
12248
+ anchorTurnId: continuity.noteAnchorTurnId
12249
+ };
10601
12250
  budget?.signal?.throwIfAborted();
10602
12251
  const systemPrompt = await readCompactionSystemPrompt(settings.context.compaction.systemPrompt, meta.cwd);
10603
12252
  budget?.signal?.throwIfAborted();
@@ -10625,6 +12274,7 @@ async function runCompactionFlow(session, settings, providers, instructions, tri
10625
12274
  try {
10626
12275
  result = await compact({
10627
12276
  entries,
12277
+ continuityNote,
10628
12278
  ...budget,
10629
12279
  ...summarize ? { summarize } : {},
10630
12280
  onCall: (call) => calls.push(call),
@@ -10660,9 +12310,15 @@ async function runCompactionFlow(session, settings, providers, instructions, tri
10660
12310
  if (result.usage) {
10661
12311
  result.usage = { ...result.usage, targetId: resolved.targetId, modelId: resolved.wireModelId };
10662
12312
  }
12313
+ const settledContinuity = resolveContinuityProjection({
12314
+ entries: filterEntriesToActivePath(readSessionEntriesForCompact(meta.id), activeLeafTurnId),
12315
+ sessionId: meta.id,
12316
+ ...meta.parentSessionId && meta.parentTurnId ? { fork: { parentSessionId: meta.parentSessionId, parentTurnId: meta.parentTurnId } } : {}
12317
+ });
12318
+ const continuityCarry = settledContinuity.current === null ? null : continuityPayloadFromFold(settledContinuity.current);
10663
12319
  const entry = {
10664
12320
  kind: "compactionSummary",
10665
- turnId: randomUUID2(),
12321
+ turnId: randomUUID3(),
10666
12322
  parentTurnId: result.firstKeptTurnId ?? null,
10667
12323
  summary: result.summary,
10668
12324
  ...result.skillContext ? { skillContext: result.skillContext } : {},
@@ -10671,12 +12327,23 @@ async function runCompactionFlow(session, settings, providers, instructions, tri
10671
12327
  firstKeptTurnId: result.firstKeptTurnId ?? "",
10672
12328
  messagesSummarized: result.messagesSummarized,
10673
12329
  isSplitTurn: result.isSplitTurn,
10674
- tokensAfter: estimateTokensAfterCompaction(entries, result),
12330
+ tokensAfter: estimateTokensAfterCompaction(entries, result, continuityNote.tokens),
12331
+ // The latest validated fold, carried so the transaction survives the cut
12332
+ // that is about to remove its earlier records from replay. It preserves
12333
+ // the immutable identity, accepted note, original policy and commit; it
12334
+ // never manufactures a commit and never turns an eviction-only outcome
12335
+ // into a summarized one. A later ordinary compaction with no handoff of
12336
+ // its own still carries the newest validated payload forward, which is
12337
+ // what keeps an eviction-only commit's state alive across summary cycles.
12338
+ ...continuityCarry === null ? {} : { continuity: continuityCarry },
10675
12339
  // The summarization call is a real model call. Persisting its provider
10676
- // usage on the entry is what puts it in front of `/cost` and `clio-coder usage
12340
+ // usage on the entry is what puts it in front of `/usage` and `clio-coder usage
10677
12341
  // report`, which folded the ledger and so counted every call but this one.
10678
12342
  ...result.usage !== void 0 ? { usage: result.usage } : {}
10679
12343
  };
12344
+ if (budget?.checkpointForSummary) {
12345
+ entry.continuity = budget.checkpointForSummary(entry.turnId, entry.tokensBefore, entry.tokensAfter ?? 0);
12346
+ }
10680
12347
  if (trigger !== void 0) entry.trigger = trigger;
10681
12348
  try {
10682
12349
  session.appendEntry(entry);
@@ -10724,11 +12391,11 @@ function estimateTokensFromSummary(result) {
10724
12391
  ...result.userContext ? { userContext: result.userContext } : {}
10725
12392
  });
10726
12393
  }
10727
- function estimateTokensAfterCompaction(entries, result) {
12394
+ function estimateTokensAfterCompaction(entries, result, continuityNoteTokens = 0) {
10728
12395
  let droppedTokens = 0;
10729
12396
  for (const entry of entries.slice(0, result.firstKeptEntryIndex)) droppedTokens += estimateTokens(entry);
10730
12397
  const summaryTokens = estimateTokensFromSummary(result);
10731
- return Math.max(summaryTokens, result.tokensBefore - droppedTokens + summaryTokens);
12398
+ return Math.max(summaryTokens + continuityNoteTokens, result.tokensBefore - droppedTokens + summaryTokens);
10732
12399
  }
10733
12400
  function advanceScopedTarget(settings, direction) {
10734
12401
  const scope = settings.chat.modelPicker.cycleSet ?? [];
@@ -11135,7 +12802,7 @@ async function bootOrchestrator(options = {}) {
11135
12802
  // A headless run submits no further turn, so a detached step could only
11136
12803
  // finish after the process that would have read it has exited.
11137
12804
  deliversDeferredReminders: options.headless === void 0,
11138
- onDeferredReminder: (message) => deferredMemoryReminderSink?.(message),
12805
+ onDeferredReminder: (message, isCurrent) => deferredMemoryReminderSink?.(message, isCurrent),
11139
12806
  getSettings: () => {
11140
12807
  const memory = effectiveSettingsForDispatch?.().context.memory ?? memorySettings;
11141
12808
  return {
@@ -11315,6 +12982,8 @@ async function bootOrchestrator(options = {}) {
11315
12982
  getCwd: () => process.cwd()
11316
12983
  }) : null;
11317
12984
  const toolBootstrap = registerAllTools(toolRegistry, {
12985
+ getContextBudget: () => chat.inspectLiveBudget(),
12986
+ requestSelfCompact: (note, toolCallId, signal) => chat.requestSelfCompact(note, toolCallId, signal),
11318
12987
  getSettings: () => getCurrentSettings(),
11319
12988
  termination,
11320
12989
  captureWorkerContext: () => chat.captureWorkerContext?.() ?? null,
@@ -11589,21 +13258,7 @@ async function bootOrchestrator(options = {}) {
11589
13258
  bus,
11590
13259
  ...prompts ? { prompts } : {},
11591
13260
  ...session ? { session } : {},
11592
- getMemorySection: () => {
11593
- try {
11594
- const records = loadMemoryRecordsSync(clioDataDir());
11595
- const settings = getCurrentSettings();
11596
- const targetId = session?.current()?.target ?? settings.chat?.target;
11597
- const runtimeId = targetId ? providers.getTarget(targetId)?.runtime : void 0;
11598
- return buildMemoryPromptSection(records, {
11599
- scopes: ["global", "repo", "runtime"],
11600
- activeRepository: canonicalMemoryRepositoryIdentity(process.cwd()),
11601
- activeRuntime: runtimeId === void 0 ? null : { kind: "runtime", key: runtimeId }
11602
- }).section;
11603
- } catch {
11604
- return "";
11605
- }
11606
- },
13261
+ getMemorySection: createMemoryPromptReader({ getDataDir: clioDataDir }),
11607
13262
  getTaskMemoryHandoffSource: () => {
11608
13263
  const meta = session?.current();
11609
13264
  if (!meta) throw new Error("task memory handoff requires an active session");
@@ -11617,6 +13272,7 @@ async function bootOrchestrator(options = {}) {
11617
13272
  agentIds: []
11618
13273
  });
11619
13274
  },
13275
+ memoryCommitBridge: memoryIntervention,
11620
13276
  registerDeferredReminderSink: (sink) => {
11621
13277
  deferredMemoryReminderSink = sink;
11622
13278
  },
@@ -11659,10 +13315,14 @@ async function bootOrchestrator(options = {}) {
11659
13315
  try {
11660
13316
  chat.resetForSession(
11661
13317
  leafTurnId,
11662
- buildModelReplayAgentMessagesFromTurns(
11663
- readCurrentSessionEntries(),
11664
- leafTurnId ? { activeLeafTurnId: leafTurnId } : {}
11665
- )
13318
+ buildModelReplayAgentMessagesFromTurns(readCurrentSessionEntries(), {
13319
+ ...leafTurnId ? { activeLeafTurnId: leafTurnId } : {},
13320
+ // The same ownership the interactive /resume overlay supplies.
13321
+ // Without it this reader owns nothing, the fold finds no
13322
+ // current origin, and a resumed session boots with its accepted
13323
+ // note silently missing from the provider context.
13324
+ continuity: continuityContextFromSession(session)
13325
+ })
11666
13326
  );
11667
13327
  } catch (err) {
11668
13328
  chat.resetForSession(leafTurnId);
@@ -11700,7 +13360,14 @@ async function bootOrchestrator(options = {}) {
11700
13360
  ...session ? { session } : {},
11701
13361
  ...session ? {
11702
13362
  readSessionEntries: readSessionEntriesForCompact,
11703
- buildReplayMessages: (entries, leafTurnId) => buildModelReplayAgentMessagesFromTurns(entries, leafTurnId === null ? {} : { activeLeafTurnId: leafTurnId })
13363
+ // Ownership is read at call time, not captured: an ACP client
13364
+ // can switch sessions between turns, and a fork opened this way
13365
+ // must separate its own transactions from the ones it inherited
13366
+ // exactly as the interactive path does.
13367
+ buildReplayMessages: (entries, leafTurnId) => buildModelReplayAgentMessagesFromTurns(entries, {
13368
+ ...leafTurnId === null ? {} : { activeLeafTurnId: leafTurnId },
13369
+ continuity: continuityContextFromSession(session)
13370
+ })
11704
13371
  } : {},
11705
13372
  providers,
11706
13373
  // The fleet controls `clio-coder/dispatch/steer` reaches. The server
@@ -11737,7 +13404,7 @@ async function bootOrchestrator(options = {}) {
11737
13404
  providers,
11738
13405
  cwd: process.cwd(),
11739
13406
  runDoctor: async ({ deep }) => {
11740
- const { collectDoctorFindings, doctorNotice } = await import("./doctor-S5KZ2GTN.js");
13407
+ const { collectDoctorFindings, doctorNotice } = await import("./doctor-53AVQXAO.js");
11741
13408
  return doctorNotice(
11742
13409
  await collectDoctorFindings({
11743
13410
  workspaceRoot: process.cwd(),
@@ -11886,7 +13553,7 @@ async function bootOrchestrator(options = {}) {
11886
13553
  dashboardPlugins = activePluginIds();
11887
13554
  });
11888
13555
  termination.onTerminate(() => unsubscribeDashboardPlugins());
11889
- const { startInteractive } = await import("./interactive-OXGYZYAS.js");
13556
+ const { startInteractive } = await import("./interactive-JRCBENLZ.js");
11890
13557
  await startInteractive({
11891
13558
  getConnections: () => ({
11892
13559
  mcp: toolBootstrap.mcpCapabilities?.connectedIds({ readyOnly: true }) ?? [],
@@ -12089,6 +13756,7 @@ async function bootOrchestrator(options = {}) {
12089
13756
  );
12090
13757
  }
12091
13758
  },
13759
+ onRecoverHandoff: (handoffId, action) => chat.recoverHandoff(handoffId, action),
12092
13760
  onCompact: async (instructions) => {
12093
13761
  await chat.compact(instructions);
12094
13762
  }
@@ -12114,4 +13782,4 @@ export {
12114
13782
  createBackgroundMemoryRouting,
12115
13783
  createProductionAutoCompact
12116
13784
  };
12117
- //# sourceMappingURL=orchestrator-DTAPXULA.js.map
13785
+ //# sourceMappingURL=orchestrator-OUW3ZN6Y.js.map