@iowarp/clio-coder 0.3.1 → 0.3.3

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 (601) hide show
  1. package/CHANGELOG.md +233 -373
  2. package/CONTRIBUTING.md +23 -23
  3. package/README.md +284 -613
  4. package/dist/{acp-FPR54DGL.js → acp-P2AQILE2.js} +43 -53
  5. package/dist/{agents-OGPIHPJH.js → agents-72W3BI7I.js} +43 -26
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-IC3K6NIZ.js → auth-5TWEIYDN.js} +20 -12
  8. package/dist/chunk-2DJ2KNFG.js +2095 -0
  9. package/dist/{chunk-IS3ONKU3.js → chunk-2IR2NMPA.js} +6 -4
  10. package/dist/chunk-2SFS6XQE.js +122 -0
  11. package/dist/{chunk-PV4JUBVJ.js → chunk-2TLUCQVG.js} +40 -21
  12. package/dist/chunk-2VTFPG5O.js +48 -0
  13. package/dist/chunk-4BJ5BYCE.js +61 -0
  14. package/dist/{chunk-474KN5II.js → chunk-4BPJXDWC.js} +111 -181
  15. package/dist/chunk-4VP4KH3K.js +962 -0
  16. package/dist/chunk-4XUGQOHA.js +797 -0
  17. package/dist/chunk-4ZG3XFUR.js +77 -0
  18. package/dist/chunk-5B2AEOW5.js +5407 -0
  19. package/dist/{chunk-K2ITRMHZ.js → chunk-5TSRNF4G.js} +6 -138
  20. package/dist/{chunk-4QKXUHSR.js → chunk-5UFT4SUX.js} +70 -20
  21. package/dist/{chunk-OLBBMFRD.js → chunk-5UUP6MWO.js} +24 -62
  22. package/dist/chunk-65DEGPJ6.js +52 -0
  23. package/dist/chunk-6EJMN2Y3.js +17 -0
  24. package/dist/chunk-6N5PTWMY.js +136 -0
  25. package/dist/chunk-6SGHMWE3.js +277 -0
  26. package/dist/chunk-6XLNIQDB.js +27 -0
  27. package/dist/chunk-7CR24IG7.js +242 -0
  28. package/dist/chunk-7MNJORFF.js +22 -0
  29. package/dist/{chunk-KY56HMHH.js → chunk-A3CYT5EX.js} +125 -31
  30. package/dist/chunk-AGYYIBLL.js +1069 -0
  31. package/dist/{chunk-GB6QRBXN.js → chunk-APJ265NV.js} +54 -1187
  32. package/dist/{chunk-673JJUWJ.js → chunk-BMEMKKIT.js} +2 -2
  33. package/dist/chunk-CBCAPZAA.js +229 -0
  34. package/dist/chunk-CMZWFGD2.js +352 -0
  35. package/dist/chunk-COU2UHX6.js +400 -0
  36. package/dist/chunk-DSELYM6W.js +1077 -0
  37. package/dist/chunk-DUYJ5IO6.js +644 -0
  38. package/dist/chunk-ECH6PKUQ.js +39 -0
  39. package/dist/chunk-ED4KHGC3.js +143 -0
  40. package/dist/chunk-EKMEHE4H.js +340 -0
  41. package/dist/chunk-FCSXB6T2.js +338 -0
  42. package/dist/chunk-FJ3H4MN5.js +48 -0
  43. package/dist/{chunk-RPTR2H26.js → chunk-FNTMWMX5.js} +21 -15
  44. package/dist/chunk-FQ4SKYE4.js +29 -0
  45. package/dist/chunk-G4BMMOKF.js +182 -0
  46. package/dist/{chunk-ZPY3JZ5E.js → chunk-GGXXDWE4.js} +183 -1233
  47. package/dist/chunk-HC4CLZ2Y.js +68 -0
  48. package/dist/{chunk-LU4TK2PR.js → chunk-HFSBBKSQ.js} +5 -56
  49. package/dist/{chunk-PIUMUEMV.js → chunk-HKIYEGME.js} +10 -6
  50. package/dist/chunk-I4HZDVNP.js +73 -0
  51. package/dist/chunk-IKCO5N3L.js +162 -0
  52. package/dist/chunk-IR4CFBFN.js +56 -0
  53. package/dist/{chunk-R5KLMSBV.js → chunk-J5Q24KAG.js} +2 -2
  54. package/dist/chunk-J7CWMCQD.js +255 -0
  55. package/dist/{chunk-K5XEMXTI.js → chunk-JVCV3ICN.js} +1 -1
  56. package/dist/chunk-KZWTDYJF.js +217 -0
  57. package/dist/chunk-LBMZMYH2.js +285 -0
  58. package/dist/{chunk-G34LV2PF.js → chunk-LM5TQCJZ.js} +84 -170
  59. package/dist/chunk-LW6DSM3M.js +5135 -0
  60. package/dist/chunk-LWLEKMDQ.js +3482 -0
  61. package/dist/{chunk-H6F6BYOH.js → chunk-LZSJBIVT.js} +7003 -7434
  62. package/dist/{chunk-HQQID6OA.js → chunk-M6SHUN7Q.js} +5 -5
  63. package/dist/{chunk-FST4FYJB.js → chunk-MFFY33HR.js} +99 -140
  64. package/dist/{chunk-BSU2YIWB.js → chunk-MVVUPGPW.js} +131 -136
  65. package/dist/chunk-OAO4GE4M.js +619 -0
  66. package/dist/{chunk-Q3RUPKEJ.js → chunk-OC7FIQPC.js} +58 -189
  67. package/dist/chunk-OKGUZO2U.js +34 -0
  68. package/dist/{chunk-GAEBEQVI.js → chunk-OOJYHWRB.js} +32 -346
  69. package/dist/{chunk-Q5WJOSJ7.js → chunk-OQ33BKR3.js} +2 -1
  70. package/dist/chunk-OQE5J4C6.js +73 -0
  71. package/dist/{chunk-KKNLWXI6.js → chunk-ORBHGJC5.js} +8 -8
  72. package/dist/{chunk-MAR7Y6HW.js → chunk-PAJK6MAQ.js} +23 -16
  73. package/dist/{chunk-M5T5VO65.js → chunk-PIWWS5BL.js} +837 -635
  74. package/dist/chunk-POHLU5DW.js +1186 -0
  75. package/dist/chunk-QKMUKYO7.js +4961 -0
  76. package/dist/chunk-SRDMMSEP.js +16405 -0
  77. package/dist/chunk-SST6Z5JA.js +80 -0
  78. package/dist/chunk-STBPMHSX.js +2456 -0
  79. package/dist/chunk-T6YILFSB.js +80 -0
  80. package/dist/chunk-TZK7PACC.js +174 -0
  81. package/dist/chunk-TZTZS7QK.js +227 -0
  82. package/dist/{chunk-ASND7OZK.js → chunk-UFIIWP2H.js} +13 -13
  83. package/dist/chunk-UOV2BYIW.js +107 -0
  84. package/dist/{chunk-PFEFKVGL.js → chunk-V6RTAOC2.js} +13 -11
  85. package/dist/chunk-VAKQQHWR.js +434 -0
  86. package/dist/chunk-VG7TBQIY.js +128 -0
  87. package/dist/chunk-VJWL6YS5.js +244 -0
  88. package/dist/{chunk-EYOKLTMF.js → chunk-VPAYEGVX.js} +17 -3
  89. package/dist/chunk-WEH5XRJQ.js +32 -0
  90. package/dist/chunk-X4RCMKVQ.js +641 -0
  91. package/dist/{chunk-TEKV33Q5.js → chunk-X6IAEBZR.js} +65 -33
  92. package/dist/chunk-XBXAASKX.js +18 -0
  93. package/dist/chunk-XN3L4EYL.js +46 -0
  94. package/dist/{chunk-RDLVBZEO.js → chunk-YCWGATWI.js} +6 -4
  95. package/dist/chunk-YHZX5GEU.js +193 -0
  96. package/dist/chunk-YXLYO42X.js +91 -0
  97. package/dist/{chunk-NMOX6HFD.js → chunk-ZDOOVTXZ.js} +29 -77
  98. package/dist/chunk-ZI647VB5.js +37 -0
  99. package/dist/{chunk-C4PTHK7P.js → chunk-ZWLZP4ZT.js} +5 -5
  100. package/dist/chunk-ZWMF7253.js +1882 -0
  101. package/dist/cli/index.js +62 -54
  102. package/dist/clio-JOU4FXVA.js +25 -0
  103. package/dist/code-nav-7AX6FYE6.js +600 -0
  104. package/dist/codewiki/build-worker.js +66 -0
  105. package/dist/compile-cache-CVJMMODC.js +18 -0
  106. package/dist/{components-DMAOEKFB.js → components-KELWS457.js} +11 -6
  107. package/dist/{config-IRUQ7SE4.js → config-XCDVKR23.js} +92 -55
  108. package/dist/configure-4GAP54ZW.js +42 -0
  109. package/dist/{context-5RADCKTR.js → context-4UOGGLQ5.js} +71 -35
  110. package/dist/context-5VKGUVJJ.js +866 -0
  111. package/dist/{context-3KWFLHJG.js → context-77FM5DV5.js} +15 -13
  112. package/dist/{context-clear-7TSNPAAI.js → context-clear-XXJRLCJJ.js} +54 -28
  113. package/dist/{context-index-W4RLWOQH.js → context-index-BZ4UYMTC.js} +30 -24
  114. package/dist/dispatch-runner-QPRDDBDX.js +1997 -0
  115. package/dist/{docs-5AWSPS37.js → docs-2C2LTVT2.js} +23 -10
  116. package/dist/{doctor-UC5NAJYQ.js → doctor-HR46URBJ.js} +27 -17
  117. package/dist/{eval-U6TJHRLX.js → eval-XSSNATB4.js} +29 -16
  118. package/dist/{evidence-YEGUW4L3.js → evidence-6HG2PY2B.js} +46 -26
  119. package/dist/{evolve-TXARCTPG.js → evolve-K7YU3NCY.js} +45 -25
  120. package/dist/{extensions-OZFJ3A3G.js → extensions-QVDOHDGJ.js} +16 -7
  121. package/dist/{fleet-6G3DHNYE.js → fleet-VY3HHKN6.js} +163 -54
  122. package/dist/{fleet-preflight-DSNT37JK.js → fleet-preflight-DDN536IT.js} +7 -4
  123. package/dist/{init-KZ5QTF6M.js → init-JYGXI3FK.js} +69 -32
  124. package/dist/{memory-73ESV5YC.js → memory-WFZMGYHX.js} +48 -27
  125. package/dist/{models-A4PVNWJK.js → models-I5QWSEOM.js} +39 -25
  126. package/dist/monitor-GE4ID3IA.js +661 -0
  127. package/dist/{chunk-FCIH3BIZ.js → orchestrator-EM5MC3HM.js} +15979 -12407
  128. package/dist/{paths-C4H6IV77.js → paths-UXLN5YYZ.js} +10 -5
  129. package/dist/{preload-6WVMHX3A.js → preload-P6DGH2PZ.js} +2 -2
  130. package/dist/{reset-BGW6OGMV.js → reset-L2FQEE3E.js} +16 -10
  131. package/dist/{run-YTPEYQOH.js → run-ZU3QMZPZ.js} +101 -61
  132. package/dist/{share-YIFFV4NQ.js → share-S5BZQC5I.js} +15 -8
  133. package/dist/{skills-2V6RA3OQ.js → skills-X5VXCRNQ.js} +34 -14
  134. package/dist/{skills-eval-S2TVJO4F.js → skills-eval-WKIHWTHR.js} +70 -34
  135. package/dist/steer-GGWFUJUD.js +77 -0
  136. package/dist/{targets-TYXLPB23.js → targets-SNCPI2NR.js} +43 -27
  137. package/dist/terminal-lease-BNAHVHBS.js +395 -0
  138. package/dist/{trace-GGOJ6Q6Z.js → trace-PNCASAXC.js} +41 -16
  139. package/dist/{chunk-N6F52NLF.js → tree-sitter-HGKH6LG4.js} +28 -2306
  140. package/dist/{uninstall-LLLT4F4W.js → uninstall-FZCQCDKC.js} +10 -5
  141. package/dist/{upgrade-33G2LMM5.js → upgrade-JQHHPQ4K.js} +45 -25
  142. package/dist/{usage-ZAFSXKKG.js → usage-OR4O5SMZ.js} +62 -31
  143. package/dist/verify-375KUB3Y.js +716 -0
  144. package/dist/web-fetch-2YHJ3KTG.js +638 -0
  145. package/dist/{wiki-generate-NUQCVOQ3.js → wiki-generate-UEXP2ARI.js} +74 -34
  146. package/dist/worker/entry.js +221 -36
  147. package/dist/workspace-G4ZWUIPR.js +22 -0
  148. package/docs/README.md +22 -17
  149. package/docs/acp.md +168 -16
  150. package/docs/alcf-provider.md +1 -1
  151. package/docs/architecture.md +136 -7
  152. package/docs/artifact-versions.md +1 -1
  153. package/docs/built-in-agents.md +1 -1
  154. package/docs/capacity-and-scheduling.md +1 -1
  155. package/docs/commands-and-modes.md +114 -71
  156. package/docs/config-knobs-audit.md +1 -3
  157. package/docs/configuration-and-targets.md +174 -46
  158. package/docs/context-engine.md +29 -6
  159. package/docs/development-pipeline.md +26 -1
  160. package/docs/dispatch-architecture-rationale.md +1 -1
  161. package/docs/documentation-coverage.md +2 -2
  162. package/docs/documentation-guide.md +1 -1
  163. package/docs/environment-variables.md +13 -5
  164. package/docs/eval-runner.md +1 -1
  165. package/docs/evals-internal.md +1 -1
  166. package/docs/evidence-and-memory.md +6 -2
  167. package/docs/evolution.md +2 -2
  168. package/docs/exit-codes-and-output.md +15 -9
  169. package/docs/extensions-and-sharing.md +9 -9
  170. package/docs/fleet-dispatch.md +7 -5
  171. package/docs/git-commit-provenance.md +120 -0
  172. package/docs/glossary.md +1 -1
  173. package/docs/installation-and-lifecycle.md +34 -27
  174. package/docs/middleware-and-components.md +1 -1
  175. package/docs/model-catalog.md +45 -14
  176. package/docs/observability.md +8 -5
  177. package/docs/performance-methodology.md +491 -0
  178. package/docs/pi-boundary.md +72 -0
  179. package/docs/proactive-memory.md +3 -3
  180. package/docs/prompt-envelope-and-tools.md +24 -3
  181. package/docs/provider-adapter-cookbook.md +57 -4
  182. package/docs/release-cut-checklist.md +129 -115
  183. package/docs/safety-model.md +9 -5
  184. package/docs/scientific-validation.md +3 -3
  185. package/docs/session-lifecycle.md +55 -12
  186. package/docs/skills-marketplace.md +12 -8
  187. package/docs/time-conventions.md +1 -1
  188. package/docs/tool-usage.md +3 -3
  189. package/docs/trace-store.md +1 -1
  190. package/docs/troubleshooting.md +10 -7
  191. package/docs/tui-design.md +47 -10
  192. package/docs/worker-dispatch-mechanics.md +1 -1
  193. package/package.json +19 -22
  194. package/skills/coding/ast-grep/SKILL.md +136 -0
  195. package/skills/coding/ast-grep/evals.md +56 -0
  196. package/skills/coding/ast-grep/references/rule_reference.md +297 -0
  197. package/skills/coding/coding-standards/SKILL.md +113 -0
  198. package/skills/coding/coding-standards/evals.md +34 -0
  199. package/skills/coding/prototype/SKILL.md +86 -0
  200. package/skills/coding/prototype/evals.md +42 -0
  201. package/skills/coding/prototype/references/LOGIC.md +67 -0
  202. package/skills/coding/prototype/references/UI.md +112 -0
  203. package/skills/coding/tdd/SKILL.md +101 -0
  204. package/skills/coding/tdd/evals.md +41 -0
  205. package/skills/coding/tdd/references/mocking.md +59 -0
  206. package/skills/coding/tdd/references/tests.md +77 -0
  207. package/skills/context/context-handoff/SKILL.md +126 -0
  208. package/skills/context/context-handoff/evals.md +57 -0
  209. package/skills/context/context-handoff/scripts/new-handoff.sh +26 -0
  210. package/skills/context/context-prime/SKILL.md +95 -0
  211. package/skills/context/context-prime/evals.md +54 -0
  212. package/skills/meta/clio-dev/SKILL.md +91 -0
  213. package/skills/meta/clio-dev/evals.md +45 -0
  214. package/skills/meta/clio-test/SKILL.md +130 -0
  215. package/skills/meta/clio-test/evals.md +43 -0
  216. package/skills/meta/clio-test/references/harness.md +97 -0
  217. package/skills/meta/clio-test/references/test-map.md +59 -0
  218. package/skills/meta/credentials/SKILL.md +125 -0
  219. package/skills/meta/credentials/evals.md +104 -0
  220. package/skills/meta/find-skills/SKILL.md +72 -0
  221. package/skills/meta/find-skills/evals.md +47 -0
  222. package/skills/meta/herdr/SKILL.md +127 -0
  223. package/skills/meta/herdr/evals.md +38 -0
  224. package/skills/meta/skill-craft/SKILL.md +102 -0
  225. package/skills/meta/skill-craft/evals.md +41 -0
  226. package/skills/planning/architecture/SKILL.md +129 -0
  227. package/skills/planning/architecture/evals.md +36 -0
  228. package/skills/planning/backlog/SKILL.md +90 -0
  229. package/skills/planning/backlog/evals.md +43 -0
  230. package/skills/planning/prd/SKILL.md +82 -0
  231. package/skills/planning/prd/evals.md +49 -0
  232. package/skills/planning/product-intent/SKILL.md +112 -0
  233. package/skills/planning/product-intent/evals.md +36 -0
  234. package/skills/planning/tech-spec/SKILL.md +115 -0
  235. package/skills/planning/tech-spec/evals.md +47 -0
  236. package/skills/registry.yaml +136 -0
  237. package/skills/research/arxiv-literature/SKILL.md +104 -0
  238. package/skills/research/arxiv-literature/evals.md +58 -0
  239. package/skills/research/experiment-protocol/SKILL.md +122 -0
  240. package/skills/research/experiment-protocol/evals.md +91 -0
  241. package/skills/research/scientific-debugging/SKILL.md +119 -0
  242. package/skills/research/scientific-debugging/evals.md +138 -0
  243. package/skills/research/scientific-modernization/SKILL.md +138 -0
  244. package/skills/research/scientific-modernization/evals.md +84 -0
  245. package/skills/workflow/design-council/SKILL.md +139 -0
  246. package/skills/workflow/design-council/evals.md +97 -0
  247. package/skills/workflow/grill-me/SKILL.md +186 -0
  248. package/skills/workflow/grill-me/evals.md +78 -0
  249. package/skills/workflow/workflow-distiller/SKILL.md +136 -0
  250. package/skills/workflow/workflow-distiller/evals.md +107 -0
  251. package/src/cli/acp.ts +31 -4
  252. package/src/cli/clio.ts +68 -6
  253. package/src/cli/config-inspect.ts +28 -22
  254. package/src/cli/configure.ts +47 -9
  255. package/src/cli/context-clear.ts +2 -2
  256. package/src/cli/context-index.ts +21 -23
  257. package/src/cli/context.ts +13 -8
  258. package/src/cli/default-target.ts +9 -17
  259. package/src/cli/docs.ts +11 -5
  260. package/src/cli/evidence.ts +4 -1
  261. package/src/cli/extensions.ts +10 -1
  262. package/src/cli/fleet.ts +47 -6
  263. package/src/cli/index.ts +55 -26
  264. package/src/cli/memory.ts +3 -1
  265. package/src/cli/models.ts +1 -1
  266. package/src/cli/modes/json-stream.ts +37 -1
  267. package/src/cli/modes/print.ts +24 -9
  268. package/src/cli/run.ts +2 -2
  269. package/src/cli/skills-eval.ts +23 -8
  270. package/src/cli/skills.ts +19 -4
  271. package/src/cli/targets.ts +4 -0
  272. package/src/cli/text-layout.ts +15 -5
  273. package/src/cli/trace.ts +62 -14
  274. package/src/cli/upgrade.ts +18 -2
  275. package/src/cli/usage.ts +10 -3
  276. package/src/cli/wiki-generate.ts +2 -1
  277. package/src/core/agent-environment.ts +7 -0
  278. package/src/core/bash-exec.ts +72 -1
  279. package/src/core/boot-trace.ts +9 -4
  280. package/src/core/bus-events.ts +20 -4
  281. package/src/core/commit-attribution.ts +157 -0
  282. package/src/core/compile-cache.ts +159 -0
  283. package/src/core/config.ts +131 -2
  284. package/src/core/defaults.ts +39 -5
  285. package/src/core/domain-loader.ts +12 -5
  286. package/src/core/git-commit-attribution.ts +387 -0
  287. package/src/core/incomplete-installation.ts +45 -0
  288. package/src/core/response-schema.ts +1 -1
  289. package/src/core/safe-exec.ts +13 -1
  290. package/src/core/settings-layers.ts +155 -21
  291. package/src/core/skill-activation.ts +1 -1
  292. package/src/core/startup-timer.ts +3 -3
  293. package/src/core/state-file-lock.ts +13 -1
  294. package/src/core/termination.ts +78 -5
  295. package/src/domains/config/classify.ts +15 -3
  296. package/src/domains/config/extension.ts +19 -13
  297. package/src/domains/config/index.ts +10 -0
  298. package/src/domains/config/keybindings.ts +45 -9
  299. package/src/domains/context/bootstrap-prompt.ts +1 -1
  300. package/src/domains/context/bootstrap.ts +111 -18
  301. package/src/domains/context/clear.ts +16 -11
  302. package/src/domains/context/clio-md.ts +111 -9
  303. package/src/domains/context/codewiki/artifact.ts +400 -0
  304. package/src/domains/context/codewiki/build-worker-protocol.ts +24 -0
  305. package/src/domains/context/codewiki/build-worker.ts +54 -0
  306. package/src/domains/context/codewiki/coordinator.ts +182 -0
  307. package/src/domains/context/codewiki/indexer.ts +59 -144
  308. package/src/domains/context/codewiki/paths.ts +67 -0
  309. package/src/domains/context/codewiki/schema.ts +80 -0
  310. package/src/domains/context/codewiki/tree-sitter.ts +1 -1
  311. package/src/domains/context/contract.ts +11 -5
  312. package/src/domains/context/extension.ts +94 -143
  313. package/src/domains/context/fingerprint.ts +3 -1
  314. package/src/domains/context/index.ts +12 -22
  315. package/src/domains/context/project-metadata.ts +19 -0
  316. package/src/domains/context/prompt-context.ts +9 -10
  317. package/src/domains/context/refresh.ts +29 -21
  318. package/src/domains/context/runtime.ts +17 -0
  319. package/src/domains/context/wiki/generate.ts +39 -34
  320. package/src/domains/context/wiki/plan.ts +1 -1
  321. package/src/domains/context/wiki/prompts.ts +21 -8
  322. package/src/domains/dispatch/code-step.ts +20 -1
  323. package/src/domains/dispatch/extension.ts +158 -21
  324. package/src/domains/dispatch/failure-classification.ts +6 -0
  325. package/src/domains/dispatch/fleet-commit-attribution.ts +56 -0
  326. package/src/domains/dispatch/orphan-recovery.ts +50 -8
  327. package/src/domains/dispatch/receipt-integrity.ts +5 -0
  328. package/src/domains/dispatch/state.ts +31 -6
  329. package/src/domains/dispatch/transport.ts +2 -1
  330. package/src/domains/dispatch/types.ts +14 -0
  331. package/src/domains/dispatch/worker-spawn.ts +21 -2
  332. package/src/domains/eval/metrics/context.ts +1 -1
  333. package/src/domains/eval/types.ts +0 -1
  334. package/src/domains/evidence/build.ts +41 -1
  335. package/src/domains/lifecycle/migrations/2026-08-18-lmstudio-runtime-id.ts +52 -0
  336. package/src/domains/lifecycle/migrations/index.ts +24 -4
  337. package/src/domains/middleware/hooks-io.ts +12 -0
  338. package/src/domains/middleware/skills-reminder.ts +30 -15
  339. package/src/domains/prompts/compiler.ts +142 -84
  340. package/src/domains/prompts/contract.ts +18 -2
  341. package/src/domains/prompts/extension.ts +39 -7
  342. package/src/domains/prompts/fragment-loader.ts +0 -1
  343. package/src/domains/prompts/fragments/identity/clio.md +2 -4
  344. package/src/domains/prompts/fragments/identity/docs-routing.md +10 -0
  345. package/src/domains/prompts/fragments/identity/self-awareness.md +1 -45
  346. package/src/domains/prompts/fragments/operating/contract.md +4 -50
  347. package/src/domains/prompts/fragments/operating/delegation.md +42 -0
  348. package/src/domains/prompts/fragments/operating/skills.md +26 -0
  349. package/src/domains/prompts/fragments/operating/worker.md +16 -0
  350. package/src/domains/prompts/fragments/safety/auto-edit.md +5 -5
  351. package/src/domains/prompts/fragments/safety/full-auto.md +3 -3
  352. package/src/domains/prompts/fragments/safety/read-only.md +4 -4
  353. package/src/domains/prompts/fragments/safety/suggest.md +2 -2
  354. package/src/domains/prompts/fragments/wiki/page.md +10 -0
  355. package/src/domains/prompts/fragments/wiki/plan.md +10 -0
  356. package/src/domains/prompts/preload.ts +3 -3
  357. package/src/domains/providers/auth/api-key.ts +1 -1
  358. package/src/domains/providers/auth/backend-file.ts +20 -10
  359. package/src/domains/providers/auth/backend-memory.ts +59 -4
  360. package/src/domains/providers/auth/boot-status.ts +65 -0
  361. package/src/domains/providers/auth/oauth.ts +2 -1
  362. package/src/domains/providers/auth/storage.ts +97 -38
  363. package/src/domains/providers/capabilities.ts +12 -4
  364. package/src/domains/providers/contract.ts +15 -4
  365. package/src/domains/providers/extension.ts +18 -6
  366. package/src/domains/providers/model-runtime-capabilities.ts +15 -4
  367. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +118 -35
  368. package/src/domains/providers/plugins.ts +5 -3
  369. package/src/domains/providers/probe/fingerprint.ts +25 -5
  370. package/src/domains/providers/registry.ts +31 -10
  371. package/src/domains/providers/runtimes/boot-manifest.ts +55 -0
  372. package/src/domains/providers/runtimes/builtins.ts +2 -2
  373. package/src/domains/providers/runtimes/common/lmstudio-http.ts +423 -0
  374. package/src/domains/providers/runtimes/common/local-synth.ts +6 -7
  375. package/src/domains/providers/runtimes/local-native/lmstudio.ts +241 -0
  376. package/src/domains/providers/support.ts +6 -3
  377. package/src/domains/providers/types/local-model-quirks.ts +7 -9
  378. package/src/domains/providers/types/runtime-descriptor.ts +12 -1
  379. package/src/domains/providers/types/target-descriptor.ts +22 -0
  380. package/src/domains/resources/contract.ts +0 -1
  381. package/src/domains/resources/extension.ts +1 -3
  382. package/src/domains/resources/loader.ts +3 -4
  383. package/src/domains/resources/prompts/loader.ts +16 -2
  384. package/src/domains/resources/prompts/substitute.ts +1 -65
  385. package/src/domains/resources/skills/content-hash.ts +2 -0
  386. package/src/domains/resources/skills/install.ts +17 -0
  387. package/src/domains/resources/skills/loader.ts +17 -10
  388. package/src/domains/resources/skills/marketplace.ts +55 -9
  389. package/src/domains/safety/action-classifier.ts +4 -2
  390. package/src/domains/safety/audit.ts +8 -2
  391. package/src/domains/safety/extension.ts +1 -1
  392. package/src/domains/session/compaction/branch-summary.ts +3 -2
  393. package/src/domains/session/compaction/cut-point.ts +2 -1
  394. package/src/domains/session/compaction/tokens.ts +2 -1
  395. package/src/domains/session/context-ledger.ts +14 -0
  396. package/src/domains/session/contract.ts +15 -0
  397. package/src/domains/session/decision-board.ts +190 -0
  398. package/src/domains/session/entries.ts +66 -3
  399. package/src/domains/session/extension.ts +93 -12
  400. package/src/domains/session/retry.ts +10 -18
  401. package/src/domains/session/session-artifacts.ts +107 -0
  402. package/src/domains/session/task-board.ts +207 -13
  403. package/src/domains/session/tree/active-path.ts +44 -5
  404. package/src/domains/session/tree/fork.ts +26 -27
  405. package/src/domains/session/tree/preview.ts +2 -2
  406. package/src/domains/session/workspace/git-probe.ts +17 -11
  407. package/src/domains/user-tasks/store.ts +297 -0
  408. package/src/engine/acp/errors.ts +96 -0
  409. package/src/engine/acp/server.ts +1728 -146
  410. package/src/engine/acp/transport.ts +135 -14
  411. package/src/engine/acp/types.ts +26 -0
  412. package/src/engine/agent.ts +3 -3
  413. package/src/engine/ai.ts +32 -27
  414. package/src/engine/alcf-oauth.ts +26 -19
  415. package/src/engine/api-registry.ts +223 -0
  416. package/src/engine/apis/index.ts +3 -7
  417. package/src/engine/apis/llamacpp-residency.ts +49 -9
  418. package/src/engine/apis/lmstudio-residency.ts +5 -21
  419. package/src/engine/apis/lmstudio.ts +243 -0
  420. package/src/engine/apis/ollama-native.ts +24 -3
  421. package/src/engine/apis/openai-completions.ts +170 -91
  422. package/src/engine/apis/residency.ts +139 -3
  423. package/src/engine/apis/types.ts +16 -0
  424. package/src/engine/env-api-keys.ts +98 -0
  425. package/src/engine/gemma-channel-filter.ts +223 -0
  426. package/src/engine/instrumented-tui.ts +192 -0
  427. package/src/engine/messages.ts +14 -0
  428. package/src/engine/models.ts +42 -0
  429. package/src/engine/oauth.ts +16 -12
  430. package/src/engine/prompt-templates.ts +1 -0
  431. package/src/engine/provider-payload.ts +16 -59
  432. package/src/engine/strip-tokenizer-sentinels.ts +1 -1
  433. package/src/engine/truncate.ts +9 -0
  434. package/src/engine/tui.ts +17 -9
  435. package/src/engine/types.ts +3 -6
  436. package/src/engine/worker-runtime-capabilities.ts +5 -0
  437. package/src/engine/worker-runtime.ts +1 -1
  438. package/src/engine/worker-tools.ts +9 -4
  439. package/src/entry/boot-options.ts +50 -0
  440. package/src/entry/orchestrator.ts +288 -150
  441. package/src/interactive/application-controller.ts +89 -2
  442. package/src/interactive/chat-loop.ts +266 -41
  443. package/src/interactive/chat-panel.ts +715 -278
  444. package/src/interactive/chat-renderer.ts +306 -85
  445. package/src/interactive/clio-editor.ts +3 -8
  446. package/src/interactive/command-fallbacks.ts +2 -2
  447. package/src/interactive/context-overlay.ts +27 -1
  448. package/src/interactive/editor-submit.ts +253 -24
  449. package/src/interactive/export-html/ansi-to-html.ts +161 -0
  450. package/src/interactive/export-html/index.ts +51 -0
  451. package/src/interactive/export-html/template.ts +45 -0
  452. package/src/interactive/export-html/tool-renderer.ts +54 -0
  453. package/src/interactive/footer/dashboard.ts +4 -0
  454. package/src/interactive/footer/notifications.ts +1 -1
  455. package/src/interactive/footer/widgets.ts +42 -22
  456. package/src/interactive/footer-panel.ts +8 -3
  457. package/src/interactive/format-time.ts +14 -2
  458. package/src/interactive/interactive-application.ts +203 -17
  459. package/src/interactive/interactive-event-projection.ts +18 -1
  460. package/src/interactive/interactive-input-runtime.ts +50 -4
  461. package/src/interactive/interactive-presentation.ts +151 -19
  462. package/src/interactive/interactive-shell.ts +268 -14
  463. package/src/interactive/interactive-slash-runtime.ts +184 -117
  464. package/src/interactive/interactive-tickers.ts +38 -7
  465. package/src/interactive/keybinding-manager.ts +1 -1
  466. package/src/interactive/layout.ts +40 -3
  467. package/src/interactive/overlay-frame.ts +1 -1
  468. package/src/interactive/overlay-general-openers.ts +58 -1
  469. package/src/interactive/overlay-key-routing.ts +3 -0
  470. package/src/interactive/overlay-lifecycle.ts +13 -0
  471. package/src/interactive/overlay-permission-lifecycle.ts +2 -1
  472. package/src/interactive/overlay-session-lifecycle.ts +69 -12
  473. package/src/interactive/overlays/ask-user.ts +146 -24
  474. package/src/interactive/overlays/decisions.ts +300 -0
  475. package/src/interactive/overlays/help-reference.ts +15 -10
  476. package/src/interactive/overlays/model-selector.ts +34 -16
  477. package/src/interactive/overlays/session-selector.ts +18 -0
  478. package/src/interactive/overlays/settings.ts +105 -17
  479. package/src/interactive/overlays/skills-hub.ts +4 -4
  480. package/src/interactive/overlays/tree-selector.ts +41 -6
  481. package/src/interactive/render-trace.ts +499 -90
  482. package/src/interactive/renderers/compaction-summary.ts +2 -2
  483. package/src/interactive/renderers/diff.ts +115 -104
  484. package/src/interactive/renderers/mermaid.ts +53 -0
  485. package/src/interactive/renderers/tool-execution.ts +516 -168
  486. package/src/interactive/renderers/worker-entry.ts +20 -4
  487. package/src/interactive/session-switch-settlement.ts +10 -0
  488. package/src/interactive/slash-autocomplete.ts +6 -114
  489. package/src/interactive/slash-commands.ts +135 -47
  490. package/src/interactive/slash-spec.ts +9 -38
  491. package/src/interactive/status/controller.ts +5 -1
  492. package/src/interactive/status/index.ts +12 -1
  493. package/src/interactive/status/reasoning.ts +87 -0
  494. package/src/interactive/status/summary.ts +13 -2
  495. package/src/interactive/stdout-backpressure.ts +99 -0
  496. package/src/interactive/stream-pacer.ts +530 -0
  497. package/src/interactive/stream-pacing-policy.ts +66 -0
  498. package/src/interactive/tasks-overlay.ts +368 -14
  499. package/src/interactive/terminal-lease.ts +485 -0
  500. package/src/interactive/theme/tokens.ts +1 -1
  501. package/src/interactive/transcript-detail.ts +120 -0
  502. package/src/interactive/turn-context.ts +4 -3
  503. package/src/interactive/turn-persistence.ts +30 -13
  504. package/src/interactive/turn-queues.ts +12 -0
  505. package/src/interactive/turn-recovery.ts +25 -8
  506. package/src/interactive/turn-runtime.ts +79 -12
  507. package/src/interactive/turn-state.ts +10 -0
  508. package/src/interactive/view/artifacts.ts +114 -4
  509. package/src/interactive/view/view-overlay.ts +3 -0
  510. package/src/interactive/welcome-dashboard.ts +17 -16
  511. package/src/interactive/worker-receipts.ts +52 -3
  512. package/src/interactive/worker-stream.ts +5 -1
  513. package/src/tools/agent-tools.ts +23 -3
  514. package/src/tools/artifact.ts +2 -2
  515. package/src/tools/ask-user.ts +23 -13
  516. package/src/tools/bash.ts +30 -2
  517. package/src/tools/bootstrap.ts +34 -431
  518. package/src/tools/builtin-tool-catalog.ts +271 -0
  519. package/src/tools/codewiki/code-nav-surface.ts +29 -0
  520. package/src/tools/codewiki/code-nav.ts +8 -22
  521. package/src/tools/codewiki/shared.ts +41 -38
  522. package/src/tools/context/docs-engine.ts +14 -3
  523. package/src/tools/context/index.ts +107 -28
  524. package/src/tools/context/surface.ts +19 -0
  525. package/src/tools/core-bootstrap.ts +168 -0
  526. package/src/tools/credential-present.ts +5 -5
  527. package/src/tools/dispatch-admission.ts +533 -0
  528. package/src/tools/dispatch-background.ts +54 -0
  529. package/src/tools/dispatch-event-text.ts +6 -0
  530. package/src/tools/dispatch-plan.ts +9 -4
  531. package/src/tools/dispatch-run-events.ts +238 -0
  532. package/src/tools/dispatch-runner.ts +2370 -0
  533. package/src/tools/dispatch-scout-admission.ts +295 -0
  534. package/src/tools/dispatch-types.ts +77 -0
  535. package/src/tools/dispatch.ts +67 -3161
  536. package/src/tools/find.ts +4 -2
  537. package/src/tools/grep.ts +2 -2
  538. package/src/tools/lazy-tool.ts +60 -0
  539. package/src/tools/ledger.ts +3 -3
  540. package/src/tools/monitor-surface.ts +36 -0
  541. package/src/tools/monitor.ts +2 -32
  542. package/src/tools/observers.ts +2 -2
  543. package/src/tools/presentation.ts +107 -0
  544. package/src/tools/registry.ts +45 -27
  545. package/src/tools/safe-exec.ts +2 -2
  546. package/src/tools/steer-surface.ts +17 -0
  547. package/src/tools/steer.ts +2 -13
  548. package/src/tools/tasks.ts +108 -11
  549. package/src/tools/truncate.ts +25 -184
  550. package/src/tools/verify/frontend.ts +3 -1
  551. package/src/tools/verify/index.ts +3 -38
  552. package/src/tools/verify/surface.ts +46 -0
  553. package/src/tools/web-fetch-surface.ts +23 -0
  554. package/src/tools/web-fetch.ts +2 -20
  555. package/src/tools/write.ts +7 -2
  556. package/src/worker/entry.ts +39 -2
  557. package/src/worker/spec-contract.ts +26 -5
  558. package/dist/chunk-7SS2CTV2.js +0 -61361
  559. package/dist/chunk-DKGKUHFA.js +0 -924
  560. package/dist/chunk-GEP36Y4X.js +0 -12796
  561. package/dist/chunk-XYWBQRDM.js +0 -137
  562. package/dist/clio-BZVGEUFJ.js +0 -58
  563. package/dist/configure-S7S6F6CL.js +0 -32
  564. package/docs/html/agents_blueprint.html +0 -936
  565. package/docs/html/alcf_blueprint.html +0 -324
  566. package/docs/html/architecture_blueprint.html +0 -850
  567. package/docs/html/commands_blueprint.html +0 -939
  568. package/docs/html/config_knobs_audit_blueprint.html +0 -178
  569. package/docs/html/configuration_blueprint.html +0 -1080
  570. package/docs/html/context_blueprint.html +0 -603
  571. package/docs/html/documentation_blueprint.html +0 -832
  572. package/docs/html/environment_blueprint.html +0 -404
  573. package/docs/html/eval_blueprint.html +0 -743
  574. package/docs/html/evals_internal_blueprint.html +0 -190
  575. package/docs/html/evolution_blueprint.html +0 -674
  576. package/docs/html/extensions_blueprint.html +0 -2065
  577. package/docs/html/fleet_dispatch_blueprint.html +0 -286
  578. package/docs/html/index.html +0 -919
  579. package/docs/html/lifecycle_blueprint.html +0 -723
  580. package/docs/html/memory_blueprint.html +0 -699
  581. package/docs/html/middleware_blueprint.html +0 -664
  582. package/docs/html/models_blueprint.html +0 -2366
  583. package/docs/html/observability_blueprint.html +0 -683
  584. package/docs/html/provider_adapter_blueprint.html +0 -245
  585. package/docs/html/safety_blueprint.html +0 -1386
  586. package/docs/html/shared.css +0 -571
  587. package/docs/html/shared.js +0 -143
  588. package/docs/html/skills_blueprint.html +0 -671
  589. package/docs/html/soak_blueprint.html +0 -182
  590. package/docs/html/tool_usage_blueprint.html +0 -350
  591. package/docs/html/tools_blueprint.html +0 -2249
  592. package/docs/html/trace_blueprint.html +0 -235
  593. package/docs/html/tui_design_blueprint.html +0 -374
  594. package/docs/html/validation_blueprint.html +0 -961
  595. package/docs/html/worker_dispatch_blueprint.html +0 -231
  596. package/src/core/release.ts +0 -2
  597. package/src/domains/providers/runtimes/common/lmstudio-logger.ts +0 -32
  598. package/src/domains/providers/runtimes/local-native/lmstudio-native.ts +0 -491
  599. package/src/engine/apis/lmstudio-native.ts +0 -1438
  600. package/src/engine/apis/thinking-replay.ts +0 -11
  601. package/src/tools/string-enum.ts +0 -15
@@ -4,6 +4,10 @@ import { loadMemoryRecordsSync, type MemoryRecord } from "../domains/memory/inde
4
4
  import type { ObservabilityContract } from "../domains/observability/index.js";
5
5
  import type { ContextLedger } from "../domains/session/context-ledger.js";
6
6
  import type { SessionMeta } from "../domains/session/index.js";
7
+ import { foldSessionArtifacts } from "../domains/session/session-artifacts.js";
8
+ import { foldSessionTaskHistory } from "../domains/session/task-board.js";
9
+ import { filterEntriesToActivePath } from "../domains/session/tree/active-path.js";
10
+ import type { UserTasksStore } from "../domains/user-tasks/store.js";
7
11
  import type { TUI } from "../engine/tui.js";
8
12
  import { type OpenContextOverlayOptions, openContextOverlay } from "./context-overlay.js";
9
13
  import { openCostOverlay } from "./cost-overlay.js";
@@ -20,7 +24,8 @@ import {
20
24
  contextResetOptions,
21
25
  openContextResetOverlay,
22
26
  } from "./overlays/context-reset.js";
23
- import type { ContextClearCommandOptions } from "./slash-commands.js";
27
+ import { formatDecisionCorrectionTurn, openDecisionsOverlay } from "./overlays/decisions.js";
28
+ import { type ContextClearCommandOptions, formatUserTaskHandoff } from "./slash-commands.js";
24
29
  import { openTasksOverlay } from "./tasks-overlay.js";
25
30
  import { type ArtifactProviderDeps, createDefaultArtifactProviders } from "./view/artifacts.js";
26
31
  import { openViewOverlay } from "./view/view-overlay.js";
@@ -40,6 +45,10 @@ export interface OverlayGeneralOpenersDeps {
40
45
  renderTaskIsland: () => void;
41
46
  requestRender: () => void;
42
47
  getTaskBoard?: Parameters<typeof openTasksOverlay>[1];
48
+ userTasks?: UserTasksStore;
49
+ getDecisionBoard?: Parameters<typeof openDecisionsOverlay>[1];
50
+ supersedeDecision?: (interviewId: string, key: string, correction?: string) => unknown;
51
+ submitChat: (text: string) => void;
43
52
  getTaskMemoryStatus?: Parameters<typeof openMemoryOverlay>[1];
44
53
  dataDir: string;
45
54
  notify: (level: "info" | "success" | "warning" | "error", text: string, key?: string) => void;
@@ -56,6 +65,7 @@ export interface OverlayGeneralOpenersDeps {
56
65
  openContextOverlay?: typeof openContextOverlay;
57
66
  openContextResetOverlay?: typeof openContextResetOverlay;
58
67
  openTasksOverlay?: typeof openTasksOverlay;
68
+ openDecisionsOverlay?: typeof openDecisionsOverlay;
59
69
  openMemoryOverlay?: typeof openMemoryOverlay;
60
70
  openViewOverlay?: typeof openViewOverlay;
61
71
  }
@@ -66,6 +76,7 @@ export interface OverlayGeneralOpeners {
66
76
  openContextReset(): void;
67
77
  toggleFooter(): void;
68
78
  openTasks(): void;
79
+ openDecisions(): void;
69
80
  openMemory(): void;
70
81
  openView(initialFilter?: string): void;
71
82
  toggleDispatchBoard(): void;
@@ -76,6 +87,7 @@ export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): Ov
76
87
  const openContextOverlayFactory = deps.openContextOverlay ?? openContextOverlay;
77
88
  const openContextResetOverlayFactory = deps.openContextResetOverlay ?? openContextResetOverlay;
78
89
  const openTasksOverlayFactory = deps.openTasksOverlay ?? openTasksOverlay;
90
+ const openDecisionsOverlayFactory = deps.openDecisionsOverlay ?? openDecisionsOverlay;
79
91
  const openMemoryOverlayFactory = deps.openMemoryOverlay ?? openMemoryOverlay;
80
92
  const openViewOverlayFactory = deps.openViewOverlay ?? openViewOverlay;
81
93
  const showOverlayFrameFactory = deps.showOverlayFrame ?? showClioOverlayFrame;
@@ -137,6 +149,50 @@ export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): Ov
137
149
  deps.transitions.state = "tasks";
138
150
  deps.transitions.handle = openTasksOverlayFactory(deps.tui, () => deps.getTaskBoard?.() ?? null, {
139
151
  onClose: deps.closeOverlay,
152
+ getSessionSnapshot: () => {
153
+ const meta = deps.getSessionMeta();
154
+ const entries = filterEntriesToActivePath(deps.readSessionEntries?.() ?? [], meta?.pinnedLeafTurnId ?? undefined);
155
+ const workspace = meta?.cwd ?? process.cwd();
156
+ return {
157
+ history: foldSessionTaskHistory(entries),
158
+ artifacts: foldSessionArtifacts(entries, { workspace }),
159
+ };
160
+ },
161
+ ...(deps.userTasks
162
+ ? {
163
+ getUserTasks: () => deps.userTasks?.snapshot() ?? [],
164
+ onAddUserTask: (title: string) => void deps.userTasks?.add(title),
165
+ onHandUserTask: (id: string) => {
166
+ const task = deps.userTasks?.hand(id, deps.getSessionId?.() ?? undefined);
167
+ if (!task) throw new Error("operator task inbox is unavailable");
168
+ deps.submitChat(formatUserTaskHandoff(task));
169
+ },
170
+ onDoneUserTask: (id: string) => void deps.userTasks?.done(id),
171
+ onDropUserTask: (id: string) => void deps.userTasks?.drop(id),
172
+ }
173
+ : {}),
174
+ onOpenArtifact: (path: string) => openView(`workspace:${path}`),
175
+ requestRender: deps.requestRender,
176
+ workspace: deps.getSessionMeta()?.cwd ?? process.cwd(),
177
+ });
178
+ deps.requestRender();
179
+ };
180
+
181
+ const openDecisions = (): void => {
182
+ if (deps.transitions.state !== "closed") return;
183
+ deps.transitions.state = "decisions";
184
+ deps.transitions.handle = openDecisionsOverlayFactory(deps.tui, () => deps.getDecisionBoard?.() ?? [], {
185
+ onSupersede: (selection) => {
186
+ if (!deps.supersedeDecision) throw new Error("decision board is unavailable in this session");
187
+ deps.supersedeDecision(selection.interviewId, selection.key);
188
+ },
189
+ onCorrection: (selection, correction) => {
190
+ if (!deps.supersedeDecision) throw new Error("decision board is unavailable in this session");
191
+ deps.supersedeDecision(selection.interviewId, selection.key, correction);
192
+ deps.closeOverlay();
193
+ deps.submitChat(formatDecisionCorrectionTurn(selection, correction));
194
+ },
195
+ onClose: deps.closeOverlay,
140
196
  });
141
197
  deps.requestRender();
142
198
  };
@@ -210,6 +266,7 @@ export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): Ov
210
266
  openContextReset,
211
267
  toggleFooter,
212
268
  openTasks,
269
+ openDecisions,
213
270
  openMemory,
214
271
  openView,
215
272
  toggleDispatchBoard,
@@ -10,6 +10,7 @@ export type OverlayState =
10
10
  | "context-view"
11
11
  | "context-reset"
12
12
  | "tasks"
13
+ | "decisions"
13
14
  | "memory"
14
15
  | "view"
15
16
  | "model"
@@ -158,6 +159,7 @@ export function routeOverlayKey(
158
159
  if (overlayState === "closed") return false;
159
160
  if (
160
161
  (overlayState === "dispatch-board" && matches(data, "clio.dispatchBoard.toggle")) ||
162
+ (overlayState === "tasks" && matches(data, "clio.tasks.open")) ||
161
163
  (overlayState === "tree" && matches(data, "clio.session.tree")) ||
162
164
  (overlayState === "model" && matches(data, "clio.model.select")) ||
163
165
  (overlayState === "help" && matches(data, "clio.leader"))
@@ -180,6 +182,7 @@ export function routeOverlayKey(
180
182
  }
181
183
  if (overlayState === "context-reset") return false;
182
184
  if (overlayState === "tasks") return false;
185
+ if (overlayState === "decisions") return false;
183
186
  if (overlayState === "memory") return false;
184
187
  if (overlayState === "view") return false;
185
188
  if (overlayState === "model") return routeModelOverlayKey(data, deps);
@@ -33,6 +33,8 @@ export type OverlayLifecycleApplicationDeps = Pick<
33
33
  | "getSessionId"
34
34
  | "getSettings"
35
35
  | "getTaskBoard"
36
+ | "userTasks"
37
+ | "getDecisionBoard"
36
38
  | "getTaskMemoryStatus"
37
39
  | "interop"
38
40
  | "observability"
@@ -46,6 +48,7 @@ export type OverlayLifecycleApplicationDeps = Pick<
46
48
  | "resources"
47
49
  | "session"
48
50
  | "stateDir"
51
+ | "supersedeDecision"
49
52
  | "toolRegistry"
50
53
  | "writeSettings"
51
54
  >;
@@ -94,6 +97,7 @@ export interface OverlayLifecycleRuntimeDeps {
94
97
  openContextOverlay?: typeof import("./context-overlay.js").openContextOverlay;
95
98
  openContextResetOverlay?: typeof import("./overlays/context-reset.js").openContextResetOverlay;
96
99
  openTasksOverlay?: typeof import("./tasks-overlay.js").openTasksOverlay;
100
+ openDecisionsOverlay?: typeof import("./overlays/decisions.js").openDecisionsOverlay;
97
101
  openMemoryOverlay?: typeof import("./memory-overlay.js").openMemoryOverlay;
98
102
  openViewOverlay?: typeof import("./view/view-overlay.js").openViewOverlay;
99
103
  openHelpOverlay?: typeof import("./overlays/help-reference.js").openHelpOverlay;
@@ -118,6 +122,7 @@ export interface OverlayLifecycleController {
118
122
  openContextResetOverlayState(): void;
119
123
  toggleFooterDashboardState(): void;
120
124
  openTasksOverlayState(): void;
125
+ openDecisionsOverlayState(): void;
121
126
  openMemoryOverlayState(): void;
122
127
  openViewOverlayState(initialFilter?: string): void;
123
128
  openModelOverlayState(): void;
@@ -169,6 +174,7 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
169
174
  openContextOverlay: openContextOverlayFactory,
170
175
  openContextResetOverlay: openContextResetOverlayFactory,
171
176
  openTasksOverlay: openTasksOverlayFactory,
177
+ openDecisionsOverlay: openDecisionsOverlayFactory,
172
178
  openMemoryOverlay: openMemoryOverlayFactory,
173
179
  openViewOverlay: openViewOverlayFactory,
174
180
  openHelpOverlay: openHelpOverlayFactory,
@@ -334,6 +340,10 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
334
340
  renderTaskIsland: () => interactiveTickers.renderTaskIsland(),
335
341
  requestRender: () => tui.requestRender(),
336
342
  ...(deps.app.getTaskBoard ? { getTaskBoard: deps.app.getTaskBoard } : {}),
343
+ ...(deps.app.userTasks ? { userTasks: deps.app.userTasks } : {}),
344
+ ...(deps.app.getDecisionBoard ? { getDecisionBoard: deps.app.getDecisionBoard } : {}),
345
+ ...(deps.app.supersedeDecision ? { supersedeDecision: deps.app.supersedeDecision } : {}),
346
+ submitChat: (text) => deps.getSlashContext().submitChat(text),
337
347
  ...(deps.app.getTaskMemoryStatus ? { getTaskMemoryStatus: deps.app.getTaskMemoryStatus } : {}),
338
348
  dataDir: deps.app.dataDir,
339
349
  notify,
@@ -350,6 +360,7 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
350
360
  ...(openContextOverlayFactory ? { openContextOverlay: openContextOverlayFactory } : {}),
351
361
  ...(openContextResetOverlayFactory ? { openContextResetOverlay: openContextResetOverlayFactory } : {}),
352
362
  ...(openTasksOverlayFactory ? { openTasksOverlay: openTasksOverlayFactory } : {}),
363
+ ...(openDecisionsOverlayFactory ? { openDecisionsOverlay: openDecisionsOverlayFactory } : {}),
353
364
  ...(openMemoryOverlayFactory ? { openMemoryOverlay: openMemoryOverlayFactory } : {}),
354
365
  ...(openViewOverlayFactory ? { openViewOverlay: openViewOverlayFactory } : {}),
355
366
  });
@@ -362,6 +373,7 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
362
373
  const openContextResetOverlayState = overlayGeneralOpeners.openContextReset;
363
374
  const toggleFooterDashboardState = overlayGeneralOpeners.toggleFooter;
364
375
  const openTasksOverlayState = overlayGeneralOpeners.openTasks;
376
+ const openDecisionsOverlayState = overlayGeneralOpeners.openDecisions;
365
377
  const openMemoryOverlayState = overlayGeneralOpeners.openMemory;
366
378
  const openViewOverlayState = overlayGeneralOpeners.openView;
367
379
  const toggleDispatchBoardOverlay = overlayGeneralOpeners.toggleDispatchBoard;
@@ -380,6 +392,7 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
380
392
  openContextResetOverlayState,
381
393
  toggleFooterDashboardState,
382
394
  openTasksOverlayState,
395
+ openDecisionsOverlayState,
383
396
  openMemoryOverlayState,
384
397
  openViewOverlayState,
385
398
  openModelOverlayState: overlayModelSelectors.openModelOverlayState,
@@ -192,6 +192,7 @@ export function createOverlayPermissionLifecycle(deps: OverlayPermissionLifecycl
192
192
  const unsubscribePermission =
193
193
  deps.toolRegistry?.onPermissionRequired((call, decision, meta) => {
194
194
  const autonomy = deps.getAutonomy();
195
+ const view = mainApprovalRequestView(call, decision, meta, autonomy, deps.toolRegistry?.parkedCount());
195
196
  // The dialog states the tool, the target, the action class, the axis
196
197
  // that asked, and the keys that answer it. While it is on screen the
197
198
  // notice repeats all of that one line above the transcript, so it is
@@ -207,13 +208,13 @@ export function createOverlayPermissionLifecycle(deps: OverlayPermissionLifecycl
207
208
  type: "tool_approval_state",
208
209
  toolCallId: meta.toolCallId,
209
210
  state: "awaiting-approval",
211
+ view,
210
212
  });
211
213
  }
212
214
  if (deps.getOverlayState() !== "closed") {
213
215
  announceParked();
214
216
  return;
215
217
  }
216
- const view = mainApprovalRequestView(call, decision, meta, autonomy, deps.toolRegistry?.parkedCount());
217
218
  if (!deps.openPermissionOverlay(view)) {
218
219
  announceParked();
219
220
  return;
@@ -12,6 +12,7 @@ import { openMessagePickerOverlay } from "./overlays/message-picker.js";
12
12
  import { openSessionOverlay } from "./overlays/session-selector.js";
13
13
  import { openTreeOverlay } from "./overlays/tree-selector.js";
14
14
  import { lastTurnSummaryFromLedger } from "./session-last-turn.js";
15
+ import { settleChatBeforeSessionSwitch } from "./session-switch-settlement.js";
15
16
  import { reseedSessionUsageFromLedger, type SessionUsageSink } from "./session-usage-reseed.js";
16
17
  import type { SlashCommandContext } from "./slash-commands.js";
17
18
  import type { TurnSummary } from "./status/index.js";
@@ -20,7 +21,7 @@ export interface OverlaySessionLifecycleDeps {
20
21
  tui: TUI;
21
22
  transitions: OverlayTransitions;
22
23
  session?: SessionContract;
23
- chat: Pick<ChatLoop, "resetForSession">;
24
+ chat: Pick<ChatLoop, "cancel" | "isStreaming" | "resetForSession" | "whenSettled">;
24
25
  chatPanel: ChatPanel;
25
26
  /** The one transcript reset: the panel plus every view folded alongside it. */
26
27
  resetTranscript(): void;
@@ -113,20 +114,58 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
113
114
  deps.transitions.state = "resume";
114
115
  deps.transitions.handle = openResumeOverlay(deps.tui, {
115
116
  session,
116
- onResume: (sessionId) => {
117
+ onResume: async (sessionId) => {
118
+ const settlement = settleChatBeforeSessionSwitch(deps.chat);
119
+ if (settlement) await settlement;
117
120
  deps.onResumeSession?.(sessionId);
121
+ // onResumeSession (wired to session.resume) catches and stderr-logs
122
+ // its own failure rather than throwing here, so this is the only
123
+ // signal available: a successful switch always leaves session.current()
124
+ // pointing at the requested id. Without this check, a failed switch
125
+ // still replayed the target's transcript and moved the chat leaf to
126
+ // it while session.current() stayed on the session the operator
127
+ // started on (issue #93), so the next message was appended with a
128
+ // parent turn from a session that was never actually opened.
129
+ if (session.current()?.id !== sessionId) {
130
+ emitCommandNotice(
131
+ deps.getSlashNotice(),
132
+ "error",
133
+ "resume",
134
+ `could not switch to that session; staying on ${preResumeSessionId ?? "no session"}`,
135
+ );
136
+ deps.refreshFooter();
137
+ deps.requestRender();
138
+ return;
139
+ }
118
140
  try {
119
141
  const turns = deps.readStructuredEntries(sessionId);
120
- deps.resetTranscript();
121
- rehydrateChatPanelFromTurns(deps.chatPanel, turns);
122
- const replayMessages = buildReplayAgentMessagesFromTurns(turns);
142
+ // The leaf is read before the transcript is rebuilt because it is
143
+ // what the rebuild has to follow. resolveLeafOnOpen prefers a
144
+ // persisted `/tree` pin over the newest turn, so a session resumed
145
+ // on a pin extends from the pinned turn while the file still holds
146
+ // the abandoned branch after it. Replaying the file unfiltered
147
+ // rendered those abandoned turns as ordinary history above the
148
+ // prompt, disagreeing with the branch the next message parents onto
149
+ // and with the tip `/tree` marks (issue #107). The `/tree` switch
150
+ // path below has always scoped its replay to the selected turn;
151
+ // this is the same active-path filter, rooted at the leaf resume
152
+ // actually landed on.
123
153
  const leafTurnId = session.tree(sessionId).leafId;
154
+ // activeLeafTurnId, not uptoTurnId: this is a live branch about to
155
+ // be extended, so sidecars anchored to a path turn but written
156
+ // after it (a compaction summary covering the leaf, above all)
157
+ // still belong on screen. uptoTurnId is the historical-truncation
158
+ // variant `/tree` uses.
159
+ const replayOptions = leafTurnId ? { activeLeafTurnId: leafTurnId } : {};
160
+ deps.resetTranscript();
161
+ rehydrateChatPanelFromTurns(deps.chatPanel, turns, replayOptions);
162
+ const replayMessages = buildReplayAgentMessagesFromTurns(turns, replayOptions);
124
163
  deps.chat.resetForSession(leafTurnId, replayMessages);
125
164
  rescopeToBranch(session, turns, leafTurnId);
126
165
  } catch (error) {
127
166
  deps.stderr(`[/resume] transcript replay failed: ${error instanceof Error ? error.message : String(error)}\n`);
128
167
  }
129
- if (session.current()?.id === sessionId && sessionId !== preResumeSessionId) {
168
+ if (sessionId !== preResumeSessionId) {
130
169
  deps.announceTaskMemorySeedOffer();
131
170
  }
132
171
  deps.refreshFooter();
@@ -160,7 +199,9 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
160
199
  deps.transitions.state = "tree";
161
200
  deps.transitions.handle = openTreeSelector(deps.tui, {
162
201
  session,
163
- onSwitchTurn: (turnId) => {
202
+ onSwitchTurn: async (turnId) => {
203
+ const settlement = settleChatBeforeSessionSwitch(deps.chat);
204
+ if (settlement) await settlement;
164
205
  try {
165
206
  session.switchTurn(turnId);
166
207
  const sessionId = session.current()?.id ?? null;
@@ -195,7 +236,8 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
195
236
  return;
196
237
  }
197
238
  const session = deps.session;
198
- if (session.current() === null) {
239
+ const preForkSessionId = session.current()?.id ?? null;
240
+ if (preForkSessionId === null) {
199
241
  emitCommandNotice(
200
242
  deps.getSlashNotice(),
201
243
  "warn",
@@ -207,14 +249,29 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
207
249
  deps.transitions.state = "message-picker";
208
250
  deps.transitions.handle = openMessagePicker(deps.tui, {
209
251
  session,
210
- onFork: (parentTurnId) => {
252
+ onFork: async (parentTurnId) => {
253
+ const settlement = settleChatBeforeSessionSwitch(deps.chat);
254
+ if (settlement) await settlement;
211
255
  try {
212
256
  if (deps.onForkSession) deps.onForkSession(parentTurnId);
213
257
  else session.fork(parentTurnId);
214
- deps.resetTranscript();
215
258
  const forkedSessionId = session.current()?.id ?? null;
216
- if (forkedSessionId) replayFork(forkedSessionId, parentTurnId, session);
217
- else deps.chat.resetForSession(null);
259
+ // A successful fork always creates a fresh session id; landing back
260
+ // on the id fork started from means the fork threw and
261
+ // onForkSession swallowed it (orchestrator.ts stderr-logs there).
262
+ // Do not replay: session.current() did not move (issue #93's
263
+ // extension.ts/fork.ts ordering fix keeps it on the session the
264
+ // operator started on instead of orphaning it), so there is
265
+ // nothing new to replay and doing so anyway just re-renders the
266
+ // same transcript while hiding that the fork failed.
267
+ if (forkedSessionId === null || forkedSessionId === preForkSessionId) {
268
+ emitCommandNotice(deps.getSlashNotice(), "error", "fork", `fork failed; staying on ${preForkSessionId}`);
269
+ deps.refreshFooter();
270
+ deps.requestRender();
271
+ return;
272
+ }
273
+ deps.resetTranscript();
274
+ replayFork(forkedSessionId, parentTurnId, session);
218
275
  } catch (error) {
219
276
  deps.stderr(`[/fork] fork failed: ${error instanceof Error ? error.message : String(error)}\n`);
220
277
  }
@@ -13,8 +13,9 @@ import {
13
13
  } from "../../engine/tui.js";
14
14
  import type { AskUserAnswer, AskUserQuestion, AskUserResult } from "../../tools/ask-user.js";
15
15
  import { ASK_USER_OTHER_LABEL, cancelledAskUserResult } from "../../tools/ask-user.js";
16
- import { buildHint, DEFAULT_SELECT_THEME, showClioOverlayFrame } from "../overlay-frame.js";
16
+ import { buildHint, DEFAULT_SELECT_THEME, type HintEntry, showClioOverlayFrame } from "../overlay-frame.js";
17
17
  import { type ClioToken, clioTheme, dotSep, GLYPH, screenTitle } from "../theme/index.js";
18
+ import { nextContentScrollOffset, type ViewScrollAction } from "../view/view-overlay.js";
18
19
 
19
20
  /**
20
21
  * The border and title token while a decision is pending.
@@ -54,7 +55,7 @@ export const ASK_USER_DEFAULT_SURFACE: AskUserSurface = "panel";
54
55
  interface AskUserSurfaceGeometry {
55
56
  /** Box width in columns; zero fills the row. The frame clamps to the terminal. */
56
57
  width: number;
57
- /** Rows the body may use, before the borders. */
58
+ /** Rows the body may use, before the borders. Zero means every row the frame has. */
58
59
  maxInnerRows: number;
59
60
  anchor: NonNullable<OverlayOptions["anchor"]>;
60
61
  margin: NonNullable<OverlayOptions["margin"]>;
@@ -74,12 +75,18 @@ interface AskUserSurfaceGeometry {
74
75
  export const ASK_USER_SURFACE_GEOMETRY: Record<AskUserSurface, AskUserSurfaceGeometry> = {
75
76
  compact: { width: 68, maxInnerRows: 10, anchor: "bottom-center", margin: { top: 1, right: 2, bottom: 1, left: 2 } },
76
77
  panel: { width: 88, maxInnerRows: 18, anchor: "center", margin: 1 },
77
- interview: { width: 0, maxInnerRows: 42, anchor: "center", margin: 1 },
78
+ // The interview is a workspace, not a modal. A cap of 42 turned a 66-row
79
+ // terminal into a centered box with twenty blank rows around it, so the
80
+ // interview takes every row the frame has and lays its own chrome out inside
81
+ // them. The one-row margin keeps the border off the top row and the footer.
82
+ interview: { width: 0, maxInnerRows: 0, anchor: "center", margin: 1 },
78
83
  };
79
84
 
80
85
  const ASK_USER_FRAME_ROWS = 2;
81
86
  const ASK_USER_FRAME_AND_MARGIN_ROWS = 4;
82
87
  const MIN_VISIBLE_OPTIONS = 3;
88
+ /** Interview rows spent on chrome before an option row: strip, header, blanks, status, one question row. */
89
+ const INTERVIEW_CHROME_ROWS = 7;
83
90
  const MAX_VISIBLE_OPTIONS = 12;
84
91
  const ELLIPSIS = "…";
85
92
 
@@ -227,6 +234,14 @@ class AskUserOverlayView implements Component {
227
234
  private list: SelectList | null = null;
228
235
  private input: Input | null = null;
229
236
  private resolveCurrent: ((result: AskUserResult) => void) | null = null;
237
+ /** Rows of the interview's scrollable middle already scrolled past. */
238
+ private contentScrollOffset = 0;
239
+ /** Whether the last interview render had more content than the region held. */
240
+ private contentOverflows = false;
241
+ /** Rows the scroll region drew on the last render, for a page-sized jump. */
242
+ private contentRegionHeight = 1;
243
+ /** Rows the scroll region wanted on the last render. */
244
+ private contentTotalRows = 0;
230
245
 
231
246
  constructor(private readonly deps: AskUserOverlayViewDeps) {}
232
247
 
@@ -240,6 +255,7 @@ class AskUserOverlayView implements Component {
240
255
  this.states = this.questions.map((question) => createQuestionState(question));
241
256
  this.list = null;
242
257
  this.input = null;
258
+ this.resetContentScroll();
243
259
  // Before the controls, because how many option rows fit is a property of
244
260
  // the box this round is about to be drawn in.
245
261
  this.setSurface(askUserSurface(this.questions, this.history.length));
@@ -283,6 +299,10 @@ class AskUserOverlayView implements Component {
283
299
  const state = this.currentState();
284
300
  if (!question || !state) return;
285
301
 
302
+ // Scrolling the transcript must not cost the operator their place in the
303
+ // options, so the region has keys of its own that neither control claims.
304
+ if (this.handleScrollInput(data, state.mode)) return;
305
+
286
306
  if (state.mode === "text") {
287
307
  if (this.isTextModePreviousKey(data)) {
288
308
  this.goToRelativeQuestion(-1);
@@ -331,21 +351,106 @@ class AskUserOverlayView implements Component {
331
351
  const question = this.currentQuestion();
332
352
  if (!question) return [clioTheme().fg("muted", "No questions.")];
333
353
 
334
- const interview = this.surface === "interview";
354
+ if (this.surface === "interview") return this.renderInterview(question, safeWidth, maxRows);
355
+
356
+ this.contentOverflows = false;
335
357
  const controlLines = this.renderControlLines(safeWidth);
336
- const ledgerLines = interview ? this.renderAnswerLedger(safeWidth) : [];
337
358
  const statusLines = this.status.length > 0 ? ["", fitLine(clioTheme().fg("dim", this.status), safeWidth)] : [];
338
- const stripLines = interview ? [this.renderQuestionStrip(safeWidth), ""] : [];
339
359
  const headerLine = this.renderQuestionHeader(question, safeWidth);
340
360
  const headerLines = headerLine.length > 0 ? [headerLine] : [];
341
- const spent =
342
- stripLines.length + headerLines.length + statusLines.length + 1 + controlLines.length + ledgerLines.length;
361
+ const spent = headerLines.length + statusLines.length + 1 + controlLines.length;
343
362
  const questionLines = wrapTextWithAnsi(question.question, safeWidth).slice(0, Math.max(1, maxRows - spent));
344
363
 
345
- return [...stripLines, ...headerLines, ...questionLines, ...statusLines, "", ...controlLines, ...ledgerLines].slice(
346
- 0,
347
- maxRows,
364
+ return [...headerLines, ...questionLines, ...statusLines, "", ...controlLines].slice(0, maxRows);
365
+ }
366
+
367
+ /**
368
+ * The interview, as a workspace with fixed chrome and a scrolling middle.
369
+ *
370
+ * The round strip and the question header hold the top; the status line and
371
+ * the active control hold the bottom, at their full row cost, so the thing the
372
+ * operator has to act on is never the thing that gets clipped. The question
373
+ * text and the answer ledger share whatever is left and scroll through it.
374
+ * Everything is re-derived per render, so a resize is just the next frame.
375
+ */
376
+ private renderInterview(question: AskUserQuestion, width: number, maxRows: number): string[] {
377
+ const theme = clioTheme();
378
+ const controlLines = this.renderControlLines(width);
379
+ const statusLines = this.status.length > 0 ? ["", fitLine(theme.fg("dim", this.status), width)] : [];
380
+ const headerLine = this.renderQuestionHeader(question, width);
381
+ const headerLines = headerLine.length > 0 ? [headerLine] : [];
382
+ const stripLines = [this.renderQuestionStrip(width), ""];
383
+ const content = [...wrapTextWithAnsi(question.question, width), ...this.renderAnswerLedger(width)];
384
+
385
+ // Chrome sheds from the outside in when the terminal is too short to carry
386
+ // all of it: the round strip first, then the header, then the status, and
387
+ // the control only if a terminal is short enough that nothing else is left.
388
+ let top = [...stripLines, ...headerLines];
389
+ let bottom = [...statusLines, "", ...controlLines];
390
+ const region = (): number => maxRows - top.length - bottom.length;
391
+ if (region() < 1) top = [...headerLines];
392
+ if (region() < 1) top = [];
393
+ if (region() < 1) bottom = ["", ...controlLines];
394
+ if (region() < 1) bottom = [...controlLines];
395
+ const regionRows = Math.max(1, region());
396
+
397
+ const overflows = content.length > regionRows;
398
+ this.contentOverflows = overflows;
399
+ this.contentTotalRows = content.length;
400
+ // One row of the region pays for the indicator when there is more than the
401
+ // region holds, so the marker never covers a row it is reporting on.
402
+ const viewRows = overflows ? Math.max(1, regionRows - 1) : regionRows;
403
+ this.contentRegionHeight = viewRows;
404
+ this.contentScrollOffset = Math.max(0, Math.min(this.contentScrollOffset, Math.max(0, content.length - viewRows)));
405
+
406
+ const visible = content.slice(this.contentScrollOffset, this.contentScrollOffset + viewRows);
407
+ const middle = [...visible];
408
+ if (overflows) middle.push(fitLine(theme.fg("dim", this.scrollIndicator(content.length, viewRows)), width));
409
+ while (middle.length < regionRows) middle.push("");
410
+
411
+ return [...top, ...middle, ...bottom].slice(0, maxRows);
412
+ }
413
+
414
+ private scrollIndicator(total: number, viewRows: number): string {
415
+ const above = this.contentScrollOffset;
416
+ const below = Math.max(0, total - viewRows - above);
417
+ const parts: string[] = [];
418
+ if (above > 0) parts.push(`↑ ${above} more`);
419
+ if (below > 0) parts.push(`↓ ${below} more`);
420
+ parts.push(`(${Math.min(total, above + viewRows)}/${total})`);
421
+ return parts.join(" ");
422
+ }
423
+
424
+ /** True when the key belonged to the scroll region rather than to a control. */
425
+ private handleScrollInput(data: string, mode: Mode): boolean {
426
+ if (this.surface !== "interview") return false;
427
+ const action = this.scrollAction(data, mode);
428
+ if (!action) return false;
429
+ const next = nextContentScrollOffset(
430
+ this.contentScrollOffset,
431
+ this.contentTotalRows,
432
+ this.contentRegionHeight,
433
+ action,
348
434
  );
435
+ if (next !== this.contentScrollOffset) {
436
+ this.contentScrollOffset = next;
437
+ this.deps.requestRender();
438
+ }
439
+ return true;
440
+ }
441
+
442
+ private scrollAction(data: string, mode: Mode): ViewScrollAction | null {
443
+ if (matchesKey(data, "pageUp")) return "page-up";
444
+ if (matchesKey(data, "pageDown")) return "page-down";
445
+ // Ctrl+U and Ctrl+D are line-kill and half-page in a text field, so they
446
+ // stay with the input while one is focused.
447
+ if (mode === "select" && matchesKey(data, "ctrl+u")) return "half-up";
448
+ if (mode === "select" && matchesKey(data, "ctrl+d")) return "half-down";
449
+ return null;
450
+ }
451
+
452
+ private resetContentScroll(): void {
453
+ this.contentScrollOffset = 0;
349
454
  }
350
455
 
351
456
  footerHint(): string {
@@ -357,11 +462,11 @@ class AskUserOverlayView implements Component {
357
462
  if (!question || !state) return buildHint([]);
358
463
  if (state.mode === "text") {
359
464
  return this.questions.length > 1
360
- ? buildHint([
465
+ ? this.withScrollHint([
361
466
  { key: "Enter", verb: "submit" },
362
467
  { key: "Alt+Left/Right", verb: "question" },
363
468
  ])
364
- : buildHint([{ key: "Enter", verb: "submit" }]);
469
+ : this.withScrollHint([{ key: "Enter", verb: "submit" }]);
365
470
  }
366
471
  // `accept` rather than `select` or `commit`: the footer of a decision prompt
367
472
  // has one job, which is to name the key that answers it. Esc keeps the
@@ -369,22 +474,32 @@ class AskUserOverlayView implements Component {
369
474
  // interview together.
370
475
  if (question.multi_select === true) {
371
476
  return this.questions.length > 1
372
- ? buildHint([
477
+ ? this.withScrollHint([
373
478
  { key: "Left/Right", verb: "question" },
374
479
  { key: "Space", verb: "toggle" },
375
480
  { key: "Enter", verb: "accept" },
376
481
  ])
377
- : buildHint([
482
+ : this.withScrollHint([
378
483
  { key: "Space", verb: "toggle" },
379
484
  { key: "Enter", verb: "accept" },
380
485
  ]);
381
486
  }
382
487
  return this.questions.length > 1
383
- ? buildHint([
488
+ ? this.withScrollHint([
384
489
  { key: "Left/Right", verb: "question" },
385
490
  { key: "Enter", verb: "accept" },
386
491
  ])
387
- : buildHint([{ key: "Enter", verb: "accept" }]);
492
+ : this.withScrollHint([{ key: "Enter", verb: "accept" }]);
493
+ }
494
+
495
+ /**
496
+ * The keys for this question, plus the scroll keys when there is something to
497
+ * scroll. A footer that always advertised PgUp would be advertising a key that
498
+ * does nothing on most rounds.
499
+ */
500
+ private withScrollHint(entries: ReadonlyArray<HintEntry>): string {
501
+ if (this.surface !== "interview" || !this.contentOverflows) return buildHint(entries);
502
+ return buildHint([...entries, { key: "PgUp/PgDn", verb: "scroll" }]);
388
503
  }
389
504
 
390
505
  private setSurface(next: AskUserSurface): void {
@@ -443,17 +558,20 @@ class AskUserOverlayView implements Component {
443
558
  private maxInnerRows(): number {
444
559
  const cap = ASK_USER_SURFACE_GEOMETRY[this.surface].maxInnerRows;
445
560
  const rows = this.deps.getTerminalRows();
446
- if (!Number.isFinite(rows) || rows <= 0) return cap;
447
- return Math.max(1, Math.min(cap, Math.floor(rows) - ASK_USER_FRAME_AND_MARGIN_ROWS));
561
+ // A zero cap is the interview's "every row the frame has", which is the
562
+ // same number the frame budgets itself when the mount passes no maxHeight.
563
+ if (!Number.isFinite(rows) || rows <= 0) return cap > 0 ? cap : 1;
564
+ const available = Math.max(1, Math.floor(rows) - ASK_USER_FRAME_AND_MARGIN_ROWS);
565
+ return cap > 0 ? Math.min(cap, available) : available;
448
566
  }
449
567
 
450
568
  private maxVisibleOptions(): number {
451
569
  const inner = this.maxInnerRows();
452
- // The interview surface spends rows on the round strip and the ledger
453
- // before an option ever renders. The other two spend them on a header, a
454
- // question, and a separator, so the options get nearly everything.
570
+ // The interview's option list is budgeted off the live row count rather
571
+ // than a fixed heuristic: the strip, header, blank, status, blank and one
572
+ // row of question are what the control shares the workspace with.
455
573
  if (this.surface === "interview") {
456
- return Math.max(MIN_VISIBLE_OPTIONS, Math.min(MAX_VISIBLE_OPTIONS, inner - 12));
574
+ return Math.max(MIN_VISIBLE_OPTIONS, Math.min(MAX_VISIBLE_OPTIONS, inner - INTERVIEW_CHROME_ROWS));
457
575
  }
458
576
  return Math.max(1, Math.min(MAX_VISIBLE_OPTIONS, inner - 4));
459
577
  }
@@ -809,6 +927,7 @@ class AskUserOverlayView implements Component {
809
927
  const next = this.nextUnansweredIndex();
810
928
  if (next !== null) {
811
929
  this.index = next;
930
+ this.resetContentScroll();
812
931
  const nextState = this.currentState();
813
932
  const nextQuestion = this.currentQuestion();
814
933
  if (nextState && nextQuestion && !nextState.answer.trim()) nextState.mode = initialMode(nextQuestion);
@@ -844,6 +963,7 @@ class AskUserOverlayView implements Component {
844
963
  this.syncActiveControl();
845
964
  this.index = (this.index + delta + this.questions.length) % this.questions.length;
846
965
  this.status = "";
966
+ this.resetContentScroll();
847
967
  this.rebuildControl();
848
968
  this.deps.requestRender();
849
969
  }
@@ -888,7 +1008,9 @@ export function openAskUserOverlay(tui: TUI, deps: OpenAskUserOverlayDeps): AskU
888
1008
  handle = showClioOverlayFrame(tui, view, {
889
1009
  anchor: geometry.anchor,
890
1010
  width: geometry.width,
891
- maxHeight: geometry.maxInnerRows + ASK_USER_FRAME_ROWS,
1011
+ // A zero cap means the frame keeps its own budget, which is every row
1012
+ // the terminal has left after the margins.
1013
+ ...(geometry.maxInnerRows > 0 ? { maxHeight: geometry.maxInnerRows + ASK_USER_FRAME_ROWS } : {}),
892
1014
  margin: geometry.margin,
893
1015
  title: () => (view.isDecisionPending() ? ASK_USER_DECISION_TITLE : ASK_USER_WAITING_TITLE),
894
1016
  tone: () => (view.isDecisionPending() ? ASK_USER_DECISION_TONE : undefined),