@iowarp/clio-coder 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/.claude-plugin/marketplace.json +4 -4
  2. package/CHANGELOG.md +35 -0
  3. package/CONTRIBUTING.md +9 -0
  4. package/README.md +12 -9
  5. package/ROADMAP.md +30 -5
  6. package/dist/{acp-KS7ARGV6.js → acp-F6PGOFZ4.js} +2 -2
  7. package/dist/{agents-YOGJ2YEJ.js → agents-ZHP4R6EX.js} +22 -21
  8. package/dist/assets/codewiki.json +1 -1
  9. package/dist/{auth-CEDCO6O6.js → auth-N75U2H3Z.js} +4 -4
  10. package/dist/{chunk-D3KWFOUT.js → chunk-23UIXH6Z.js} +3 -3
  11. package/dist/{chunk-ZR5YA6ZF.js → chunk-36XVK5P5.js} +5 -5
  12. package/dist/{chunk-AVSR2HKI.js → chunk-3DZ4LPLS.js} +6 -6
  13. package/dist/{chunk-LYJJ6546.js → chunk-3GSPCWFX.js} +12 -12
  14. package/dist/{chunk-CRHPCPZP.js → chunk-4KC4CHKJ.js} +2 -2
  15. package/dist/{chunk-4VDHLCF5.js → chunk-4WJBWKMI.js} +2 -2
  16. package/dist/{chunk-KBUTSJSR.js → chunk-4XBYHXGK.js} +2 -2
  17. package/dist/{chunk-6W75D37F.js → chunk-53P775GE.js} +6 -3
  18. package/dist/{chunk-XZWSP67B.js → chunk-5APYAXCB.js} +2 -2
  19. package/dist/{chunk-D65E25JG.js → chunk-67HPYNTJ.js} +2 -2
  20. package/dist/{chunk-5JQR5B4D.js → chunk-6KV4KCQZ.js} +4 -4
  21. package/dist/{chunk-V4XJWDHR.js → chunk-6UKFRTXI.js} +2 -1
  22. package/dist/{chunk-NDPF2N2L.js → chunk-7DW7CJW2.js} +2 -2
  23. package/dist/{chunk-5KQEUIWM.js → chunk-7LUEQODV.js} +4 -4
  24. package/dist/{chunk-LNOXCGN2.js → chunk-7UFDIOBT.js} +3 -3
  25. package/dist/{chunk-DXSETN72.js → chunk-7WUGNN43.js} +2 -2
  26. package/dist/{chunk-V35KZDUZ.js → chunk-AA3GRZWS.js} +2 -2
  27. package/dist/{chunk-X4CEFOSG.js → chunk-ADO3OQIC.js} +4 -4
  28. package/dist/{chunk-UX2L5CUD.js → chunk-AI36V3ON.js} +3 -3
  29. package/dist/{chunk-B7GY6QER.js → chunk-B26OUMJW.js} +2 -2
  30. package/dist/{chunk-LAR7DX5E.js → chunk-B5TASTCK.js} +153 -62
  31. package/dist/{chunk-TGWZXJ7L.js → chunk-BEMQK5Q7.js} +92 -84
  32. package/dist/{chunk-RNDCROSG.js → chunk-CNXU7TR2.js} +3 -3
  33. package/dist/{chunk-3JLTRPN7.js → chunk-CVMUXT64.js} +35 -66
  34. package/dist/{chunk-733ZBT5P.js → chunk-E74MJF6G.js} +4 -4
  35. package/dist/{chunk-T2MDZES3.js → chunk-EBMTBE76.js} +3 -3
  36. package/dist/{chunk-OZ76TLGU.js → chunk-EEITU7S6.js} +2 -2
  37. package/dist/{chunk-GRHOXDKB.js → chunk-ELDJWJKM.js} +97 -14
  38. package/dist/{chunk-GCQFN7YI.js → chunk-EMYEQLWO.js} +2 -2
  39. package/dist/{chunk-LTWH2ACR.js → chunk-EQPFEDFV.js} +222 -127
  40. package/dist/{chunk-TDGYTWM6.js → chunk-F4I3H56H.js} +2 -2
  41. package/dist/chunk-F6KCJO3U.js +523 -0
  42. package/dist/{chunk-FGSQHUCK.js → chunk-F6VKWTTY.js} +5 -5
  43. package/dist/{chunk-UJD5MPP6.js → chunk-FLNXQQ5B.js} +2 -2
  44. package/dist/{chunk-EX57VVWW.js → chunk-FOSRJ2VZ.js} +4 -4
  45. package/dist/{chunk-ZARZ4POR.js → chunk-G5ZDQQE3.js} +3 -3
  46. package/dist/{chunk-HQVN7G4F.js → chunk-G62JBPXN.js} +7 -7
  47. package/dist/{chunk-ZJPORZTC.js → chunk-GIAI7A2K.js} +2 -2
  48. package/dist/chunk-GPTR6OTU.js +2551 -0
  49. package/dist/{chunk-EZD2BXGP.js → chunk-GUFQPTQK.js} +2 -2
  50. package/dist/{chunk-OPD7GL6F.js → chunk-H6FQVNZC.js} +8 -7
  51. package/dist/chunk-H7REJ7L5.js +80 -0
  52. package/dist/{chunk-EYTLV3W3.js → chunk-HTWTORF7.js} +2 -2
  53. package/dist/{chunk-DF6PI6GN.js → chunk-I6UKQPGS.js} +3 -3
  54. package/dist/{chunk-ISHUS7HC.js → chunk-JRW236JT.js} +3 -3
  55. package/dist/{chunk-DE2LU267.js → chunk-KKAJGT3B.js} +3 -2
  56. package/dist/{chunk-OV62D6KO.js → chunk-KQMYL5GR.js} +3 -3
  57. package/dist/{chunk-QDUUOZ3S.js → chunk-KWMFC7FN.js} +3 -3
  58. package/dist/{chunk-R7FGRZNF.js → chunk-LXYHCCIZ.js} +9 -9
  59. package/dist/{chunk-NFSK2VNU.js → chunk-LZMTVVN4.js} +2 -2
  60. package/dist/{chunk-62UK7AGW.js → chunk-M5PMRZSR.js} +397 -44
  61. package/dist/chunk-MMZMM6SW.js +131 -0
  62. package/dist/{chunk-FFYTC2KQ.js → chunk-MNABNEBX.js} +2 -2
  63. package/dist/{chunk-XMDBTYCH.js → chunk-N3CWOGVF.js} +3 -3
  64. package/dist/{chunk-VYRQHORQ.js → chunk-NMQSO6Z6.js} +4 -3
  65. package/dist/{chunk-R2UUNSNQ.js → chunk-O3ZAF7UY.js} +18 -5
  66. package/dist/{chunk-4SYHGVDE.js → chunk-OQNCLZI4.js} +4 -4
  67. package/dist/{chunk-EG4ARXPC.js → chunk-OX6QTA4N.js} +3 -3
  68. package/dist/{chunk-P4GNZPXO.js → chunk-OYUS3UZP.js} +2 -2
  69. package/dist/{chunk-OQ2EOAHA.js → chunk-PF36KOGI.js} +291 -112
  70. package/dist/{chunk-55RMDDFM.js → chunk-PGQG7ZMW.js} +2 -2
  71. package/dist/{chunk-CBAQPTDU.js → chunk-PIU6BXXW.js} +2 -2
  72. package/dist/{chunk-EKV4UBCM.js → chunk-PZBQUJ2F.js} +2 -2
  73. package/dist/{chunk-7RET77VB.js → chunk-Q2HKY32Y.js} +2 -2
  74. package/dist/{chunk-U6YTUVOX.js → chunk-Q4RMNWMZ.js} +4 -4
  75. package/dist/chunk-Q6FRR3AQ.js +48 -0
  76. package/dist/{chunk-6RJMYYIE.js → chunk-QJSIAJBY.js} +3 -3
  77. package/dist/{chunk-DOUISWEE.js → chunk-QOJ4ERMU.js} +5 -5
  78. package/dist/{chunk-IFPDAD7R.js → chunk-RWSRKXLT.js} +3 -3
  79. package/dist/{chunk-T33IYTZM.js → chunk-SAASVMIY.js} +3 -3
  80. package/dist/{chunk-2DTIWSAW.js → chunk-SEQDH6JC.js} +3 -3
  81. package/dist/{chunk-BRCOSE7O.js → chunk-SZUO5BSE.js} +2 -2
  82. package/dist/{chunk-HOEDA42N.js → chunk-T7HZ7KJF.js} +2 -2
  83. package/dist/{chunk-OJXOA6YU.js → chunk-TL6LNERH.js} +2 -2
  84. package/dist/{chunk-UCINIRHW.js → chunk-TPCZXWLT.js} +3 -3
  85. package/dist/{chunk-ZUKUCZYZ.js → chunk-TQ2KTH4A.js} +2 -2
  86. package/dist/{chunk-4WOOTKFD.js → chunk-U3FETNQB.js} +341 -20
  87. package/dist/{chunk-5B3RTYAK.js → chunk-UGDSB4AN.js} +2 -2
  88. package/dist/{chunk-VKHLUZNO.js → chunk-UZ7YBL43.js} +3 -3
  89. package/dist/{chunk-QNUQ7K7D.js → chunk-VKMSEO7Y.js} +4 -4
  90. package/dist/{chunk-IN7DGBVS.js → chunk-VLX5VZ35.js} +5 -5
  91. package/dist/{chunk-SJGS3GDI.js → chunk-WAGBMMNX.js} +5 -5
  92. package/dist/{chunk-BFOSV5EZ.js → chunk-WX2YCH7F.js} +2 -2
  93. package/dist/{chunk-3LT34CAM.js → chunk-XBIGUILU.js} +32 -17
  94. package/dist/{chunk-XDHUDE5K.js → chunk-XKYBFRWR.js} +5 -5
  95. package/dist/{chunk-U5QU5ZOD.js → chunk-XYPWFSU5.js} +3 -3
  96. package/dist/{chunk-YBUECLAF.js → chunk-YEJQDODA.js} +2 -2
  97. package/dist/{chunk-YQ6XEFVK.js → chunk-Z74OGONW.js} +39 -7
  98. package/dist/{chunk-I4ELN5BX.js → chunk-ZTNOEJRI.js} +2 -2
  99. package/dist/cli/index.js +26 -26
  100. package/dist/{clio-K2PBVCEM.js → clio-4IKFE25F.js} +2 -2
  101. package/dist/{clio-context-tools-GR4OHFSO.js → clio-context-tools-OHJXYX4R.js} +25 -23
  102. package/dist/{code-nav-4X3OI6DK.js → code-nav-XPM3MY5I.js} +7 -7
  103. package/dist/{config-Q5J7UBAB.js → config-343YS25T.js} +43 -41
  104. package/dist/{config-graph-P3555574.js → config-graph-WI263KDX.js} +43 -41
  105. package/dist/{configure-KIJ32ZO6.js → configure-GZDG4HER.js} +21 -21
  106. package/dist/{context-LFBQOSXT.js → context-4OFO7N7Y.js} +12 -12
  107. package/dist/{context-WBH3KSVR.js → context-JLK2RLOG.js} +42 -40
  108. package/dist/{context-NNLBES2Y.js → context-O6DIWCF6.js} +23 -21
  109. package/dist/{context-clear-YOHMQ6SQ.js → context-clear-MR7KOGQI.js} +42 -40
  110. package/dist/{context-working-set-MRAEVDNV.js → context-working-set-YZAD3KWO.js} +14 -13
  111. package/dist/{data-tool-JJPBGMQJ.js → data-tool-EKD7DFHG.js} +7 -7
  112. package/dist/{detail-RHJOTGN4.js → detail-YZPHFEVJ.js} +43 -41
  113. package/dist/{dispatch-runner-NC7PYNIO.js → dispatch-runner-VKVXVTWJ.js} +44 -42
  114. package/dist/{doctor-S5KZ2GTN.js → doctor-53AVQXAO.js} +17 -16
  115. package/dist/{doctor-deep-2EJQSHIZ.js → doctor-deep-BLRB2NCG.js} +5 -5
  116. package/dist/{eval-3D6M36N7.js → eval-P3BQSLQE.js} +21 -20
  117. package/dist/{evidence-XNVPPLVK.js → evidence-6LLI6SZH.js} +44 -42
  118. package/dist/{evidence-6IYN52ET.js → evidence-HTN3JLUO.js} +42 -40
  119. package/dist/{evidence-JVECPI3F.js → evidence-NJRE3R5H.js} +42 -40
  120. package/dist/{evolve-TLPUDJDM.js → evolve-WWZHJVJP.js} +42 -40
  121. package/dist/{fleet-WKVBF5SZ.js → fleet-GGZ4BVJE.js} +64 -62
  122. package/dist/{fleet-6RJWIOEO.js → fleet-QG3IIWWM.js} +44 -42
  123. package/dist/{fleet-commands-L6WFT7KZ.js → fleet-commands-J2ZIKISQ.js} +7 -7
  124. package/dist/{fleet-decisions-BTQMWU5A.js → fleet-decisions-Y2Q7375Q.js} +8 -8
  125. package/dist/{fleet-graph-553DZ67Q.js → fleet-graph-TCBCCCBP.js} +11 -11
  126. package/dist/{fleet-inspect-236FZPRB.js → fleet-inspect-KOLKBC56.js} +44 -42
  127. package/dist/{fleet-preflight-NZQHABWO.js → fleet-preflight-44UNCC4Y.js} +27 -25
  128. package/dist/{fleet-validate-7NLAE26R.js → fleet-validate-K2FXRZVA.js} +13 -13
  129. package/dist/{fleet-verify-E3RO6A5W.js → fleet-verify-AS444WDW.js} +42 -40
  130. package/dist/{fleet-view-KMZQY7YJ.js → fleet-view-X4JU4J6O.js} +43 -41
  131. package/dist/gui/ops-worker.js +9 -9
  132. package/dist/gui/reads-worker.js +9 -8
  133. package/dist/{init-DV5B6HQG.js → init-VSOWFHVL.js} +54 -52
  134. package/dist/{interactive-OXGYZYAS.js → interactive-JRCBENLZ.js} +1733 -577
  135. package/dist/{interop-J7UOBOSF.js → interop-JR6BCY7R.js} +10 -10
  136. package/dist/{inventory-OEGIL6PY.js → inventory-3JUGLVJI.js} +43 -41
  137. package/dist/{library-ROZAMUIL.js → library-7BOH3Y7N.js} +15 -15
  138. package/dist/{library-B3UPGY4V.js → library-7PFONXUI.js} +9 -9
  139. package/dist/{library-MXQA5DLT.js → library-TUYZMFIA.js} +7 -7
  140. package/dist/{library-import-2LTWOVBD.js → library-import-TJVXKLDK.js} +10 -10
  141. package/dist/{library-inventory-LJ3XHLWO.js → library-inventory-MD3AYTP4.js} +9 -9
  142. package/dist/{library-validation-DARHHUNW.js → library-validation-ZDLITEA3.js} +7 -7
  143. package/dist/{mcp-2GSPYQ54.js → mcp-EFCWUPUR.js} +3 -3
  144. package/dist/{memory-J3STUUIK.js → memory-Q2YVOKK5.js} +42 -40
  145. package/dist/{models-Y3GMFJ2V.js → models-M7BOCW7F.js} +17 -16
  146. package/dist/{monitor-LHNH577J.js → monitor-2HD3MC67.js} +47 -45
  147. package/dist/{orchestrator-DTAPXULA.js → orchestrator-OUW3ZN6Y.js} +1894 -226
  148. package/dist/{panes-A743ONZ5.js → panes-TTPPSYCL.js} +3 -3
  149. package/dist/{preload-OJBMAIXF.js → preload-A47C2NUF.js} +42 -40
  150. package/dist/{providers-CMETPZNS.js → providers-T43W7EL4.js} +4 -4
  151. package/dist/{resources-FY6XYFN2.js → resources-TP6X2V6W.js} +20 -11
  152. package/dist/{run-H64MEVTA.js → run-LDSBPMCS.js} +61 -59
  153. package/dist/{share-CAFMMQLM.js → share-URHKKMHR.js} +9 -9
  154. package/dist/{skills-ERKQUC7L.js → skills-E477MOOO.js} +13 -11
  155. package/dist/{skills-eval-CQ23MYQK.js → skills-eval-AIBR2L7H.js} +583 -93
  156. package/dist/{skills-inventory-TVUYJDVN.js → skills-inventory-HLMKY3R5.js} +13 -11
  157. package/dist/{slash-commands-ROG3KIFJ.js → slash-commands-LIIZKA5Y.js} +28 -26
  158. package/dist/{startup-background-KDWCQMWT.js → startup-background-JR2KDK6R.js} +43 -41
  159. package/dist/{steer-KKYXJSH4.js → steer-D7RTAPCM.js} +3 -3
  160. package/dist/{system-ZDW44ZGX.js → system-X2P5N4IY.js} +14 -13
  161. package/dist/{targets-AJWA6E7Z.js → targets-VFFAKUFM.js} +25 -24
  162. package/dist/{tasks-6L2JJRC5.js → tasks-ERAWZA22.js} +6 -6
  163. package/dist/{terminal-lease-YFUSDPAD.js → terminal-lease-V2N2BIKQ.js} +2 -2
  164. package/dist/{trace-LA5MVR4Y.js → trace-MA5BT7XY.js} +3 -3
  165. package/dist/{usage-XSILDHKF.js → usage-WYFY2L6M.js} +50 -48
  166. package/dist/{verifiers-ECA5GIHL.js → verifiers-TYIW2XKF.js} +7 -7
  167. package/dist/{verify-R6QQCCEB.js → verify-242UFYTP.js} +6 -6
  168. package/dist/{web-fetch-PVZNMBOB.js → web-fetch-LRAGYU6Y.js} +3 -3
  169. package/dist/{wiki-generate-RP22WP5K.js → wiki-generate-OFKOX2T6.js} +53 -51
  170. package/dist/worker/entry.js +31 -29
  171. package/docs/architecture/architecture.md +1 -0
  172. package/docs/architecture/context-engine.md +4 -2
  173. package/docs/architecture/observability.md +6 -4
  174. package/docs/architecture/prompt-envelope-and-tools.md +1 -1
  175. package/docs/architecture/session-lifecycle.md +1 -1
  176. package/docs/architecture/tui-design.md +12 -9
  177. package/docs/gui/parity/02-slash-and-surfaces.md +1 -1
  178. package/docs/guide/commands-and-modes.md +91 -5
  179. package/docs/guide/configuration-and-targets.md +2 -2
  180. package/docs/guide/context-continuity.md +49 -0
  181. package/docs/guide/proactive-memory.md +6 -3
  182. package/library/registry.yaml +292 -32
  183. package/library/skills/README.md +27 -2
  184. package/library/skills/context/context-handoff/SKILL.md +10 -4
  185. package/library/skills/meta/clio-coder-dev/SKILL.md +92 -88
  186. package/library/skills/meta/clio-coder-dev/evals.md +51 -46
  187. package/library/skills/meta/clio-coder-dev/plugin.json +2 -2
  188. package/library/skills/meta/clio-coder-dev/references/change-map.md +49 -0
  189. package/library/skills/meta/clio-coder-test/SKILL.md +89 -142
  190. package/library/skills/meta/clio-coder-test/evals.md +58 -62
  191. package/library/skills/meta/clio-coder-test/plugin.json +2 -2
  192. package/library/skills/meta/clio-coder-test/references/harness.md +3 -3
  193. package/library/skills/meta/clio-coder-test/references/lifecycle-validation.md +30 -0
  194. package/library/skills/meta/clio-coder-test/references/test-map.md +60 -89
  195. package/library/skills/registry.yaml +5 -5
  196. package/library/skills/skill-marketplace.json +12 -14
  197. package/package.json +1 -1
  198. package/src/cli/skills-eval.ts +1064 -54
  199. package/src/cli/usage.ts +2 -2
  200. package/src/core/clio-repo.ts +3 -0
  201. package/src/core/tool-names.ts +1 -0
  202. package/src/domains/context/budget/inspection.ts +9 -0
  203. package/src/domains/context/budget/live-view.ts +418 -0
  204. package/src/domains/context/budget/pressure.ts +393 -0
  205. package/src/domains/context/budget/request-fit.ts +15 -0
  206. package/src/domains/evidence/build.ts +24 -0
  207. package/src/domains/gateway/mcp/client.ts +1 -1
  208. package/src/domains/memory/commit-state.ts +160 -0
  209. package/src/domains/memory/operations.ts +18 -10
  210. package/src/domains/memory/prompt-cache.ts +95 -0
  211. package/src/domains/memory/prompt-section.ts +50 -7
  212. package/src/domains/memory/relevance.ts +93 -0
  213. package/src/domains/memory/restoration.ts +78 -0
  214. package/src/domains/memory/store.ts +39 -1
  215. package/src/domains/middleware/memory-intervention.ts +116 -24
  216. package/src/domains/observability/background-memory-usage.ts +1 -1
  217. package/src/domains/observability/cost.ts +3 -3
  218. package/src/domains/observability/extension.ts +1 -1
  219. package/src/domains/observability/metrics.ts +1 -1
  220. package/src/domains/prompts/compiler.ts +1 -1
  221. package/src/domains/prompts/extension.ts +38 -1
  222. package/src/domains/quota/anthropic-max-provider.ts +134 -0
  223. package/src/domains/quota/anthropic-usage.ts +220 -0
  224. package/src/domains/quota/antigravity-provider.ts +339 -0
  225. package/src/domains/quota/cache.ts +87 -0
  226. package/src/domains/quota/claude-code-provider.ts +169 -0
  227. package/src/domains/quota/codex-provider.ts +237 -0
  228. package/src/domains/quota/presentation.ts +240 -0
  229. package/src/domains/quota/registry.ts +23 -0
  230. package/src/domains/quota/service.ts +93 -0
  231. package/src/domains/quota/summary-feed.ts +86 -0
  232. package/src/domains/quota/types.ts +84 -0
  233. package/src/domains/resources/index.ts +11 -0
  234. package/src/domains/resources/skills/catalog-view.ts +571 -0
  235. package/src/domains/resources/skills/lexical-match.ts +136 -0
  236. package/src/domains/resources/skills/loader.ts +38 -0
  237. package/src/domains/resources/skills/promotion.ts +1 -55
  238. package/src/domains/resources/skills/provenance-pin.ts +50 -20
  239. package/src/domains/safety/action-classifier.ts +1 -0
  240. package/src/domains/session/compaction/branch-summary.ts +4 -1
  241. package/src/domains/session/compaction/compact.ts +18 -2
  242. package/src/domains/session/compaction/cut-point.ts +20 -1
  243. package/src/domains/session/compaction/tokens.ts +48 -3
  244. package/src/domains/session/context-accounting.ts +8 -1
  245. package/src/domains/session/continuity/carry.ts +59 -0
  246. package/src/domains/session/continuity/contract.ts +592 -0
  247. package/src/domains/session/continuity/evidence.ts +290 -0
  248. package/src/domains/session/continuity/fold.ts +1075 -0
  249. package/src/domains/session/continuity/note.ts +79 -0
  250. package/src/domains/session/continuity/operator-request.ts +104 -0
  251. package/src/domains/session/continuity/persistence.ts +408 -0
  252. package/src/domains/session/continuity/ports.ts +231 -0
  253. package/src/domains/session/continuity/projection.ts +538 -0
  254. package/src/domains/session/continuity/validate.ts +352 -0
  255. package/src/domains/session/entries.ts +50 -3
  256. package/src/domains/session/index.ts +48 -0
  257. package/src/domains/session/migrations/index.ts +7 -3
  258. package/src/domains/session/tree/fork.ts +15 -1
  259. package/src/domains/session/usage.ts +2 -2
  260. package/src/engine/acp/commands.ts +1 -1
  261. package/src/engine/agent.ts +133 -9
  262. package/src/engine/session.ts +13 -6
  263. package/src/entry/orchestrator.ts +116 -33
  264. package/src/interactive/chat-loop-messages.ts +2 -2
  265. package/src/interactive/chat-loop.ts +219 -63
  266. package/src/interactive/chat-renderer.ts +99 -8
  267. package/src/interactive/context-overlay.ts +24 -3
  268. package/src/interactive/continuity-controller.ts +526 -0
  269. package/src/interactive/dispatch-board.ts +21 -3
  270. package/src/interactive/footer/dashboard.ts +25 -1
  271. package/src/interactive/footer/key-hints.ts +2 -2
  272. package/src/interactive/footer/pages.ts +70 -27
  273. package/src/interactive/footer/widgets.ts +32 -7
  274. package/src/interactive/footer-panel.ts +1 -1
  275. package/src/interactive/interactive-application.ts +7 -2
  276. package/src/interactive/interactive-input-runtime.ts +2 -2
  277. package/src/interactive/interactive-presentation.ts +15 -0
  278. package/src/interactive/interactive-slash-runtime.ts +45 -8
  279. package/src/interactive/interactive-tickers.ts +7 -1
  280. package/src/interactive/model-session-replay.ts +130 -3
  281. package/src/interactive/output-reserve.ts +35 -0
  282. package/src/interactive/overlay-general-openers.ts +12 -8
  283. package/src/interactive/overlay-key-routing.ts +6 -9
  284. package/src/interactive/overlay-lifecycle.ts +10 -6
  285. package/src/interactive/overlay-session-lifecycle.ts +21 -14
  286. package/src/interactive/quota-view.ts +229 -0
  287. package/src/interactive/session-last-turn.ts +1 -1
  288. package/src/interactive/session-transcript.ts +2 -1
  289. package/src/interactive/session-usage-reseed.ts +2 -2
  290. package/src/interactive/side-question.ts +2 -2
  291. package/src/interactive/slash-commands.ts +28 -9
  292. package/src/interactive/turn-context.ts +686 -63
  293. package/src/interactive/turn-middleware.ts +39 -6
  294. package/src/interactive/turn-persistence.ts +15 -2
  295. package/src/interactive/turn-prewarm.ts +1 -1
  296. package/src/interactive/turn-runtime.ts +48 -5
  297. package/src/interactive/{cost-overlay.ts → usage-overlay.ts} +121 -33
  298. package/src/interactive/welcome-dashboard.ts +26 -3
  299. package/src/tools/agent-tools.ts +3 -1
  300. package/src/tools/bootstrap.ts +6 -0
  301. package/src/tools/builtin-tool-catalog.ts +9 -0
  302. package/src/tools/context/index.ts +148 -113
  303. package/src/tools/context/surface.ts +11 -7
  304. package/src/tools/core-bootstrap.ts +4 -0
  305. package/src/tools/observation.ts +8 -2
  306. package/src/tools/policy.ts +3 -0
  307. package/src/tools/self-compact.ts +31 -0
  308. package/src/tools/surface.ts +1 -0
  309. package/src/tools/tasks.ts +1 -1
  310. package/dist/chunk-CTFPFW3H.js +0 -44
  311. package/dist/chunk-EOJPDNUP.js +0 -1325
  312. package/dist/chunk-I4Y4WKZR.js +0 -386
@@ -1,5 +1,6 @@
1
1
  import type { ClioSettings } from "../../core/config.js";
2
2
  import { readClioVersion } from "../../core/package-root.js";
3
+ import type { LiveBudgetView } from "../../domains/context/budget/live-view.js";
3
4
  import type { ContextState } from "../../domains/context/index.js";
4
5
  import type { TaskMemoryOperatorStatus } from "../../domains/memory/index.js";
5
6
  import {
@@ -14,6 +15,7 @@ import {
14
15
  resolveModelCapabilities,
15
16
  resolveModelRuntimeCapabilitiesForProviders,
16
17
  } from "../../domains/providers/index.js";
18
+ import type { UsageSnapshot } from "../../domains/quota/types.js";
17
19
  import type { LocalCapacity } from "../../domains/scheduling/local-capacity.js";
18
20
  import type { ContextUsageSnapshot } from "../../domains/session/context-accounting.js";
19
21
  import type { ContextLedger } from "../../domains/session/context-ledger.js";
@@ -88,6 +90,7 @@ export type { ToolTallySnapshot } from "./widgets.js";
88
90
  export type FooterDashboardMode = "compact" | "expanded";
89
91
 
90
92
  export interface FooterDashboardDeps {
93
+ getQuotaSnapshots?: () => ReadonlyArray<UsageSnapshot>;
91
94
  getConnections?: () => { mcp: string[]; plugins: string[] };
92
95
  getExtensionStatus?: () => ReadonlyArray<string>;
93
96
  providers: ProvidersContract;
@@ -98,7 +101,8 @@ export interface FooterDashboardDeps {
98
101
  getSessionTokens?: () => UsageBreakdown;
99
102
  getTokenThroughput?: () => TokenThroughputSnapshot | null;
100
103
  getSessionCost?: () => CostAggregate;
101
- getContextUsage?: () => ContextUsageSnapshot;
104
+ getContextUsage?: () => ContextUsageSnapshot &
105
+ Partial<Pick<LiveBudgetView, "revision" | "historical" | "inputSource">>;
102
106
  getContextLedger?: () => ContextLedger;
103
107
  getDispatchRows?: () => ReadonlyArray<DispatchBoardRow>;
104
108
  getTaskBoard?: () => TaskBoardSnapshot | null;
@@ -127,6 +131,8 @@ export interface FooterDashboardDeps {
127
131
  }
128
132
 
129
133
  export interface FooterDashboardRenderState {
134
+ quota?: ReadonlyArray<UsageSnapshot>;
135
+ quotaRoute?: Pick<DispatchBoardRow, "runtimeId" | "wireModelId" | "node">;
130
136
  demoHint?: string | null;
131
137
  resources?: LocalMachineMetrics | null;
132
138
  connections?: { mcp: string[]; plugins: string[] };
@@ -413,6 +419,15 @@ export function buildFooterDashboard(deps: FooterDashboardDeps): FooterDashboard
413
419
 
414
420
  return {
415
421
  resources: machine.snapshot(),
422
+ quota: deps.getQuotaSnapshots?.() ?? [],
423
+ ...(current?.runtime && wireModelId
424
+ ? {
425
+ quotaRoute: {
426
+ runtimeId: current.runtime.id,
427
+ wireModelId,
428
+ },
429
+ }
430
+ : {}),
416
431
  ...(deps.getConnections ? { connections: deps.getConnections() } : {}),
417
432
  ...(settings ? { costCeilingUsd: settings.safety.limits.sessionCostUsd } : {}),
418
433
 
@@ -445,6 +460,15 @@ export function buildFooterDashboard(deps: FooterDashboardDeps): FooterDashboard
445
460
  : null,
446
461
  },
447
462
  context: {
463
+ ...(contextUsage?.revision
464
+ ? {
465
+ budget: {
466
+ revision: contextUsage.revision,
467
+ historical: contextUsage.historical ?? true,
468
+ inputSource: contextUsage.inputSource ?? "unknown",
469
+ },
470
+ }
471
+ : {}),
448
472
  label: null,
449
473
  used: contextUsage?.tokens ?? null,
450
474
  contextWindow: contextUsage?.contextWindow ?? null,
@@ -14,8 +14,8 @@ export function footerKeyHint(now: number, narrow = false): string | null {
14
14
  }),
15
15
  ];
16
16
  const count = narrow ? 1 : 2;
17
- const page = Math.floor(now / 12_000) % (Math.ceil(entries.length / count) + 1);
18
- if (page === Math.ceil(entries.length / count)) return null;
17
+ if (!entries.length) return null;
18
+ const page = Math.floor(now / 12_000) % Math.ceil(entries.length / count);
19
19
  const start = page * count;
20
20
  return entries.slice(start, start + count).join(" · ");
21
21
  }
@@ -1,24 +1,31 @@
1
1
  import { costAggregateForAmount, formatCostAggregate } from "../../domains/observability/index.js";
2
+ import type { UsageSnapshot } from "../../domains/quota/types.js";
2
3
  /** Expanded footer pages: dense inspection, separate from transcript output style. */
3
4
  import { sanitizeCallTargetText } from "../../domains/safety/call-target.js";
4
5
  import { redactSecretString } from "../../domains/safety/redaction.js";
5
6
  import { getKeybindings, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../../engine/tui.js";
6
- import { contextCategorySwatch, renderContextMeterBar, renderContextMeterGrid } from "../context-meter.js";
7
+ import { contextCategorySwatch, renderContextMeterGrid } from "../context-meter.js";
7
8
  import { type DispatchBoardRow, dispatchStatusPresentation, renderDispatchActivity } from "../dispatch-board.js";
8
9
  import { formatFooterTokens } from "../footer-panel.js";
10
+ import { renderQuotaAccounts, routeWeeklyQuota } from "../quota-view.js";
9
11
  import { previewRows } from "../renderers/preview.js";
10
12
  import { clioTheme, formatCompactMs, rule } from "../theme/index.js";
11
13
  import { fitIdentityLabel } from "../theme/labels.js";
12
14
  import type { FooterDashboardRenderState } from "./dashboard.js";
13
15
  import { footerKeyHint } from "./key-hints.js";
14
- import { activityQuadrant, contextQuadrant, zipColumns } from "./widgets.js";
16
+ import { activityQuadrant, contextOccupancyBar, contextQuadrant, contextUsageText, zipColumns } from "./widgets.js";
15
17
 
16
18
  export const DASHBOARD_PAGES = ["Activity", "Context", "Status"] as const;
17
19
  export type DashboardPage = (typeof DASHBOARD_PAGES)[number];
18
20
  const ACTIVE_AGENT_STATUSES = new Set(["running", "enqueued", "cancelling", "retrying", "stale"]);
19
21
  const clean = (value: string) => sanitizeCallTargetText(redactSecretString(value));
20
22
 
21
- function agentCard(row: DispatchBoardRow, width: number, compact = false): string[] {
23
+ function agentCard(
24
+ row: DispatchBoardRow,
25
+ width: number,
26
+ compact = false,
27
+ quota: ReadonlyArray<UsageSnapshot> = [],
28
+ ): string[] {
22
29
  const theme = clioTheme();
23
30
  const presentation = dispatchStatusPresentation(row.status, { compact: false });
24
31
  const name = clean(row.agentId).replace(
@@ -34,6 +41,9 @@ function agentCard(row: DispatchBoardRow, width: number, compact = false): strin
34
41
  const field = (label: string, value: string) =>
35
42
  wrapTextWithAnsi(`${theme.fg("dim", `${label} `)}${clean(value)}`, width);
36
43
  lines.push(...previewRows(field("Route", `${row.targetId}/${row.wireModelId}`), 2, width));
44
+ const weekly = routeWeeklyQuota(row, quota);
45
+ if (weekly)
46
+ lines.push(...previewRows(field("Account", `Shared ${weekly.account} · ${weekly.label}`), compact ? 1 : 2, width));
37
47
  if (row.taskSummary)
38
48
  lines.push(...(compact ? previewRows(field("Task", row.taskSummary), 2, width) : field("Task", row.taskSummary)));
39
49
  const activity = renderDispatchActivity(row, width);
@@ -107,8 +117,8 @@ function activityPage(state: FooterDashboardRenderState, width: number, budget:
107
117
  if (first && second) {
108
118
  cards.push(
109
119
  ...zipColumns(
110
- agentCard(first, col, true),
111
- agentCard(second, width - col - 5, true),
120
+ agentCard(first, col, true, state.quota),
121
+ agentCard(second, width - col - 5, true, state.quota),
112
122
  col,
113
123
  width - col - 5,
114
124
  ` ${theme.fg("frame", "│")} `,
@@ -119,7 +129,7 @@ function activityPage(state: FooterDashboardRenderState, width: number, budget:
119
129
  }
120
130
  } else
121
131
  for (const row of active) {
122
- const card = agentCard(row, width, active.length > 1 || cardBudget < 18);
132
+ const card = agentCard(row, width, active.length > 1 || cardBudget < 18, state.quota);
123
133
  if (cards.length + card.length + 2 > cardBudget && shown > 0) break;
124
134
  cards.push(...card, "");
125
135
  shown++;
@@ -173,16 +183,19 @@ function contextPage(state: FooterDashboardRenderState, width: number): string[]
173
183
  ...wrapTextWithAnsi(
174
184
  theme.style(
175
185
  "accent",
176
- `${formatFooterTokens(ledger.usedTokens)} / ${ledger.contextWindow > 0 ? formatFooterTokens(ledger.contextWindow) : "unknown window"} tokens${ledger.percent === null ? "" : ` · ${ledger.percent.toFixed(1)}% occupied`}`,
186
+ state.context.budget
187
+ ? `${contextUsageText(state.context)} tokens`
188
+ : `${formatFooterTokens(ledger.usedTokens)} / ${ledger.contextWindow > 0 ? formatFooterTokens(ledger.contextWindow) : "unknown window"} tokens${ledger.percent === null ? "" : ` · ${ledger.percent.toFixed(1)}% occupied`}`,
177
189
  { bold: true },
178
190
  ),
179
191
  width,
180
192
  ),
181
193
  ...wrapTextWithAnsi(
182
- `Free ${ledger.contextWindow > 0 ? formatFooterTokens(ledger.freeTokens) : "unknown"} · reserved ${formatFooterTokens(ledger.reserveTokens)} · ${ledger.toolCount} tool definitions · compaction ${ledger.compactionAuto ? `auto${ledger.compactionThreshold === null ? "" : ` @${Math.round(ledger.compactionThreshold * 100)}%`}` : "manual"}`,
194
+ `${state.context.budget ? "Capture diagnostics: " : ""}Free ${ledger.contextWindow > 0 ? formatFooterTokens(ledger.freeTokens) : "unknown"} · reserved ${formatFooterTokens(ledger.reserveTokens)} · ${ledger.toolCount} tool definitions · compaction ${ledger.compactionAuto ? `auto${ledger.compactionThreshold === null ? "" : ` @${Math.round(ledger.compactionThreshold * 100)}%`}` : "manual"}`,
183
195
  width,
184
196
  ),
185
197
  ];
198
+ out.push(theme.fg("dim", "Context occupancy is per request; subscription limits are account-wide · /usage"));
186
199
  const gridWidth = Math.max(12, Math.min(64, width >= 76 ? Math.floor(width * 0.38) : width));
187
200
  const gridHeight = width >= 76 ? 8 : 3;
188
201
  const grid = renderContextMeterGrid(ledger, gridWidth, gridHeight, theme);
@@ -195,7 +208,13 @@ function contextPage(state: FooterDashboardRenderState, width: number): string[]
195
208
  legendWidth,
196
209
  ),
197
210
  );
198
- out.push("", rule(theme, width, { left: "CONTEXT COMPOSITION", leftToken: "accent" }));
211
+ out.push(
212
+ "",
213
+ rule(theme, width, {
214
+ left: state.context.budget ? "CAPTURE DIAGNOSTICS" : "CONTEXT COMPOSITION",
215
+ leftToken: "accent",
216
+ }),
217
+ );
199
218
  out.push(...(width >= 76 ? zipColumns(grid, legend, gridWidth, legendWidth, " ") : [...grid, "", ...legend]));
200
219
  out.push(
201
220
  ...wrapTextWithAnsi(
@@ -248,7 +267,7 @@ function contextPage(state: FooterDashboardRenderState, width: number): string[]
248
267
  return out;
249
268
  }
250
269
 
251
- /** Two-line ambient strip. Notices borrow workspace space until dismissed/expired. */
270
+ /** Model and context above; persistent workspace and a rotating hint area below. */
252
271
  export function renderCompactDashboard(state: FooterDashboardRenderState, width: number): string[] {
253
272
  const theme = clioTheme();
254
273
  const w = Math.max(1, width);
@@ -259,11 +278,24 @@ export function renderCompactDashboard(state: FooterDashboardRenderState, width:
259
278
  const activity = `${theme.fg("accent", phase)}${workers ? theme.fg("agent", ` · ${workers} active`) : ""}`;
260
279
  const identity = clean(state.session.target ?? "No model selected");
261
280
  const thinking = theme.fg("reason", `think ${clean(state.session.thinking ?? "off")}`);
262
- const usage = `${formatFooterTokens(ledger?.usedTokens ?? state.context.used ?? 0)} / ${(ledger?.contextWindow ?? state.context.contextWindow) ? formatFooterTokens(ledger?.contextWindow ?? state.context.contextWindow ?? 0) : "?"}`;
263
- const context = `${ledger ? renderContextMeterBar(ledger, w >= 100 ? 14 : 8, theme) : ""} ${usage}`;
281
+ const usage = contextUsageText(state.context);
282
+ const context = `${ledger || state.context.budget ? contextOccupancyBar(state.context, w >= 100 ? 14 : 8, theme) : ""} ${usage}`;
264
283
  const rightWidth = Math.min(Math.floor(w * 0.48), visibleWidth(context));
265
- const identityRoom = Math.max(8, w - rightWidth - visibleWidth(thinking) - visibleWidth(activity) - 10);
266
- const left = `${activity} · ${theme.fg("muted", fitIdentityLabel(identity, identityRoom))} · ${thinking}`;
284
+
285
+ const weekly = state.quotaRoute ? routeWeeklyQuota(state.quotaRoute, state.quota ?? []) : null;
286
+ const leftRoom = Math.max(1, w - rightWidth - 3);
287
+ const activityLabel = fit(activity, Math.min(visibleWidth(activity), Math.max(5, Math.floor(leftRoom / 3))));
288
+ const badge =
289
+ weekly && leftRoom - visibleWidth(activityLabel) - visibleWidth(weekly.label) >= 16
290
+ ? theme.fg(
291
+ weekly.severity === "critical" ? "error" : weekly.severity === "normal" ? "muted" : "warning",
292
+ weekly.label,
293
+ )
294
+ : "";
295
+ const baseRoom = leftRoom - visibleWidth(activityLabel) - (badge ? visibleWidth(badge) + 3 : 0) - 5;
296
+ const showThinking = baseRoom - visibleWidth(thinking) - 3 >= 12;
297
+ const identityRoom = Math.max(1, baseRoom - (showThinking ? visibleWidth(thinking) + 3 : 0));
298
+ const left = `${activityLabel} · ${theme.fg("muted", fitIdentityLabel(identity, identityRoom))}${badge ? ` · ${badge}` : ""}${showThinking ? ` · ${thinking}` : ""}`;
267
299
  const pair = (l: string, r: string, rw: number) => `${fit(l, w - rw - 3)} ${fit(r, rw)}`;
268
300
  const notice = [...state.notices]
269
301
  .filter((n) => n.expiresAt === null || n.expiresAt > state.now)
@@ -283,21 +315,31 @@ export function renderCompactDashboard(state: FooterDashboardRenderState, width:
283
315
  )
284
316
  : state.demoHint
285
317
  ? `${theme.fg("accent", "Tip")} ${theme.fg("muted", clean(state.demoHint))}`
286
- : theme.fg(
287
- "dim",
288
- footerKeyHint(state.now, w < 80) ??
289
- `${clean(state.workspace.cwd)} · ${clean(state.workspace.branch ?? "no Git branch")}${state.workspace.dirty ? " *" : ""}`,
290
- );
291
- const hint = theme.fg("muted", `${state.session.throughput ? `${state.session.throughput} · ` : ""}${key}`);
292
- return [
293
- fit(pair(left, context, rightWidth)),
294
- fit(pair(foot, hint, Math.min(Math.floor(w * 0.4), visibleWidth(hint)))),
295
- ];
318
+ : theme.fg("dim", footerKeyHint(state.now, w < 120) ?? `${key} Dashboard`);
319
+ const hintWidth = Math.min(Math.floor(w * 0.48), visibleWidth(foot));
320
+ const workspaceWidth = Math.max(1, w - hintWidth - 3);
321
+ const git = `${clean(state.workspace.branch ?? "no Git branch")}${state.workspace.dirty ? " *" : ""}`;
322
+ const gitWidth = Math.min(visibleWidth(git), Math.max(4, Math.floor(workspaceWidth * 0.45)));
323
+ const cwdWidth = Math.max(1, workspaceWidth - gitWidth - 3);
324
+ const workspace = theme.fg(
325
+ "dim",
326
+ `${fitIdentityLabel(clean(state.workspace.cwd), cwdWidth)} · ${fitIdentityLabel(git, gitWidth)}`,
327
+ );
328
+ return [fit(pair(left, context, rightWidth)), fit(pair(workspace, foot, hintWidth))];
296
329
  }
297
330
 
298
331
  function statusPage(state: FooterDashboardRenderState, width: number): string[] {
299
332
  const theme = clioTheme();
300
333
 
334
+ const quotaRows = [
335
+ theme.style(
336
+ "accent",
337
+ `SESSION · ${formatFooterTokens(state.sessionTokens?.totalTokens ?? 0)} recorded tokens · ${formatCostAggregate(state.sessionCost) ?? "cost not yet priced"}`,
338
+ { bold: true },
339
+ ),
340
+ ...renderQuotaAccounts(state.quota ?? [], width, { compact: true, now: state.now }),
341
+ ];
342
+
301
343
  const active = state.dispatchRows.filter((row) => ACTIVE_AGENT_STATUSES.has(row.status));
302
344
  const completed = state.dispatchRows.filter((row) => row.status === "completed").length;
303
345
  const failed = state.dispatchRows.filter((row) => ["failed", "dead", "aborted"].includes(row.status)).length;
@@ -334,7 +376,6 @@ function statusPage(state: FooterDashboardRenderState, width: number): string[]
334
376
  ["Target", state.session.target ?? "not selected"],
335
377
  ["Tracked cost", formatCostAggregate(state.sessionCost) ?? "not yet priced"],
336
378
  ["Clio ceiling", state.costCeilingUsd === undefined ? "unknown" : `$${state.costCeilingUsd} · tracked pricing only`],
337
- ["Provider quota", "not reported by provider"],
338
379
  ["MCP connected", names(state.connections?.mcp)],
339
380
  ["Plugins active", names(state.connections?.plugins)],
340
381
  [
@@ -404,6 +445,7 @@ function statusPage(state: FooterDashboardRenderState, width: number): string[]
404
445
  ];
405
446
  if (width < 76)
406
447
  return [
448
+ ...quotaRows,
407
449
  ...section("COST & CONNECTIONS", left, width),
408
450
  ...section("LOCAL MACHINE", right, width),
409
451
  theme.fg("accent", "CONTEXT ENGINE"),
@@ -414,6 +456,7 @@ function statusPage(state: FooterDashboardRenderState, width: number): string[]
414
456
  ];
415
457
  const col = Math.floor((width - 5) / 2);
416
458
  return [
459
+ ...quotaRows,
417
460
  ...zipColumns(
418
461
  section("COST & CONNECTIONS", left, col),
419
462
  section("LOCAL MACHINE", right, width - col - 5),
@@ -457,7 +500,7 @@ export function renderDashboardPage(
457
500
  const hint = truncateToWidth(
458
501
  theme.fg(
459
502
  "muted",
460
- `${cycleKey || "Dashboard"} → ${next} · ${page === "Status" ? "/cost · /mcp · /library" : "composer stays active"}`,
503
+ `${cycleKey || "Dashboard"} → ${next} · ${page === "Status" ? "/usage · /mcp · /library" : "composer stays active"}`,
461
504
  ),
462
505
  safeWidth,
463
506
  "…",
@@ -475,7 +518,7 @@ export function renderDashboardPage(
475
518
  ? `${getKeybindings().getKeys("clio-coder.dispatchBoard.toggle").join("/") || "Fleet Runs"} · /view`
476
519
  : page === "Context"
477
520
  ? "/context"
478
- : "/cost · /context";
521
+ : "/usage · /context";
479
522
  content = [
480
523
  ...content.slice(0, available - 1),
481
524
  truncateToWidth(theme.fg("dim", `More detail: ${detail}`), safeWidth, "…", true),
@@ -1,5 +1,6 @@
1
1
  import type { OutputStyle } from "../../core/defaults.js";
2
2
  import { ToolNames } from "../../core/tool-names.js";
3
+ import type { LiveBudgetView } from "../../domains/context/budget/live-view.js";
3
4
  import {
4
5
  type CostAggregate,
5
6
  formatCostAggregate,
@@ -99,6 +100,8 @@ export interface SessionFacts {
99
100
 
100
101
  /** Context engine telemetry. */
101
102
  export interface ContextEngineFacts {
103
+ /** Identifies the published accounting used by the meter; ledger stays diagnostic. */
104
+ budget?: Pick<LiveBudgetView, "revision" | "historical" | "inputSource">;
102
105
  label: string | null;
103
106
  used: number | null;
104
107
  contextWindow: number | null;
@@ -393,14 +396,36 @@ function contextComposition(context: ContextEngineFacts): {
393
396
  system: finiteNonNegative(breakdown?.systemPromptTokens),
394
397
  tools: finiteNonNegative(breakdown?.toolSchemaTokens),
395
398
  chat: finiteNonNegative(breakdown?.messageTokens) + finiteNonNegative(breakdown?.pendingUserTokens),
396
- free: window > 0 ? Math.max(0, window - used) : null,
399
+ free:
400
+ window > 0 && typeof context.used === "number" && Number.isFinite(context.used) ? Math.max(0, window - used) : null,
397
401
  };
398
402
  }
399
403
 
404
+ /** Short truthful occupancy label shared by compact and expanded surfaces. */
405
+ export function contextUsageText(context: ContextEngineFacts): string {
406
+ const used = context.budget ? context.used : (context.ledger?.usedTokens ?? context.used);
407
+ const window = context.budget ? context.contextWindow : (context.ledger?.contextWindow ?? context.contextWindow);
408
+ const source = context.budget?.inputSource === "historical" ? "saved " : context.budget && used !== null ? "~" : "";
409
+ return `${source}${used === null ? "?" : formatFooterTokens(used)} / ${window ? formatFooterTokens(window) : "?"}`;
410
+ }
411
+
412
+ /** Reads published numbers only; never refreshes accounting from a renderer. */
413
+ export function contextOccupancyBar(context: ContextEngineFacts, cells: number, theme: ClioTheme): string {
414
+ if (!context.budget && context.ledger) return renderContextMeterBar(context.ledger, cells, theme);
415
+ return buildSegmentedContextBar(
416
+ theme,
417
+ cells,
418
+ context.contextWindow ?? 0,
419
+ context.used === null ? undefined : contextBreakdownForBar(context),
420
+ );
421
+ }
422
+
400
423
  /** The live window after the percent; null when unknown or the row is too narrow to carry it. */
401
424
  export function compactContextWindowLabel(context: ContextEngineFacts, width: number): string | null {
402
425
  if (width < 72) return null;
403
- const window = finiteNonNegative(context.ledger?.contextWindow ?? context.contextWindow);
426
+ const window = finiteNonNegative(
427
+ context.budget ? context.contextWindow : (context.ledger?.contextWindow ?? context.contextWindow),
428
+ );
404
429
  return window > 0 ? formatFooterTokens(window) : null;
405
430
  }
406
431
 
@@ -427,7 +452,7 @@ export function compactSecondaryLine(
427
452
  const safeWidth = Math.max(1, Math.floor(width));
428
453
  const barCells = compactContextBarWidth(safeWidth);
429
454
  let left = "";
430
- if (context.ledger) {
455
+ if (context.ledger && !context.budget) {
431
456
  // The row follows the kv grammar: dim key, muted value. An unmeasured
432
457
  // percent renders the ?% placeholder dim because it is scaffolding for a
433
458
  // number that has not arrived, not a measurement.
@@ -437,7 +462,7 @@ export function compactSecondaryLine(
437
462
  );
438
463
  left = `${theme.fg("dim", "ctx")} ${renderContextMeterBar(context.ledger, barCells, theme)} ${percent}`;
439
464
  } else {
440
- left = buildSegmentedContextBar(theme, barCells, context.contextWindow ?? 0, contextBreakdownForBar(context));
465
+ left = `${context.budget?.inputSource === "historical" ? theme.fg("dim", "saved ") : ""}${contextOccupancyBar(context, barCells, theme)}`;
441
466
  }
442
467
  // A percentage alone does not say whether the window is 128k or 1M, so at
443
468
  // widths that can afford it the live window follows the percent. It comes
@@ -713,7 +738,7 @@ function ledgerBar(theme: ClioTheme, ledger: ContextLedger, cells: number): stri
713
738
  export function contextQuadrant(facts: ContextEngineFacts, options: ExpandedQuadrantOptions = {}): string[] {
714
739
  const theme = clioTheme();
715
740
  const ledger = facts.ledger ?? null;
716
- const hasLedger = ledger !== null && ledger.contextWindow > 0;
741
+ const hasLedger = !facts.budget && ledger !== null && ledger.contextWindow > 0;
717
742
  const barCells = expandedContextBarCells(options.width);
718
743
 
719
744
  let bar: string;
@@ -737,7 +762,7 @@ export function contextQuadrant(facts: ContextEngineFacts, options: ExpandedQuad
737
762
  ? theme.style("frame", `free ${formatFooterTokens(composition.free)}`, { dim: true })
738
763
  : null,
739
764
  ]);
740
- bar = buildSegmentedContextBar(theme, barCells, facts.contextWindow ?? 0, contextBreakdownForBar(facts));
765
+ bar = contextOccupancyBar(facts, barCells, theme);
741
766
  const filledChar = visibleWidth(GLYPH.contextFull) === 1 ? GLYPH.contextFull : GLYPH.barFull;
742
767
  const freeChar = visibleWidth(GLYPH.contextFree) === 1 ? GLYPH.contextFree : GLYPH.barEmpty;
743
768
  legendRows = [
@@ -752,7 +777,7 @@ export function contextQuadrant(facts: ContextEngineFacts, options: ExpandedQuad
752
777
  "Context",
753
778
  [
754
779
  statusRow(bar),
755
- kv("used", formatUsedWindow(usedTokens, windowTokens)),
780
+ kv("used", facts.budget ? contextUsageText(facts) : formatUsedWindow(usedTokens, windowTokens)),
756
781
  fill ? styledKv("fill", fill) : statusRow(null),
757
782
  chatFree ? styledKv("budget", chatFree) : statusRow(null),
758
783
  kv("compact", formatCompaction(facts)),
@@ -30,7 +30,7 @@ export function formatFooterTokens(n: number): string {
30
30
  * Build the token-counter footer segment. Returns `null` when no usage has
31
31
  * landed yet so the footer stays uncluttered at session start. Cache-read
32
32
  * tokens are omitted here to keep the line scannable; reasoning tokens are
33
- * shown only when the provider exposes them. The `/cost` overlay exposes the
33
+ * shown only when the provider exposes them. The `/usage` overlay exposes the
34
34
  * full breakdown.
35
35
  */
36
36
  export function tokensSegment(usage: UsageBreakdown | null | undefined): string | null {
@@ -252,6 +252,7 @@ export interface InteractiveDeps {
252
252
  * engine, and persists a compactionSummary entry.
253
253
  */
254
254
  onCompact?: (instructions: string | undefined) => Promise<void>;
255
+ onRecoverHandoff?: (handoffId: string, action: "reduce" | "deliver") => Promise<void>;
255
256
  /** Run /context init for the current working directory. */
256
257
  onInit?: (options: InitCommandOptions, io?: RunIo) => Promise<void>;
257
258
  /** Run /context reset for the current working directory. */
@@ -827,6 +828,7 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
827
828
  ...(deps.onSelectModel ? { onSelectModel: deps.onSelectModel } : {}),
828
829
  ...(deps.onSetThinkingLevel ? { onSetThinkingLevel: deps.onSetThinkingLevel } : {}),
829
830
  ...(deps.onCompact ? { onCompact: deps.onCompact } : {}),
831
+ ...(deps.onRecoverHandoff ? { onRecoverHandoff: deps.onRecoverHandoff } : {}),
830
832
  ...(deps.onInit ? { onInit: deps.onInit } : {}),
831
833
  ...(deps.onContextClear ? { onContextClear: deps.onContextClear } : {}),
832
834
  ...(deps.onContextRefresh ? { onContextRefresh: deps.onContextRefresh } : {}),
@@ -840,7 +842,7 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
840
842
  expandSubmit: (text) => expandInteractiveSubmitAsync(text, deps.resources),
841
843
  openAskUser: (questions, options) => openAskUserOverlayState(questions, options),
842
844
  openSkillsHub: (request) => openSkillsHubState(request),
843
- openCost: () => openCostOverlayState(),
845
+ openUsage: () => openUsageOverlayState(),
844
846
  openSideQuestion: (question) => openSideQuestionOverlayState(question),
845
847
  startHandoff: (goal) => startHandoffState(goal),
846
848
  startFleetRun: (name, vars) => startFleetRunState(name, vars),
@@ -901,6 +903,7 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
901
903
  editor.onSubmit = editorSubmit.submitEditorText;
902
904
 
903
905
  const interactiveTickers = createInteractiveTickers({
906
+ getQuotaSnapshots: presentation.getQuotaSnapshots,
904
907
  tui,
905
908
  dispatchBoardStore,
906
909
  contextActivityStore,
@@ -937,6 +940,8 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
937
940
  resetTranscript();
938
941
  };
939
942
  overlayLifecycle = createOverlayLifecycle({
943
+ getQuotaSnapshots: presentation.getQuotaSnapshots,
944
+ getDispatchRows: () => dispatchBoardStore.rows(),
940
945
  app: deps,
941
946
  tui,
942
947
  footer,
@@ -972,7 +977,7 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
972
977
  const {
973
978
  closeOverlay,
974
979
  openAskUserOverlayState,
975
- openCostOverlayState,
980
+ openUsageOverlayState,
976
981
  openSideQuestionOverlayState,
977
982
  startHandoffState,
978
983
  startFleetRunState,
@@ -326,7 +326,7 @@ export function createInteractiveInputRuntime(deps: InteractiveInputRuntimeDeps)
326
326
  },
327
327
  cancelFocusedOwner: () => {
328
328
  const state = deps.overlay.getState();
329
- if (["permission-confirm", "cost", "context-view", "side-question", "dispatch-board", "auth"].includes(state))
329
+ if (["permission-confirm", "usage", "context-view", "side-question", "dispatch-board", "auth"].includes(state))
330
330
  deps.overlay.closeOverlay();
331
331
  else if (focused()?.handleInput) {
332
332
  focused()?.handleInput?.("\x1b");
@@ -531,7 +531,7 @@ function overlayScopeLabel(state: OverlayState): string {
531
531
  "permission-confirm": "Permission",
532
532
  "dispatch-board": "Workers",
533
533
  auth: "Authentication",
534
- cost: "Usage",
534
+ usage: "Usage",
535
535
  "context-view": "Context",
536
536
  "context-reset": "Context reset",
537
537
  tasks: "Tasks",
@@ -13,6 +13,8 @@ import type {
13
13
  TokenThroughputSnapshot,
14
14
  } from "../domains/observability/index.js";
15
15
  import { type ProvidersContract, resolveModelRuntimeCapabilitiesForProviders } from "../domains/providers/index.js";
16
+ import { createQuotaSummaryFeed } from "../domains/quota/summary-feed.js";
17
+ import type { UsageSnapshot } from "../domains/quota/types.js";
16
18
  import type { ResourcesContract } from "../domains/resources/index.js";
17
19
  import type { LocalCapacity } from "../domains/scheduling/local-capacity.js";
18
20
  import { ceilChars, contentChars } from "../domains/session/context-accounting.js";
@@ -160,6 +162,7 @@ export interface InteractivePresentation {
160
162
  changeOutputStyle(mutation: () => void): void;
161
163
  setLocalBashRunning(running: boolean): void;
162
164
  getObservabilitySnapshot(): ObservabilitySnapshot;
165
+ getQuotaSnapshots(): ReadonlyArray<UsageSnapshot>;
163
166
  /** Fold one raw chat event into the ephemeral throughput shown only while this turn is active. */
164
167
  recordChatEvent(event: ChatLoopEvent): void;
165
168
  recordToolStart(toolCallId: string, toolName: string): void;
@@ -234,6 +237,12 @@ export function createInteractivePresentation(deps: InteractivePresentationDeps)
234
237
  return key === undefined || key.length === 0 ? null : formatKeyLabel(key, "");
235
238
  };
236
239
 
240
+ const quotaSummary = createQuotaSummaryFeed({
241
+ onUpdate: () => {
242
+ footer?.refresh();
243
+ requestRender();
244
+ },
245
+ });
237
246
  const banner = factories.createBanner({
238
247
  providers: deps.providers,
239
248
  ...(deps.agents ? { getAgentCount: () => deps.agents?.listSpecs().filter(isUserVisibleAgent).length ?? 0 } : {}),
@@ -248,6 +257,9 @@ export function createInteractivePresentation(deps: InteractivePresentationDeps)
248
257
  return key ? formatKeyLabel(key, "") : null;
249
258
  },
250
259
  onFactsRefreshed: requestRender,
260
+ // Subscription usage is read lazily off the render path and cached, so the
261
+ // banner reads a string and never awaits a provider.
262
+ getQuotaSummary: () => quotaSummary.peek(),
251
263
  ...(deps.getSettings ? { getSettings: deps.getSettings } : {}),
252
264
  });
253
265
  // A boot-time `--continue`/`--session` resume opens onto an existing
@@ -391,6 +403,7 @@ export function createInteractivePresentation(deps: InteractivePresentationDeps)
391
403
  getTokenThroughput: () =>
392
404
  liveThroughput === null ? observabilitySnapshot.session.latestThroughput : currentLiveThroughput(),
393
405
  getSessionCost: () => observabilitySnapshot.session.cost,
406
+ getQuotaSnapshots: () => quotaSummary.peekSnapshots(),
394
407
  getContextUsage: () => deps.chat.contextUsage(),
395
408
  getContextLedger: () => deps.chat.contextLedger(),
396
409
  getDispatchRows: () => dispatchBoardStore.rows(),
@@ -622,6 +635,7 @@ export function createInteractivePresentation(deps: InteractivePresentationDeps)
622
635
  beforeStatusDisposed = true;
623
636
  // Before the subscriptions go: a project-context refresh already in flight
624
637
  // must not land afterwards and ask a torn-down presentation for a frame.
638
+ quotaSummary.dispose();
625
639
  banner.dispose();
626
640
  footer.dispose();
627
641
  unsubscribeObservability();
@@ -664,6 +678,7 @@ export function createInteractivePresentation(deps: InteractivePresentationDeps)
664
678
  changeOutputStyle: (mutation) =>
665
679
  chatRenderer.mutate(() => preserveTranscriptScroll(transcriptView, deps.terminal.columns, mutation), "output-style"),
666
680
  getObservabilitySnapshot: () => observabilitySnapshot,
681
+ getQuotaSnapshots: () => quotaSummary.peekSnapshots(),
667
682
  recordChatEvent,
668
683
  recordToolStart: (toolCallId, toolName) => {
669
684
  footerActiveTools.add(toolCallId);
@@ -36,6 +36,7 @@ import { appendNotice, appendOperatorAside, appendOperatorCommand, appendReferen
36
36
  import { runOperatorRecall } from "./context-recall-command.js";
37
37
  import { renderSessionHtml } from "./export-html/index.js";
38
38
  import { dateLocal } from "./format-time.js";
39
+ import { withContinuityReplay } from "./model-session-replay.js";
39
40
  import type { OracleDigestSources } from "./oracle.js";
40
41
  import type { PendingModelScope } from "./overlays/model-scope.js";
41
42
  import { renderDoctorReport } from "./renderers/doctor-report.js";
@@ -57,6 +58,15 @@ import type { WorkerEntryState } from "./worker-stream.js";
57
58
 
58
59
  const EXPORT_RENDER_WIDTH = 100;
59
60
 
61
+ /** Longest run of consecutive backticks in the rendered transcript, for fence sizing. */
62
+ function longestBacktickRun(lines: ReadonlyArray<string>): number {
63
+ let longest = 0;
64
+ for (const line of lines) {
65
+ for (const run of line.match(/`+/gu) ?? []) longest = Math.max(longest, run.length);
66
+ }
67
+ return longest;
68
+ }
69
+
60
70
  export interface InteractiveSlashSubmitExpansion {
61
71
  text: string;
62
72
  images: ImageContent[];
@@ -104,6 +114,7 @@ export interface InteractiveSlashRuntimeDeps {
104
114
  onSelectModel?: (ref: { target: string; model: string }, scope: "session" | "global") => void;
105
115
  onSetThinkingLevel?: (level: ThinkingLevel, scope?: "session" | "global") => void;
106
116
  onCompact?: (instructions: string | undefined) => Promise<void>;
117
+ onRecoverHandoff?: (handoffId: string, action: "reduce" | "deliver") => Promise<void>;
107
118
  onInit?: (options: InitCommandOptions, io?: RunIo) => Promise<void>;
108
119
  onContextClear?: (options: ContextClearCommandOptions, io?: RunIo) => Promise<void>;
109
120
  onContextRefresh?: (io?: RunIo) => Promise<void>;
@@ -127,7 +138,7 @@ export interface InteractiveSlashRuntimeDeps {
127
138
  expandSubmit: (text: string) => Promise<InteractiveSlashSubmitExpansion>;
128
139
  openAskUser: AskUserHandler;
129
140
  openSkillsHub: (request?: LibraryBrowseRequest) => void;
130
- openCost: () => void;
141
+ openUsage: () => void;
131
142
  /** Run one `/btw` side question in its own overlay. */
132
143
  openSideQuestion: (question: string) => void;
133
144
  /** Run one `/handoff <goal>`: extract, review, and seed a successor session. */
@@ -461,7 +472,7 @@ export function createInteractiveSlashRuntime(deps: InteractiveSlashRuntimeDeps)
461
472
  ? deps.share.planImport(resolve(archivePath), importOptions)
462
473
  : deps.share.importArchive(resolve(archivePath), importOptions);
463
474
  },
464
- openCost: deps.openCost,
475
+ openUsage: deps.openUsage,
465
476
  openSideQuestion: deps.openSideQuestion,
466
477
  // `/oracle` is refused, never queued, while a turn streams: the digest it
467
478
  // packs describes the record as it stands.
@@ -584,6 +595,17 @@ export function createInteractiveSlashRuntime(deps: InteractiveSlashRuntimeDeps)
584
595
  deps.setEditorText(text);
585
596
  deps.requestRender();
586
597
  },
598
+ runHandoffRecovery: (handoffId, action) => {
599
+ if (!deps.onRecoverHandoff) {
600
+ appendCommandNotice("error", "[/context recover] native handoff recovery is unavailable");
601
+ return;
602
+ }
603
+ void deps
604
+ .onRecoverHandoff(handoffId, action)
605
+ .catch((error) =>
606
+ appendCommandNotice("error", `[/context recover] ${error instanceof Error ? error.message : String(error)}`),
607
+ );
608
+ },
587
609
  runCompact: (instructions) => {
588
610
  runCompactWithNotice(deps.onCompact, appendCommandNotice, instructions);
589
611
  },
@@ -608,10 +630,19 @@ export function createInteractiveSlashRuntime(deps: InteractiveSlashRuntimeDeps)
608
630
  // rehydrate exported it as ordinary history (issue #109). No session
609
631
  // contract means no pin can exist for this reader, so the file replays whole.
610
632
  const leafTurnId = deps.session?.tree(sessionId).leafId ?? null;
611
- rehydrateChatPanelFromTurns(exportPanel, turns, {
612
- unboundedToolBodies: true,
613
- ...(leafTurnId ? { activeLeafTurnId: leafTurnId } : {}),
614
- });
633
+ // The export shows the same continuity the model sees, resolved from
634
+ // the same ledger through the same helper, so the two cannot drift.
635
+ // It is labelled as assistant handoff text and is never rendered as an
636
+ // operator turn.
637
+ rehydrateChatPanelFromTurns(
638
+ exportPanel,
639
+ turns,
640
+ withContinuityReplay(
641
+ turns,
642
+ { unboundedToolBodies: true, ...(leafTurnId ? { activeLeafTurnId: leafTurnId } : {}) },
643
+ deps.session,
644
+ ),
645
+ );
615
646
  const ansiLines = exportPanel.render(EXPORT_RENDER_WIDTH);
616
647
  // The operator names this file by the day they ran the export, so the
617
648
  // date in it is their calendar date. The header below keeps the ISO
@@ -626,8 +657,14 @@ export function createInteractiveSlashRuntime(deps: InteractiveSlashRuntimeDeps)
626
657
  mkdirSync(resolve(target, ".."), { recursive: true });
627
658
  if (markdown) {
628
659
  const lines = ansiLines.map(stripTerminalSequences);
629
- const header = [`# Clio session ${sessionId}`, "", `Exported ${exportedAt.toISOString()}`, "", "```text"];
630
- writeFileSync(target, `${[...header, ...lines, "```", ""].join("\n")}`, "utf8");
660
+ // The fence has to be longer than the longest backtick run the
661
+ // transcript contains, or a body that includes its own fence closes
662
+ // the block early and the rest of the session renders as Markdown.
663
+ // A handoff note is assistant-authored prose that routinely carries
664
+ // fenced code, so this is reachable content, not a hypothetical.
665
+ const fence = "`".repeat(Math.max(3, longestBacktickRun(lines) + 1));
666
+ const header = [`# Clio session ${sessionId}`, "", `Exported ${exportedAt.toISOString()}`, "", `${fence}text`];
667
+ writeFileSync(target, `${[...header, ...lines, fence, ""].join("\n")}`, "utf8");
631
668
  } else {
632
669
  writeFileSync(target, renderSessionHtml({ sessionId, exportedAt: exportedAt.toISOString(), ansiLines }), "utf8");
633
670
  }