@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
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Structural validation for continuity records (CONTRACTS.md §3).
3
+ *
4
+ * These guards are independent of `isSessionEntry` and check the envelope
5
+ * locally, so `entries.ts` can call them in 02B without this module ever
6
+ * value-importing the session entry union back. They validate payload structure
7
+ * and references, not just the kind string: a record that merely says
8
+ * `kind: "continuityCommit"` and carries nothing usable is rejected here rather
9
+ * than surfacing as an empty-but-present commit later.
10
+ *
11
+ * Everything numeric must be a finite integer. `Number.MAX_SAFE_INTEGER`
12
+ * arithmetic and `Infinity` deadlines are both ways to obtain an unbounded
13
+ * automatic window, so neither is accepted.
14
+ */
15
+
16
+ import {
17
+ type AcceptedNote,
18
+ type ContinuityCarriedState,
19
+ type ContinuityCarryingSummary,
20
+ type ContinuityCheckpointPayload,
21
+ type ContinuityCommitData,
22
+ type ContinuityCommitEntry,
23
+ type ContinuityOutcome,
24
+ HANDOFF_NOTE_MAX_BYTES,
25
+ HANDOFF_POLICY_LIMITS,
26
+ HANDOFF_SCHEMA_VERSION,
27
+ type HandoffEvent,
28
+ type HandoffFailureReason,
29
+ type HandoffIdentity,
30
+ type HandoffPauseReason,
31
+ type HandoffPolicy,
32
+ type HandoffResumeAuthority,
33
+ type HandoffTransactionEntry,
34
+ type TransitionLink,
35
+ } from "./contract.js";
36
+
37
+ const OUTCOMES: ReadonlySet<string> = new Set<ContinuityOutcome>(["summarized", "evicted", "continuity_only"]);
38
+ const PAUSE_REASONS: ReadonlySet<string> = new Set<HandoffPauseReason>([
39
+ "operator_cancelled",
40
+ "origin_changed",
41
+ "delivery_uncertain",
42
+ ]);
43
+ const FAILURE_REASONS: ReadonlySet<string> = new Set<HandoffFailureReason>([
44
+ "invalid_note",
45
+ "note_exceeds_replay_budget",
46
+ "budget_unsafe_no_material",
47
+ "durability_unresolved",
48
+ "state_root_removed",
49
+ "origin_changed",
50
+ "provider_failed",
51
+ "attempts_exhausted",
52
+ "deadline_exceeded",
53
+ ]);
54
+
55
+ function isRecord(value: unknown): value is Record<string, unknown> {
56
+ return typeof value === "object" && value !== null && !Array.isArray(value);
57
+ }
58
+
59
+ function isNonEmptyString(value: unknown): value is string {
60
+ return typeof value === "string" && value.length > 0;
61
+ }
62
+
63
+ function isNullableString(value: unknown): value is string | null {
64
+ return value === null || isNonEmptyString(value);
65
+ }
66
+
67
+ function isFiniteInteger(value: unknown): value is number {
68
+ return typeof value === "number" && Number.isSafeInteger(value);
69
+ }
70
+
71
+ function isNonNegativeInteger(value: unknown): value is number {
72
+ return isFiniteInteger(value) && value >= 0;
73
+ }
74
+
75
+ function isPositiveInteger(value: unknown): value is number {
76
+ return isFiniteInteger(value) && value > 0;
77
+ }
78
+
79
+ /** A valid `BaseSessionEntry` envelope, checked without importing the union. */
80
+ function hasEntryEnvelope(value: unknown, kind: string): value is Record<string, unknown> {
81
+ if (!isRecord(value)) return false;
82
+ return (
83
+ value.kind === kind &&
84
+ isNonEmptyString(value.turnId) &&
85
+ isNullableString(value.parentTurnId) &&
86
+ isNonEmptyString(value.timestamp)
87
+ );
88
+ }
89
+
90
+ function isAcceptedNote(value: unknown): value is AcceptedNote {
91
+ if (!isRecord(value)) return false;
92
+ return (
93
+ typeof value.note === "string" &&
94
+ value.note.length > 0 &&
95
+ isPositiveInteger(value.noteBytes) &&
96
+ value.noteBytes <= HANDOFF_NOTE_MAX_BYTES &&
97
+ typeof value.noteSha256 === "string" &&
98
+ /^[0-9a-f]{64}$/.test(value.noteSha256)
99
+ );
100
+ }
101
+
102
+ function isHandoffIdentity(value: unknown): value is HandoffIdentity {
103
+ if (!isRecord(value)) return false;
104
+ return (
105
+ isNonEmptyString(value.handoffId) &&
106
+ isNonEmptyString(value.preparedEntryId) &&
107
+ isNonEmptyString(value.commitId) &&
108
+ isNonEmptyString(value.commitEntryId) &&
109
+ isNonEmptyString(value.originSessionId) &&
110
+ isNullableString(value.branchAnchorTurnId) &&
111
+ isNonEmptyString(value.initiatingTurnId) &&
112
+ isNonEmptyString(value.toolCallId) &&
113
+ typeof value.sourceRevision === "string"
114
+ );
115
+ }
116
+
117
+ /**
118
+ * A persisted policy is held to the frozen §2/§7 ceilings, not merely to being
119
+ * positive. A ledger record claiming three attempts, a summary-retry loop, or
120
+ * eight stream invocations would otherwise widen the automatic budget just by
121
+ * surviving a restart.
122
+ */
123
+ function isHandoffPolicy(value: unknown): value is HandoffPolicy {
124
+ if (!isRecord(value)) return false;
125
+ if (!isNonNegativeInteger(value.preparedAtMs)) return false;
126
+ if (!isPositiveInteger(value.automaticDeadlineAtMs)) return false;
127
+ // A deadline at or before preparation is not a window.
128
+ if (value.automaticDeadlineAtMs <= value.preparedAtMs) return false;
129
+ if (!isPositiveInteger(value.maxAttempts) || value.maxAttempts > HANDOFF_POLICY_LIMITS.maxAttempts) return false;
130
+ if (
131
+ !isPositiveInteger(value.maxSummaryCallsPerAttempt) ||
132
+ value.maxSummaryCallsPerAttempt > HANDOFF_POLICY_LIMITS.maxSummaryCallsPerAttempt
133
+ ) {
134
+ return false;
135
+ }
136
+ if (value.additionalSummaryRetriesPerCall !== HANDOFF_POLICY_LIMITS.additionalSummaryRetriesPerCall) return false;
137
+ if (
138
+ !isPositiveInteger(value.maxSummaryStreamInvocations) ||
139
+ value.maxSummaryStreamInvocations > HANDOFF_POLICY_LIMITS.maxSummaryStreamInvocations
140
+ ) {
141
+ return false;
142
+ }
143
+ return isNonNegativeInteger(value.flushRetryLimit) && value.flushRetryLimit <= HANDOFF_POLICY_LIMITS.flushRetryLimit;
144
+ }
145
+
146
+ /**
147
+ * Stable value identity with normalized key order, used for comparing two
148
+ * copies of one record and for the persistence progress token. `undefined`
149
+ * fields are dropped so an explicitly-absent optional and a missing one are the
150
+ * same record.
151
+ */
152
+ export function canonicalJson(value: unknown): string {
153
+ if (value === undefined) return "undefined";
154
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
155
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
156
+ const fields = Object.entries(value as Record<string, unknown>)
157
+ .filter(([, field]) => field !== undefined)
158
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
159
+ return `{${fields.map(([key, field]) => `${JSON.stringify(key)}:${canonicalJson(field)}`).join(",")}}`;
160
+ }
161
+
162
+ function isTransitionLink(value: unknown): value is TransitionLink {
163
+ if (!isRecord(value)) return false;
164
+ return (
165
+ isNonEmptyString(value.entryId) &&
166
+ isNullableString(value.prevEntryId) &&
167
+ isNonNegativeInteger(value.sequence) &&
168
+ isNonNegativeInteger(value.attempt)
169
+ );
170
+ }
171
+
172
+ function isResumeAuthority(value: unknown): value is HandoffResumeAuthority {
173
+ if (!isRecord(value)) return false;
174
+ return (
175
+ isNonEmptyString(value.operatorRequestEntryId) &&
176
+ isNonEmptyString(value.pausedOrFailedEntryId) &&
177
+ (value.action === "reduce" || value.action === "deliver") &&
178
+ isPositiveInteger(value.automaticDeadlineAtMs)
179
+ );
180
+ }
181
+
182
+ /** Validate one event, discriminating on `phase` and requiring that phase's fields. */
183
+ export function isHandoffEvent(value: unknown): value is HandoffEvent {
184
+ if (!isRecord(value)) return false;
185
+ switch (value.phase) {
186
+ case "prepared":
187
+ return isAcceptedNote(value.accepted) && isHandoffPolicy(value.policy);
188
+ case "reducing":
189
+ return value.resumeRef === undefined || isNonEmptyString(value.resumeRef);
190
+ case "delivered":
191
+ return (
192
+ isNonEmptyString(value.deliveryId) &&
193
+ isNonEmptyString(value.continuationTurnId) &&
194
+ (value.resumeRef === undefined || isNonEmptyString(value.resumeRef))
195
+ );
196
+ case "acknowledged":
197
+ return isNonEmptyString(value.deliveryId) && isNonEmptyString(value.terminalResponseEntryId);
198
+ case "paused":
199
+ return typeof value.reason === "string" && PAUSE_REASONS.has(value.reason);
200
+ case "failed":
201
+ return typeof value.reason === "string" && FAILURE_REASONS.has(value.reason);
202
+ case "resumed":
203
+ return isResumeAuthority(value.authority);
204
+ default:
205
+ return false;
206
+ }
207
+ }
208
+
209
+ /**
210
+ * A transaction record. Beyond the field shapes this enforces the two identity
211
+ * equalities the chain depends on: the envelope's `turnId` is the transition's
212
+ * `entryId`, and a `prepared` event must occupy the reserved prepared identity
213
+ * at sequence 0 with no predecessor.
214
+ */
215
+ export function isHandoffTransactionEntry(value: unknown): value is HandoffTransactionEntry {
216
+ if (!hasEntryEnvelope(value, "handoffTransaction")) return false;
217
+ if (value.schemaVersion !== HANDOFF_SCHEMA_VERSION) return false;
218
+ if (!isHandoffIdentity(value.identity)) return false;
219
+ if (!isTransitionLink(value.transition)) return false;
220
+ if (!isHandoffEvent(value.event)) return false;
221
+ if (value.transition.entryId !== value.turnId) return false;
222
+ if (value.event.phase === "prepared") {
223
+ return (
224
+ value.transition.entryId === value.identity.preparedEntryId &&
225
+ value.transition.prevEntryId === null &&
226
+ value.transition.sequence === 0 &&
227
+ value.transition.attempt === 0
228
+ );
229
+ }
230
+ // Only `prepared` may be the chain root.
231
+ return value.transition.prevEntryId !== null && value.transition.sequence > 0;
232
+ }
233
+
234
+ function isCommitData(value: unknown): value is ContinuityCommitData {
235
+ if (!isRecord(value)) return false;
236
+ if (typeof value.outcome !== "string" || !OUTCOMES.has(value.outcome)) return false;
237
+ if (!isRecord(value.entry)) return false;
238
+ if (!isNonEmptyString(value.entry.turnId)) return false;
239
+ if (!isNullableString(value.entry.parentTurnId)) return false;
240
+ if (!isNonEmptyString(value.entry.timestamp)) return false;
241
+ if (!isTransitionLink(value.transition)) return false;
242
+ if (value.transition.entryId !== value.entry.turnId) return false;
243
+ if (!isNonNegativeInteger(value.tokensBefore) || !isNonNegativeInteger(value.tokensAfter)) return false;
244
+ // The ref an outcome names is required for that outcome and forbidden for
245
+ // the others: an eviction-only commit that also claims a summary ref is not
246
+ // a record this core will treat as valid evidence.
247
+ if (value.outcome === "summarized") {
248
+ return isNonEmptyString(value.summaryRef) && value.evictionRef === undefined;
249
+ }
250
+ if (value.outcome === "evicted") {
251
+ return isNonEmptyString(value.evictionRef) && value.summaryRef === undefined;
252
+ }
253
+ return value.summaryRef === undefined && value.evictionRef === undefined;
254
+ }
255
+
256
+ /**
257
+ * A head must be internally consistent, not merely well-shaped. A phase name is
258
+ * not authority: a `resumed` head has to carry the resume it names, and a
259
+ * `delivered` head the delivery intent it describes, or the payload is claiming
260
+ * a state it does not actually record.
261
+ */
262
+ function isCarriedState(value: unknown): value is ContinuityCarriedState {
263
+ if (!isRecord(value)) return false;
264
+ if (!isTransitionLink(value.transition)) return false;
265
+ if (!isRecord(value.event)) return false;
266
+ if (value.event.phase !== "ready" && !isHandoffEvent(value.event)) return false;
267
+ if (value.event.phase === "ready" && Object.keys(value.event).length !== 1) return false;
268
+ if (value.activeResume !== null) {
269
+ if (!isRecord(value.activeResume)) return false;
270
+ if (!isNonEmptyString(value.activeResume.entryId)) return false;
271
+ if (!isResumeAuthority(value.activeResume.authority)) return false;
272
+ }
273
+ if (value.delivery !== null) {
274
+ if (!isRecord(value.delivery)) return false;
275
+ if (!isNonEmptyString(value.delivery.deliveryId)) return false;
276
+ if (!isNonEmptyString(value.delivery.continuationTurnId)) return false;
277
+ }
278
+ if (value.event.phase === "resumed") {
279
+ const resume = value.activeResume;
280
+ if (!isRecord(resume)) return false;
281
+ if (resume.entryId !== value.transition.entryId) return false;
282
+ if (canonicalJson(resume.authority) !== canonicalJson(value.event.authority)) return false;
283
+ }
284
+ if (value.event.phase === "delivered") {
285
+ const delivery = value.delivery;
286
+ if (!isRecord(delivery)) return false;
287
+ if (delivery.deliveryId !== value.event.deliveryId) return false;
288
+ if (delivery.continuationTurnId !== value.event.continuationTurnId) return false;
289
+ }
290
+ return true;
291
+ }
292
+
293
+ /**
294
+ * The shared payload a commit embeds and a later summary carries.
295
+ *
296
+ * The carried `state` must extend the commit's own chain, so its sequence is
297
+ * required to be at least the commit's. A payload whose head sits before its
298
+ * own commit describes no chain this core can fold.
299
+ */
300
+ export function isContinuityCheckpointPayload(value: unknown): value is ContinuityCheckpointPayload {
301
+ if (!isRecord(value)) return false;
302
+ if (value.schemaVersion !== HANDOFF_SCHEMA_VERSION) return false;
303
+ if (!isHandoffIdentity(value.identity)) return false;
304
+ if (!isAcceptedNote(value.accepted)) return false;
305
+ if (!isHandoffPolicy(value.policy)) return false;
306
+ if (!isCommitData(value.commit)) return false;
307
+ if (!isCarriedState(value.state)) return false;
308
+ if (value.commit.entry.turnId !== value.identity.commitEntryId) return false;
309
+ // A commit exists because a reduction was spent, and no more of them than the
310
+ // policy allows. An attempt of 0 or 99 on a commit describes a history the
311
+ // policy could not have produced, whatever else the payload says.
312
+ if (value.commit.transition.attempt < 1 || value.commit.transition.attempt > value.policy.maxAttempts) return false;
313
+ if (value.state.transition.attempt > value.policy.maxAttempts) return false;
314
+ if (value.state.transition.sequence < value.commit.transition.sequence) return false;
315
+ // `ready` is definitionally the commit's own transition, so a payload
316
+ // claiming ready at some other link is describing a state that never
317
+ // existed. This is what stops a carry from asserting `ready` at an invented
318
+ // sequence the chain never reached.
319
+ if (value.state.event.phase === "ready") {
320
+ return canonicalJson(value.state.transition) === canonicalJson(value.commit.transition);
321
+ }
322
+ // Past the commit, a reduction has necessarily been spent.
323
+ return value.state.transition.attempt >= value.commit.transition.attempt;
324
+ }
325
+
326
+ /**
327
+ * An original commit record. Beyond the shared payload it must be the envelope
328
+ * its payload reserved and must carry the exact original `ready` state: an
329
+ * advanced head belongs on a later summary carry, never on the commit itself.
330
+ */
331
+ export function isContinuityCommitEntry(value: unknown): value is ContinuityCommitEntry {
332
+ if (!hasEntryEnvelope(value, "continuityCommit")) return false;
333
+ if (!isContinuityCheckpointPayload(value.continuity)) return false;
334
+ const payload = value.continuity;
335
+ if (payload.state.event.phase !== "ready") return false;
336
+ if (payload.state.activeResume !== null || payload.state.delivery !== null) return false;
337
+ return (
338
+ value.turnId === payload.commit.entry.turnId &&
339
+ value.parentTurnId === payload.commit.entry.parentTurnId &&
340
+ value.timestamp === payload.commit.entry.timestamp
341
+ );
342
+ }
343
+
344
+ /**
345
+ * A compaction summary carrying continuity. Recognized structurally so 02A adds
346
+ * no field to the live `CompactionSummaryEntry`; a summary without the field is
347
+ * an ordinary summary and not this shape.
348
+ */
349
+ export function isContinuityCarryingSummary(value: unknown): value is ContinuityCarryingSummary {
350
+ if (!hasEntryEnvelope(value, "compactionSummary")) return false;
351
+ return isContinuityCheckpointPayload(value.continuity);
352
+ }
@@ -12,6 +12,13 @@
12
12
  * (interactive/editor-bash.ts).
13
13
  * - fileEntry is consumed by the renderer, compaction, handoff, and
14
14
  * evidence builders but has no in-tree producer.
15
+ * - handoffTransaction and continuityCommit are durable continuity records
16
+ * (CONTRACTS §3), validated by src/domains/session/continuity/validate.ts.
17
+ * Format v5 registers them; packet 03 wires the runtime that writes them.
18
+ *
19
+ * The continuity import is a value import in one direction only: this module
20
+ * calls those validators, and the continuity modules reach back for
21
+ * `BaseSessionEntry` as an erased `import type`, so there is no runtime cycle.
15
22
  */
16
23
 
17
24
  import { createHash } from "node:crypto";
@@ -19,6 +26,17 @@ import { createHash } from "node:crypto";
19
26
  import { isSkillActivation, type PendingSkillToolPolicy, type SkillActivation } from "../../core/skill-activation.js";
20
27
  import type { ClioTurnRecord } from "../../engine/session.js";
21
28
  import { AUTONOMY_EXPOSURES, type AutonomyExposure } from "../safety/autonomy.js";
29
+ import type {
30
+ ContinuityCheckpointPayload,
31
+ ContinuityCommitEntry,
32
+ HandoffTransactionEntry,
33
+ } from "./continuity/contract.js";
34
+ import { HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE, isHandoffRecoveryRequestData } from "./continuity/operator-request.js";
35
+ import {
36
+ isContinuityCheckpointPayload,
37
+ isContinuityCommitEntry,
38
+ isHandoffTransactionEntry,
39
+ } from "./continuity/validate.js";
22
40
 
23
41
  export interface SessionHeader {
24
42
  type: "session";
@@ -288,6 +306,15 @@ export interface CompactionSummaryEntry extends BaseSessionEntry {
288
306
  messagesSummarized?: number;
289
307
  /** True when the cut split a turn (caller may want to render a banner). */
290
308
  isSplitTurn?: boolean;
309
+ /**
310
+ * The latest validated continuity fold this summary carries (CONTRACTS §3).
311
+ *
312
+ * Optional because every summary written before v5, and every ordinary
313
+ * compaction with no agent handoff behind it, has none. A present payload is
314
+ * fully validated by `isSessionEntry`: a summary carrying a malformed
315
+ * checkpoint is a malformed summary, not a summary with a field to ignore.
316
+ */
317
+ continuity?: ContinuityCheckpointPayload;
291
318
  }
292
319
 
293
320
  export interface SessionInfoEntry extends BaseSessionEntry {
@@ -561,7 +588,9 @@ export type SessionEntry =
561
588
  | DecisionLedgerEntry
562
589
  | WorkerRunEntry
563
590
  | ContextEvictionEntry
564
- | ContextRecallEntry;
591
+ | ContextRecallEntry
592
+ | HandoffTransactionEntry
593
+ | ContinuityCommitEntry;
565
594
 
566
595
  export type SessionFileEntry = SessionHeader | SessionEntry;
567
596
 
@@ -589,6 +618,12 @@ export const SESSION_ENTRY_KINDS = [
589
618
  "workerRun",
590
619
  "contextEviction",
591
620
  "contextRecall",
621
+ // Durable continuity (CONTRACTS §3). Both are bookkeeping sidecars: they
622
+ // never become model messages by themselves, never move the message leaf,
623
+ // and never become a cut point. The accepted note reaches the model exactly
624
+ // once, through the continuity replay projection.
625
+ "handoffTransaction",
626
+ "continuityCommit",
592
627
  ] as const;
593
628
 
594
629
  export type SessionEntryKind = (typeof SESSION_ENTRY_KINDS)[number];
@@ -793,7 +828,10 @@ export function isSessionEntry(value: unknown): value is SessionEntry {
793
828
  return (
794
829
  isString(v.customType) &&
795
830
  isOptionalBoolean(v.display) &&
796
- (v.customType !== SKILL_CONTEXT_STATE || isSkillContextState(v.data))
831
+ (v.customType !== SKILL_CONTEXT_STATE || isSkillContextState(v.data)) &&
832
+ // A reserved control subtype is strictly validated, because opaque
833
+ // custom data must never become resume authority by claiming the name.
834
+ (v.customType !== HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE || isHandoffRecoveryRequestData(v.data))
797
835
  );
798
836
  case "modelChange":
799
837
  return isString(v.provider) && isString(v.modelId) && isOptionalString(v.target);
@@ -816,7 +854,8 @@ export function isSessionEntry(value: unknown): value is SessionEntry {
816
854
  isOptionalNumber(v.messagesSummarized) &&
817
855
  isOptionalBoolean(v.isSplitTurn) &&
818
856
  (v.skillContext === undefined || isSkillContextCheckpoint(v.skillContext)) &&
819
- (v.userContext === undefined || isPreservedUserContext(v.userContext))
857
+ (v.userContext === undefined || isPreservedUserContext(v.userContext)) &&
858
+ (v.continuity === undefined || isContinuityCheckpointPayload(v.continuity))
820
859
  );
821
860
  case "sessionInfo":
822
861
  return isOptionalString(v.name) && isOptionalString(v.targetTurnId) && isOptionalString(v.label);
@@ -889,6 +928,14 @@ export function isSessionEntry(value: unknown): value is SessionEntry {
889
928
  isNumber(v.tokensReadmitted) &&
890
929
  isOptionalString(v.toolCallId)
891
930
  );
931
+ // The continuity guards check payload structure and the identity/link
932
+ // equalities the chain depends on, not just the kind string. A record
933
+ // that says `continuityCommit` and carries nothing usable is rejected
934
+ // here rather than surfacing later as an empty-but-present commit.
935
+ case "handoffTransaction":
936
+ return isHandoffTransactionEntry(v);
937
+ case "continuityCommit":
938
+ return isContinuityCommitEntry(v);
892
939
  }
893
940
  return false;
894
941
  }
@@ -19,6 +19,54 @@ export {
19
19
  readAuditRows,
20
20
  readSessionEntriesForId,
21
21
  } from "./archive-readers.js";
22
+ // Durable continuity (CONTRACTS §§2-8). Only the consumer-facing half surfaces
23
+ // here: the record and fold contracts, the reader-side projection, the operator
24
+ // control carrier and the persistence binding. The pure fold, its validators
25
+ // and the group persister stay reachable through their own modules, because the
26
+ // packets that use them are inside this domain or hold an explicit grant.
27
+ export type {
28
+ AcceptedNote,
29
+ ContinuityAuthority,
30
+ ContinuityCarriedState,
31
+ ContinuityCheckpointPayload,
32
+ ContinuityCommitData,
33
+ ContinuityCommitEntry,
34
+ ContinuityFoldResult,
35
+ ContinuityOutcome,
36
+ ContinuityPersistencePorts,
37
+ ContinuityPhase,
38
+ ContinuityRecoveryAction,
39
+ HandoffEvent,
40
+ HandoffIdentity,
41
+ HandoffPolicy,
42
+ HandoffTransactionEntry,
43
+ TransitionLink,
44
+ } from "./continuity/contract.js";
45
+ export type {
46
+ HandoffRecoveryRequestData,
47
+ HandoffRecoveryRequestEntry,
48
+ } from "./continuity/operator-request.js";
49
+ export {
50
+ HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE,
51
+ isHandoffRecoveryRequestData,
52
+ isHandoffRecoveryRequestEntry,
53
+ } from "./continuity/operator-request.js";
54
+ export type { ContinuityPortOrigin, ContinuityPortsInput } from "./continuity/ports.js";
55
+ export { createContinuityPersistencePorts, readContinuityRecordExact } from "./continuity/ports.js";
56
+ export type {
57
+ ContinuityForkBinding,
58
+ ContinuityProjection,
59
+ ContinuityProjectionInput,
60
+ ContinuityRecallNote,
61
+ ProjectedContinuityNote,
62
+ } from "./continuity/projection.js";
63
+ export {
64
+ continuityProjectionTokens,
65
+ continuityReplayBlocks,
66
+ continuityReplayText,
67
+ inheritedContinuityReplayText,
68
+ resolveContinuityProjection,
69
+ } from "./continuity/projection.js";
22
70
  export type {
23
71
  ClioSessionMetaExtension,
24
72
  DeleteSessionOptions,
@@ -8,9 +8,13 @@
8
8
  * whatever that build understood and this one does not. Downgrading and
9
9
  * resuming would then write the truncated reading back over the original.
10
10
  *
11
- * Version 3 to 4 is the one additive step: 4 only adds the working-set kinds
12
- * (`contextEviction`, `contextRecall`), so a version-3 ledger is read as-is
13
- * and the metadata is restamped. Nothing in the file changes.
11
+ * Versions 3, 4 and 5 are one additive chain. 4 added the working-set kinds
12
+ * (`contextEviction`, `contextRecall`); 5 adds the continuity kinds
13
+ * (`handoffTransaction`, `continuityCommit`) and the optional continuity
14
+ * payload on a compaction summary. Each step only makes new records legal, so
15
+ * a version-3 or version-4 ledger is read exactly as it stands and only the
16
+ * metadata is restamped. Nothing in the file changes, and an old ledger simply
17
+ * has no handoff in it.
14
18
  */
15
19
 
16
20
  import { CURRENT_SESSION_FORMAT_VERSION } from "../../../engine/session.js";
@@ -1,5 +1,6 @@
1
1
  import { readSessionFileEntries, type SessionTreeNode, sessionPaths } from "../../../engine/session.js";
2
2
  import { collectSessionEntries } from "../compaction/session-entries.js";
3
+ import { HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE } from "../continuity/operator-request.js";
3
4
  import type { SessionMeta } from "../contract.js";
4
5
  import { isSessionHeader, type SessionEntry } from "../entries.js";
5
6
  import { enrichForkMeta } from "../history.js";
@@ -87,13 +88,26 @@ function treeFromLinearPath(path: ReadonlyArray<LinkedRecord>): SessionTreeNode[
87
88
  * happens to be on the path. Live `/tree`-switch replay does not need this
88
89
  * extra gate (see the comment on the shared function for why), which is the
89
90
  * one place fork and live replay still deliberately disagree.
91
+ *
92
+ * Continuity records need the same gate and for a stronger reason. They anchor
93
+ * to a turn on the path, so the shared verdict would carry a pause, a delivery,
94
+ * an acknowledgement or an operator recovery request written *after* the fork
95
+ * point into a branch that stands before it. That is not just stale display: a
96
+ * fork inheriting a later `resumed` record, or the control request behind it,
97
+ * would inherit the evidence for a state it never reached. §8 requires ancestry
98
+ * **and** file position for both new kinds; the reserved operator control
99
+ * subtype is named explicitly because §3.1 puts it under the same cutoff, and
100
+ * because it is the one `custom` record that is authority rather than display.
90
101
  */
91
102
  function sessionEntryBelongsToPath(
92
103
  entry: SessionEntry,
93
104
  pathIds: ReadonlySet<string>,
94
105
  atOrBeforeForkPoint: boolean,
95
106
  ): boolean {
96
- if (entry.kind === "compactionSummary" && !atOrBeforeForkPoint) return false;
107
+ if (atOrBeforeForkPoint) return entryBelongsToPath(entry, pathIds, atOrBeforeForkPoint);
108
+ if (entry.kind === "compactionSummary") return false;
109
+ if (entry.kind === "handoffTransaction" || entry.kind === "continuityCommit") return false;
110
+ if (entry.kind === "custom" && entry.customType === HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE) return false;
97
111
  return entryBelongsToPath(entry, pathIds, atOrBeforeForkPoint);
98
112
  }
99
113
 
@@ -5,7 +5,7 @@
5
5
  * for each call.
6
6
  *
7
7
  * This lives in the session domain because the ledger is the only durable
8
- * record of what a session spent, and three surfaces read it: the `/cost`
8
+ * record of what a session spent, and three surfaces read it: the `/usage`
9
9
  * overlay and footer reseed from it on every session change, and `clio-coder usage
10
10
  * report` folds it across sessions. It was written for the overlay first and
11
11
  * sat under src/interactive; a headless report reaching into the TUI surface's
@@ -94,7 +94,7 @@ export function ledgerUsageCalls(
94
94
  // A compaction summarizes history through a real model call, billed like
95
95
  // any other. Its usage rides on the compactionSummary entry rather than on
96
96
  // an assistant message, because the summary is context machinery and never
97
- // enters the conversation; folding it here is what puts it on `/cost` and
97
+ // enters the conversation; folding it here is what puts it on `/usage` and
98
98
  // in `clio-coder usage report`.
99
99
  if (entry?.kind === "compactionSummary") {
100
100
  const usage = entry.usage;
@@ -590,7 +590,7 @@ function headlessContext(
590
590
  listAgents: () => unreachable("listAgents"),
591
591
  listDelegationAgents: () => unreachable("listDelegationAgents"),
592
592
  shutdown: () => unreachable("shutdown"),
593
- openCost: () => unreachable("openCost"),
593
+ openUsage: () => unreachable("openUsage"),
594
594
  openSideQuestion: () => unreachable("openSideQuestion"),
595
595
  startHandoff: () => unreachable("startHandoff"),
596
596
  openContextView: () => unreachable("openContextView"),