@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,538 @@
1
+ /**
2
+ * The reader-side adapter: a ledger and its fork bindings in, one execution
3
+ * fold plus separately typed inherited recall out.
4
+ *
5
+ * `foldContinuity` is pure and single-origin. Its applicability check requires
6
+ * `identity.originSessionId === selection.sessionId`, which is exactly right for
7
+ * the session that minted a transaction and exactly wrong for a fork. A fork
8
+ * gets a new session id from `buildMeta` while the seeded entries keep their
9
+ * original ids, parent links and continuity origins, so folding a child's
10
+ * ledger once under the child id drops every inherited note; substituting the
11
+ * parent id loses the child's own transactions; marking the whole child
12
+ * historical permanently disables its later fresh handoffs; and rewriting the
13
+ * persisted origin would invalidate the immutable identity all the evidence
14
+ * hangs off. None of those is acceptable, so this adapter keeps 02A unchanged
15
+ * and partitions around it.
16
+ *
17
+ * What it produces:
18
+ *
19
+ * - **One current-origin fold** over the complete applicable ledger, under the
20
+ * session's real id, which is the only result that can reach `execution`.
21
+ * - **Inherited recall**, one entry per foreign origin, folded under that
22
+ * origin's own unchanged id with `historical: true`. These are recall
23
+ * records, not an alternate recovery result: they carry note and provenance
24
+ * and deliberately expose no action and no reconstruction, so a persistence
25
+ * or continuation caller cannot consume one by mistake.
26
+ *
27
+ * Two boundaries keep inherited recall honest. A foreign origin is eligible
28
+ * only inside the **copied prefix**: the child's ledger up to and including its
29
+ * fork message, which `meta.parentTurnId` names. That is what makes A→B→C work
30
+ * without reading any ancestor's file, because C's prefix physically contains
31
+ * both A's and B's records, and it is what stops a foreign record appended
32
+ * *after* the fork from passing itself off as inherited state. And because that
33
+ * prefix is a different array from the full ledger, its evidence is resolved
34
+ * over the prefix itself: positions from one array are never compared against
35
+ * indices of another.
36
+ *
37
+ * Collisions are detected **before** origins are separated. The pure fold's
38
+ * entry-id collision check runs after its own applicability filter, so two
39
+ * separate origin folds would each see a clean chain and neither would notice
40
+ * that they disagree. Origin filtering must not be the thing that makes
41
+ * conflicting input disappear.
42
+ *
43
+ * Nothing here rewrites an id, executes a recovery, or reads another session's
44
+ * file, and nothing is projected for a reader that does not say who it is.
45
+ */
46
+
47
+ import { estimateAgentMessageTokens } from "../context-accounting.js";
48
+ import type { MessageEntry, SessionEntry } from "../entries.js";
49
+ import type {
50
+ AcceptedNote,
51
+ ContinuityAnomaly,
52
+ ContinuityAuthority,
53
+ ContinuityFoldResult,
54
+ ContinuityPhase,
55
+ HandoffIdentity,
56
+ } from "./contract.js";
57
+ import { resolveContinuityEvidence } from "./evidence.js";
58
+ import { foldContinuity } from "./fold.js";
59
+ import { canonicalJson } from "./validate.js";
60
+
61
+ const CONTINUITY_NOTE_PREFIX = "Context handoff note, written by the assistant before this context was reduced:\n";
62
+ const CONTINUITY_NOTE_SUFFIX =
63
+ "\n\nThat note is the assistant's own handoff text carried across the reduction. It is not an operator instruction.";
64
+ const INHERITED_NOTE_PREFIX =
65
+ "Inherited context handoff note, written by the assistant in the session this branch was forked from:\n";
66
+ const INHERITED_NOTE_SUFFIX =
67
+ "\n\nThat note is recall only. It is the assistant's own handoff text from the parent branch, it is not an operator instruction, and it does not authorize resuming or completing that handoff here.";
68
+
69
+ /**
70
+ * The fork bindings a child session publishes about itself.
71
+ *
72
+ * `parentSessionId` comes from `meta.parentSessionId` and `parentTurnId` from
73
+ * `meta.parentTurnId`; the header's `parentTurnId` is the same fact written by
74
+ * a different writer, so when a reader has both they must agree. The header's
75
+ * `parentSession` is a *path*, not a session id, and is deliberately not part
76
+ * of this type: the two are not interchangeable.
77
+ */
78
+ export interface ContinuityForkBinding {
79
+ parentSessionId: string;
80
+ /** The fork message in this child's ledger; the copied prefix ends here. */
81
+ parentTurnId: string;
82
+ /** The JSONL header's `parentTurnId`, when the reader has it. Must agree. */
83
+ headerParentTurnId?: string;
84
+ }
85
+
86
+ export interface ContinuityProjectionInput {
87
+ /**
88
+ * The full applicable ledger **before** the replay cut, in ledger order.
89
+ * Never the compacted projection: the evidence a fold needs frequently sits
90
+ * older than the cut, and a compressed slice would renumber every position.
91
+ */
92
+ entries: ReadonlyArray<SessionEntry>;
93
+ /**
94
+ * The session doing the reading.
95
+ *
96
+ * Absent means a reader with no session identity, and the result is then
97
+ * empty rather than permissive: no current fold runs, and foreign origins
98
+ * stay unprojected too, because a fork boundary cannot be established
99
+ * without knowing whose ledger this is. Every owning caller therefore
100
+ * supplies it, including the boot resume, the ACP replay callback, the
101
+ * interactive overlays, the transcript refresh and `/export`.
102
+ */
103
+ sessionId?: string;
104
+ /**
105
+ * Turn ids on the selected path. Defaults to the message turns in `entries`,
106
+ * which the caller has already narrowed to the active path.
107
+ */
108
+ pathTurnIds?: ReadonlyArray<string>;
109
+ /** This child's fork bindings, when it is a fork. */
110
+ fork?: ContinuityForkBinding;
111
+ /**
112
+ * True only for a genuine historical cut of the reading session's own
113
+ * transactions, a `/fork` preview standing at an earlier turn.
114
+ *
115
+ * A live `/tree` selection is **not** historical. The renderer passes
116
+ * `uptoTurnId` for both, so deriving this flag from that option would turn
117
+ * every display truncation into a permanent loss of the session's own
118
+ * execution ownership. Callers state it explicitly or leave it false.
119
+ */
120
+ historical?: boolean;
121
+ /** Parse failures the reader already counted, carried through to the fold. */
122
+ unreadableRecords?: number;
123
+ nowMs?: number;
124
+ }
125
+
126
+ export interface ProjectedContinuityNote {
127
+ /** The accepted bytes, exactly as admitted. Never trimmed or regenerated. */
128
+ note: string;
129
+ handoffId: string;
130
+ commitId: string;
131
+ originSessionId: string;
132
+ phase: ContinuityPhase;
133
+ authority: ContinuityAuthority;
134
+ }
135
+
136
+ export type ContinuityRecallProvenance = "inherited" | "inherited_prior";
137
+
138
+ /**
139
+ * One inherited note, published for recall and nothing else.
140
+ *
141
+ * No action, no reconstruction and no carry is exposed on purpose. §8 makes an
142
+ * inherited note recallable in every phase, including `ready`, `delivered` and
143
+ * `acknowledged`, and a caller holding one of these has nothing it could
144
+ * mistake for permission.
145
+ */
146
+ export interface ContinuityRecallNote {
147
+ originSessionId: string;
148
+ handoffId: string;
149
+ commitId: string;
150
+ /** Exact accepted bytes. */
151
+ note: string;
152
+ phase: ContinuityPhase;
153
+ provenance: ContinuityRecallProvenance;
154
+ /** False when that origin's own fold could not validate every reference. */
155
+ validated: boolean;
156
+ }
157
+
158
+ export interface ContinuityProjection {
159
+ /** The reading session's own fold, or null when it has no identity here. */
160
+ current: ContinuityFoldResult | null;
161
+ /** The current transaction's note, when one is projectable. */
162
+ note: ProjectedContinuityNote | null;
163
+ /** Inherited recall, oldest origin first. Never executable. */
164
+ inherited: ReadonlyArray<ContinuityRecallNote>;
165
+ /** Cross-origin collisions, found before the origins were separated. */
166
+ anomalies: ReadonlyArray<ContinuityAnomaly>;
167
+ /** True when a collision or an unresolved fork boundary suppressed authority. */
168
+ authoritySuppressed: boolean;
169
+ /**
170
+ * The ledger turn id of the first record of the projected note's
171
+ * transaction, or null when no note is projected.
172
+ *
173
+ * Accounting needs it: a note introduced before the provider usage anchor is
174
+ * already inside that measurement, and adding it again would charge it twice
175
+ * on every later compaction cycle.
176
+ */
177
+ noteAnchorTurnId: string | null;
178
+ }
179
+
180
+ interface ContinuityRecordFacts {
181
+ entryId: string;
182
+ identity: HandoffIdentity;
183
+ accepted: AcceptedNote | null;
184
+ position: number;
185
+ }
186
+
187
+ /**
188
+ * Identity facts from every continuity-bearing record, in ledger order.
189
+ *
190
+ * Read structurally rather than through the validators, because a conflicting
191
+ * claim has to be visible even when the record carrying it is otherwise
192
+ * malformed. Skipping records that failed validation is how a hostile copy
193
+ * would hide the collision it creates.
194
+ */
195
+ function continuityRecordFacts(entries: ReadonlyArray<SessionEntry>): ContinuityRecordFacts[] {
196
+ const facts: ContinuityRecordFacts[] = [];
197
+ for (let position = 0; position < entries.length; position += 1) {
198
+ const entry = entries[position];
199
+ if (entry === undefined) continue;
200
+ if (entry.kind === "handoffTransaction") {
201
+ facts.push({
202
+ entryId: entry.turnId,
203
+ identity: entry.identity,
204
+ accepted: entry.event.phase === "prepared" ? entry.event.accepted : null,
205
+ position,
206
+ });
207
+ continue;
208
+ }
209
+ const payload =
210
+ entry.kind === "continuityCommit"
211
+ ? entry.continuity
212
+ : entry.kind === "compactionSummary"
213
+ ? entry.continuity
214
+ : undefined;
215
+ if (payload === undefined) continue;
216
+ facts.push({ entryId: entry.turnId, identity: payload.identity, accepted: payload.accepted, position });
217
+ }
218
+ return facts;
219
+ }
220
+
221
+ /**
222
+ * Collisions on the stable identifiers the whole protocol rests on.
223
+ *
224
+ * Two checks, both across every origin: one entry id carrying two different
225
+ * continuity owners or payloads, and one handoff id carrying two different
226
+ * immutable identities or two different accepted notes. Either means the ledger
227
+ * disagrees with itself about what a name refers to, and no origin may act on
228
+ * the disputed name.
229
+ */
230
+ function detectCollisions(facts: ReadonlyArray<ContinuityRecordFacts>): {
231
+ anomalies: ContinuityAnomaly[];
232
+ disputedHandoffs: Set<string>;
233
+ } {
234
+ const anomalies: ContinuityAnomaly[] = [];
235
+ const disputedHandoffs = new Set<string>();
236
+
237
+ const ownerByEntryId = new Map<string, string>();
238
+ for (const fact of facts) {
239
+ const owner = canonicalJson({ handoffId: fact.identity.handoffId, originSessionId: fact.identity.originSessionId });
240
+ const previous = ownerByEntryId.get(fact.entryId);
241
+ if (previous === undefined) {
242
+ ownerByEntryId.set(fact.entryId, owner);
243
+ continue;
244
+ }
245
+ if (previous === owner) continue;
246
+ disputedHandoffs.add(fact.identity.handoffId);
247
+ const prior = facts.find((candidate) => candidate.entryId === fact.entryId);
248
+ if (prior) disputedHandoffs.add(prior.identity.handoffId);
249
+ anomalies.push({
250
+ kind: "entry_id_reused",
251
+ entryId: fact.entryId,
252
+ detail: "one entry id is claimed by two different continuity transactions or origins",
253
+ });
254
+ }
255
+
256
+ const identityByHandoff = new Map<string, string>();
257
+ const noteByHandoff = new Map<string, string>();
258
+ for (const fact of facts) {
259
+ const identity = canonicalJson(fact.identity);
260
+ const previous = identityByHandoff.get(fact.identity.handoffId);
261
+ if (previous === undefined) identityByHandoff.set(fact.identity.handoffId, identity);
262
+ else if (previous !== identity) {
263
+ disputedHandoffs.add(fact.identity.handoffId);
264
+ anomalies.push({
265
+ kind: "duplicate_conflict",
266
+ entryId: fact.entryId,
267
+ detail: `handoff ${fact.identity.handoffId} carries two different immutable identities`,
268
+ });
269
+ }
270
+ if (fact.accepted === null) continue;
271
+ const note = canonicalJson(fact.accepted);
272
+ const priorNote = noteByHandoff.get(fact.identity.handoffId);
273
+ if (priorNote === undefined) noteByHandoff.set(fact.identity.handoffId, note);
274
+ else if (priorNote !== note) {
275
+ // Two different accepted notes under one handoff are never reconciled by
276
+ // taking the newer one. Both texts are retained in the record; neither
277
+ // is authority.
278
+ disputedHandoffs.add(fact.identity.handoffId);
279
+ anomalies.push({
280
+ kind: "duplicate_conflict",
281
+ entryId: fact.entryId,
282
+ detail: `handoff ${fact.identity.handoffId} carries two different accepted notes`,
283
+ });
284
+ }
285
+ }
286
+ return { anomalies, disputedHandoffs };
287
+ }
288
+
289
+ /** The first record of the projected note's transaction, for accounting. */
290
+ function noteAnchor(facts: ReadonlyArray<ContinuityRecordFacts>, note: ProjectedContinuityNote | null): string | null {
291
+ if (note === null) return null;
292
+ for (const fact of facts) {
293
+ if (fact.identity.handoffId === note.handoffId) return fact.entryId;
294
+ }
295
+ return null;
296
+ }
297
+
298
+ function messageTurnIds(entries: ReadonlyArray<SessionEntry>): string[] {
299
+ return entries.filter((entry): entry is MessageEntry => entry.kind === "message").map((entry) => entry.turnId);
300
+ }
301
+
302
+ /**
303
+ * The copied prefix a fork inherited: the child's ledger up to and including
304
+ * its fork message. Null when the boundary cannot be established, which is a
305
+ * conservative refusal rather than a repair: §8 forbids inventing a boundary
306
+ * from the latest handoff or from a summary's `firstKeptTurnId`.
307
+ */
308
+ function inheritedPrefix(
309
+ entries: ReadonlyArray<SessionEntry>,
310
+ fork: ContinuityForkBinding,
311
+ ): { prefix: ReadonlyArray<SessionEntry> } | { problem: string } {
312
+ if (fork.headerParentTurnId !== undefined && fork.headerParentTurnId !== fork.parentTurnId) {
313
+ return { problem: "the session header and metadata disagree about the fork message" };
314
+ }
315
+ const boundary = entries.findIndex((entry) => entry.turnId === fork.parentTurnId);
316
+ if (boundary < 0) return { problem: `the fork message ${fork.parentTurnId} is not present in this ledger` };
317
+ return { prefix: entries.slice(0, boundary + 1) };
318
+ }
319
+
320
+ function recallFromFold(
321
+ fold: ContinuityFoldResult,
322
+ originSessionId: string,
323
+ disputed: ReadonlySet<string>,
324
+ ): ContinuityRecallNote[] {
325
+ const notes: ContinuityRecallNote[] = [];
326
+ if (fold.identity !== null && fold.accepted !== null && !disputed.has(fold.identity.handoffId)) {
327
+ notes.push({
328
+ originSessionId,
329
+ handoffId: fold.identity.handoffId,
330
+ commitId: fold.identity.commitId,
331
+ note: fold.accepted.note,
332
+ phase: fold.phase,
333
+ provenance: "inherited",
334
+ validated: fold.validated,
335
+ });
336
+ }
337
+ for (const prior of fold.priorHandoffs) {
338
+ if (prior.accepted === null || disputed.has(prior.handoffId)) continue;
339
+ notes.push({
340
+ originSessionId,
341
+ handoffId: prior.handoffId,
342
+ commitId: prior.commitId,
343
+ note: prior.accepted.note,
344
+ phase: prior.phase,
345
+ // A prior handoff is a recall projection of an earlier completed cycle,
346
+ // never independently finalized authority.
347
+ provenance: "inherited_prior",
348
+ validated: fold.validated,
349
+ });
350
+ }
351
+ return notes;
352
+ }
353
+
354
+ export function resolveContinuityProjection(input: ContinuityProjectionInput): ContinuityProjection {
355
+ const facts = continuityRecordFacts(input.entries);
356
+ if (facts.length === 0) {
357
+ return {
358
+ current: null,
359
+ note: null,
360
+ inherited: [],
361
+ anomalies: [],
362
+ authoritySuppressed: false,
363
+ noteAnchorTurnId: null,
364
+ };
365
+ }
366
+
367
+ // Collisions first, over every origin, before any partition exists.
368
+ const collisions = detectCollisions(facts);
369
+ const anomalies: ContinuityAnomaly[] = [...collisions.anomalies];
370
+
371
+ const pathTurnIds = input.pathTurnIds ?? messageTurnIds(input.entries);
372
+ const nowMs = input.nowMs ?? Date.now();
373
+
374
+ const current =
375
+ input.sessionId === undefined
376
+ ? null
377
+ : foldContinuity({
378
+ entries: input.entries,
379
+ selection: { sessionId: input.sessionId, pathTurnIds, historical: input.historical ?? false },
380
+ // Evidence for the full ledger, resolved over the full ledger. Every
381
+ // position in it indexes this same array.
382
+ evidence: resolveContinuityEvidence({
383
+ entries: input.entries,
384
+ unreadableRecords: input.unreadableRecords ?? 0,
385
+ }),
386
+ nowMs,
387
+ });
388
+
389
+ const inherited: ContinuityRecallNote[] = [];
390
+ const foreignOrigins = new Set<string>();
391
+ for (const fact of facts) {
392
+ if (fact.identity.originSessionId !== input.sessionId) foreignOrigins.add(fact.identity.originSessionId);
393
+ }
394
+ let boundaryUnresolved = false;
395
+ if (foreignOrigins.size > 0) {
396
+ if (input.fork === undefined) {
397
+ boundaryUnresolved = true;
398
+ anomalies.push({
399
+ kind: "malformed_continuity_record",
400
+ entryId: null,
401
+ detail: `this ledger holds continuity records from ${foreignOrigins.size} other origin session(s) but publishes no fork binding; they are not projected`,
402
+ });
403
+ } else {
404
+ const bounded = inheritedPrefix(input.entries, input.fork);
405
+ if ("problem" in bounded) {
406
+ boundaryUnresolved = true;
407
+ anomalies.push({
408
+ kind: "malformed_continuity_record",
409
+ entryId: null,
410
+ detail: `inherited boundary unresolved: ${bounded.problem}`,
411
+ });
412
+ } else {
413
+ // One array for every inherited origin, with its own evidence. A
414
+ // foreign record appended after the fork message is outside this
415
+ // prefix and is therefore not inherited state, whatever it claims.
416
+ const prefix = bounded.prefix;
417
+ const prefixEvidence = resolveContinuityEvidence({
418
+ entries: prefix,
419
+ unreadableRecords: input.unreadableRecords ?? 0,
420
+ });
421
+ const prefixPath = input.pathTurnIds ?? messageTurnIds(prefix);
422
+ const eligible = [...new Set(continuityRecordFacts(prefix).map((fact) => fact.identity.originSessionId))].filter(
423
+ (origin) => origin !== input.sessionId,
424
+ );
425
+ for (const originSessionId of eligible) {
426
+ const fold = foldContinuity({
427
+ entries: prefix,
428
+ // The origin is the record's own, unchanged. `historical: true` is
429
+ // the honest description of the projection: another branch's
430
+ // transaction, which the fold answers recall-only in every phase.
431
+ selection: { sessionId: originSessionId, pathTurnIds: prefixPath, historical: true },
432
+ evidence: prefixEvidence,
433
+ nowMs,
434
+ });
435
+ inherited.push(...recallFromFold(fold, originSessionId, collisions.disputedHandoffs));
436
+ }
437
+ }
438
+ }
439
+ }
440
+
441
+ // Exact repeats collapse; different text under one handoff never does, because
442
+ // `detectCollisions` already disputed that handoff and dropped both.
443
+ const seen = new Set<string>();
444
+ const deduped = inherited.filter((entry) => {
445
+ const key = canonicalJson({ h: entry.handoffId, c: entry.commitId, o: entry.originSessionId, n: entry.note });
446
+ if (seen.has(key)) return false;
447
+ seen.add(key);
448
+ return true;
449
+ });
450
+
451
+ const authoritySuppressed =
452
+ boundaryUnresolved ||
453
+ (current !== null && current.identity !== null && collisions.disputedHandoffs.has(current.identity.handoffId));
454
+ // Suppression is enforced on the exported fold, not left as an advisory flag
455
+ // beside an otherwise untouched result. A caller holding `current` reaches
456
+ // `continuityPayloadFromFold` and `missingCommitFromCarry` with it, and both
457
+ // gate on `validated`; publishing a `validated: true` fold next to a boolean
458
+ // saying "but not really" would republish evidence this adapter just
459
+ // disputed, and every consumer would have to remember to check. The disputed
460
+ // note stays readable, which is the whole point of recall.
461
+ const published: ContinuityFoldResult | null =
462
+ current === null
463
+ ? null
464
+ : authoritySuppressed
465
+ ? {
466
+ ...current,
467
+ authority: current.authority === "none" ? "none" : "recall_only",
468
+ validated: false,
469
+ action: { kind: "recall_only", reason: "conflicting_evidence" },
470
+ missingCommit: null,
471
+ anomalies: [...current.anomalies, ...anomalies],
472
+ }
473
+ : current;
474
+ const note =
475
+ published === null || published.identity === null || published.accepted === null
476
+ ? null
477
+ : {
478
+ note: published.accepted.note,
479
+ handoffId: published.identity.handoffId,
480
+ commitId: published.identity.commitId,
481
+ originSessionId: published.identity.originSessionId,
482
+ phase: published.phase,
483
+ authority: published.authority as ContinuityAuthority,
484
+ };
485
+ return {
486
+ current: published,
487
+ note,
488
+ inherited: deduped,
489
+ anomalies,
490
+ authoritySuppressed,
491
+ noteAnchorTurnId: noteAnchor(facts, note),
492
+ };
493
+ }
494
+
495
+ /** The exact text replay projects for the current session's own note. */
496
+ export function continuityReplayText(note: ProjectedContinuityNote): string {
497
+ return `${CONTINUITY_NOTE_PREFIX}${note.note}${CONTINUITY_NOTE_SUFFIX}`;
498
+ }
499
+
500
+ /** The exact text replay projects for one inherited note. */
501
+ export function inheritedContinuityReplayText(note: ContinuityRecallNote): string {
502
+ return `${INHERITED_NOTE_PREFIX}${note.note}${INHERITED_NOTE_SUFFIX}`;
503
+ }
504
+
505
+ /**
506
+ * Every block replay projects, in order: inherited recall first, then this
507
+ * session's own note.
508
+ *
509
+ * Each note appears exactly once. Copies of a carried payload add nothing:
510
+ * three summary cycles carrying one transaction still put one note in front of
511
+ * the model. The accepted bytes go in verbatim and never through the replay
512
+ * text cap, because §4 forbids cropping or regenerating accepted text and a
513
+ * truncated handoff note is a corrupted one.
514
+ */
515
+ export function continuityReplayBlocks(projection: ContinuityProjection): string[] {
516
+ const blocks = projection.inherited.map(inheritedContinuityReplayText);
517
+ if (projection.note !== null && projection.note.authority !== "none") {
518
+ blocks.push(continuityReplayText(projection.note));
519
+ }
520
+ return blocks;
521
+ }
522
+
523
+ /**
524
+ * Tokens for exactly the blocks above, priced once each.
525
+ *
526
+ * Each block is emitted as its own user message, so it costs its text plus the
527
+ * per-message structural overhead the shared estimator charges every other
528
+ * replayed message. Pricing the text alone under-counted the projection by that
529
+ * overhead per block. This is the same chars/4 approximation the rest of the
530
+ * accounting uses and is not provider-exact tokenization.
531
+ */
532
+ export function continuityProjectionTokens(projection: ContinuityProjection): number {
533
+ let total = 0;
534
+ for (const block of continuityReplayBlocks(projection)) {
535
+ total += estimateAgentMessageTokens({ role: "user", payload: { text: block } });
536
+ }
537
+ return total;
538
+ }