@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
@@ -1,5 +1,6 @@
1
1
  import { mkdirSync, writeFileSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
+ import { BusChannels } from "../core/bus-events.js";
3
4
  import type { ClioSettings } from "../core/config.js";
4
5
  import type { SafeEventBus } from "../core/event-bus.js";
5
6
  import type { PendingSkillRequest } from "../core/skill-activation.js";
@@ -28,6 +29,7 @@ import { type ChatPanel, createChatPanel } from "./chat-panel.js";
28
29
  import { rehydrateChatPanelFromTurns } from "./chat-renderer.js";
29
30
  import { runCompactWithNotice } from "./command-fallbacks.js";
30
31
  import { appendNotice, appendOperatorCommand } from "./command-output.js";
32
+ import { runOperatorRecall } from "./context-recall-command.js";
31
33
  import { renderSessionHtml } from "./export-html/index.js";
32
34
  import { dateLocal } from "./format-time.js";
33
35
  import type { SettingsCenterRowId, SettingsSectionId } from "./overlays/settings.js";
@@ -100,8 +102,10 @@ export interface InteractiveSlashRuntimeDeps {
100
102
  * Leaf lookup for `/export`, so the export follows the branch the session
101
103
  * is actually on. current.jsonl still holds the abandoned turns after a
102
104
  * `/tree` pin, and an unscoped rehydrate reproduced them (issue #109).
105
+ * `/context recall` uses the same leaf for its fold, plus `appendEntry` for
106
+ * the `contextRecall` record.
103
107
  */
104
- session?: Pick<SessionContract, "tree">;
108
+ session?: Pick<SessionContract, "tree" | "current" | "appendEntry">;
105
109
  expandSubmit: (text: string) => Promise<InteractiveSlashSubmitExpansion>;
106
110
  openAskUser: AskUserHandler;
107
111
  openSkillsHub: () => void;
@@ -512,6 +516,38 @@ export function createInteractiveSlashRuntime(deps: InteractiveSlashRuntimeDeps)
512
516
  }
513
517
  deps.openContextReset();
514
518
  },
519
+ ...(deps.session
520
+ ? {
521
+ runContextRecall: (ref: string) => {
522
+ const session = deps.session;
523
+ if (!session) return;
524
+ const outcome = runOperatorRecall(ref, {
525
+ hasSession: () => session.current() !== null,
526
+ readEntries: () => {
527
+ const sessionId = deps.chat.getSessionId();
528
+ return sessionId ? deps.readStructuredEntries(sessionId) : [];
529
+ },
530
+ activeLeafTurnId: () => {
531
+ const meta = session.current();
532
+ return meta ? (session.tree(meta.id).leafId ?? undefined) : undefined;
533
+ },
534
+ appendEntry: (entry) => session.appendEntry(entry),
535
+ onRecalled: (payload) => deps.bus.emit(BusChannels.ContextRecalled, payload),
536
+ ...(deps.now ? { now: () => (deps.now?.() ?? new Date()).getTime() } : {}),
537
+ });
538
+ if (!outcome.ok) {
539
+ appendCommandNotice("error", outcome.message);
540
+ return;
541
+ }
542
+ appendCommandNotice("success", outcome.headline);
543
+ // The body is transcript output, not a notice: notices collapse
544
+ // newlines, and a recalled build log is worth nothing on one line.
545
+ // It is a replay block either way, so it never enters model context.
546
+ deps.io.stdout(`${outcome.body}\n`);
547
+ deps.requestRender();
548
+ },
549
+ }
550
+ : {}),
515
551
  runContextRefresh: () => {
516
552
  if (!deps.onContextRefresh) {
517
553
  deps.io.stderr("[/context refresh] context refresh not wired; pass onContextRefresh to startInteractive\n");
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  describeTaskMemoryActivity,
3
+ type MemoryProposalResult,
3
4
  type MemoryRecord,
4
5
  type TaskMemoryActivityEvent,
5
6
  type TaskMemoryEntry,
@@ -182,6 +183,7 @@ function memorySignature(status: TaskMemoryOperatorStatus, records: ReadonlyArra
182
183
 
183
184
  interface OpenMemoryOverlayOptions {
184
185
  onClose?: () => void;
186
+ onPromote?: (entry: TaskMemoryEntry, scope: "repo" | "global") => Promise<MemoryProposalResult>;
185
187
  }
186
188
 
187
189
  /** Master-detail memory view: status header, grouped list, scrollable detail pane. */
@@ -189,12 +191,18 @@ export class MemoryOverlayView implements Component {
189
191
  private readonly list: ListOverlayView;
190
192
  private signature: string | null = null;
191
193
  private renderMemo: { width: number; status: string; listLines: string[]; lines: string[] } | null = null;
194
+ private pendingGlobalEntryId: string | null = null;
195
+ private promotionMessage: { token: "muted" | "warning" | "success" | "error"; text: string } | null = null;
196
+ private promotionInFlight = false;
192
197
 
193
198
  constructor(
194
199
  private readonly getStatus: () => TaskMemoryOperatorStatus,
195
200
  private readonly getRecords: () => ReadonlyArray<MemoryRecord>,
196
201
  onClose: () => void,
197
202
  onChange: () => void,
203
+ private readonly promotion?: {
204
+ onPromote: (entry: TaskMemoryEntry, scope: "repo" | "global") => Promise<MemoryProposalResult>;
205
+ },
198
206
  ) {
199
207
  this.list = new ListOverlayView(
200
208
  {
@@ -203,6 +211,18 @@ export class MemoryOverlayView implements Component {
203
211
  filterable: true,
204
212
  layout: "split",
205
213
  emptyMessage: EMPTY_MESSAGE,
214
+ ...(promotion
215
+ ? {
216
+ hints: [
217
+ { key: "p", verb: "propose repo" },
218
+ { key: "g", verb: "propose global" },
219
+ ],
220
+ actions: {
221
+ p: (item: ListOverlayItem) => this.promote(item, "repo", onChange),
222
+ g: (item: ListOverlayItem) => this.promote(item, "global", onChange),
223
+ },
224
+ }
225
+ : {}),
206
226
  onClose,
207
227
  },
208
228
  onChange,
@@ -217,11 +237,21 @@ export class MemoryOverlayView implements Component {
217
237
  render(width: number): string[] {
218
238
  const status = this.sync();
219
239
  const statusLine = formatMemoryStatusLine(status, width);
240
+ const promotionLine =
241
+ this.promotionMessage === null
242
+ ? null
243
+ : fitUnits(clioTheme(), "", [clioTheme().fg(this.promotionMessage.token, this.promotionMessage.text)], width);
244
+ const statusKey = promotionLine === null ? statusLine : `${statusLine}\n${promotionLine}`;
220
245
  const listLines = this.list.render(width);
221
246
  const memo = this.renderMemo;
222
- if (memo && memo.width === width && memo.status === statusLine && memo.listLines === listLines) return memo.lines;
223
- const lines = [statusLine, rule(clioTheme(), width), ...listLines];
224
- this.renderMemo = { width, status: statusLine, listLines, lines };
247
+ if (memo && memo.width === width && memo.status === statusKey && memo.listLines === listLines) return memo.lines;
248
+ const lines = [
249
+ statusLine,
250
+ ...(promotionLine === null ? [] : [promotionLine]),
251
+ rule(clioTheme(), width),
252
+ ...listLines,
253
+ ];
254
+ this.renderMemo = { width, status: statusKey, listLines, lines };
225
255
  return lines;
226
256
  }
227
257
 
@@ -247,9 +277,63 @@ export class MemoryOverlayView implements Component {
247
277
  }
248
278
  return status;
249
279
  }
280
+
281
+ private promote(item: ListOverlayItem, scope: "repo" | "global", onChange: () => void): void {
282
+ if (this.promotion === undefined || this.promotionInFlight) return;
283
+ const entry = this.selectedBankEntry(item);
284
+ if (entry === null) {
285
+ this.pendingGlobalEntryId = null;
286
+ this.promotionMessage = { token: "warning", text: "select a knowledge or procedural task-bank entry" };
287
+ this.invalidate();
288
+ onChange();
289
+ return;
290
+ }
291
+ if (scope === "global" && this.pendingGlobalEntryId !== entry.id) {
292
+ this.pendingGlobalEntryId = entry.id;
293
+ this.promotionMessage = {
294
+ token: "warning",
295
+ text: `global scope broadens applicability for ${entry.id}; press g again to acknowledge`,
296
+ };
297
+ this.invalidate();
298
+ onChange();
299
+ return;
300
+ }
301
+ this.pendingGlobalEntryId = null;
302
+ this.promotionInFlight = true;
303
+ this.promotionMessage = { token: "muted", text: `proposing ${entry.id} with ${scope} scope` };
304
+ this.invalidate();
305
+ onChange();
306
+ void this.promotion
307
+ .onPromote(entry, scope)
308
+ .then((result) => {
309
+ this.promotionMessage = {
310
+ token: "success",
311
+ text: `${result.created ? "proposed" : "found existing"} ${result.record.id}; review, then run clio-coder memory approve ${result.record.id}`,
312
+ };
313
+ })
314
+ .catch((error: unknown) => {
315
+ this.promotionMessage = {
316
+ token: "error",
317
+ text: `promotion failed: ${error instanceof Error ? error.message : String(error)}`,
318
+ };
319
+ })
320
+ .finally(() => {
321
+ this.promotionInFlight = false;
322
+ this.invalidate();
323
+ onChange();
324
+ });
325
+ }
326
+
327
+ private selectedBankEntry(item: ListOverlayItem): TaskMemoryEntry | null {
328
+ if (!item.id.startsWith("bank:")) return null;
329
+ const entryId = item.id.slice("bank:".length);
330
+ const entry = bankEntries(this.getStatus().bank).find((candidate) => candidate.id === entryId);
331
+ if (entry === undefined || entry.kind === "status") return null;
332
+ return entry;
333
+ }
250
334
  }
251
335
 
252
- /** Mount the read-only durable-lessons and live task-bank view. */
336
+ /** Mount durable lessons and the live task bank with reviewed promotion actions. */
253
337
  export function openMemoryOverlay(
254
338
  tui: TUI,
255
339
  getStatus: () => TaskMemoryOperatorStatus,
@@ -261,6 +345,7 @@ export function openMemoryOverlay(
261
345
  getRecords,
262
346
  () => options.onClose?.(),
263
347
  () => tui.requestRender(),
348
+ options.onPromote ? { onPromote: options.onPromote } : undefined,
264
349
  );
265
350
  const handle = showClioOverlayFrame(tui, view, {
266
351
  anchor: "center",
@@ -0,0 +1,21 @@
1
+ import { foldWorkingSet } from "../domains/context/working-set/fold.js";
2
+ import { projectWorkingSet } from "../domains/context/working-set/project.js";
3
+ import type { SessionEntry } from "../domains/session/entries.js";
4
+ import type { AgentMessage } from "../engine/types.js";
5
+ import { buildReplayAgentMessagesFromTurns, type RehydrateChatPanelOptions } from "./chat-renderer.js";
6
+
7
+ /**
8
+ * Build provider-facing replay messages from the durable session ledger.
9
+ * Projection always honors existing eviction and recall entries; the enabled
10
+ * setting gates creation of new evictions, not replay of durable state.
11
+ * Visible transcript and export callers intentionally keep using the raw
12
+ * rehydration helpers so eviction remains a model projection, not data loss.
13
+ */
14
+ export function buildModelReplayAgentMessagesFromTurns(
15
+ entries: ReadonlyArray<SessionEntry>,
16
+ options: RehydrateChatPanelOptions = {},
17
+ ): AgentMessage[] {
18
+ const activeLeafTurnId = options.activeLeafTurnId ?? options.uptoTurnId;
19
+ const projected = projectWorkingSet(entries, foldWorkingSet(entries, activeLeafTurnId));
20
+ return buildReplayAgentMessagesFromTurns(projected, options);
21
+ }
@@ -77,7 +77,7 @@ export function createOverlayAskUserLifecycle(deps: OverlayAskUserLifecycleDeps)
77
77
  const activeSession = ensureSession();
78
78
  if (!activeSession) return cancelledAskUserResult();
79
79
  pendingCancel = cancel;
80
- const result = await activeSession.ask(questions);
80
+ const result = await activeSession.ask(questions, invokeOptions?.decisionPresentation);
81
81
  if (result.cancelled === true || !toolBacked) {
82
82
  if (result.cancelled === true) cancelledForTurn = true;
83
83
  close();
@@ -478,16 +478,19 @@ export function showClioOverlayFrame(
478
478
  const { title, footerHint, tone, width, visible, maxHeight, margin, ...overlayOptions } = options;
479
479
  const boxWidth = typeof width === "number" ? width : 0;
480
480
  const frame = new ClioOverlayFrame(child, title, footerHint, boxWidth, frameAlignForAnchor(options.anchor), tone);
481
- const marginRows = typeof margin === "number" ? margin * 2 : (margin?.top ?? 0) + (margin?.bottom ?? 0);
482
481
  return tui.showOverlay(frame, {
483
482
  ...overlayOptions,
484
483
  ...(margin !== undefined ? { margin } : {}),
485
484
  width: "100%",
486
485
  visible: (termWidth, termHeight) => {
486
+ const shown = visible ? visible(termWidth, termHeight) : true;
487
+ // A caller may update an object margin in its visibility callback to
488
+ // follow live content geometry. Read it after that callback, every frame.
489
+ const marginRows = typeof margin === "number" ? margin * 2 : (margin?.top ?? 0) + (margin?.bottom ?? 0);
487
490
  const available = Math.max(1, termHeight - marginRows);
488
491
  const requested = resolveRowSize(maxHeight, termHeight);
489
492
  frame.setRowBudget(requested === null ? available : Math.min(requested, available));
490
- return visible ? visible(termWidth, termHeight) : true;
493
+ return shown;
491
494
  },
492
495
  });
493
496
  }
@@ -1,6 +1,13 @@
1
+ import type { ClioSettings } from "../core/config.js";
1
2
  import type { SafeEventBus } from "../core/event-bus.js";
3
+ import { foldWorkingSet } from "../domains/context/working-set/fold.js";
2
4
  import type { DispatchContract } from "../domains/dispatch/index.js";
3
- import { loadMemoryRecordsSync, type MemoryRecord } from "../domains/memory/index.js";
5
+ import {
6
+ canonicalMemoryRepositoryIdentity,
7
+ loadMemoryRecordsSync,
8
+ type MemoryRecord,
9
+ proposeMemoryPromotion,
10
+ } from "../domains/memory/index.js";
4
11
  import type { ObservabilityContract } from "../domains/observability/index.js";
5
12
  import type { ContextLedger } from "../domains/session/context-ledger.js";
6
13
  import type { SessionMeta } from "../domains/session/index.js";
@@ -56,6 +63,8 @@ export interface OverlayGeneralOpenersDeps {
56
63
  stateDir: string;
57
64
  getSessionMeta: () => SessionMeta | null;
58
65
  readSessionEntries?: ArtifactProviderDeps["readSessionEntries"];
66
+ /** Live settings, so `/context` can state the configured working-set policy. */
67
+ getSettings?: () => Readonly<ClioSettings>;
59
68
  terminal: { columns: number };
60
69
  dispatchBoard: ReturnType<typeof createDispatchBoardView>;
61
70
  startDispatchBoardTicker: () => void;
@@ -107,6 +116,15 @@ export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): Ov
107
116
  deps.transitions.handle = openContextOverlayFactory(deps.tui, deps.getContextLedger, {
108
117
  bus: deps.bus,
109
118
  chat: deps.contextChat,
119
+ getWorkingSet: () => {
120
+ const readSessionEntries = deps.readSessionEntries;
121
+ if (!readSessionEntries) return null;
122
+ return foldWorkingSet(readSessionEntries(), deps.getSessionMeta()?.pinnedLeafTurnId ?? undefined);
123
+ },
124
+ getWorkingSetConfig: () => {
125
+ const workingSet = deps.getSettings?.().context.workingSet;
126
+ return workingSet ? { enabled: workingSet.enabled, policy: workingSet.policy } : null;
127
+ },
110
128
  });
111
129
  deps.requestRender();
112
130
  };
@@ -212,6 +230,32 @@ export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): Ov
212
230
  deps.transitions.state = "memory";
213
231
  deps.transitions.handle = openMemoryOverlayFactory(deps.tui, deps.getTaskMemoryStatus, () => records, {
214
232
  onClose: deps.closeOverlay,
233
+ onPromote: async (entry, scope) => {
234
+ const meta = deps.getSessionMeta();
235
+ if (!meta) throw new Error("memory promotion requires an active session");
236
+ const selection =
237
+ scope === "global"
238
+ ? ({ scope: "global", acknowledgeGlobal: true } as const)
239
+ : (() => {
240
+ const repository = canonicalMemoryRepositoryIdentity(meta.cwd);
241
+ if (repository === null) {
242
+ throw new Error("repo promotion requires a canonical active repository identity");
243
+ }
244
+ return { scope: "repo", repository } as const;
245
+ })();
246
+ const result = await proposeMemoryPromotion(
247
+ deps.dataDir,
248
+ {
249
+ kind: "task-bank-entry",
250
+ sessionId: meta.id,
251
+ evidenceRefs: [`session-${meta.id}`],
252
+ entry,
253
+ },
254
+ selection,
255
+ );
256
+ records = loadMemoryRecordsSync(deps.dataDir);
257
+ return result;
258
+ },
215
259
  });
216
260
  deps.requestRender();
217
261
  };
@@ -255,6 +299,7 @@ export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): Ov
255
299
  const dispatchBoardHint = (): string => {
256
300
  const row = deps.dispatchBoard.selectedRow();
257
301
  const entries = [{ key: "↑↓", verb: "select" }];
302
+ if (row) entries.push({ key: "Enter", verb: deps.dispatchBoard.detailExpanded() ? "hide detail" : "detail" });
258
303
  if (row && isDispatchBoardRowSteerable(row)) entries.push({ key: "s", verb: "steer" });
259
304
  if (row && isDispatchBoardRowCancellable(row)) entries.push({ key: "x", verb: "cancel" });
260
305
  return buildHint(entries);
@@ -31,6 +31,19 @@ export interface PermissionOverlayKeyDeps {
31
31
  cancelPermission: () => void;
32
32
  confirmPermission: () => void;
33
33
  stopTurnFromPermission: () => void;
34
+ /**
35
+ * Whether the composer holds text. Enter must not allow a parked call while
36
+ * it does (issue #186): an operator who typed a message and pressed the
37
+ * habitual send key meant to send, and on a safety rail the ambiguous key
38
+ * resolves away from "allow". Absent means an empty composer.
39
+ */
40
+ composerHasDraft?: () => boolean;
41
+ /**
42
+ * Deliver a draft-editing key to the composer while the prompt owns input,
43
+ * so the draft that makes Enter inert can be cleared without first denying
44
+ * the call. Only the deletion keys in `isDraftEditKey` arrive here.
45
+ */
46
+ editDraft?: (data: string) => void;
34
47
  }
35
48
 
36
49
  export interface DispatchBoardOverlayKeyDeps {
@@ -39,6 +52,8 @@ export interface DispatchBoardOverlayKeyDeps {
39
52
  selectNextDispatch: () => void;
40
53
  steerSelectedDispatch: () => void;
41
54
  cancelSelectedDispatch: () => void;
55
+ /** Open or close the selected run's worker-progress detail. */
56
+ toggleSelectedDispatchDetail: () => void;
42
57
  }
43
58
 
44
59
  interface CloseOverlayKeyDeps {
@@ -55,10 +70,29 @@ export function isEscapeKey(data: string): boolean {
55
70
  return matchesKey(data, "escape") && !isKeyRelease(data);
56
71
  }
57
72
 
73
+ const DRAFT_EDIT_KEYS = ["backspace", "delete", "ctrl+u", "ctrl+w", "alt+backspace", "ctrl+k"] as const;
74
+
75
+ /**
76
+ * The keys that only ever remove text from the composer. They are the one
77
+ * class of input a pending permission prompt lets through, because they
78
+ * cannot type a command, cannot submit, and are the way out of the inert-Enter
79
+ * state a draft puts the prompt in.
80
+ */
81
+ function isDraftEditKey(data: string): boolean {
82
+ return !isKeyRelease(data) && DRAFT_EDIT_KEYS.some((key) => matchesKey(data, key));
83
+ }
84
+
58
85
  /** Pure permission overlay key router: returns true when the input was consumed. */
59
86
  export function routePermissionOverlayKey(data: string, deps: PermissionOverlayKeyDeps): boolean {
60
87
  if (matchesKey(data, "enter") && !isKeyRelease(data)) {
61
- deps.confirmPermission();
88
+ // Consumed either way: with a draft the press changes nothing, and the
89
+ // composer rail says what clears it. Letting it fall through would hand
90
+ // Enter to the editor, which is the send path this guard exists to block.
91
+ if (!(deps.composerHasDraft?.() ?? false)) deps.confirmPermission();
92
+ return true;
93
+ }
94
+ if (deps.editDraft && isDraftEditKey(data)) {
95
+ deps.editDraft(data);
62
96
  return true;
63
97
  }
64
98
  if (isEscapeKey(data)) {
@@ -98,6 +132,12 @@ export function routeDispatchBoardOverlayKey(data: string, deps: DispatchBoardOv
98
132
  deps.cancelSelectedDispatch();
99
133
  return true;
100
134
  }
135
+ // Worker progress is expanded detail the operator asks for. The default list
136
+ // stays compact so a fan-out of scouts costs one card each.
137
+ if (matchesKey(data, "enter")) {
138
+ deps.toggleSelectedDispatchDetail();
139
+ return true;
140
+ }
101
141
  return false;
102
142
  }
103
143
 
@@ -12,7 +12,9 @@ import {
12
12
  createPermissionOverlayBody,
13
13
  PERMISSION_OVERLAY_WIDTH,
14
14
  permissionOverlayHint,
15
+ permissionOverlayPlacement,
15
16
  permissionOverlayTitle,
17
+ permissionOverlayTone,
16
18
  } from "./permission-overlay.js";
17
19
 
18
20
  export * from "./overlay-key-routing.js";
@@ -82,7 +84,7 @@ export interface OverlayLifecycleRuntimeDeps {
82
84
  /** Rescopes the footer's last-turn line when a session overlay changes the branch. */
83
85
  setLastTurnSummary?: (summary: import("./status/index.js").TurnSummary | null) => void;
84
86
  keybindings: ReturnType<typeof import("./keybinding-manager.js").createKeybindingManager>;
85
- editor: Pick<import("./clio-editor.js").ClioEditor, "getText" | "setText">;
87
+ editor: Pick<import("./clio-editor.js").ClioEditor, "getText" | "render" | "setText">;
86
88
  getSlashContext: () => import("./slash-commands.js").SlashCommandContext;
87
89
  showOverlayFrame?: typeof showClioOverlayFrame;
88
90
  openAuthDialog?: typeof import("./overlays/auth-dialog.js").openAuthDialog;
@@ -194,6 +196,7 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
194
196
  cancelPendingAskUser: () => overlayAskUser?.cancelPending() ?? false,
195
197
  finishAuth: (dismiss) => overlayAuth.finish(dismiss),
196
198
  onPermissionOverlayClosed: () => overlayPermission?.onPermissionOverlayClosed(),
199
+ onOverlayClosed: () => overlayPermission?.retryPending(),
197
200
  });
198
201
  const closeOverlay = overlayTransitions.close;
199
202
 
@@ -227,10 +230,13 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
227
230
  if (overlayTransitions.state !== "closed") return false;
228
231
  overlayTransitions.state = "permission-confirm";
229
232
  overlayTransitions.handle = showOverlayFrame(tui, createPermissionOverlayBody(view), {
230
- anchor: "center",
233
+ ...permissionOverlayPlacement(tui, editor, footer.view),
231
234
  width: PERMISSION_OVERLAY_WIDTH,
232
- title: permissionOverlayTitle(),
233
- footerHint: (innerWidth) => permissionOverlayHint(innerWidth),
235
+ title: permissionOverlayTitle(view),
236
+ tone: permissionOverlayTone(view),
237
+ // Read per frame: the footer names what Enter does right now, and
238
+ // that depends on whether the composer holds a draft.
239
+ footerHint: (innerWidth) => permissionOverlayHint(innerWidth, editor.getText().length > 0),
234
240
  });
235
241
  tui.requestRender();
236
242
  return true;
@@ -351,6 +357,7 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
351
357
  stateDir: deps.app.stateDir,
352
358
  getSessionMeta: () => deps.app.session?.current() ?? null,
353
359
  ...(deps.app.readSessionEntries ? { readSessionEntries: deps.app.readSessionEntries } : {}),
360
+ ...(deps.app.getSettings ? { getSettings: deps.app.getSettings } : {}),
354
361
  terminal,
355
362
  dispatchBoard,
356
363
  startDispatchBoardTicker: () => interactiveTickers.startDispatchBoardTicker(),
@@ -2,9 +2,11 @@ import { BusChannels, type PermissionRequestedPayload } from "../core/bus-events
2
2
  import type { SafeEventBus } from "../core/event-bus.js";
3
3
  import { ToolNames } from "../core/tool-names.js";
4
4
  import type { DispatchContract } from "../domains/dispatch/contract.js";
5
- import type { ClassifierCall } from "../domains/safety/action-classifier.js";
5
+ import type { ActionClass, ClassifierCall } from "../domains/safety/action-classifier.js";
6
6
  import { sanitizeCallTargetText } from "../domains/safety/call-target.js";
7
7
  import type { SafetyDecision } from "../domains/safety/contract.js";
8
+ import { decisionActionClass } from "../domains/safety/decision-presentation.js";
9
+ import { askUserExposure } from "../tools/ask-user.js";
8
10
  import type { PermissionRequiredMeta, ToolRegistry } from "../tools/registry.js";
9
11
  import { approvalParkedNotice, autonomyDeniedNotice, workerEscalationNotice } from "./bus-notices.js";
10
12
  import type { ToolApprovalStateEvent } from "./chat-loop.js";
@@ -29,7 +31,7 @@ interface WorkerEscalationEntry {
29
31
  requestId: string;
30
32
  agentId: string;
31
33
  tool: string;
32
- actionClass: string;
34
+ actionClass: ActionClass;
33
35
  axis: ApprovalRequestView["axis"];
34
36
  reason: string;
35
37
  target?: string;
@@ -60,6 +62,14 @@ export interface OverlayPermissionLifecycle {
60
62
  */
61
63
  stopTurn(): void;
62
64
  onPermissionOverlayClosed(): void;
65
+ /**
66
+ * Re-present whatever is still parked once no overlay holds the screen. A
67
+ * request that arrived while `/context` or a picker was open could only be
68
+ * announced, and nothing re-attempted the dialog when that overlay closed:
69
+ * the transcript said awaiting approval, the footer said confirm, and the
70
+ * operator had no key to press (issue #186).
71
+ */
72
+ retryPending(): void;
63
73
  dispose(): void;
64
74
  }
65
75
 
@@ -114,7 +124,7 @@ function mainApprovalRequestView(
114
124
  (axisFromDecision.kind === "net"
115
125
  ? { kind: "net" as const, ruleId: axisFromDecision.ruleId }
116
126
  : { kind: "autonomy" as const, level: autonomy });
117
- const target = describeCallTarget(call.args);
127
+ const target = describeCallTarget(call.tool, call.args);
118
128
  return {
119
129
  requestId: meta?.requestId ?? "permission-pending",
120
130
  tool: call.tool,
@@ -126,6 +136,7 @@ function mainApprovalRequestView(
126
136
  ...(call.tool === ToolNames.Dispatch && decision.kind === "ask"
127
137
  ? { artifact: { kind: "dispatch-plan" as const, text: decision.rejection.detail } }
128
138
  : {}),
139
+ ...(call.tool === ToolNames.AskUser ? { exposure: askUserExposure(call.args) } : {}),
129
140
  ...(target.length > 0 ? { target } : {}),
130
141
  ...(queueDepth !== undefined && queueDepth > 1 ? { queueDepth } : {}),
131
142
  };
@@ -141,7 +152,7 @@ function workerEscalationEntry(payload: PermissionRequestedPayload, autonomy: st
141
152
  requestId: payload.requestId,
142
153
  agentId: typeof payload.agentId === "string" ? payload.agentId : "worker",
143
154
  tool: typeof payload.tool === "string" ? payload.tool : "unknown",
144
- actionClass: typeof payload.actionClass === "string" ? payload.actionClass : "unknown",
155
+ actionClass: decisionActionClass(payload.actionClass),
145
156
  axis:
146
157
  axisViewFromId(typeof payload.axis === "string" ? payload.axis : undefined, autonomy) ??
147
158
  (typeof payload.ruleId === "string"
@@ -188,6 +199,12 @@ export function createOverlayPermissionLifecycle(deps: OverlayPermissionLifecycl
188
199
  const next = workerQueue.shift();
189
200
  if (next) openWorker(next);
190
201
  };
202
+ const retryPending = (): void => {
203
+ maybeOpenWorker();
204
+ if (deps.getOverlayState() === "closed" && deps.toolRegistry?.hasParkedCalls()) {
205
+ deps.toolRegistry.renotifyHead();
206
+ }
207
+ };
191
208
 
192
209
  const unsubscribePermission =
193
210
  deps.toolRegistry?.onPermissionRequired((call, decision, meta) => {
@@ -310,10 +327,7 @@ export function createOverlayPermissionLifecycle(deps: OverlayPermissionLifecycl
310
327
  workerQueue.length = 0;
311
328
  return;
312
329
  }
313
- maybeOpenWorker();
314
- if (deps.getOverlayState() === "closed" && deps.toolRegistry?.hasParkedCalls()) {
315
- deps.toolRegistry.renotifyHead();
316
- }
330
+ retryPending();
317
331
  };
318
332
 
319
333
  return {
@@ -336,6 +350,7 @@ export function createOverlayPermissionLifecycle(deps: OverlayPermissionLifecycl
336
350
  );
337
351
  },
338
352
  onPermissionOverlayClosed,
353
+ retryPending,
339
354
  dispose: () => {
340
355
  unsubscribePermission();
341
356
  unsubscribeWorker();
@@ -3,9 +3,10 @@ import type { SessionContract, SessionEntry } from "../domains/session/index.js"
3
3
  import type { TUI } from "../engine/tui.js";
4
4
  import type { ChatLoop } from "./chat-loop.js";
5
5
  import type { ChatPanel } from "./chat-panel.js";
6
- import { buildReplayAgentMessagesFromTurns, rehydrateChatPanelFromTurns } from "./chat-renderer.js";
6
+ import { rehydrateChatPanelFromTurns } from "./chat-renderer.js";
7
7
  import { emitCommandNotice } from "./command-fallbacks.js";
8
8
  import type { InteractiveNoticeLevel } from "./interactive-subscriptions.js";
9
+ import { buildModelReplayAgentMessagesFromTurns } from "./model-session-replay.js";
9
10
  import type { OverlayTransitions } from "./overlay-transitions.js";
10
11
  import { openCwdFallbackOverlay } from "./overlays/cwd-fallback.js";
11
12
  import { openMessagePickerOverlay } from "./overlays/message-picker.js";
@@ -159,7 +160,7 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
159
160
  const replayOptions = leafTurnId ? { activeLeafTurnId: leafTurnId } : {};
160
161
  deps.resetTranscript();
161
162
  rehydrateChatPanelFromTurns(deps.chatPanel, turns, replayOptions);
162
- const replayMessages = buildReplayAgentMessagesFromTurns(turns, replayOptions);
163
+ const replayMessages = buildModelReplayAgentMessagesFromTurns(turns, replayOptions);
163
164
  deps.chat.resetForSession(leafTurnId, replayMessages);
164
165
  rescopeToBranch(session, turns, leafTurnId);
165
166
  } catch (error) {
@@ -209,7 +210,7 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
209
210
  const turns = deps.readStructuredEntries(sessionId);
210
211
  deps.resetTranscript();
211
212
  rehydrateChatPanelFromTurns(deps.chatPanel, turns, { uptoTurnId: turnId });
212
- const replayMessages = buildReplayAgentMessagesFromTurns(turns, { uptoTurnId: turnId });
213
+ const replayMessages = buildModelReplayAgentMessagesFromTurns(turns, { uptoTurnId: turnId });
213
214
  deps.chat.resetForSession(turnId, replayMessages);
214
215
  // The same branch the transcript above was just scoped to. Without the
215
216
  // leaf, /cost, the footer Σ, and the last-turn line kept reporting the
@@ -287,8 +288,11 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
287
288
  try {
288
289
  const turns = deps.readStructuredEntries(forkedSessionId);
289
290
  rehydrateChatPanelFromTurns(deps.chatPanel, turns);
290
- const replayMessages = buildReplayAgentMessagesFromTurns(turns);
291
291
  const leafTurnId = session.tree(forkedSessionId).leafId ?? parentTurnId;
292
+ const replayMessages = buildModelReplayAgentMessagesFromTurns(
293
+ turns,
294
+ leafTurnId ? { activeLeafTurnId: leafTurnId } : {},
295
+ );
292
296
  deps.chat.resetForSession(leafTurnId, replayMessages);
293
297
  rescopeToBranch(session, turns, leafTurnId);
294
298
  } catch (error) {
@@ -9,6 +9,12 @@ export interface OverlayTransitionsDeps {
9
9
  cancelPendingAskUser: () => boolean;
10
10
  finishAuth: (dismiss: boolean) => void;
11
11
  onPermissionOverlayClosed: () => void;
12
+ /**
13
+ * Runs after any overlay has closed and the state is back to `closed`. The
14
+ * permission lifecycle uses it to re-present a call that parked while this
15
+ * overlay held the screen; nothing else re-attempts that dialog.
16
+ */
17
+ onOverlayClosed?: () => void;
12
18
  }
13
19
 
14
20
  export interface OverlayTransitions {
@@ -26,7 +32,11 @@ export function createOverlayTransitions(deps: OverlayTransitionsDeps): OverlayT
26
32
  return state;
27
33
  },
28
34
  set state(next) {
35
+ const closing = state !== "closed" && next === "closed";
29
36
  state = next;
37
+ // The ask-user and auth lifecycles leave by assigning the state rather
38
+ // than through `close`, so the hook fires here for them.
39
+ if (closing) deps.onOverlayClosed?.();
30
40
  },
31
41
  get handle() {
32
42
  return handle;
@@ -50,6 +60,7 @@ export function createOverlayTransitions(deps: OverlayTransitionsDeps): OverlayT
50
60
  handle?.hide();
51
61
  handle = null;
52
62
  if (leaving === "permission-confirm") deps.onPermissionOverlayClosed();
63
+ else deps.onOverlayClosed?.();
53
64
  deps.renderContextIsland();
54
65
  deps.renderTaskIsland();
55
66
  deps.requestRender();