@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,393 @@
1
+ /**
2
+ * Pure context-pressure policy (shadow mode).
3
+ *
4
+ * One function decides, from explicitly supplied accounting facts, which
5
+ * pressure phase a session is in, whether the next request can be admitted, and
6
+ * whether an advisory should be shown to the agent. It performs no I/O, issues
7
+ * no model call, triggers no reduction, and changes no existing auto-compaction
8
+ * behavior: callers in shadow mode record the verdict and act on nothing.
9
+ *
10
+ * The policy deliberately takes facts rather than a `ContextSnapshot`:
11
+ * - `ContextSnapshot.categories.reserve` is compaction headroom, not
12
+ * necessarily the model's actual output reserve, so the caller must supply
13
+ * the real reserve it will ask the provider for.
14
+ * - `snapshotId` is not a trustworthy live revision after tool-schema or
15
+ * working-set growth, so the caller supplies the revision identity it
16
+ * considers material.
17
+ *
18
+ * The 0.8 default mirrors `DEFAULT_COMPACTION_THRESHOLD`
19
+ * (src/domains/session/compaction/auto.ts:23). It is duplicated rather than
20
+ * imported to keep this module free of session-domain dependencies; the live
21
+ * adapter passes the configured threshold explicitly.
22
+ */
23
+
24
+ export type PressurePhase = "normal" | "notice" | "prepare" | "reduce" | "recover";
25
+
26
+ /** Phases that can produce an agent-visible advisory. */
27
+ export type AdvisoryPhase = "normal" | "notice" | "prepare";
28
+
29
+ export type AdmissionResult = "admit" | "reduce-first" | "unsafe" | "unknown";
30
+
31
+ export type AdmissionReason =
32
+ | "fits"
33
+ | "pressure-at-reduce-threshold"
34
+ | "exceeds-window"
35
+ | "exceeds-window-no-material"
36
+ | "input-unknown"
37
+ | "window-unknown";
38
+
39
+ export type AdvisorySuppression =
40
+ | "not-crossed"
41
+ | "already-announced"
42
+ | "no-useful-cut-unchanged-revision"
43
+ | "measurement-unknown";
44
+
45
+ /** Identity the caller considers material for re-arming. */
46
+ export interface ContextRevision {
47
+ sessionId: string;
48
+ branchAnchorTurnId: string | null;
49
+ /** Changes whenever context content materially changed. */
50
+ revisionId: string;
51
+ }
52
+
53
+ export type ReductionAvailability = "available" | "no-useful-cut" | "unknown";
54
+
55
+ /**
56
+ * Material context identity for advisory re-arming.
57
+ *
58
+ * Ordinary appended content changes `revisionId` on almost every tool call and
59
+ * must stay quiet while the same advisory phase holds. A change here is a
60
+ * different budget basis — another model, target, system prompt, or tool
61
+ * surface — and re-arms the advisory. `epoch` lets a caller declare a material
62
+ * change directly. All fields are optional so an omitted identity keeps the
63
+ * previous behavior.
64
+ */
65
+ export interface AdvisoryContextIdentity {
66
+ targetId?: string | null;
67
+ modelId?: string | null;
68
+ promptFingerprint?: string | null;
69
+ toolSignature?: string | null;
70
+ epoch?: string | number | null;
71
+ }
72
+
73
+ export interface PressureFacts {
74
+ revision: ContextRevision;
75
+ /** Complete prompt-side input. `null` when not measured; never coerced to 0. */
76
+ inputTokens: number | null;
77
+ /**
78
+ * Where `inputTokens` came from. It is a provenance label only: a `trusted`
79
+ * reading does not change any arithmetic here, and `measured: true` never
80
+ * means the figure is provider-exact.
81
+ */
82
+ inputSource: "estimated" | "trusted";
83
+ /** `null` when the effective window is unresolved. */
84
+ effectiveContextWindow: number | null;
85
+ /** Tokens actually reserved for model output on the next request. */
86
+ outputReserveTokens: number;
87
+ /**
88
+ * Incremental tokens **not already counted in `inputTokens`**, priced exactly
89
+ * once by the caller. When the adapter's live input figure already includes
90
+ * pending user input, tool schemas, and injected guidance — as the
91
+ * authoritative reading normally does — this is 0. It exists for a caller
92
+ * that knows of an addition the reading predates.
93
+ */
94
+ pendingRequestOverheadTokens: number;
95
+ reduction: ReductionAvailability;
96
+ contextIdentity?: AdvisoryContextIdentity;
97
+ }
98
+
99
+ export interface PressurePolicyInput {
100
+ reduceThreshold?: number;
101
+ workingSetTarget?: number;
102
+ /** Supply both or neither; omitted advisories are derived from `reduceThreshold`. */
103
+ noticeThreshold?: number;
104
+ prepareThreshold?: number;
105
+ }
106
+
107
+ export interface ResolvedPressurePolicy {
108
+ notice: number;
109
+ prepare: number;
110
+ reduce: number;
111
+ target: number;
112
+ /** True when notice/prepare were derived rather than configured. */
113
+ derived: boolean;
114
+ }
115
+
116
+ export type PolicyRejection =
117
+ | "reduce-threshold-out-of-range"
118
+ | "target-out-of-range"
119
+ | "advisory-thresholds-incomplete"
120
+ | "advisory-thresholds-out-of-order";
121
+
122
+ export type PolicyResolution = { ok: true; policy: ResolvedPressurePolicy } | { ok: false; reason: PolicyRejection };
123
+
124
+ /** Prior advisory state; `INITIAL_ADVISORY_MEMORY` starts a session. */
125
+ export interface AdvisoryMemory {
126
+ announcedPhase: AdvisoryPhase;
127
+ /** Advisory identity the announcement was made against; null before anything is armed. */
128
+ advisoryKey: string | null;
129
+ /**
130
+ * Complete request identity (advisory identity plus revision) for which
131
+ * reduction reported no useful cut. Scoped this tightly so a refusal cannot
132
+ * leak into another session, branch, window, policy, or model.
133
+ */
134
+ noCutKey: string | null;
135
+ }
136
+
137
+ export interface RequestAdmission {
138
+ result: AdmissionResult;
139
+ reason: AdmissionReason;
140
+ /** Complete input plus output reserve, or null when unmeasured. */
141
+ requiredTokens: number | null;
142
+ }
143
+
144
+ export interface AdvisoryDecision {
145
+ emit: boolean;
146
+ phase: AdvisoryPhase;
147
+ suppressedBy: AdvisorySuppression | null;
148
+ }
149
+
150
+ export interface PressureDecision {
151
+ phase: PressurePhase;
152
+ /** `completeInput / window`, or null when unmeasured. */
153
+ pressure: number | null;
154
+ /** `window - (completeInput + outputReserve)`; negative means overflow. */
155
+ headroomTokens: number | null;
156
+ /**
157
+ * True when a usable numeric reading existed. It says nothing about
158
+ * accuracy: an estimated input yields `measured: true` with
159
+ * `inputSource: "estimated"`.
160
+ */
161
+ measured: boolean;
162
+ /** Provenance of the input figure this decision used. */
163
+ inputSource: "estimated" | "trusted";
164
+ completeInputTokens: number | null;
165
+ admission: RequestAdmission;
166
+ advisory: AdvisoryDecision;
167
+ /** Immutable next advisory state; pass back on the following call. */
168
+ memory: AdvisoryMemory;
169
+ policy: ResolvedPressurePolicy;
170
+ }
171
+
172
+ export const DEFAULT_REDUCE_THRESHOLD = 0.8;
173
+ export const DEFAULT_WORKING_SET_TARGET = 0.6;
174
+
175
+ /**
176
+ * Candidate advisory levels are 0.65 and 0.72 against the default 0.8 reduce
177
+ * threshold, expressed as proportions so a configured lower threshold scales
178
+ * deterministically instead of producing an advisory above its own reduce point.
179
+ */
180
+ export const NOTICE_PROPORTION = 0.65 / 0.8;
181
+ export const PREPARE_PROPORTION = 0.72 / 0.8;
182
+
183
+ /** An announcement clears once pressure falls this far below the notice level. */
184
+ export const ADVISORY_RESET_BAND = 0.05;
185
+
186
+ export const INITIAL_ADVISORY_MEMORY: AdvisoryMemory = Object.freeze({
187
+ announcedPhase: "normal" as AdvisoryPhase,
188
+ advisoryKey: null,
189
+ noCutKey: null,
190
+ });
191
+
192
+ const ADVISORY_RANK: Record<AdvisoryPhase, number> = { normal: 0, notice: 1, prepare: 2 };
193
+
194
+ function isPositiveFinite(value: number | null | undefined): value is number {
195
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
196
+ }
197
+
198
+ function isNonNegativeFinite(value: number | null | undefined): value is number {
199
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
200
+ }
201
+
202
+ /**
203
+ * Resolve and validate a policy. Explicit advisory thresholds are rejected when
204
+ * they are incomplete or not strictly ordered; omitted ones are derived from the
205
+ * configured reduce threshold.
206
+ */
207
+ export function resolvePressurePolicy(input: PressurePolicyInput = {}): PolicyResolution {
208
+ const reduce = input.reduceThreshold ?? DEFAULT_REDUCE_THRESHOLD;
209
+ const target = input.workingSetTarget ?? DEFAULT_WORKING_SET_TARGET;
210
+ if (!Number.isFinite(reduce) || reduce <= 0 || reduce > 1) {
211
+ return { ok: false, reason: "reduce-threshold-out-of-range" };
212
+ }
213
+ if (!Number.isFinite(target) || target <= 0 || target >= reduce) {
214
+ return { ok: false, reason: "target-out-of-range" };
215
+ }
216
+
217
+ const hasNotice = input.noticeThreshold !== undefined;
218
+ const hasPrepare = input.prepareThreshold !== undefined;
219
+ if (hasNotice !== hasPrepare) return { ok: false, reason: "advisory-thresholds-incomplete" };
220
+
221
+ const notice = hasNotice ? (input.noticeThreshold as number) : reduce * NOTICE_PROPORTION;
222
+ const prepare = hasPrepare ? (input.prepareThreshold as number) : reduce * PREPARE_PROPORTION;
223
+ if (!Number.isFinite(notice) || !Number.isFinite(prepare)) {
224
+ return { ok: false, reason: "advisory-thresholds-out-of-order" };
225
+ }
226
+ if (!(notice > 0 && notice < prepare && prepare < reduce)) {
227
+ return { ok: false, reason: "advisory-thresholds-out-of-order" };
228
+ }
229
+
230
+ return {
231
+ ok: true,
232
+ policy: Object.freeze({ notice, prepare, reduce, target, derived: !hasNotice }),
233
+ };
234
+ }
235
+
236
+ /**
237
+ * Advisory identity: everything that makes one advisory epoch different from
238
+ * another, excluding the revision. Encoded as a JSON tuple so a separator
239
+ * inside a session or branch id cannot collide with a different split of the
240
+ * same characters.
241
+ */
242
+ function advisoryIdentity(facts: PressureFacts, policy: ResolvedPressurePolicy): string {
243
+ const identity = facts.contextIdentity ?? {};
244
+ return JSON.stringify([
245
+ "advisory/1",
246
+ facts.revision.sessionId,
247
+ facts.revision.branchAnchorTurnId,
248
+ Number.isFinite(facts.effectiveContextWindow) ? facts.effectiveContextWindow : null,
249
+ policy.notice,
250
+ policy.prepare,
251
+ policy.reduce,
252
+ policy.target,
253
+ identity.epoch ?? null,
254
+ identity.targetId ?? null,
255
+ identity.modelId ?? null,
256
+ identity.promptFingerprint ?? null,
257
+ identity.toolSignature ?? null,
258
+ ]);
259
+ }
260
+
261
+ /** Complete request identity: the advisory identity plus the exact revision. */
262
+ function requestIdentity(advisoryKey: string, facts: PressureFacts): string {
263
+ return JSON.stringify(["request/1", advisoryKey, facts.revision.revisionId]);
264
+ }
265
+
266
+ function unmeasuredDecision(
267
+ facts: PressureFacts,
268
+ policy: ResolvedPressurePolicy,
269
+ prior: AdvisoryMemory,
270
+ reason: AdmissionReason,
271
+ ): PressureDecision {
272
+ return Object.freeze({
273
+ phase: "normal" as PressurePhase,
274
+ pressure: null,
275
+ headroomTokens: null,
276
+ measured: false,
277
+ inputSource: facts.inputSource,
278
+ completeInputTokens: null,
279
+ admission: Object.freeze({ result: "unknown" as AdmissionResult, reason, requiredTokens: null }),
280
+ advisory: Object.freeze({
281
+ emit: false,
282
+ phase: "normal" as AdvisoryPhase,
283
+ suppressedBy: "measurement-unknown" as AdvisorySuppression,
284
+ }),
285
+ // An unmeasured turn carries prior memory through untouched. Advancing
286
+ // the advisory key here would consume the re-arm owed to a target, model,
287
+ // window, or policy change first seen without a usable reading.
288
+ memory: Object.freeze({ ...prior }),
289
+ policy,
290
+ });
291
+ }
292
+
293
+ /**
294
+ * Decide the phase, admission, and advisory for one request.
295
+ *
296
+ * Shadow mode: the returned verdict causes nothing on its own.
297
+ *
298
+ * Unknown or malformed measurements are reported as unknown, never as zero or
299
+ * safe. A request whose complete input plus actual output reserve exceeds a
300
+ * known window is `recover`/`unsafe` even when the percentage thresholds were
301
+ * never crossed, which is how a single oversized observation skips every
302
+ * advisory phase.
303
+ */
304
+ export function evaluatePressure(
305
+ facts: PressureFacts,
306
+ policy: ResolvedPressurePolicy,
307
+ prior: AdvisoryMemory = INITIAL_ADVISORY_MEMORY,
308
+ ): PressureDecision {
309
+ if (!isPositiveFinite(facts.effectiveContextWindow)) {
310
+ return unmeasuredDecision(facts, policy, prior, "window-unknown");
311
+ }
312
+ if (facts.inputTokens === null || !Number.isFinite(facts.inputTokens) || facts.inputTokens < 0) {
313
+ return unmeasuredDecision(facts, policy, prior, "input-unknown");
314
+ }
315
+ const overhead = isNonNegativeFinite(facts.pendingRequestOverheadTokens) ? facts.pendingRequestOverheadTokens : null;
316
+ const reserve = isNonNegativeFinite(facts.outputReserveTokens) ? facts.outputReserveTokens : null;
317
+ if (overhead === null || reserve === null) {
318
+ return unmeasuredDecision(facts, policy, prior, "input-unknown");
319
+ }
320
+
321
+ const window = facts.effectiveContextWindow;
322
+ const completeInput = facts.inputTokens + overhead;
323
+ const required = completeInput + reserve;
324
+ const pressure = completeInput / window;
325
+ const headroom = window - required;
326
+
327
+ const advisoryKey = advisoryIdentity(facts, policy);
328
+ const requestKey = requestIdentity(advisoryKey, facts);
329
+ const noCutNow = facts.reduction === "no-useful-cut";
330
+ const noCutCarried = prior.noCutKey !== null && prior.noCutKey === requestKey;
331
+ const suppressedByNoCut = noCutNow || noCutCarried;
332
+
333
+ let phase: PressurePhase;
334
+ let admission: RequestAdmission;
335
+ if (required > window) {
336
+ phase = "recover";
337
+ admission = {
338
+ result: "unsafe",
339
+ reason: suppressedByNoCut ? "exceeds-window-no-material" : "exceeds-window",
340
+ requiredTokens: required,
341
+ };
342
+ } else if (pressure >= policy.reduce) {
343
+ phase = "reduce";
344
+ admission = {
345
+ result: suppressedByNoCut ? "admit" : "reduce-first",
346
+ reason: suppressedByNoCut ? "fits" : "pressure-at-reduce-threshold",
347
+ requiredTokens: required,
348
+ };
349
+ } else {
350
+ phase = pressure >= policy.prepare ? "prepare" : pressure >= policy.notice ? "notice" : "normal";
351
+ admission = { result: "admit", reason: "fits", requiredTokens: required };
352
+ }
353
+
354
+ const advisoryPhase: AdvisoryPhase = phase === "notice" || phase === "prepare" ? phase : "normal";
355
+ const rearmed = prior.advisoryKey !== null && prior.advisoryKey !== advisoryKey;
356
+ const resetLevel = policy.notice * (1 - ADVISORY_RESET_BAND);
357
+ const fellBelowReset = pressure < resetLevel;
358
+ const announcedBefore = rearmed || fellBelowReset ? "normal" : prior.announcedPhase;
359
+
360
+ // Report the reason that actually held back an action. A reduce/recover
361
+ // phase would otherwise ask for a cut, so an unchanged no-cut revision is
362
+ // the operative suppression there even though no advisory phase was armed.
363
+ const actionWouldBeRequested = advisoryPhase !== "normal" || phase === "reduce" || phase === "recover";
364
+ let emit = false;
365
+ let suppressedBy: AdvisorySuppression | null = null;
366
+ if (suppressedByNoCut && actionWouldBeRequested) {
367
+ suppressedBy = "no-useful-cut-unchanged-revision";
368
+ } else if (advisoryPhase === "normal") {
369
+ suppressedBy = "not-crossed";
370
+ } else if (ADVISORY_RANK[advisoryPhase] > ADVISORY_RANK[announcedBefore]) {
371
+ emit = true;
372
+ } else {
373
+ suppressedBy = "already-announced";
374
+ }
375
+
376
+ const nextAnnounced: AdvisoryPhase = emit ? advisoryPhase : announcedBefore;
377
+ // A refusal survives only for the identical request. Any other identity
378
+ // clears it rather than carrying it into a scope it was never measured in.
379
+ const nextNoCutKey = noCutNow || noCutCarried ? requestKey : null;
380
+
381
+ return Object.freeze({
382
+ phase,
383
+ pressure,
384
+ headroomTokens: headroom,
385
+ measured: true,
386
+ inputSource: facts.inputSource,
387
+ completeInputTokens: completeInput,
388
+ admission: Object.freeze(admission),
389
+ advisory: Object.freeze({ emit, phase: advisoryPhase, suppressedBy }),
390
+ memory: Object.freeze({ announcedPhase: nextAnnounced, advisoryKey, noCutKey: nextNoCutKey }),
391
+ policy,
392
+ });
393
+ }
@@ -0,0 +1,15 @@
1
+ /** Hard request admission; pressure thresholds remain a separate preference. */
2
+ export function requestFits(input: number | null, output: number | null, window: number | null): boolean {
3
+ return (
4
+ input !== null &&
5
+ output !== null &&
6
+ window !== null &&
7
+ Number.isFinite(input) &&
8
+ input >= 0 &&
9
+ Number.isFinite(output) &&
10
+ output >= 0 &&
11
+ Number.isFinite(window) &&
12
+ window > 0 &&
13
+ input + output <= window
14
+ );
15
+ }
@@ -1532,6 +1532,30 @@ function renderSessionTranscriptEntry(linked: LinkedSessionEntry, calls: Readonl
1532
1532
  if (entry.kind === "contextRecall") {
1533
1533
  return [`${prefix} contextRecall ref=${entry.ref.entry} trigger=${entry.trigger} tokens=${entry.tokensReadmitted}`];
1534
1534
  }
1535
+ // Continuity provenance, bounded to identity and position in the chain. The
1536
+ // accepted note is deliberately not rendered here: it is agent-authored
1537
+ // handoff text, it is already carried under the existing redaction policy on
1538
+ // the transcript surfaces that do show it, and an evidence bundle needs to
1539
+ // know that a handoff happened and where it stands, not to hold a second copy.
1540
+ if (entry.kind === "handoffTransaction") {
1541
+ const link = entry.transition;
1542
+ const detail =
1543
+ entry.event.phase === "failed" || entry.event.phase === "paused"
1544
+ ? ` reason=${entry.event.reason}`
1545
+ : entry.event.phase === "resumed"
1546
+ ? ` action=${entry.event.authority.action} answers=${entry.event.authority.pausedOrFailedEntryId}`
1547
+ : "";
1548
+ return [
1549
+ `${prefix} handoffTransaction handoff=${entry.identity.handoffId} phase=${entry.event.phase} seq=${link.sequence} attempt=${link.attempt} prev=${link.prevEntryId ?? "none"}${detail}`,
1550
+ ];
1551
+ }
1552
+ if (entry.kind === "continuityCommit") {
1553
+ const commit = entry.continuity.commit;
1554
+ const ref = commit.summaryRef ?? commit.evictionRef ?? "none";
1555
+ return [
1556
+ `${prefix} continuityCommit handoff=${entry.continuity.identity.handoffId} commit=${entry.continuity.identity.commitId} outcome=${commit.outcome} ref=${ref} tokens=${commit.tokensBefore}->${commit.tokensAfter} state=${entry.continuity.state.event.phase}`,
1557
+ ];
1558
+ }
1535
1559
  const _exhaustive: never = entry;
1536
1560
  return [`${prefix} ${String(_exhaustive)}`];
1537
1561
  }
@@ -35,7 +35,7 @@ import {
35
35
  } from "./protocol.js";
36
36
 
37
37
  export const MCP_CLIENT_NAME = "clio-coder";
38
- export const DEFAULT_MCP_CLIENT_VERSION = "0.5.0";
38
+ export const DEFAULT_MCP_CLIENT_VERSION = "0.5.1";
39
39
  export const DEFAULT_INITIALIZE_TIMEOUT_MS = 15_000;
40
40
  export const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
41
41
  export const DEFAULT_STDERR_TAIL_BYTES = 16 * 1024;
@@ -0,0 +1,160 @@
1
+ import { type MemoryRestorationInput, type MemoryRestorationRender, renderMemoryRestoration } from "./restoration.js";
2
+
3
+ export interface MemoryCommitScope {
4
+ readonly sessionId: string;
5
+ readonly branchAnchorTurnId: string | null;
6
+ }
7
+
8
+ /** The host must verify successful durability/current scope before calling the notifier. */
9
+ export interface SuccessfulMemoryContextCommit extends MemoryCommitScope {
10
+ readonly kind: "continuity" | "summary";
11
+ readonly commitId: string;
12
+ readonly outcome: "summarized" | "evicted" | "continuity_only";
13
+ }
14
+
15
+ export interface MemoryContentStamp {
16
+ readonly generation: number;
17
+ readonly commitEpoch: number;
18
+ }
19
+
20
+ export interface MemoryRestorationOffer extends MemoryRestorationRender {
21
+ readonly commitId: string;
22
+ readonly scope: MemoryCommitScope;
23
+ readonly authority: MemoryContentStamp;
24
+ }
25
+
26
+ export type MemoryCommitVerdict = "accepted" | "duplicate" | "conflict" | "stale" | "disposed";
27
+
28
+ export interface PrepareMemoryRestorationInput extends MemoryRestorationInput {
29
+ readonly scope: MemoryCommitScope;
30
+ readonly generation: number;
31
+ }
32
+
33
+ /**
34
+ * In-process content authority only. Owns no bank, usage, queue, IO or model.
35
+ * Retain seen identities for the active scope rather than evicting old IDs and
36
+ * permitting duplicate commits to advance the epoch again. Cancellation revokes
37
+ * offers but keeps pending restoration; navigation explicitly clears it.
38
+ */
39
+ export class MemoryCommitState {
40
+ #scope: MemoryCommitScope;
41
+ #generation: number;
42
+ #commitEpoch = 0;
43
+ #disposed = false;
44
+ #seen = new Map<string, SuccessfulMemoryContextCommit["outcome"]>();
45
+ #pending: string | null = null;
46
+ #issued: MemoryRestorationOffer | null = null;
47
+
48
+ constructor(scope: MemoryCommitScope, generation: number) {
49
+ assertScope(scope);
50
+ assertGeneration(generation);
51
+ this.#scope = copyScope(scope);
52
+ this.#generation = generation;
53
+ }
54
+
55
+ capture(): MemoryContentStamp {
56
+ return Object.freeze({ generation: this.#generation, commitEpoch: this.#commitEpoch });
57
+ }
58
+
59
+ isCurrent(stamp: MemoryContentStamp, scope: MemoryCommitScope): boolean {
60
+ return !this.#disposed && this.#matches(scope, stamp.generation) && stamp.commitEpoch === this.#commitEpoch;
61
+ }
62
+
63
+ notifyContextCommitted(input: SuccessfulMemoryContextCommit, generation: number): MemoryCommitVerdict {
64
+ if (this.#disposed) return "disposed";
65
+ if (!this.#matches(input, generation)) {
66
+ return generation === this.#generation && this.#seen.has(input.commitId) ? "conflict" : "stale";
67
+ }
68
+ if (input.commitId.trim().length === 0) throw new Error("memory commit identity must be nonblank");
69
+ const previous = this.#seen.get(input.commitId);
70
+ if (previous !== undefined) return previous === input.outcome ? "duplicate" : "conflict";
71
+ if (this.#commitEpoch === Number.MAX_SAFE_INTEGER) throw new Error("memory commit epoch exhausted");
72
+ this.#seen.set(input.commitId, input.outcome);
73
+ this.#commitEpoch += 1;
74
+ this.#pending = input.commitId;
75
+ this.#issued = null;
76
+ return "accepted";
77
+ }
78
+
79
+ prepareRestoration(input: PrepareMemoryRestorationInput): MemoryRestorationOffer | null {
80
+ if (this.#disposed || !this.#matches(input.scope, input.generation) || this.#pending === null) return null;
81
+ const rendered = renderMemoryRestoration(input);
82
+ this.#issued = Object.freeze({
83
+ ...rendered,
84
+ commitId: this.#pending,
85
+ scope: this.#scope,
86
+ authority: this.capture(),
87
+ });
88
+ return this.#issued;
89
+ }
90
+
91
+ /** Host calls only after installing the exact offered bytes into its admitted context. */
92
+ acknowledgeRestoration(offer: MemoryRestorationOffer, scope: MemoryCommitScope, generation: number): boolean {
93
+ if (
94
+ this.#disposed ||
95
+ offer !== this.#issued ||
96
+ !this.#matches(scope, generation) ||
97
+ !this.isCurrent(offer.authority, offer.scope)
98
+ )
99
+ return false;
100
+ this.#pending = null;
101
+ this.#issued = null;
102
+ return true;
103
+ }
104
+
105
+ /** Caller supplies a strictly newer generation; no bank reset and no new commit epoch. */
106
+ cancel(generation: number): boolean {
107
+ if (this.#disposed || !validGeneration(generation) || generation <= this.#generation) return false;
108
+ this.#generation = generation;
109
+ this.#issued = null;
110
+ return true;
111
+ }
112
+
113
+ /** Navigation is explicit even when returning to the same session/branch identity. */
114
+ reset(scope: MemoryCommitScope, generation: number): boolean {
115
+ if (this.#disposed || !validGeneration(generation) || generation <= this.#generation) return false;
116
+ assertScope(scope);
117
+ this.#scope = copyScope(scope);
118
+ this.#generation = generation;
119
+ this.#commitEpoch = 0;
120
+ this.#seen.clear();
121
+ this.#pending = null;
122
+ this.#issued = null;
123
+ return true;
124
+ }
125
+
126
+ dispose(): void {
127
+ this.#disposed = true;
128
+ this.#seen.clear();
129
+ this.#pending = null;
130
+ this.#issued = null;
131
+ }
132
+
133
+ #matches(scope: MemoryCommitScope, generation: number): boolean {
134
+ return (
135
+ generation === this.#generation &&
136
+ scope.sessionId === this.#scope.sessionId &&
137
+ scope.branchAnchorTurnId === this.#scope.branchAnchorTurnId
138
+ );
139
+ }
140
+ }
141
+
142
+ function validGeneration(value: number): boolean {
143
+ return Number.isSafeInteger(value) && value >= 0;
144
+ }
145
+
146
+ function assertGeneration(value: number): void {
147
+ if (!validGeneration(value)) throw new Error("memory generation must be a nonnegative safe integer");
148
+ }
149
+
150
+ function assertScope(scope: MemoryCommitScope): void {
151
+ if (
152
+ scope.sessionId.trim().length === 0 ||
153
+ (scope.branchAnchorTurnId !== null && scope.branchAnchorTurnId.trim().length === 0)
154
+ )
155
+ throw new Error("memory scope identities must be nonblank");
156
+ }
157
+
158
+ function copyScope(scope: MemoryCommitScope): MemoryCommitScope {
159
+ return Object.freeze({ sessionId: scope.sessionId, branchAnchorTurnId: scope.branchAnchorTurnId });
160
+ }
@@ -35,11 +35,28 @@ export function selectApprovedMemory(
35
35
  options: MemoryRetrievalOptions,
36
36
  ): MemoryRecord[] {
37
37
  if (options.tokenBudget <= 0) return [];
38
+ const candidates = eligibleMemoryRecords(records, options);
39
+ const selected: MemoryRecord[] = [];
40
+ let spent = 0;
41
+ for (const record of candidates) {
42
+ const cost = estimateMemoryTokens(record);
43
+ if (spent + cost > options.tokenBudget) continue;
44
+ selected.push(record);
45
+ spent += cost;
46
+ }
47
+ return selected;
48
+ }
49
+
50
+ /** Eligibility precedes ranking or budget truncation; output retains legacy priority. */
51
+ export function eligibleMemoryRecords(
52
+ records: ReadonlyArray<MemoryRecord>,
53
+ options: Omit<MemoryRetrievalOptions, "tokenBudget">,
54
+ ): MemoryRecord[] {
38
55
  const allowedScopes = options.scopes === undefined ? null : new Set(options.scopes);
39
56
  const activeRepository = canonicalActiveRepository(options.activeRepository);
40
57
  const activeRuntime = validActiveNamedIdentity(options.activeRuntime, "runtime");
41
58
  const activeAgent = validActiveNamedIdentity(options.activeAgent, "agent");
42
- const candidates = sortMemoryRecords(records)
59
+ return sortMemoryRecords(records)
43
60
  .filter((record) => record.approved)
44
61
  .filter((record) => record.evidenceRefs.length > 0)
45
62
  .filter((record) => record.regressions === undefined || record.regressions.length === 0)
@@ -48,15 +65,6 @@ export function selectApprovedMemory(
48
65
  .filter((record) => runtimeApplies(record, activeRuntime))
49
66
  .filter((record) => agentApplies(record, activeAgent))
50
67
  .sort(compareRetrievalPriority);
51
- const selected: MemoryRecord[] = [];
52
- let spent = 0;
53
- for (const record of candidates) {
54
- const cost = estimateMemoryTokens(record);
55
- if (spent + cost > options.tokenBudget) continue;
56
- selected.push(record);
57
- spent += cost;
58
- }
59
- return selected;
60
68
  }
61
69
 
62
70
  export function estimateMemoryTokens(record: MemoryRecord): number {