@iowarp/clio-coder 0.3.3 → 0.3.6

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 (370) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/CONTRIBUTING.md +7 -7
  3. package/README.md +3 -3
  4. package/dist/{acp-P2AQILE2.js → acp-2BEHC4DL.js} +9 -8
  5. package/dist/{agents-72W3BI7I.js → agents-LNNFTM53.js} +29 -24
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-5TWEIYDN.js → auth-KXXFI2VS.js} +14 -10
  8. package/dist/{chunk-GGXXDWE4.js → chunk-22NAGB7X.js} +2 -2
  9. package/dist/{chunk-YCWGATWI.js → chunk-24I7BN55.js} +2 -2
  10. package/dist/{chunk-EKMEHE4H.js → chunk-33YXPOE3.js} +2 -3
  11. package/dist/chunk-3BPUFZDL.js +37 -0
  12. package/dist/chunk-43AOLP7E.js +375 -0
  13. package/dist/{chunk-ZDOOVTXZ.js → chunk-4OC57DA6.js} +27 -4
  14. package/dist/{chunk-6SGHMWE3.js → chunk-5JGRAMKL.js} +5 -5
  15. package/dist/{chunk-V6RTAOC2.js → chunk-6US73PDB.js} +572 -51
  16. package/dist/{chunk-5UFT4SUX.js → chunk-6XXKFVSN.js} +3 -3
  17. package/dist/{chunk-A3CYT5EX.js → chunk-AD2SYQYC.js} +55 -2
  18. package/dist/chunk-AOCYTWAV.js +449 -0
  19. package/dist/chunk-CFGTUFWB.js +67 -0
  20. package/dist/chunk-CJUB2JJ2.js +1478 -0
  21. package/dist/{chunk-FNTMWMX5.js → chunk-CKXWIANG.js} +14 -12
  22. package/dist/{chunk-PIWWS5BL.js → chunk-CYQKWTG3.js} +63 -78
  23. package/dist/{chunk-LZSJBIVT.js → chunk-DJVECN66.js} +271 -762
  24. package/dist/{chunk-CBCAPZAA.js → chunk-E25LMLRW.js} +2 -2
  25. package/dist/{chunk-ZWMF7253.js → chunk-E2ER4LJF.js} +304 -9
  26. package/dist/{chunk-STBPMHSX.js → chunk-EKY57CSP.js} +51 -84
  27. package/dist/{chunk-DUYJ5IO6.js → chunk-EYPA3EGJ.js} +12 -4
  28. package/dist/{chunk-M6SHUN7Q.js → chunk-FO5ZOVUY.js} +2 -2
  29. package/dist/chunk-FYYLNIL5.js +313 -0
  30. package/dist/{chunk-OQ33BKR3.js → chunk-G7MUEIGA.js} +3 -60
  31. package/dist/chunk-GEYXPTRF.js +613 -0
  32. package/dist/chunk-GOXNB3AO.js +261 -0
  33. package/dist/{chunk-G4BMMOKF.js → chunk-HVDIIIQW.js} +2 -2
  34. package/dist/chunk-HWUFFB6L.js +83 -0
  35. package/dist/{chunk-4XUGQOHA.js → chunk-K7T3E2SR.js} +15 -8
  36. package/dist/chunk-K7VKOLQQ.js +15 -0
  37. package/dist/{chunk-UFIIWP2H.js → chunk-KHSFENX2.js} +8 -8
  38. package/dist/{chunk-BMEMKKIT.js → chunk-KOHPCX4K.js} +2 -2
  39. package/dist/chunk-LCGCVYZ4.js +57 -0
  40. package/dist/chunk-LL4KHSZI.js +22 -0
  41. package/dist/{chunk-PAJK6MAQ.js → chunk-LYF7OHWH.js} +42 -15
  42. package/dist/{chunk-POHLU5DW.js → chunk-M6L6IDJG.js} +3 -3
  43. package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
  44. package/dist/{chunk-X4RCMKVQ.js → chunk-NDINPTJ4.js} +2 -2
  45. package/dist/{chunk-TZK7PACC.js → chunk-NILBFAPG.js} +14 -8
  46. package/dist/chunk-ODFEOB4F.js +1082 -0
  47. package/dist/{chunk-AGYYIBLL.js → chunk-OH3TOQTB.js} +6 -2
  48. package/dist/chunk-OZNBF4L3.js +23 -0
  49. package/dist/{chunk-DSELYM6W.js → chunk-PBTHKCPN.js} +30 -10
  50. package/dist/{verify-375KUB3Y.js → chunk-PCZJO5TI.js} +127 -42
  51. package/dist/{chunk-ED4KHGC3.js → chunk-PPAMZ32Z.js} +9 -2
  52. package/dist/{chunk-SRDMMSEP.js → chunk-QM3F2GKX.js} +1063 -1645
  53. package/dist/{chunk-X6IAEBZR.js → chunk-QNQHSOLF.js} +7 -7
  54. package/dist/{chunk-OC7FIQPC.js → chunk-R46L2BIR.js} +10 -7
  55. package/dist/{chunk-2TLUCQVG.js → chunk-RD5U66HV.js} +3 -3
  56. package/dist/{chunk-6N5PTWMY.js → chunk-RY3LY4J5.js} +50 -13
  57. package/dist/{chunk-OKGUZO2U.js → chunk-SPULKLCF.js} +4 -3
  58. package/dist/{chunk-OOJYHWRB.js → chunk-TSHXZTOQ.js} +6 -5
  59. package/dist/chunk-TZSKNMZG.js +434 -0
  60. package/dist/{chunk-7MNJORFF.js → chunk-UL3WSD3F.js} +6 -1
  61. package/dist/{chunk-VJWL6YS5.js → chunk-UUVG37B4.js} +2 -2
  62. package/dist/{chunk-COU2UHX6.js → chunk-VEZEGCGW.js} +170 -2
  63. package/dist/chunk-W6GROXXM.js +69 -0
  64. package/dist/{chunk-OAO4GE4M.js → chunk-WHGPSPT5.js} +2 -2
  65. package/dist/chunk-WHJYKASB.js +677 -0
  66. package/dist/{chunk-ORBHGJC5.js → chunk-WR67VIZY.js} +3 -3
  67. package/dist/{chunk-YHZX5GEU.js → chunk-XAKHZX5N.js} +2 -2
  68. package/dist/{chunk-TZTZS7QK.js → chunk-XE2VEJHX.js} +5 -3
  69. package/dist/{chunk-LM5TQCJZ.js → chunk-XF5N4U5A.js} +8 -7
  70. package/dist/{chunk-LWLEKMDQ.js → chunk-XXQNGV4M.js} +1073 -552
  71. package/dist/{chunk-KZWTDYJF.js → chunk-XYDYPRZI.js} +7 -7
  72. package/dist/chunk-ZGVHUX3M.js +66 -0
  73. package/dist/{chunk-LW6DSM3M.js → chunk-ZRGEBJ4T.js} +1192 -1119
  74. package/dist/{chunk-2DJ2KNFG.js → chunk-ZXF4XRKW.js} +202 -40
  75. package/dist/chunk-ZZMN5OM4.js +122 -0
  76. package/dist/cli/index.js +34 -30
  77. package/dist/{clio-JOU4FXVA.js → clio-M2KGYUFZ.js} +7 -6
  78. package/dist/{code-nav-7AX6FYE6.js → code-nav-GQNL7XA6.js} +8 -6
  79. package/dist/codewiki/build-worker.js +4 -4
  80. package/dist/{components-KELWS457.js → components-5TTYYX6G.js} +3 -3
  81. package/dist/{config-XCDVKR23.js → config-XUUYQIWO.js} +47 -35
  82. package/dist/{configure-4GAP54ZW.js → configure-IHJ7YOMV.js} +18 -15
  83. package/dist/{context-77FM5DV5.js → context-74JLXAWD.js} +18 -10
  84. package/dist/{context-4UOGGLQ5.js → context-75MIWW3U.js} +41 -29
  85. package/dist/{context-5VKGUVJJ.js → context-ZQ7SIFJV.js} +85 -9
  86. package/dist/{context-clear-XXJRLCJJ.js → context-clear-GYKWNUML.js} +41 -29
  87. package/dist/{context-index-BZ4UYMTC.js → context-index-SSR5ECNE.js} +3 -3
  88. package/dist/context-working-set-UX5KEP4J.js +1553 -0
  89. package/dist/{dispatch-runner-QPRDDBDX.js → dispatch-runner-GIJBHNFL.js} +47 -32
  90. package/dist/{docs-2C2LTVT2.js → docs-6FZSCG5B.js} +3 -3
  91. package/dist/{doctor-HR46URBJ.js → doctor-SVJ5BZCW.js} +12 -12
  92. package/dist/{eval-XSSNATB4.js → eval-CG6LLBLD.js} +54 -238
  93. package/dist/{evidence-6HG2PY2B.js → evidence-ZYFIEN42.js} +57 -28
  94. package/dist/{evolve-K7YU3NCY.js → evolve-QGEXEMDW.js} +36 -25
  95. package/dist/{extensions-QVDOHDGJ.js → extensions-ADGNCJJD.js} +3 -3
  96. package/dist/{fleet-VY3HHKN6.js → fleet-S5R4ZOQY.js} +73 -44
  97. package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-BHSNPBMH.js} +3 -3
  98. package/dist/{init-JYGXI3FK.js → init-5DRU55YR.js} +49 -37
  99. package/dist/memory-7YKKR6UC.js +467 -0
  100. package/dist/{models-I5QWSEOM.js → models-ZPOLRU2C.js} +24 -21
  101. package/dist/{monitor-GE4ID3IA.js → monitor-US5F5YGZ.js} +73 -46
  102. package/dist/{orchestrator-EM5MC3HM.js → orchestrator-E2AL4T5N.js} +1624 -1007
  103. package/dist/{paths-UXLN5YYZ.js → paths-E7KYAQWE.js} +3 -3
  104. package/dist/{reset-L2FQEE3E.js → reset-KZ652EK6.js} +6 -5
  105. package/dist/{run-ZU3QMZPZ.js → run-SRNBKDWD.js} +76 -54
  106. package/dist/{share-S5BZQC5I.js → share-CGZE33UP.js} +7 -6
  107. package/dist/{skills-X5VXCRNQ.js → skills-S2X4DLY5.js} +4 -4
  108. package/dist/{skills-eval-WKIHWTHR.js → skills-eval-W2GGIC4R.js} +40 -29
  109. package/dist/{targets-SNCPI2NR.js → targets-54SWINWB.js} +28 -23
  110. package/dist/{terminal-lease-BNAHVHBS.js → terminal-lease-SAIF2OGY.js} +6 -4
  111. package/dist/{uninstall-FZCQCDKC.js → uninstall-BVLWXKBT.js} +3 -3
  112. package/dist/{upgrade-JQHHPQ4K.js → upgrade-JKAR27XC.js} +20 -19
  113. package/dist/{usage-OR4O5SMZ.js → usage-MSAWCLX4.js} +79 -36
  114. package/dist/verifiers-NCBTHHN2.js +1220 -0
  115. package/dist/verify-X5HDROLA.js +25 -0
  116. package/dist/{wiki-generate-UEXP2ARI.js → wiki-generate-GUSOQ6ZP.js} +50 -37
  117. package/dist/worker/entry.js +90 -70
  118. package/dist/{workspace-G4ZWUIPR.js → workspace-ZJ6BFM3Q.js} +4 -4
  119. package/docs/README.md +8 -7
  120. package/docs/acp.md +1 -1
  121. package/docs/alcf-provider.md +1 -1
  122. package/docs/architecture.md +2 -2
  123. package/docs/artifact-placement.md +1 -2
  124. package/docs/artifact-versions.md +1 -1
  125. package/docs/built-in-agents.md +1 -1
  126. package/docs/capacity-and-scheduling.md +1 -1
  127. package/docs/commands-and-modes.md +60 -26
  128. package/docs/config-knobs-audit.md +1 -2
  129. package/docs/configuration-and-targets.md +26 -1
  130. package/docs/context-engine.md +67 -13
  131. package/docs/context-working-set.md +194 -0
  132. package/docs/development-pipeline.md +1 -1
  133. package/docs/documentation-coverage.md +6 -6
  134. package/docs/documentation-guide.md +7 -6
  135. package/docs/environment-variables.md +2 -1
  136. package/docs/eval-runner.md +1 -1
  137. package/docs/evals-internal.md +4 -32
  138. package/docs/evidence-and-memory.md +139 -7
  139. package/docs/evolution.md +1 -1
  140. package/docs/exit-codes-and-output.md +1 -1
  141. package/docs/extensions-and-sharing.md +2 -2
  142. package/docs/fleet-dispatch.md +49 -8
  143. package/docs/glossary.md +21 -1
  144. package/docs/installation-and-lifecycle.md +2 -2
  145. package/docs/middleware-and-components.md +19 -2
  146. package/docs/model-catalog.md +7 -9
  147. package/docs/observability.md +4 -4
  148. package/docs/proactive-memory.md +26 -16
  149. package/docs/prompt-envelope-and-tools.md +7 -5
  150. package/docs/provider-adapter-cookbook.md +1 -1
  151. package/docs/release-cut-checklist.md +43 -40
  152. package/docs/safety-model.md +49 -8
  153. package/docs/scientific-validation.md +21 -3
  154. package/docs/session-lifecycle.md +3 -3
  155. package/docs/skills-marketplace.md +1 -1
  156. package/docs/tool-usage.md +79 -12
  157. package/docs/trace-store.md +1 -1
  158. package/docs/troubleshooting.md +1 -1
  159. package/docs/tui-design.md +38 -4
  160. package/docs/worker-dispatch-mechanics.md +11 -1
  161. package/package.json +13 -13
  162. package/skills/meta/clio-test/SKILL.md +20 -17
  163. package/skills/meta/clio-test/evals.md +3 -3
  164. package/skills/meta/clio-test/references/harness.md +35 -6
  165. package/skills/meta/clio-test/references/test-map.md +20 -10
  166. package/skills/registry.yaml +2 -2
  167. package/skills/skill-marketplace.json +1 -1
  168. package/src/cli/agents.ts +2 -3
  169. package/src/cli/argv.ts +14 -1
  170. package/src/cli/context-working-set.ts +513 -0
  171. package/src/cli/context.ts +8 -0
  172. package/src/cli/evidence.ts +20 -2
  173. package/src/cli/fleet.ts +15 -0
  174. package/src/cli/index.ts +5 -1
  175. package/src/cli/memory.ts +272 -10
  176. package/src/cli/modes/json-stream.ts +2 -2
  177. package/src/cli/modes/print.ts +12 -1
  178. package/src/cli/run.ts +22 -2
  179. package/src/cli/targets.ts +12 -3
  180. package/src/cli/usage.ts +55 -7
  181. package/src/cli/verifiers.ts +325 -0
  182. package/src/core/bash-exec.ts +39 -14
  183. package/src/core/bus-events.ts +22 -4
  184. package/src/core/config.ts +54 -0
  185. package/src/core/defaults.ts +50 -3
  186. package/src/core/response-model-id.ts +134 -0
  187. package/src/core/toml.ts +62 -0
  188. package/src/core/verification-scripts.ts +6 -0
  189. package/src/core/workspace-files.ts +0 -1
  190. package/src/domains/agents/builtins/architect.md +1 -1
  191. package/src/domains/agents/builtins/verifier.md +3 -0
  192. package/src/domains/agents/catalog.ts +5 -4
  193. package/src/domains/agents/recipe.ts +54 -14
  194. package/src/domains/agents/result-contract.ts +7 -4
  195. package/src/domains/config/classify.ts +1 -0
  196. package/src/domains/context/bootstrap.ts +36 -27
  197. package/src/domains/context/project-metadata.ts +19 -63
  198. package/src/domains/context/prompt-context.ts +8 -0
  199. package/src/domains/context/working-set/contract.ts +161 -0
  200. package/src/domains/context/working-set/defaults.ts +28 -0
  201. package/src/domains/context/working-set/engine.ts +203 -0
  202. package/src/domains/context/working-set/fold.ts +62 -0
  203. package/src/domains/context/working-set/horizon.ts +38 -0
  204. package/src/domains/context/working-set/marker.ts +103 -0
  205. package/src/domains/context/working-set/path-index.ts +436 -0
  206. package/src/domains/context/working-set/payload.ts +152 -0
  207. package/src/domains/context/working-set/policies/age-horizon.ts +55 -0
  208. package/src/domains/context/working-set/policies/index.ts +20 -0
  209. package/src/domains/context/working-set/policies/structural.ts +160 -0
  210. package/src/domains/context/working-set/project.ts +132 -0
  211. package/src/domains/context/working-set/protect.ts +109 -0
  212. package/src/domains/context/working-set/recall.ts +177 -0
  213. package/src/domains/context/working-set/replay/controls.ts +112 -0
  214. package/src/domains/context/working-set/replay/load-clio.ts +199 -0
  215. package/src/domains/context/working-set/replay/metrics.ts +185 -0
  216. package/src/domains/context/working-set/replay/reference-graph.ts +79 -0
  217. package/src/domains/context/working-set/replay/report.ts +139 -0
  218. package/src/domains/context/working-set/replay/runner.ts +325 -0
  219. package/src/domains/context/working-set/replay/synthetic.ts +422 -0
  220. package/src/domains/context/working-set/replay/trace.ts +21 -0
  221. package/src/domains/context/working-set/visible.ts +54 -0
  222. package/src/domains/dispatch/budget-envelope.ts +396 -0
  223. package/src/domains/dispatch/contract.ts +2 -0
  224. package/src/domains/dispatch/extension.ts +81 -27
  225. package/src/domains/dispatch/orphan-recovery.ts +1 -0
  226. package/src/domains/dispatch/receipt-integrity.ts +4 -0
  227. package/src/domains/dispatch/state.ts +1 -0
  228. package/src/domains/dispatch/types.ts +10 -3
  229. package/src/domains/dispatch/validation.ts +14 -0
  230. package/src/domains/dispatch/worker-spawn.ts +14 -3
  231. package/src/domains/eval/metrics/evidence.ts +0 -116
  232. package/src/domains/eval/metrics/invariants.ts +1 -1
  233. package/src/domains/eval/runners/clio-run.ts +1 -10
  234. package/src/domains/eval/runners/external-command.ts +2 -29
  235. package/src/domains/eval/schema/suite.ts +0 -7
  236. package/src/domains/eval/suites/run.ts +1 -7
  237. package/src/domains/evidence/build.ts +112 -45
  238. package/src/domains/evidence/eval.ts +24 -7
  239. package/src/domains/evidence/index.ts +53 -0
  240. package/src/domains/evidence/ordering.ts +12 -0
  241. package/src/domains/evidence/run-trust.ts +221 -0
  242. package/src/domains/evidence/store.ts +46 -6
  243. package/src/domains/evidence/trust-status.ts +854 -0
  244. package/src/domains/evidence/types.ts +26 -0
  245. package/src/domains/memory/index.ts +22 -0
  246. package/src/domains/memory/operations.ts +58 -1
  247. package/src/domains/memory/promotion.ts +281 -0
  248. package/src/domains/memory/prompt-section.ts +25 -5
  249. package/src/domains/memory/proposal.ts +51 -7
  250. package/src/domains/memory/task-bank.ts +3 -2
  251. package/src/domains/memory/task-memory-handoff.ts +181 -24
  252. package/src/domains/memory/task-memory-policy.ts +3 -1
  253. package/src/domains/memory/types.ts +37 -0
  254. package/src/domains/memory/validate.ts +178 -0
  255. package/src/domains/middleware/memory-intervention.ts +38 -25
  256. package/src/domains/middleware/runtime.ts +6 -0
  257. package/src/domains/middleware/skills-reminder.ts +19 -4
  258. package/src/domains/middleware/stalled-turn.ts +208 -5
  259. package/src/domains/middleware/types.ts +10 -0
  260. package/src/domains/observability/contract.ts +6 -1
  261. package/src/domains/observability/cost.ts +20 -4
  262. package/src/domains/observability/extension.ts +2 -2
  263. package/src/domains/providers/index.ts +3 -0
  264. package/src/domains/providers/model-discovery.ts +9 -0
  265. package/src/domains/providers/runtime-resolution.ts +38 -1
  266. package/src/domains/providers/runtimes/common/probe-helpers.ts +97 -16
  267. package/src/domains/providers/types/context-window-slots.ts +18 -0
  268. package/src/domains/providers/types/runtime-descriptor.ts +3 -1
  269. package/src/domains/safety/autonomy.ts +1 -1
  270. package/src/domains/safety/call-target.ts +211 -14
  271. package/src/domains/safety/decision-presentation.ts +268 -0
  272. package/src/domains/safety/default-path-policy.ts +8 -0
  273. package/src/domains/safety/finish-contract.ts +4 -3
  274. package/src/domains/safety/policy-engine.ts +48 -6
  275. package/src/domains/safety/redaction.ts +73 -0
  276. package/src/domains/session/compaction/compact.ts +23 -1
  277. package/src/domains/session/compaction/cut-point.ts +2 -0
  278. package/src/domains/session/compaction/tokens.ts +16 -1
  279. package/src/domains/session/context-ledger.ts +12 -1
  280. package/src/domains/session/decision-board.ts +4 -0
  281. package/src/domains/session/entries.ts +110 -1
  282. package/src/domains/session/history.ts +68 -19
  283. package/src/domains/session/manager.ts +9 -2
  284. package/src/domains/session/migrations/index.ts +22 -3
  285. package/src/domains/session/usage.ts +24 -7
  286. package/src/engine/acp/event-mapper.ts +7 -0
  287. package/src/engine/acp/server.ts +32 -2
  288. package/src/engine/agent.ts +18 -1
  289. package/src/engine/apis/lmstudio.ts +25 -4
  290. package/src/engine/apis/openai-completions.ts +147 -22
  291. package/src/engine/claude/sdk-runtime.ts +8 -2
  292. package/src/engine/claude/tool-safety.ts +13 -0
  293. package/src/engine/loop-guard.ts +27 -3
  294. package/src/engine/session.ts +9 -3
  295. package/src/engine/worker-events.ts +4 -3
  296. package/src/engine/worker-runtime.ts +59 -54
  297. package/src/entry/orchestrator.ts +34 -5
  298. package/src/interactive/chat-loop-messages.ts +40 -6
  299. package/src/interactive/chat-loop.ts +13 -0
  300. package/src/interactive/chat-panel.ts +17 -1
  301. package/src/interactive/chat-renderer.ts +49 -24
  302. package/src/interactive/clio-editor.ts +44 -7
  303. package/src/interactive/context-meter.ts +10 -0
  304. package/src/interactive/context-overlay.ts +120 -7
  305. package/src/interactive/context-recall-command.ts +110 -0
  306. package/src/interactive/cost-overlay.ts +39 -8
  307. package/src/interactive/dispatch-board.ts +212 -35
  308. package/src/interactive/footer/widgets.ts +13 -0
  309. package/src/interactive/interactive-application.ts +6 -1
  310. package/src/interactive/interactive-input-runtime.ts +11 -1
  311. package/src/interactive/interactive-presentation.ts +11 -1
  312. package/src/interactive/interactive-slash-runtime.ts +37 -1
  313. package/src/interactive/memory-overlay.ts +89 -4
  314. package/src/interactive/model-session-replay.ts +21 -0
  315. package/src/interactive/overlay-ask-user-lifecycle.ts +1 -1
  316. package/src/interactive/overlay-frame.ts +5 -2
  317. package/src/interactive/overlay-general-openers.ts +46 -1
  318. package/src/interactive/overlay-key-routing.ts +41 -1
  319. package/src/interactive/overlay-lifecycle.ts +11 -4
  320. package/src/interactive/overlay-permission-lifecycle.ts +23 -8
  321. package/src/interactive/overlay-session-lifecycle.ts +8 -4
  322. package/src/interactive/overlay-transitions.ts +11 -0
  323. package/src/interactive/overlays/ask-user.ts +74 -30
  324. package/src/interactive/overlays/decisions.ts +3 -1
  325. package/src/interactive/permission-hint.ts +35 -0
  326. package/src/interactive/permission-overlay.ts +95 -45
  327. package/src/interactive/renderers/tool-execution.ts +37 -51
  328. package/src/interactive/session-last-turn.ts +8 -1
  329. package/src/interactive/session-transcript.ts +2 -2
  330. package/src/interactive/session-usage-reseed.ts +36 -10
  331. package/src/interactive/slash-commands.ts +31 -4
  332. package/src/interactive/status/summary.ts +5 -0
  333. package/src/interactive/status/types.ts +5 -0
  334. package/src/interactive/terminal-lease.ts +1 -0
  335. package/src/interactive/turn-context.ts +333 -110
  336. package/src/interactive/turn-middleware.ts +7 -6
  337. package/src/interactive/turn-runtime.ts +37 -8
  338. package/src/interactive/turn-state.ts +3 -0
  339. package/src/interactive/worker-progress.ts +440 -0
  340. package/src/interactive/worker-stream.ts +51 -110
  341. package/src/tools/agent-tools.ts +39 -7
  342. package/src/tools/ask-user.ts +21 -1
  343. package/src/tools/bash.ts +144 -82
  344. package/src/tools/builtin-tool-catalog.ts +11 -5
  345. package/src/tools/context/index.ts +107 -5
  346. package/src/tools/context/surface.ts +3 -2
  347. package/src/tools/core-bootstrap.ts +21 -0
  348. package/src/tools/dispatch-arguments.ts +8 -0
  349. package/src/tools/dispatch-event-text.ts +19 -0
  350. package/src/tools/dispatch-runner.ts +9 -7
  351. package/src/tools/dispatch.ts +24 -1
  352. package/src/tools/monitor.ts +43 -20
  353. package/src/tools/registry.ts +72 -10
  354. package/src/tools/result-disposition.ts +706 -0
  355. package/src/tools/result-shaping.ts +321 -20
  356. package/src/tools/safe-exec.ts +2 -0
  357. package/src/tools/verify/authoring.ts +1120 -0
  358. package/src/tools/verify/catalog.ts +346 -0
  359. package/src/tools/verify/index.ts +13 -3
  360. package/src/tools/verify/scripts.ts +135 -37
  361. package/src/tools/verify/surface.ts +9 -5
  362. package/src/tools/worker-evidence.ts +54 -12
  363. package/src/worker/spec-contract.ts +43 -3
  364. package/dist/chunk-J7CWMCQD.js +0 -255
  365. package/dist/chunk-T6YILFSB.js +0 -80
  366. package/dist/chunk-VAKQQHWR.js +0 -434
  367. package/dist/chunk-VPAYEGVX.js +0 -184
  368. package/dist/chunk-XBXAASKX.js +0 -18
  369. package/dist/memory-WFZMGYHX.js +0 -236
  370. package/src/domains/eval/metrics/chaos-stream.ts +0 -93
@@ -7,6 +7,13 @@
7
7
 
8
8
  import { randomUUID } from "node:crypto";
9
9
  import { settingsPath } from "../core/config.js";
10
+ import {
11
+ addResponseModelIdObservationCount,
12
+ emptyResponseModelIdObservationCounts,
13
+ type ResponseModelIdObservation,
14
+ type ResponseModelIdObservationCounts,
15
+ responseModelIdObservationFromRecord,
16
+ } from "../core/response-model-id.js";
10
17
  import type { PendingSkillRequest, PendingSkillToolPolicy, SkillDeclaredToolPolicy } from "../core/skill-activation.js";
11
18
  import { ToolNames } from "../core/tool-names.js";
12
19
  import { canonicalJson, sha256 } from "../domains/prompts/hash.js";
@@ -334,6 +341,7 @@ export function createAskUserToolPolicy(activeTools: ReadonlyArray<{ name: strin
334
341
  status: "idle",
335
342
  startedAt: now,
336
343
  updatedAt: now,
344
+ exposure: "local",
337
345
  rounds: [],
338
346
  decisions: [],
339
347
  inFlight: false,
@@ -356,6 +364,9 @@ export interface RunUsageSummary {
356
364
  apiCalls: number;
357
365
  hadReasoning: boolean;
358
366
  hadUsage: boolean;
367
+ responseModelIdObservationCounts: ResponseModelIdObservationCounts;
368
+ lastResponseModelIdObservation: ResponseModelIdObservation;
369
+ lastDifferingResponseModelId: string | null;
359
370
  }
360
371
 
361
372
  /**
@@ -380,6 +391,9 @@ export function sumRunUsage(messages: ReadonlyArray<AgentMessage>): RunUsageSumm
380
391
  apiCalls: 0,
381
392
  hadReasoning: false,
382
393
  hadUsage: false,
394
+ responseModelIdObservationCounts: emptyResponseModelIdObservationCounts(),
395
+ lastResponseModelIdObservation: { state: "not-observed" },
396
+ lastDifferingResponseModelId: null,
383
397
  };
384
398
  for (const raw of messages) {
385
399
  const message = raw as
@@ -397,8 +411,15 @@ export function sumRunUsage(messages: ReadonlyArray<AgentMessage>): RunUsageSumm
397
411
  };
398
412
  if (!message || typeof message !== "object") continue;
399
413
  if (message.role !== "assistant") continue;
414
+ const record = message as unknown as Record<string, unknown>;
400
415
  const usage = message.usage;
401
416
  if (!usage || typeof usage !== "object") continue;
417
+ summary.lastResponseModelIdObservation = responseModelIdObservationFromRecord(record, "not-observed");
418
+ addResponseModelIdObservationCount(summary.responseModelIdObservationCounts, summary.lastResponseModelIdObservation);
419
+ summary.lastDifferingResponseModelId =
420
+ typeof record.responseModel === "string" && record.responseModel.trim().length > 0
421
+ ? record.responseModel.trim()
422
+ : null;
402
423
  summary.hadUsage = true;
403
424
  summary.apiCalls += 1;
404
425
  const input = typeof usage.input === "number" ? usage.input : 0;
@@ -459,6 +480,7 @@ export function assistantSessionPayload(
459
480
  const raw = message as unknown as Record<string, unknown>;
460
481
  if (Array.isArray(raw.content)) payload.content = raw.content;
461
482
  if (thinking.length > 0) payload.thinking = thinking;
483
+ payload.responseModelIdObservation = responseModelIdObservationFromRecord(raw, "not-observed");
462
484
  for (const key of ["usage", "api", "provider", "model", "responseModel", "responseId", "diagnostics"]) {
463
485
  if (raw[key] !== undefined) payload[key] = raw[key];
464
486
  }
@@ -555,21 +577,33 @@ export function toolResultSummary(result: unknown): Record<string, unknown> {
555
577
  const bytes = Buffer.byteLength(text, "utf8");
556
578
  const details = recordValue(obj?.details);
557
579
  const size = recordValue(details?.resultSize);
580
+ const disposition = recordValue(details?.resultDisposition);
558
581
  const truncation = recordValue(details?.truncation);
559
582
  const observation = recordValue(details?.observation);
560
583
  const offloadPath =
561
- typeof observation?.offloadPath === "string"
562
- ? observation.offloadPath
563
- : typeof size?.offloadPath === "string"
564
- ? size.offloadPath
565
- : null;
584
+ typeof disposition?.offloadPath === "string"
585
+ ? disposition.offloadPath
586
+ : typeof observation?.offloadPath === "string"
587
+ ? observation.offloadPath
588
+ : typeof size?.offloadPath === "string"
589
+ ? size.offloadPath
590
+ : null;
591
+ const displayedBytes =
592
+ typeof disposition?.displayedBytes === "number" && Number.isFinite(disposition.displayedBytes)
593
+ ? disposition.displayedBytes
594
+ : bytes;
566
595
  return {
567
- bytes,
596
+ bytes: displayedBytes,
568
597
  truncated:
598
+ disposition?.presentationTruncated === true ||
569
599
  size?.truncated === true ||
570
600
  truncation?.truncated === true ||
571
601
  observation?.truncated === true ||
572
602
  text.includes("[tool result truncated]"),
603
+ ...(typeof disposition?.capturedBytes === "number" ? { capturedBytes: disposition.capturedBytes } : {}),
604
+ ...(typeof disposition?.displayedBytes === "number" ? { displayedBytes: disposition.displayedBytes } : {}),
605
+ ...(typeof disposition?.contextBytes === "number" ? { contextBytes: disposition.contextBytes } : {}),
606
+ ...(disposition?.contextTruncated === true ? { contextTruncated: true } : {}),
573
607
  ...(typeof size?.policy === "string" ? { policy: size.policy } : {}),
574
608
  ...(typeof size?.followUpHint === "string" ? { followUpHint: size.followUpHint } : {}),
575
609
  ...(offloadPath !== null ? { offloadPath } : {}),
@@ -549,6 +549,12 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
549
549
  emitNotice,
550
550
  toolStartTimes,
551
551
  });
552
+ // A fresh ledger and footer render before the first submit. Start the same
553
+ // live capability probe that submit and resume use so those boot surfaces
554
+ // refresh to the probed model window as soon as provider discovery lands.
555
+ // The turn runtime coalesces the first submit onto this request, then its
556
+ // target and model TTL keeps later submits from repeating it.
557
+ void turnRuntime.ensureLiveCapabilitiesForSelectedModel().catch(() => {});
552
558
 
553
559
  // --- bus subscriptions --------------------------------------------------
554
560
 
@@ -664,6 +670,7 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
664
670
  // 1. Accept the prompt: reset per-turn accounting, freeze the tool
665
671
  // surface, fire turn_start, and assemble the submitted text.
666
672
  state.turnToolCalls = 0;
673
+ state.turnToolNames = [];
667
674
  state.turnSharedWorkerNote = isWorkerShareNote(text);
668
675
  middlewareToolChoice.reset();
669
676
  if (options.requestContinuation !== true) state.stalledTurnNudgeSpent = false;
@@ -988,6 +995,12 @@ export function createChatLoop(deps: CreateChatLoopDeps): ChatLoop {
988
995
  middlewareToolChoice.reset();
989
996
  state.lastTurnId = leafTurnId;
990
997
  context.resetForSession();
998
+ // The resumed ledger renders before the first new turn (issue #189),
999
+ // and the window it renders against should be the probed one rather
1000
+ // than the catalog's, so the live capability probe the first submit
1001
+ // would run runs now, under the same TTL; the footer refreshes on the
1002
+ // provider-health event the probe publishes.
1003
+ void turnRuntime.ensureLiveCapabilitiesForSelectedModel().catch(() => {});
991
1004
  state.replayedContextMessages = replayMessages ? [...replayMessages] : [];
992
1005
  if (state.runtime) {
993
1006
  state.runtime.agent.state.messages = [...state.replayedContextMessages];
@@ -5,6 +5,7 @@ import { rawDurationMs } from "../core/timers.js";
5
5
  import { type Component, Markdown, truncateToWidth, wrapTextWithAnsi } from "../engine/tui.js";
6
6
  import type { AgentMessage } from "../engine/types.js";
7
7
  import { toolPresentationPolicy } from "../tools/presentation.js";
8
+ import { toolResultPresentationPolicy } from "../tools/result-disposition.js";
8
9
  import type { ChatLoopEvent, RetryStatusPayload } from "./chat-loop.js";
9
10
  import { extractText, isSelfExplainingAbort } from "./chat-loop-messages.js";
10
11
  import type { ApprovalRequestView } from "./permission-overlay.js";
@@ -202,6 +203,12 @@ type ToolSegment = {
202
203
  * was refused and leaves the operator no way to learn why.
203
204
  */
204
205
  blockReason?: string | undefined;
206
+ /**
207
+ * Working-set eviction reason for a replayed result whose body the
208
+ * projection has replaced. Set only on rehydrate, from the folded ledger;
209
+ * a live call is never evicted while it is still being rendered.
210
+ */
211
+ evictedReason?: string | undefined;
205
212
  /** View-only marker: historical calls render mutation diffs without live color. */
206
213
  replayed?: true;
207
214
  };
@@ -835,7 +842,7 @@ function styleStatusVerb(text: string, toneHint: VerbRender["toneHint"]): string
835
842
  function policySegmentFold(seg: ToolSegment, detail: TranscriptDetailPolicy): Fold {
836
843
  if (!seg.finished) return policyRunningToolFold(detail);
837
844
  if (seg.isError && detail.errors === "body") return "expanded";
838
- return policyToolFold(detail, toolPresentationPolicy(seg.name, seg.args));
845
+ return policyToolFold(detail, toolResultPresentationPolicy(seg.result) ?? toolPresentationPolicy(seg.name, seg.args));
839
846
  }
840
847
 
841
848
  /** Effective state of a tool segment: the operator's override, else the policy. */
@@ -884,6 +891,7 @@ function renderToolSegmentLines(
884
891
  resultSummary: seg.resultSummary,
885
892
  outcome: seg.settlement,
886
893
  blockReason: seg.blockReason,
894
+ evictedReason: seg.evictedReason,
887
895
  }
888
896
  : { toolCallId: seg.id, toolName: seg.name, args: seg.args, elapsedMs, phase },
889
897
  width,
@@ -916,6 +924,7 @@ function renderToolSegmentLines(
916
924
  resultSummary: seg.resultSummary,
917
925
  outcome: seg.settlement,
918
926
  blockReason: seg.blockReason,
927
+ evictedReason: seg.evictedReason,
919
928
  },
920
929
  width,
921
930
  { unbounded: unboundedToolBodies, diffStyle: seg.replayed === true ? "plain" : "color" },
@@ -1936,7 +1945,14 @@ export function createChatPanel(options: ChatPanelOptions = {}): ChatPanel {
1936
1945
  resultSummary?: unknown;
1937
1946
  outcome?: unknown;
1938
1947
  blockReason?: unknown;
1948
+ evictedReason?: unknown;
1939
1949
  };
1950
+ // Replay-only: the rehydrator reads the working-set fold and tags
1951
+ // the rows whose bodies the projection has replaced for the model.
1952
+ tool.evictedReason =
1953
+ typeof enriched.evictedReason === "string" && enriched.evictedReason.length > 0
1954
+ ? enriched.evictedReason
1955
+ : undefined;
1940
1956
  // Settlement is that verdict, never an inference from result text.
1941
1957
  // The text of a tool result is the tool's own output: `node --test`
1942
1958
  // prints `cancelled 0` on every run and a linter can print
@@ -13,7 +13,10 @@
13
13
  * `isTransparentAssistantWrapper`.
14
14
  */
15
15
 
16
+ import { existsSync } from "node:fs";
16
17
  import { ToolNames } from "../core/tool-names.js";
18
+ import { foldWorkingSet } from "../domains/context/working-set/fold.js";
19
+ import { compactionCut } from "../domains/context/working-set/visible.js";
17
20
  import type {
18
21
  BashExecutionEntry,
19
22
  BranchSummaryEntry,
@@ -40,8 +43,9 @@ import {
40
43
  import { wrapTextWithAnsi } from "../engine/tui.js";
41
44
  import type { AgentMessage } from "../engine/types.js";
42
45
  import { toolPresentationPolicy } from "../tools/presentation.js";
46
+ import { toolResultPresentationText } from "../tools/result-disposition.js";
43
47
  import type { ChatLoopEvent, RetryStatusPayload } from "./chat-loop.js";
44
- import { isSelfExplainingAbort } from "./chat-loop-messages.js";
48
+ import { isSelfExplainingAbort, toolResultSummary } from "./chat-loop-messages.js";
45
49
  import type { ChatPanel } from "./chat-panel.js";
46
50
  import { renderBranchSummaryEntry } from "./renderers/branch-summary.js";
47
51
  import { renderCompactionSummaryEntry } from "./renderers/compaction-summary.js";
@@ -437,6 +441,7 @@ function richMessageFromEntry(entry: MessageEntry, maxTextChars?: number): Agent
437
441
  "api",
438
442
  "provider",
439
443
  "model",
444
+ "responseModelIdObservation",
440
445
  "responseModel",
441
446
  "responseId",
442
447
  "diagnostics",
@@ -482,7 +487,11 @@ function toolResultContent(result: unknown, unbounded = false): unknown[] {
482
487
  }
483
488
 
484
489
  function displayReplayToolResult(result: unknown, unbounded = false): unknown {
485
- const content = toolResultContent(result, unbounded);
490
+ const presentationText = toolResultPresentationText(result);
491
+ const content =
492
+ presentationText === null
493
+ ? toolResultContent(result, unbounded)
494
+ : [{ type: "text", text: unbounded ? presentationText : truncateReplayText(presentationText) }];
486
495
  // Preserve the details record (observation envelope, exec records) so the
487
496
  // replayed ledger line carries the same outcome facts as the live one.
488
497
  const details = payloadObject(result)?.details;
@@ -664,6 +673,19 @@ interface ReplayToolResult {
664
673
  blockReason?: string;
665
674
  }
666
675
 
676
+ /**
677
+ * Resolve a replayed offload pointer once, when the ledger row becomes a
678
+ * display event. Terminal repaints consume the recorded fact and never touch
679
+ * the filesystem.
680
+ */
681
+ function replayResultSummary(result: ReplayToolResult): Record<string, unknown> | undefined {
682
+ const summary = result.resultSummary ?? toolResultSummary(result.result);
683
+ const offloadPath =
684
+ typeof summary.offloadPath === "string" && summary.offloadPath.length > 0 ? summary.offloadPath : null;
685
+ if (offloadPath === null) return result.resultSummary;
686
+ return { ...summary, offloadFileMissing: !existsSync(offloadPath) };
687
+ }
688
+
667
689
  function extractToolResult(entry: MessageEntry): ReplayToolResult {
668
690
  const payload = entry.payload;
669
691
  const obj = payloadObject(payload);
@@ -835,13 +857,6 @@ function truncateAtTurn(entries: ReadonlyArray<SessionEntry>, uptoTurnId?: strin
835
857
  return entries.slice(0, index + 1);
836
858
  }
837
859
 
838
- function latestCompactionIndex(entries: ReadonlyArray<SessionEntry>): number {
839
- for (let i = entries.length - 1; i >= 0; i--) {
840
- if (entries[i]?.kind === "compactionSummary") return i;
841
- }
842
- return -1;
843
- }
844
-
845
860
  function toolCallIdsInEntry(entry: SessionEntry): string[] {
846
861
  if (entry.kind !== "message") return [];
847
862
  if (entry.role === "tool_call") return [extractToolCall(entry).id];
@@ -919,19 +934,13 @@ export function selectReplayEntries(
919
934
  ): SessionEntry[] {
920
935
  const active = filterEntriesToActivePath(turns, options.activeLeafTurnId ?? options.uptoTurnId);
921
936
  const entries = truncateAtTurn(active, options.uptoTurnId);
922
- const compactionIndex = latestCompactionIndex(entries);
923
- if (compactionIndex < 0) return dropLegacyToolResultAssistantDuplicates(entries);
924
-
925
- const compaction = entries[compactionIndex] as CompactionSummaryEntry;
926
- const selected: SessionEntry[] = [compaction];
927
- const firstKeptIndex = compaction.firstKeptTurnId
928
- ? entries.findIndex((entry) => entry.turnId === compaction.firstKeptTurnId)
929
- : -1;
930
- if (firstKeptIndex >= 0 && firstKeptIndex < compactionIndex) {
931
- selected.push(...entries.slice(firstKeptIndex, compactionIndex));
932
- }
933
- selected.push(...entries.slice(compactionIndex + 1));
934
- return dropLegacyToolResultAssistantDuplicates(repairToolResultOrphans(entries, selected, compactionIndex));
937
+ // The cut itself is the working-set layer's definition of "what the model
938
+ // can see", shared with the eviction policy input so the two cannot drift.
939
+ const cut = compactionCut(entries);
940
+ if (cut.compactionIndex < 0) return dropLegacyToolResultAssistantDuplicates(entries);
941
+ const compaction = entries[cut.compactionIndex] as CompactionSummaryEntry;
942
+ const selected: SessionEntry[] = [compaction, ...cut.visible];
943
+ return dropLegacyToolResultAssistantDuplicates(repairToolResultOrphans(entries, selected, cut.compactionIndex));
935
944
  }
936
945
 
937
946
  function dropLegacyToolResultAssistantDuplicates(entries: ReadonlyArray<SessionEntry>): SessionEntry[] {
@@ -1043,6 +1052,11 @@ export function buildReplayAgentMessagesFromTurns(
1043
1052
  // It reaches the model only when an operator shares it, and a share
1044
1053
  // is already a user message by the time it lands in the ledger.
1045
1054
  case "workerRun":
1055
+ // Working-set entries are folded into a view by the context domain
1056
+ // and applied as a projection before this builder runs; the
1057
+ // entries themselves never become messages.
1058
+ case "contextEviction":
1059
+ case "contextRecall":
1046
1060
  break;
1047
1061
  }
1048
1062
  }
@@ -1080,6 +1094,11 @@ export function rehydrateChatPanelFromTurns(
1080
1094
  const pendingToolIds: string[] = [];
1081
1095
  let runAssistantMessages: AgentMessage[] = [];
1082
1096
  const selected = selectReplayEntries(turns, options);
1097
+ // The transcript shows the ledger, never the projection: an evicted result
1098
+ // still renders its full body here, tagged with the reason it left the
1099
+ // model's working set. Folded once over the same active path the replay
1100
+ // uses, so a /tree switch cannot tag a row from an abandoned branch.
1101
+ const workingSet = foldWorkingSet(turns, options.activeLeafTurnId ?? options.uptoTurnId);
1083
1102
  // One block per assignment, drawn where its first attempt started. Later
1084
1103
  // attempts of the same assignment fold into that block as `↻` rail lines, so
1085
1104
  // a failover replays as the one run it was rather than as two.
@@ -1127,6 +1146,8 @@ export function rehydrateChatPanelFromTurns(
1127
1146
  }
1128
1147
  if (entry.role === "tool_result") {
1129
1148
  const result = extractToolResult(entry);
1149
+ const resultSummary = replayResultSummary(result);
1150
+ const evictedReason = workingSet.evicted.get(entry.turnId)?.reason;
1130
1151
  const fallbackId = result.id ?? pendingToolIds.pop() ?? null;
1131
1152
  if (fallbackId) {
1132
1153
  const pendingIndex = pendingToolIds.indexOf(fallbackId);
@@ -1138,9 +1159,10 @@ export function rehydrateChatPanelFromTurns(
1138
1159
  result: displayReplayToolResult(result.result, options.unboundedToolBodies === true),
1139
1160
  isError: result.isError,
1140
1161
  ...(result.durationMs !== undefined ? { durationMs: result.durationMs } : {}),
1141
- ...(result.resultSummary !== undefined ? { resultSummary: result.resultSummary } : {}),
1162
+ ...(resultSummary !== undefined ? { resultSummary } : {}),
1142
1163
  ...(result.outcome !== undefined ? { outcome: result.outcome } : {}),
1143
1164
  ...(result.blockReason !== undefined ? { blockReason: result.blockReason } : {}),
1165
+ ...(evictedReason !== undefined ? { evictedReason } : {}),
1144
1166
  } as ChatLoopEvent);
1145
1167
  } else {
1146
1168
  chatPanel.appendReplayBlock((width) =>
@@ -1151,9 +1173,10 @@ export function rehydrateChatPanelFromTurns(
1151
1173
  result: displayReplayToolResult(result.result, options.unboundedToolBodies === true),
1152
1174
  isError: result.isError,
1153
1175
  ...(result.durationMs !== undefined ? { durationMs: result.durationMs } : {}),
1154
- ...(result.resultSummary !== undefined ? { resultSummary: result.resultSummary } : {}),
1176
+ ...(resultSummary !== undefined ? { resultSummary } : {}),
1155
1177
  ...(result.outcome === "blocked" ? { outcome: "blocked" as const } : {}),
1156
1178
  ...(result.blockReason !== undefined ? { blockReason: result.blockReason } : {}),
1179
+ ...(evictedReason !== undefined ? { evictedReason } : {}),
1157
1180
  },
1158
1181
  width,
1159
1182
  { unbounded: options.unboundedToolBodies === true },
@@ -1239,6 +1262,8 @@ export function rehydrateChatPanelFromTurns(
1239
1262
  case "label":
1240
1263
  case "taskLedger":
1241
1264
  case "decisionLedger":
1265
+ case "contextEviction":
1266
+ case "contextRecall":
1242
1267
  break;
1243
1268
  }
1244
1269
  }
@@ -1,9 +1,12 @@
1
1
  import { Editor, stripTerminalSequences, type TUI, truncateToWidth, visibleWidth } from "../engine/tui.js";
2
+ import { fitHintEntries } from "./overlay-frame.js";
3
+ import { permissionHintEntries } from "./permission-hint.js";
2
4
  import type { ClioTheme } from "./theme/index.js";
3
5
  import { clioTheme, editorTheme, GLYPH, rule } from "./theme/index.js";
4
6
 
5
7
  const REVERSE_VIDEO_BLANK = `${String.fromCharCode(27)}[7m ${String.fromCharCode(27)}[0m`;
6
8
  const EMPTY_PROMPT = "Ask Clio… / for commands";
9
+ const CONFIRM_PROMPT = "A parked call is waiting for your decision";
7
10
  const MIN_HINT_WIDTH = 60;
8
11
 
9
12
  function hasScrollIndicator(line: string): boolean {
@@ -18,6 +21,13 @@ export interface EditorChrome {
18
21
  getThinkingLabel: () => string;
19
22
  /** Whether Enter currently targets the active Clio response. */
20
23
  isStreaming?: () => boolean;
24
+ /**
25
+ * Whether a permission prompt owns the keyboard. The dialog once sat at the
26
+ * vertical center of a tall viewport, far from the composer. It now anchors
27
+ * above that composer, while the rail still says CONFIRM and carries the
28
+ * dialog's keys whenever the prompt owns input (issues #186 and #194).
29
+ */
30
+ isAwaitingApproval?: () => boolean;
21
31
  /** Whether the current draft will actually steer Clio or live dispatch work on Enter. */
22
32
  willEnterSteer?: (text: string) => boolean;
23
33
  /** Resolved submit binding, formatted for display. */
@@ -26,7 +36,7 @@ export interface EditorChrome {
26
36
  getNewlineKeyLabel?: () => string;
27
37
  }
28
38
 
29
- type ComposerMode = "MESSAGE" | "FOLLOW-UP" | "STEER";
39
+ type ComposerMode = "MESSAGE" | "FOLLOW-UP" | "STEER" | "CONFIRM";
30
40
 
31
41
  function normalizeThinkingHint(value: string): string {
32
42
  return value
@@ -61,6 +71,7 @@ function styledRailLabel(theme: ClioTheme, chrome: EditorChrome): string {
61
71
  }
62
72
 
63
73
  function composerMode(chrome: EditorChrome, text: string): ComposerMode {
74
+ if (chrome.isAwaitingApproval?.() ?? false) return "CONFIRM";
64
75
  if (!(chrome.isStreaming?.() ?? false)) return "MESSAGE";
65
76
  const willSteer = chrome.willEnterSteer?.(text) ?? text.trim().length > 0;
66
77
  return willSteer ? "STEER" : "FOLLOW-UP";
@@ -73,12 +84,27 @@ function lowerRailHint(theme: ClioTheme, chrome: EditorChrome): string {
73
84
  );
74
85
  }
75
86
 
76
- function renderEmptyPrompt(line: string, width: number, theme: ClioTheme): string {
87
+ /**
88
+ * The permission keys on the composer rail, fitted like the dialog footer so
89
+ * both surfaces narrow in the same order and never drop allow or stop first.
90
+ * The rule spends three columns around a right label, hence the subtraction.
91
+ */
92
+ function confirmRailHint(theme: ClioTheme, width: number, hasDraft: boolean): string {
93
+ return theme.fg("warning", fitHintEntries(permissionHintEntries(hasDraft), Math.max(1, width - 3)));
94
+ }
95
+
96
+ function modeToken(mode: ComposerMode): "action" | "accentDeep" | "warning" {
97
+ if (mode === "STEER") return "action";
98
+ if (mode === "CONFIRM") return "warning";
99
+ return "accentDeep";
100
+ }
101
+
102
+ function renderEmptyPrompt(line: string, width: number, theme: ClioTheme, text = EMPTY_PROMPT): string {
77
103
  const cursorAt = line.indexOf(REVERSE_VIDEO_BLANK);
78
104
  if (cursorAt < 0) return line;
79
105
  const afterCursorAt = cursorAt + REVERSE_VIDEO_BLANK.length;
80
106
  const available = Math.max(0, width - 1);
81
- const prompt = truncateToWidth(theme.fg("dim", EMPTY_PROMPT), available, "…", false);
107
+ const prompt = truncateToWidth(theme.fg("dim", text), available, "…", false);
82
108
  const consumed = visibleWidth(prompt);
83
109
  return `${line.slice(0, afterCursorAt)}${prompt}${line.slice(afterCursorAt + consumed)}`;
84
110
  }
@@ -111,12 +137,12 @@ export class ClioEditor extends Editor {
111
137
  // The base editor has already fitted the scroll indicator to this width.
112
138
  // Prefix the mode and trim only the indicator rail's trailing fill, keeping
113
139
  // its direction/count text and its own narrow-width fallback intact.
114
- const modeLabel = theme.style(mode === "STEER" ? "action" : "accentDeep", mode, { bold: true });
140
+ const modeLabel = theme.style(modeToken(mode), mode, { bold: true });
115
141
  lines[0] = truncateToWidth(`${modeLabel} ${lines[0] ?? ""}`, safeWidth, "", true);
116
142
  } else {
117
143
  lines[0] = rule(theme, safeWidth, {
118
144
  left: mode,
119
- leftToken: mode === "STEER" ? "action" : "accentDeep",
145
+ leftToken: modeToken(mode),
120
146
  right: styledRailLabel(theme, this.chrome),
121
147
  fillToken: "frameStrong",
122
148
  rightRaw: true,
@@ -124,10 +150,21 @@ export class ClioEditor extends Editor {
124
150
  });
125
151
  }
126
152
 
127
- if (text.length === 0 && lines[1]) lines[1] = renderEmptyPrompt(lines[1], safeWidth, theme);
153
+ if (text.length === 0 && lines[1]) {
154
+ lines[1] = renderEmptyPrompt(lines[1], safeWidth, theme, mode === "CONFIRM" ? CONFIRM_PROMPT : EMPTY_PROMPT);
155
+ }
128
156
 
129
157
  const bottomRail = findBottomRail(lines, safeWidth);
130
- if (bottomRail >= 0 && safeWidth >= MIN_HINT_WIDTH) {
158
+ // The confirm keys render at every width: the send hint is a convenience
159
+ // that a narrow composer can drop, the allow and deny keys are not.
160
+ if (bottomRail >= 0 && mode === "CONFIRM") {
161
+ lines[bottomRail] = rule(theme, safeWidth, {
162
+ right: confirmRailHint(theme, safeWidth, text.length > 0),
163
+ fillToken: "frameStrong",
164
+ rightRaw: true,
165
+ rightTail: theme.style("frameStrong", "─", { bold: true }),
166
+ });
167
+ } else if (bottomRail >= 0 && safeWidth >= MIN_HINT_WIDTH) {
131
168
  lines[bottomRail] = rule(theme, safeWidth, {
132
169
  right: lowerRailHint(theme, this.chrome),
133
170
  fillToken: "frameStrong",
@@ -156,6 +156,16 @@ export function renderContextMeterGrid(
156
156
  return lines;
157
157
  }
158
158
 
159
+ /**
160
+ * Evicted tokens live outside the window: the working-set projection removed
161
+ * them from what the model sees, so they are not a meter category and must
162
+ * not claim cells or a legend row. One line after the legend names them.
163
+ */
164
+ export function renderEvictedTokensLine(evictedTokens: number, theme: ClioTheme = clioTheme()): string {
165
+ const tokens = Math.round(Math.max(0, evictedTokens)).toLocaleString("en-US");
166
+ return `${theme.fg("dim", GLYPH.contextReserve)} ${theme.fg("muted", "evicted (outside window)")} ${theme.fg("dim", `${tokens} tokens`)}`;
167
+ }
168
+
159
169
  /** A small colored swatch for a category, for inline legends. */
160
170
  export function contextCategorySwatch(category: ContextLedgerCategory, theme: ClioTheme = clioTheme()): string {
161
171
  return theme.fg(CONTEXT_CATEGORY_TOKEN[category], contextCategoryGlyph(category));