@iowarp/clio-coder 0.3.4 → 0.3.7

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 (376) hide show
  1. package/CHANGELOG.md +65 -2
  2. package/CONTRIBUTING.md +6 -6
  3. package/README.md +16 -5
  4. package/dist/{acp-S5R4RR5B.js → acp-SK4MD6MM.js} +11 -11
  5. package/dist/{agents-P6DMMVZY.js → agents-2FN2K6ME.js} +33 -26
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-2XCZLPKS.js → auth-QIYZWM5I.js} +15 -15
  8. package/dist/{chunk-EKMEHE4H.js → chunk-33YXPOE3.js} +2 -3
  9. package/dist/{chunk-VAWWTKDP.js → chunk-3HAPLH5M.js} +11 -11
  10. package/dist/{chunk-YCWGATWI.js → chunk-465YSENW.js} +3 -3
  11. package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
  12. package/dist/{chunk-UZHIZC5S.js → chunk-4DWFMQDR.js} +61 -76
  13. package/dist/{chunk-ZWMF7253.js → chunk-5C3AQNDW.js} +328 -9
  14. package/dist/{chunk-22NAGB7X.js → chunk-5C77SEEY.js} +5 -94
  15. package/dist/{chunk-WPQLXFOZ.js → chunk-5FR74PWO.js} +3 -2
  16. package/dist/{chunk-35MKKU5R.js → chunk-5UJ6ECTS.js} +18 -10
  17. package/dist/{chunk-BRXQQJFP.js → chunk-6M7VS3J3.js} +571 -50
  18. package/dist/{chunk-QQK64KLB.js → chunk-6TUKSZVF.js} +141 -23
  19. package/dist/{chunk-N4CZJQRK.js → chunk-AB4XIIVB.js} +8 -6
  20. package/dist/{chunk-KRPY7NTG.js → chunk-BMWK7ZIZ.js} +14 -20
  21. package/dist/{chunk-4BPJXDWC.js → chunk-C4JBQ5SR.js} +30 -14
  22. package/dist/{chunk-ZYKPLLNQ.js → chunk-CEYBNUGC.js} +821 -83
  23. package/dist/{chunk-VEZEGCGW.js → chunk-D4MDIG46.js} +20 -18
  24. package/dist/chunk-DJNLUABN.js +843 -0
  25. package/dist/{chunk-BP4OYD6A.js → chunk-DMD2AGVS.js} +21 -2
  26. package/dist/{chunk-KOHPCX4K.js → chunk-DOOEX22V.js} +2 -2
  27. package/dist/chunk-DQA7QLMD.js +123 -0
  28. package/dist/chunk-DR52UMZW.js +21 -0
  29. package/dist/{chunk-3HZ5RWN2.js → chunk-EBEFWSGL.js} +9 -7
  30. package/dist/{chunk-EDRHSCIE.js → chunk-EELBMBT6.js} +128 -13
  31. package/dist/{chunk-HV5X7OR2.js → chunk-EOOQZZDE.js} +16 -14
  32. package/dist/{chunk-WR67VIZY.js → chunk-FOT2FX5J.js} +63 -5
  33. package/dist/{chunk-BPGS2WCQ.js → chunk-GEYXPTRF.js} +2 -1
  34. package/dist/{chunk-FYYLNIL5.js → chunk-GH5622CP.js} +2 -2
  35. package/dist/{chunk-BEY543CS.js → chunk-GOXNB3AO.js} +5 -2
  36. package/dist/chunk-GWS3VEIW.js +195 -0
  37. package/dist/{chunk-G4BMMOKF.js → chunk-HVDIIIQW.js} +2 -2
  38. package/dist/chunk-HWUFFB6L.js +83 -0
  39. package/dist/{chunk-X6COSD2O.js → chunk-J7PIKKWC.js} +8 -436
  40. package/dist/{chunk-NILBFAPG.js → chunk-JNXPYBB4.js} +2 -2
  41. package/dist/{chunk-4VP4KH3K.js → chunk-JRIO5UD2.js} +4 -4
  42. package/dist/{chunk-K6WL7QZT.js → chunk-JTSEDYVQ.js} +7 -7
  43. package/dist/{chunk-QKMUKYO7.js → chunk-KCMKRQX4.js} +236 -84
  44. package/dist/chunk-KZ2H5X4G.js +1026 -0
  45. package/dist/{chunk-A2GZF7DC.js → chunk-LADCF22A.js} +13 -13
  46. package/dist/chunk-LCGCVYZ4.js +57 -0
  47. package/dist/chunk-M4AKACEO.js +382 -0
  48. package/dist/{chunk-POHLU5DW.js → chunk-M6L6IDJG.js} +3 -3
  49. package/dist/{chunk-4JUF2NNX.js → chunk-MXI6J5JF.js} +7 -7
  50. package/dist/{chunk-X4RCMKVQ.js → chunk-NDINPTJ4.js} +2 -2
  51. package/dist/{chunk-TTNYS3EA.js → chunk-OB5HIGJY.js} +1 -1
  52. package/dist/{chunk-7RXG6QRZ.js → chunk-OBMAI2DP.js} +61 -840
  53. package/dist/{chunk-5M54SPOL.js → chunk-ODFEOB4F.js} +161 -5
  54. package/dist/chunk-PD3MESLB.js +242 -0
  55. package/dist/{chunk-ED4KHGC3.js → chunk-PPAMZ32Z.js} +9 -2
  56. package/dist/{chunk-VMNQ6OZA.js → chunk-QCTRSGHQ.js} +963 -786
  57. package/dist/chunk-RVG5JXAL.js +41 -0
  58. package/dist/{chunk-RD5U66HV.js → chunk-SROCI7ZU.js} +7 -7
  59. package/dist/{chunk-MFFY33HR.js → chunk-THKY7CD7.js} +466 -205
  60. package/dist/{chunk-34475P3I.js → chunk-TSHXZTOQ.js} +5 -4
  61. package/dist/{chunk-PCZJO5TI.js → chunk-UFQ3F4FW.js} +13 -178
  62. package/dist/{chunk-AD2SYQYC.js → chunk-UHXRNZ2J.js} +121 -3
  63. package/dist/chunk-UND3GU2L.js +103 -0
  64. package/dist/{chunk-QQL5RT5M.js → chunk-UUANF5CR.js} +2323 -2114
  65. package/dist/{chunk-VJWL6YS5.js → chunk-UUVG37B4.js} +2 -2
  66. package/dist/chunk-UVDSQ6LW.js +472 -0
  67. package/dist/{chunk-QWU7ZBO7.js → chunk-VQNODYQ4.js} +215 -56
  68. package/dist/chunk-VREKEFLL.js +37 -0
  69. package/dist/{chunk-2TZWSW76.js → chunk-WHGPSPT5.js} +2 -2
  70. package/dist/{chunk-TW3WDMVS.js → chunk-WHJYKASB.js} +2 -2
  71. package/dist/{chunk-MEQ45TQ4.js → chunk-WJHBC77E.js} +21 -7
  72. package/dist/{chunk-HXG4IURW.js → chunk-X2KV5FXT.js} +2 -2
  73. package/dist/{chunk-YHZX5GEU.js → chunk-XAKHZX5N.js} +2 -2
  74. package/dist/{chunk-2LZI5CAG.js → chunk-XEGB6BCN.js} +228 -36
  75. package/dist/{chunk-E25LMLRW.js → chunk-YD734TPH.js} +2 -2
  76. package/dist/{verifiers-4UUM6TEE.js → chunk-YTYFXUI3.js} +121 -372
  77. package/dist/{chunk-3JLKSKD7.js → chunk-ZGH7FGS5.js} +17 -7
  78. package/dist/{chunk-VSNATDE6.js → chunk-ZZMN5OM4.js} +2 -2
  79. package/dist/cli/index.js +34 -32
  80. package/dist/{clio-J5JIOIDS.js → clio-WBVQEBKO.js} +7 -7
  81. package/dist/{code-nav-AXCXSBHX.js → code-nav-FGGFIE7L.js} +7 -7
  82. package/dist/codewiki/build-worker.js +4 -4
  83. package/dist/{components-KELWS457.js → components-F7OEATSO.js} +5 -5
  84. package/dist/{config-OEBMIN2U.js → config-TRBL3RCF.js} +48 -41
  85. package/dist/{configure-PUQOSIXQ.js → configure-OLCVPHNM.js} +17 -17
  86. package/dist/{context-URSXPBCK.js → context-MJIJ6GOX.js} +12 -12
  87. package/dist/{context-EKDCKUUZ.js → context-WFPKQSM6.js} +26 -9
  88. package/dist/{context-MGSE4Z2T.js → context-XEWE3MOJ.js} +44 -37
  89. package/dist/{context-clear-KDAJRNUK.js → context-clear-KNOS2JPB.js} +44 -37
  90. package/dist/{context-index-BZ4UYMTC.js → context-index-SSR5ECNE.js} +3 -3
  91. package/dist/{context-working-set-SBKMPPI2.js → context-working-set-EUXAZI6N.js} +14 -13
  92. package/dist/{dispatch-runner-MSWN72NK.js → dispatch-runner-B7MTOVKL.js} +321 -60
  93. package/dist/{docs-2C2LTVT2.js → docs-FLJTIDSE.js} +5 -5
  94. package/dist/{doctor-7BSE27PJ.js → doctor-RN4YKO2X.js} +15 -15
  95. package/dist/{eval-IZGDOO4H.js → eval-RUBJVSNQ.js} +52 -236
  96. package/dist/{evidence-SR7WXB5B.js → evidence-JZNBUOQZ.js} +39 -33
  97. package/dist/{evolve-K7VE2CBX.js → evolve-FJVC4KKI.js} +39 -33
  98. package/dist/{extensions-QVDOHDGJ.js → extensions-IQL36S7K.js} +5 -5
  99. package/dist/{fleet-7XMJNQNF.js → fleet-BDKYJFCP.js} +243 -370
  100. package/dist/fleet-commands-ZFIWZSB3.js +70 -0
  101. package/dist/fleet-graph-Y6HPXIVF.js +125 -0
  102. package/dist/fleet-new-RDVJLHHH.js +48 -0
  103. package/dist/{fleet-preflight-AQNAH644.js → fleet-preflight-BHSNPBMH.js} +2 -2
  104. package/dist/fleet-validate-BIYREGIK.js +79 -0
  105. package/dist/{init-JGNPAYXT.js → init-LQUB5COQ.js} +57 -48
  106. package/dist/library-NJAHIGG4.js +217 -0
  107. package/dist/memory-OG6HOYKM.js +472 -0
  108. package/dist/{models-ZMMLFJNN.js → models-5ZG5XY7J.js} +23 -22
  109. package/dist/{monitor-2F3T5KHP.js → monitor-TJ7AMTGB.js} +69 -35
  110. package/dist/{orchestrator-ORHT43JB.js → orchestrator-WZYB54DM.js} +4868 -1189
  111. package/dist/{paths-UXLN5YYZ.js → paths-XUC7GS6E.js} +5 -5
  112. package/dist/{reset-NXGTYNUO.js → reset-PXQT45IY.js} +8 -8
  113. package/dist/{run-RF4WJGMT.js → run-FQ74YF62.js} +82 -62
  114. package/dist/{share-UT3W6E4M.js → share-FW7SVCL3.js} +34 -10
  115. package/dist/{skills-PSACKC5Q.js → skills-7E7IRB3R.js} +25 -9
  116. package/dist/{skills-eval-WJSI55RZ.js → skills-eval-LI75W6OK.js} +43 -35
  117. package/dist/{targets-PIIRAOYS.js → targets-4CIFKCTW.js} +27 -24
  118. package/dist/{terminal-lease-ULWXWNVY.js → terminal-lease-WUZY7ZV5.js} +5 -4
  119. package/dist/{uninstall-FZCQCDKC.js → uninstall-7FV7IP4E.js} +5 -5
  120. package/dist/{upgrade-346TZ6AV.js → upgrade-K2HVIVMQ.js} +21 -20
  121. package/dist/{usage-6KKXR32N.js → usage-GTZELZQX.js} +159 -59
  122. package/dist/verifiers-RLAHT27O.js +336 -0
  123. package/dist/{verify-X5HDROLA.js → verify-BX3BRKH5.js} +7 -6
  124. package/dist/{wiki-generate-7STOCIFZ.js → wiki-generate-ASIFASCN.js} +58 -48
  125. package/dist/worker/entry.js +98 -84
  126. package/dist/{workspace-G4ZWUIPR.js → workspace-ZJ6BFM3Q.js} +4 -4
  127. package/docs/README.md +4 -3
  128. package/docs/acp.md +1 -1
  129. package/docs/alcf-provider.md +1 -1
  130. package/docs/architecture.md +2 -2
  131. package/docs/artifact-placement.md +1 -2
  132. package/docs/artifact-versions.md +10 -6
  133. package/docs/built-in-agents.md +26 -2
  134. package/docs/capacity-and-scheduling.md +1 -1
  135. package/docs/commands-and-modes.md +90 -8
  136. package/docs/configuration-and-targets.md +90 -2
  137. package/docs/context-engine.md +4 -2
  138. package/docs/context-working-set.md +4 -4
  139. package/docs/development-pipeline.md +1 -1
  140. package/docs/dispatch-architecture-rationale.md +1 -1
  141. package/docs/documentation-coverage.md +4 -4
  142. package/docs/documentation-guide.md +4 -4
  143. package/docs/eval-runner.md +1 -1
  144. package/docs/evals-internal.md +4 -45
  145. package/docs/evidence-and-memory.md +70 -10
  146. package/docs/evolution.md +1 -1
  147. package/docs/exit-codes-and-output.md +4 -1
  148. package/docs/extensions-and-sharing.md +6 -2
  149. package/docs/fleet-demo-runbook.md +2 -2
  150. package/docs/fleet-dispatch.md +224 -11
  151. package/docs/git-commit-provenance.md +2 -2
  152. package/docs/glossary.md +1 -1
  153. package/docs/installation-and-lifecycle.md +2 -2
  154. package/docs/middleware-and-components.md +20 -2
  155. package/docs/model-catalog.md +1 -1
  156. package/docs/observability.md +55 -8
  157. package/docs/proactive-memory.md +26 -16
  158. package/docs/prompt-envelope-and-tools.md +4 -2
  159. package/docs/provider-adapter-cookbook.md +1 -1
  160. package/docs/release-cut-checklist.md +83 -65
  161. package/docs/resource-library.md +59 -0
  162. package/docs/safety-model.md +29 -7
  163. package/docs/scientific-validation.md +3 -3
  164. package/docs/session-lifecycle.md +37 -1
  165. package/docs/skills-marketplace.md +16 -3
  166. package/docs/tool-usage.md +14 -7
  167. package/docs/trace-store.md +1 -1
  168. package/docs/troubleshooting.md +1 -1
  169. package/docs/tui-design.md +38 -4
  170. package/docs/worker-dispatch-mechanics.md +3 -3
  171. package/package.json +7 -4
  172. package/src/cli/agents.ts +2 -3
  173. package/src/cli/argv.ts +14 -1
  174. package/src/cli/fleet-commands.ts +37 -0
  175. package/src/cli/fleet-graph.ts +102 -0
  176. package/src/cli/fleet-new.ts +36 -0
  177. package/src/cli/fleet-preflight.ts +121 -0
  178. package/src/cli/fleet-validate.ts +30 -0
  179. package/src/cli/fleet.ts +188 -335
  180. package/src/cli/index.ts +4 -2
  181. package/src/cli/library.ts +190 -0
  182. package/src/cli/memory.ts +272 -10
  183. package/src/cli/modes/json-stream.ts +2 -2
  184. package/src/cli/modes/print.ts +12 -1
  185. package/src/cli/run.ts +22 -2
  186. package/src/cli/share.ts +13 -1
  187. package/src/cli/targets.ts +12 -3
  188. package/src/cli/usage.ts +160 -20
  189. package/src/core/bus-events.ts +7 -0
  190. package/src/core/commit-attribution.ts +4 -4
  191. package/src/core/config.ts +130 -0
  192. package/src/core/defaults.ts +81 -0
  193. package/src/core/response-model-id.ts +134 -0
  194. package/src/core/toml.ts +62 -0
  195. package/src/core/workspace-files.ts +0 -1
  196. package/src/domains/agents/builtins/architect.md +2 -1
  197. package/src/domains/agents/builtins/oracle.md +33 -0
  198. package/src/domains/agents/catalog.ts +18 -5
  199. package/src/domains/agents/fleet-contract.ts +278 -16
  200. package/src/domains/agents/index.ts +14 -0
  201. package/src/domains/agents/recipe.ts +54 -14
  202. package/src/domains/agents/result-contract.ts +242 -5
  203. package/src/domains/config/classify.ts +4 -0
  204. package/src/domains/context/bootstrap.ts +36 -27
  205. package/src/domains/context/project-metadata.ts +19 -63
  206. package/src/domains/context/prompt-context.ts +8 -0
  207. package/src/domains/context/working-set/policies/index.ts +3 -4
  208. package/src/domains/dispatch/active-route-planner.ts +14 -0
  209. package/src/domains/dispatch/backoff.ts +2 -1
  210. package/src/domains/dispatch/budget-envelope.ts +396 -0
  211. package/src/domains/dispatch/capability-match.ts +1 -0
  212. package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
  213. package/src/domains/dispatch/contract.ts +36 -0
  214. package/src/domains/dispatch/delegation-plan.ts +167 -0
  215. package/src/domains/dispatch/execution-plan.ts +76 -5
  216. package/src/domains/dispatch/execution-role.ts +3 -1
  217. package/src/domains/dispatch/execution-scheduler.ts +183 -67
  218. package/src/domains/dispatch/extension.ts +339 -36
  219. package/src/domains/dispatch/fleet-gate.ts +14 -0
  220. package/src/domains/dispatch/fleet-plan.ts +63 -3
  221. package/src/domains/dispatch/fleet-run.ts +737 -0
  222. package/src/domains/dispatch/gate-role-prompts.ts +9 -0
  223. package/src/domains/dispatch/host-verification.ts +178 -0
  224. package/src/domains/dispatch/index.ts +38 -0
  225. package/src/domains/dispatch/intent.ts +159 -0
  226. package/src/domains/dispatch/orphan-recovery.ts +1 -0
  227. package/src/domains/dispatch/receipt-integrity.ts +12 -4
  228. package/src/domains/dispatch/state.ts +37 -3
  229. package/src/domains/dispatch/types.ts +61 -9
  230. package/src/domains/dispatch/validation.ts +80 -6
  231. package/src/domains/dispatch/worker-spawn.ts +14 -3
  232. package/src/domains/eval/metrics/evidence.ts +0 -116
  233. package/src/domains/eval/metrics/invariants.ts +1 -1
  234. package/src/domains/eval/runners/clio-run.ts +1 -10
  235. package/src/domains/eval/runners/external-command.ts +2 -29
  236. package/src/domains/eval/schema/suite.ts +0 -7
  237. package/src/domains/eval/suites/run.ts +1 -7
  238. package/src/domains/evidence/trust-status.ts +10 -1
  239. package/src/domains/memory/index.ts +22 -0
  240. package/src/domains/memory/operations.ts +58 -1
  241. package/src/domains/memory/promotion.ts +281 -0
  242. package/src/domains/memory/prompt-section.ts +25 -5
  243. package/src/domains/memory/proposal.ts +51 -7
  244. package/src/domains/memory/task-bank.ts +3 -2
  245. package/src/domains/memory/task-memory-handoff.ts +181 -24
  246. package/src/domains/memory/task-memory-policy.ts +3 -1
  247. package/src/domains/memory/types.ts +37 -0
  248. package/src/domains/memory/validate.ts +178 -0
  249. package/src/domains/middleware/index.ts +15 -0
  250. package/src/domains/middleware/memory-intervention.ts +35 -25
  251. package/src/domains/middleware/runtime.ts +6 -0
  252. package/src/domains/middleware/skills-reminder.ts +19 -4
  253. package/src/domains/middleware/stalled-turn.ts +43 -1
  254. package/src/domains/middleware/types.ts +10 -0
  255. package/src/domains/middleware/watchdog.ts +281 -0
  256. package/src/domains/observability/contract.ts +9 -2
  257. package/src/domains/observability/cost.ts +31 -4
  258. package/src/domains/observability/extension.ts +2 -2
  259. package/src/domains/observability/index.ts +10 -0
  260. package/src/domains/observability/out-of-turn-usage.ts +223 -0
  261. package/src/domains/providers/index.ts +3 -0
  262. package/src/domains/providers/model-discovery.ts +9 -0
  263. package/src/domains/providers/runtime-resolution.ts +38 -1
  264. package/src/domains/providers/runtimes/common/probe-helpers.ts +97 -16
  265. package/src/domains/providers/types/context-window-slots.ts +18 -0
  266. package/src/domains/providers/types/runtime-descriptor.ts +3 -1
  267. package/src/domains/resources/index.ts +20 -0
  268. package/src/domains/resources/library.ts +326 -0
  269. package/src/domains/resources/skills/marketplace.ts +37 -12
  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/redaction.ts +73 -0
  273. package/src/domains/session/context-ledger.ts +10 -1
  274. package/src/domains/session/decision-board.ts +4 -0
  275. package/src/domains/session/entries.ts +3 -0
  276. package/src/domains/session/handoff.ts +629 -0
  277. package/src/domains/session/history.ts +68 -19
  278. package/src/domains/session/usage.ts +24 -7
  279. package/src/domains/share/archive.ts +67 -2
  280. package/src/engine/acp/event-mapper.ts +7 -0
  281. package/src/engine/acp/server.ts +29 -2
  282. package/src/engine/apis/lmstudio.ts +25 -4
  283. package/src/engine/apis/openai-completions.ts +147 -22
  284. package/src/engine/claude/sdk-runtime.ts +8 -2
  285. package/src/engine/claude/tool-safety.ts +13 -0
  286. package/src/engine/loop-guard.ts +27 -3
  287. package/src/engine/worker-events.ts +4 -3
  288. package/src/engine/worker-runtime.ts +59 -54
  289. package/src/entry/orchestrator.ts +55 -1
  290. package/src/interactive/bus-notices.ts +26 -0
  291. package/src/interactive/chat-loop-messages.ts +22 -0
  292. package/src/interactive/chat-loop.ts +248 -1
  293. package/src/interactive/chat-renderer.ts +41 -3
  294. package/src/interactive/clio-editor.ts +44 -7
  295. package/src/interactive/context-overlay.ts +43 -5
  296. package/src/interactive/cost-overlay.ts +70 -11
  297. package/src/interactive/council-dispatch.ts +30 -0
  298. package/src/interactive/council-grid.ts +213 -0
  299. package/src/interactive/council.ts +99 -0
  300. package/src/interactive/dispatch-board.ts +471 -50
  301. package/src/interactive/fleet-run-preview.ts +307 -0
  302. package/src/interactive/footer/notifications.ts +219 -0
  303. package/src/interactive/footer/widgets.ts +13 -0
  304. package/src/interactive/handoff-round.ts +56 -0
  305. package/src/interactive/interactive-application.ts +49 -2
  306. package/src/interactive/interactive-event-projection.ts +9 -1
  307. package/src/interactive/interactive-input-runtime.ts +11 -1
  308. package/src/interactive/interactive-presentation.ts +11 -1
  309. package/src/interactive/interactive-slash-runtime.ts +52 -2
  310. package/src/interactive/interactive-subscriptions.ts +14 -2
  311. package/src/interactive/memory-overlay.ts +89 -4
  312. package/src/interactive/oracle.ts +179 -0
  313. package/src/interactive/overlay-ask-user-lifecycle.ts +7 -1
  314. package/src/interactive/overlay-frame.ts +5 -2
  315. package/src/interactive/overlay-general-openers.ts +230 -2
  316. package/src/interactive/overlay-key-routing.ts +58 -2
  317. package/src/interactive/overlay-lifecycle.ts +52 -5
  318. package/src/interactive/overlay-permission-lifecycle.ts +33 -8
  319. package/src/interactive/overlay-resource-openers.ts +11 -3
  320. package/src/interactive/overlay-session-lifecycle.ts +234 -2
  321. package/src/interactive/overlay-transitions.ts +11 -0
  322. package/src/interactive/overlays/ask-user.ts +74 -30
  323. package/src/interactive/overlays/decisions.ts +3 -1
  324. package/src/interactive/overlays/fleet-run-approval.ts +208 -0
  325. package/src/interactive/overlays/handoff-review.ts +185 -0
  326. package/src/interactive/overlays/library-install-confirm.ts +151 -0
  327. package/src/interactive/overlays/list-overlay.ts +168 -2
  328. package/src/interactive/overlays/settings.ts +101 -4
  329. package/src/interactive/overlays/side-question.ts +139 -0
  330. package/src/interactive/overlays/skills-hub.ts +401 -15
  331. package/src/interactive/permission-hint.ts +35 -0
  332. package/src/interactive/permission-overlay.ts +95 -45
  333. package/src/interactive/renderers/tool-execution.ts +19 -49
  334. package/src/interactive/session-last-turn.ts +8 -1
  335. package/src/interactive/session-usage-reseed.ts +36 -10
  336. package/src/interactive/side-question.ts +171 -0
  337. package/src/interactive/slash-commands.ts +434 -7
  338. package/src/interactive/slash-spec.ts +19 -6
  339. package/src/interactive/status/summary.ts +5 -0
  340. package/src/interactive/status/types.ts +5 -0
  341. package/src/interactive/terminal-lease.ts +1 -0
  342. package/src/interactive/theme/tokens.ts +30 -0
  343. package/src/interactive/turn-context.ts +96 -23
  344. package/src/interactive/turn-middleware.ts +16 -1
  345. package/src/interactive/turn-runtime.ts +37 -8
  346. package/src/interactive/turn-state.ts +3 -0
  347. package/src/interactive/watchdog-run.ts +75 -0
  348. package/src/interactive/worker-progress.ts +440 -0
  349. package/src/interactive/worker-share.ts +56 -1
  350. package/src/interactive/worker-stream.ts +58 -110
  351. package/src/tools/agent-tools.ts +28 -3
  352. package/src/tools/ask-user.ts +21 -1
  353. package/src/tools/bootstrap.ts +3 -0
  354. package/src/tools/compete-worktrees.ts +13 -79
  355. package/src/tools/context/index.ts +2 -2
  356. package/src/tools/dispatch-admission.ts +242 -8
  357. package/src/tools/dispatch-arguments.ts +65 -1
  358. package/src/tools/dispatch-event-text.ts +19 -0
  359. package/src/tools/dispatch-plan.ts +136 -6
  360. package/src/tools/dispatch-runner.ts +319 -13
  361. package/src/tools/dispatch-types.ts +20 -1
  362. package/src/tools/dispatch.ts +96 -3
  363. package/src/tools/monitor.ts +31 -0
  364. package/src/tools/profiles.ts +18 -4
  365. package/src/tools/registry.ts +15 -5
  366. package/src/tools/result-disposition.ts +156 -0
  367. package/src/tools/result-shaping.ts +59 -1
  368. package/src/tools/task-worktree.ts +238 -0
  369. package/src/tools/verify/authoring.ts +116 -55
  370. package/src/tools/verify/scripts.ts +62 -0
  371. package/src/tools/worker-evidence.ts +21 -1
  372. package/src/worker/spec-contract.ts +44 -3
  373. package/dist/chunk-EFADSJET.js +0 -18
  374. package/dist/chunk-HC4CLZ2Y.js +0 -68
  375. package/dist/memory-4ALKDJ4Q.js +0 -246
  376. package/src/domains/eval/metrics/chaos-stream.ts +0 -93
@@ -0,0 +1,179 @@
1
+ /**
2
+ * `/oracle <question>`: the briefing an advisor is given, and the note its
3
+ * answer becomes.
4
+ *
5
+ * Oracle never receives a forked transcript. A transcript is the session's raw
6
+ * history, and handing it to an advisor makes the advisor agree with whatever
7
+ * the session already believes, which is the opposite of what an operator asks
8
+ * an oracle for. It is briefed on the record instead: the decisions the
9
+ * operator settled, the work the board still owes, and the last compaction
10
+ * summary when one exists.
11
+ *
12
+ * Pure: no I/O, no bus, no session, no dispatch. The caller owns the run.
13
+ */
14
+
15
+ import type { OracleResult } from "../domains/agents/index.js";
16
+ import type { DecisionLedgerEntry } from "../domains/session/entries.js";
17
+ import type { TaskBoardTask } from "../domains/session/task-board.js";
18
+
19
+ /** The recipe `/oracle` dispatches. Shadow, so `/run` can never reach it. */
20
+ export const ORACLE_AGENT_ID = "oracle";
21
+
22
+ /**
23
+ * Total bound on the packed briefing, in UTF-8 bytes. Well under the 64 KiB
24
+ * an internal-origin dispatch briefing may carry, because the point of the
25
+ * digest is that a small local advisor can hold all of it at once.
26
+ */
27
+ export const ORACLE_DIGEST_MAX_BYTES = 12 * 1024;
28
+
29
+ /**
30
+ * Per-section byte caps. They sum to 11776, which leaves 512 bytes of the
31
+ * total for the section headers and blank lines the packer writes between
32
+ * them. Each section is clamped on its own before the whole digest is clamped
33
+ * again, so one runaway section can never starve the others: a 40 KiB
34
+ * compaction summary loses its own tail rather than the question's.
35
+ */
36
+ export const ORACLE_DECISIONS_MAX_BYTES = 5120;
37
+ export const ORACLE_TASKS_MAX_BYTES = 3072;
38
+ export const ORACLE_COMPACTION_MAX_BYTES = 2048;
39
+ export const ORACLE_QUESTION_MAX_BYTES = 1536;
40
+
41
+ /** Row caps, applied before the byte caps. Newest decisions and lowest task ids first. */
42
+ export const ORACLE_DECISIONS_MAX_ROWS = 24;
43
+ export const ORACLE_TASKS_MAX_ROWS = 24;
44
+
45
+ /** Appended wherever a cap cut content, so the advisor knows it is reading a tail-less record. */
46
+ export const ORACLE_TRUNCATION_MARKER = "\n[truncated]";
47
+
48
+ export const ORACLE_NO_DECISIONS = "(no settled decisions on this branch)";
49
+ export const ORACLE_NO_TASKS = "(no open tasks on the board)";
50
+
51
+ export interface OracleDigestSources {
52
+ /** Settled interviews, newest first, exactly as the decision board folds them. */
53
+ decisions: ReadonlyArray<DecisionLedgerEntry>;
54
+ /** The live task board's tasks; the packer keeps the open ones. */
55
+ tasks: ReadonlyArray<TaskBoardTask>;
56
+ /** The most recent compaction summary on the active branch, or null when the session never compacted. */
57
+ compactionSummary: string | null;
58
+ question: string;
59
+ }
60
+
61
+ export interface OracleDigest {
62
+ text: string;
63
+ /** True when any cap cut content. The digest says so in its own text as well. */
64
+ truncated: boolean;
65
+ }
66
+
67
+ function byteLength(text: string): number {
68
+ return Buffer.byteLength(text, "utf8");
69
+ }
70
+
71
+ /**
72
+ * Clamp to `maxBytes` including the marker, never past a UTF-8 code point. A
73
+ * marker that pushed the result over its own cap would make the section caps
74
+ * unable to add up to the total, which is the one property the packer relies
75
+ * on.
76
+ */
77
+ function clamp(text: string, maxBytes: number): { text: string; truncated: boolean } {
78
+ if (byteLength(text) <= maxBytes) return { text, truncated: false };
79
+ const room = Math.max(0, maxBytes - byteLength(ORACLE_TRUNCATION_MARKER));
80
+ const buffer = Buffer.from(text, "utf8");
81
+ let cut = Math.min(room, buffer.byteLength);
82
+ while (cut > 0) {
83
+ const next = buffer[cut];
84
+ if (next === undefined || (next & 0xc0) !== 0x80) break;
85
+ cut -= 1;
86
+ }
87
+ return { text: `${buffer.subarray(0, cut).toString("utf8")}${ORACLE_TRUNCATION_MARKER}`, truncated: true };
88
+ }
89
+
90
+ /** One decision line. Superseded decisions carry their correction so a reversal is visible. */
91
+ function decisionLine(decision: DecisionLedgerEntry["decisions"][number]): string {
92
+ const label = decision.label ? ` (${decision.label})` : "";
93
+ if (decision.status === "superseded") {
94
+ const correction = decision.correction ? `; corrected to ${decision.correction}` : "";
95
+ return `- ${decision.key}${label}: ${decision.value} [superseded${correction}]`;
96
+ }
97
+ return `- ${decision.key}${label}: ${decision.value}`;
98
+ }
99
+
100
+ function taskLine(task: TaskBoardTask): string {
101
+ return `- ${task.id} [${task.status}] ${task.title}`;
102
+ }
103
+
104
+ function isOpenTask(task: TaskBoardTask): boolean {
105
+ return task.status === "pending" || task.status === "active";
106
+ }
107
+
108
+ /**
109
+ * Pack the briefing. Sections are fixed and always present, so an advisor
110
+ * reading an empty one learns that the board is empty rather than that the
111
+ * packer omitted it.
112
+ */
113
+ export function packOracleDigest(sources: OracleDigestSources): OracleDigest {
114
+ let truncated = false;
115
+
116
+ const decisionRows = sources.decisions
117
+ .flatMap((entry) => entry.decisions.map(decisionLine))
118
+ .slice(0, ORACLE_DECISIONS_MAX_ROWS);
119
+ if (decisionRows.length < sources.decisions.reduce((total, entry) => total + entry.decisions.length, 0)) {
120
+ truncated = true;
121
+ }
122
+ const decisions = clamp(decisionRows.join("\n") || ORACLE_NO_DECISIONS, ORACLE_DECISIONS_MAX_BYTES);
123
+ truncated = truncated || decisions.truncated;
124
+
125
+ const openTasks = sources.tasks.filter(isOpenTask);
126
+ const taskRows = openTasks.map(taskLine).slice(0, ORACLE_TASKS_MAX_ROWS);
127
+ if (taskRows.length < openTasks.length) truncated = true;
128
+ const tasks = clamp(taskRows.join("\n") || ORACLE_NO_TASKS, ORACLE_TASKS_MAX_BYTES);
129
+ truncated = truncated || tasks.truncated;
130
+
131
+ const summaryText = sources.compactionSummary?.trim() ?? "";
132
+ const summary = clamp(summaryText, ORACLE_COMPACTION_MAX_BYTES);
133
+ truncated = truncated || summary.truncated;
134
+
135
+ const question = clamp(sources.question.trim(), ORACLE_QUESTION_MAX_BYTES);
136
+ truncated = truncated || question.truncated;
137
+
138
+ const sections = [
139
+ "## Settled decisions",
140
+ decisions.text,
141
+ "",
142
+ "## Open tasks",
143
+ tasks.text,
144
+ "",
145
+ "## Last compaction summary",
146
+ summary.text.length > 0 ? summary.text : "(this session has not compacted)",
147
+ "",
148
+ "## Question",
149
+ question.text,
150
+ ];
151
+ const whole = clamp(sections.join("\n"), ORACLE_DIGEST_MAX_BYTES);
152
+ return { text: whole.text, truncated: truncated || whole.truncated };
153
+ }
154
+
155
+ /** The bounded task an oracle run is given. The briefing carries the record; this carries the job. */
156
+ export const ORACLE_TASK =
157
+ "Answer the question in the briefing's Question section. Check it against the settled decisions first, " +
158
+ "then return the strongest challenge you can support and the evidence that would change your mind.";
159
+
160
+ /**
161
+ * The advisor's structured answer as the prose the operator note carries. The
162
+ * contract keeps the four fields typed so a run cannot half-answer; the note
163
+ * renders them because the main agent reads prose, not a payload.
164
+ */
165
+ export function formatOracleAnswer(result: OracleResult): string {
166
+ const cited =
167
+ result.citedDecisions.length === 0
168
+ ? "Cited decisions: none bear on this question."
169
+ : `Cited decisions: ${result.citedDecisions.join(", ")}`;
170
+ return [
171
+ `Verdict: ${result.verdict}`,
172
+ "",
173
+ `Strongest challenge: ${result.challenge}`,
174
+ "",
175
+ `What would change its mind: ${result.changesMyMind}`,
176
+ "",
177
+ cited,
178
+ ].join("\n");
179
+ }
@@ -14,6 +14,11 @@ export interface OverlayAskUserLifecycleDeps {
14
14
  requestRender(): void;
15
15
  registerHandler?(handler: AskUserHandler): () => void;
16
16
  openAskUserOverlay?: typeof openAskUserOverlay;
17
+ /**
18
+ * An ask_user request parked waiting for the operator. Fired when the
19
+ * overlay session opens, not per question, so one interview notifies once.
20
+ */
21
+ onOperatorParked?(): void;
17
22
  }
18
23
 
19
24
  export interface OverlayAskUserLifecycle {
@@ -61,6 +66,7 @@ export function createOverlayAskUserLifecycle(deps: OverlayAskUserLifecycleDeps)
61
66
  deps.setOverlayState("ask-user");
62
67
  session = openSession(deps.tui, { onCancel: () => pendingCancel?.() });
63
68
  deps.setOverlayHandle(session);
69
+ deps.onOperatorParked?.();
64
70
  deps.requestRender();
65
71
  return session;
66
72
  };
@@ -77,7 +83,7 @@ export function createOverlayAskUserLifecycle(deps: OverlayAskUserLifecycleDeps)
77
83
  const activeSession = ensureSession();
78
84
  if (!activeSession) return cancelledAskUserResult();
79
85
  pendingCancel = cancel;
80
- const result = await activeSession.ask(questions);
86
+ const result = await activeSession.ask(questions, invokeOptions?.decisionPresentation);
81
87
  if (result.cancelled === true || !toolBacked) {
82
88
  if (result.cancelled === true) cancelledForTurn = true;
83
89
  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,7 +1,16 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import type { ClioSettings } from "../core/config.js";
1
3
  import type { SafeEventBus } from "../core/event-bus.js";
4
+ import type { AgentsContract } from "../domains/agents/contract.js";
2
5
  import { foldWorkingSet } from "../domains/context/working-set/fold.js";
3
- import type { DispatchContract } from "../domains/dispatch/index.js";
4
- import { loadMemoryRecordsSync, type MemoryRecord } from "../domains/memory/index.js";
6
+ import type { DispatchContract, DispatchRequest } from "../domains/dispatch/index.js";
7
+ import { agentRoleFactsResolver, executeFleetRun, requestExecutionRole } from "../domains/dispatch/index.js";
8
+ import {
9
+ canonicalMemoryRepositoryIdentity,
10
+ loadMemoryRecordsSync,
11
+ type MemoryRecord,
12
+ proposeMemoryPromotion,
13
+ } from "../domains/memory/index.js";
5
14
  import type { ObservabilityContract } from "../domains/observability/index.js";
6
15
  import type { ContextLedger } from "../domains/session/context-ledger.js";
7
16
  import type { SessionMeta } from "../domains/session/index.js";
@@ -17,6 +26,7 @@ import {
17
26
  isDispatchBoardRowCancellable,
18
27
  isDispatchBoardRowSteerable,
19
28
  } from "./dispatch-board.js";
29
+ import { compileFleetRunPreview, type FleetRunPreview, type FleetRunPreviewInput } from "./fleet-run-preview.js";
20
30
  import { openMemoryOverlay } from "./memory-overlay.js";
21
31
  import { buildHint, showClioOverlayFrame } from "./overlay-frame.js";
22
32
  import type { OverlayTransitions } from "./overlay-transitions.js";
@@ -26,6 +36,8 @@ import {
26
36
  openContextResetOverlay,
27
37
  } from "./overlays/context-reset.js";
28
38
  import { formatDecisionCorrectionTurn, openDecisionsOverlay } from "./overlays/decisions.js";
39
+ import { openFleetRunApprovalOverlay } from "./overlays/fleet-run-approval.js";
40
+ import { openSideQuestionOverlay } from "./overlays/side-question.js";
29
41
  import { type ContextClearCommandOptions, formatUserTaskHandoff } from "./slash-commands.js";
30
42
  import { openTasksOverlay } from "./tasks-overlay.js";
31
43
  import { type ArtifactProviderDeps, createDefaultArtifactProviders } from "./view/artifacts.js";
@@ -57,6 +69,8 @@ export interface OverlayGeneralOpenersDeps {
57
69
  stateDir: string;
58
70
  getSessionMeta: () => SessionMeta | null;
59
71
  readSessionEntries?: ArtifactProviderDeps["readSessionEntries"];
72
+ /** Live settings, so `/context` can state the configured working-set policy. */
73
+ getSettings?: () => Readonly<ClioSettings>;
60
74
  terminal: { columns: number };
61
75
  dispatchBoard: ReturnType<typeof createDispatchBoardView>;
62
76
  startDispatchBoardTicker: () => void;
@@ -69,6 +83,37 @@ export interface OverlayGeneralOpenersDeps {
69
83
  openDecisionsOverlay?: typeof openDecisionsOverlay;
70
84
  openMemoryOverlay?: typeof openMemoryOverlay;
71
85
  openViewOverlay?: typeof openViewOverlay;
86
+ openSideQuestionOverlay?: typeof openSideQuestionOverlay;
87
+ openFleetRunApprovalOverlay?: typeof openFleetRunApprovalOverlay;
88
+ /** Recipe registry `/fleet run` resolves every step's agent against. */
89
+ agents?: AgentsContract;
90
+ /** Session budget state the run would be admitted under; absent leaves it unknown. */
91
+ getBudgetPreflight?: () => { ceilingUsd: number; currentUsd: number; verdict: "under" | "at" | "over" };
92
+ /**
93
+ * True while a turn is streaming. `/fleet run` is refused then, never
94
+ * queued: an approved plan describes the workspace as it stands, and a turn
95
+ * still in flight is about to change it.
96
+ */
97
+ isTurnInFlight?: () => boolean;
98
+ /** Record which wave and step a dispatched run belongs to, for the board's phase column. */
99
+ setFleetRunPhase?: (runId: string, phase: { wave: number; stepId: string }) => void;
100
+ /** The shared fleet-run path; overridable so a test can observe one call. */
101
+ runFleet?: typeof executeFleetRun;
102
+ /** Contract and command-registry loader; overridable so a test projects a fixture. */
103
+ loadFleetSources?: FleetRunPreviewInput["load"];
104
+ /**
105
+ * Run one `/btw` round. Absent on a host with no chat loop, in which case
106
+ * `/btw` reports that instead of opening an empty overlay.
107
+ */
108
+ askSideQuestion?: (
109
+ question: string,
110
+ options: { signal: AbortSignal; onDelta: (partialText: string) => void },
111
+ ) => Promise<
112
+ | { status: "answered"; text: string }
113
+ | { status: "aborted"; text: string }
114
+ | { status: "refused"; reason: string }
115
+ | { status: "failed"; reason: string }
116
+ >;
72
117
  }
73
118
 
74
119
  export interface OverlayGeneralOpeners {
@@ -81,6 +126,9 @@ export interface OverlayGeneralOpeners {
81
126
  openMemory(): void;
82
127
  openView(initialFilter?: string): void;
83
128
  toggleDispatchBoard(): void;
129
+ openSideQuestion(question: string): void;
130
+ /** `/fleet run <name> [--var k=v ...]`: preview the plan, then dispatch on approval. */
131
+ startFleetRun(name: string, vars: Readonly<Record<string, string>>): void;
84
132
  }
85
133
 
86
134
  export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): OverlayGeneralOpeners {
@@ -92,6 +140,7 @@ export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): Ov
92
140
  const openMemoryOverlayFactory = deps.openMemoryOverlay ?? openMemoryOverlay;
93
141
  const openViewOverlayFactory = deps.openViewOverlay ?? openViewOverlay;
94
142
  const showOverlayFrameFactory = deps.showOverlayFrame ?? showClioOverlayFrame;
143
+ const openSideQuestionOverlayFactory = deps.openSideQuestionOverlay ?? openSideQuestionOverlay;
95
144
 
96
145
  const openCost = (): void => {
97
146
  if (deps.transitions.state !== "closed") return;
@@ -113,6 +162,10 @@ export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): Ov
113
162
  if (!readSessionEntries) return null;
114
163
  return foldWorkingSet(readSessionEntries(), deps.getSessionMeta()?.pinnedLeafTurnId ?? undefined);
115
164
  },
165
+ getWorkingSetConfig: () => {
166
+ const workingSet = deps.getSettings?.().context.workingSet;
167
+ return workingSet ? { enabled: workingSet.enabled, policy: workingSet.policy } : null;
168
+ },
116
169
  });
117
170
  deps.requestRender();
118
171
  };
@@ -218,6 +271,32 @@ export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): Ov
218
271
  deps.transitions.state = "memory";
219
272
  deps.transitions.handle = openMemoryOverlayFactory(deps.tui, deps.getTaskMemoryStatus, () => records, {
220
273
  onClose: deps.closeOverlay,
274
+ onPromote: async (entry, scope) => {
275
+ const meta = deps.getSessionMeta();
276
+ if (!meta) throw new Error("memory promotion requires an active session");
277
+ const selection =
278
+ scope === "global"
279
+ ? ({ scope: "global", acknowledgeGlobal: true } as const)
280
+ : (() => {
281
+ const repository = canonicalMemoryRepositoryIdentity(meta.cwd);
282
+ if (repository === null) {
283
+ throw new Error("repo promotion requires a canonical active repository identity");
284
+ }
285
+ return { scope: "repo", repository } as const;
286
+ })();
287
+ const result = await proposeMemoryPromotion(
288
+ deps.dataDir,
289
+ {
290
+ kind: "task-bank-entry",
291
+ sessionId: meta.id,
292
+ evidenceRefs: [`session-${meta.id}`],
293
+ entry,
294
+ },
295
+ selection,
296
+ );
297
+ records = loadMemoryRecordsSync(deps.dataDir);
298
+ return result;
299
+ },
221
300
  });
222
301
  deps.requestRender();
223
302
  };
@@ -261,11 +340,158 @@ export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): Ov
261
340
  const dispatchBoardHint = (): string => {
262
341
  const row = deps.dispatchBoard.selectedRow();
263
342
  const entries = [{ key: "↑↓", verb: "select" }];
343
+ if (row) entries.push({ key: "Enter", verb: deps.dispatchBoard.detailExpanded() ? "hide detail" : "detail" });
264
344
  if (row && isDispatchBoardRowSteerable(row)) entries.push({ key: "s", verb: "steer" });
265
345
  if (row && isDispatchBoardRowCancellable(row)) entries.push({ key: "x", verb: "cancel" });
266
346
  return buildHint(entries);
267
347
  };
268
348
 
349
+ /**
350
+ * `/btw <question>`: one model round beside the session, rendered in an
351
+ * overlay and nowhere else. Esc aborts a round that is still streaming and
352
+ * closes one that has settled; both paths run through the same abort
353
+ * controller, so a cancelled round never keeps a stream alive behind a closed
354
+ * overlay.
355
+ */
356
+ const openSideQuestion = (question: string): void => {
357
+ if (deps.transitions.state !== "closed") return;
358
+ const ask = deps.askSideQuestion;
359
+ if (!ask) {
360
+ deps.notify("error", "/btw is not wired in this session", "btw:unavailable");
361
+ return;
362
+ }
363
+ const controller = new AbortController();
364
+ deps.transitions.state = "side-question";
365
+ const session = openSideQuestionOverlayFactory(deps.tui, {
366
+ question,
367
+ columns: deps.terminal.columns,
368
+ onClose: () => controller.abort(),
369
+ });
370
+ deps.transitions.handle = session;
371
+ deps.requestRender();
372
+ void ask(question, { signal: controller.signal, onDelta: (partialText) => session.setAnswer(partialText) })
373
+ .then((outcome) => {
374
+ if (outcome.status === "answered") {
375
+ session.settle({ kind: "answered", text: outcome.text });
376
+ return;
377
+ }
378
+ if (outcome.status === "aborted") {
379
+ session.settle({ kind: "aborted", text: outcome.text });
380
+ return;
381
+ }
382
+ session.settle({ kind: "error", reason: outcome.reason });
383
+ })
384
+ .catch((error: unknown) => {
385
+ session.settle({ kind: "error", reason: error instanceof Error ? error.message : String(error) });
386
+ });
387
+ };
388
+
389
+ /**
390
+ * `/fleet run <name>`: compile the plan, show it, and dispatch only what the
391
+ * operator accepted.
392
+ *
393
+ * Nothing is dispatched and nothing is written before the accept key. A
394
+ * contract that fails preflight opens the same overlay with its diagnostics
395
+ * and no accept action, so the only exit from a broken plan is to leave it.
396
+ */
397
+ const startFleetRun = (name: string, vars: Readonly<Record<string, string>>): void => {
398
+ if (deps.transitions.state !== "closed") return;
399
+ // Refused, never queued: an approved plan describes the workspace as it
400
+ // stands, and a turn still in flight is about to change it.
401
+ if (deps.isTurnInFlight?.() === true) {
402
+ deps.notify("warning", "a turn is in flight; /fleet run is refused rather than queued", "fleet-run:in-flight");
403
+ return;
404
+ }
405
+ const agents = deps.agents;
406
+ if (!agents) {
407
+ deps.notify("error", "/fleet run needs the agents domain, which is not wired in this session", "fleet-run:agents");
408
+ return;
409
+ }
410
+ const workspaceRoot = deps.getSessionMeta()?.cwd ?? process.cwd();
411
+ const roleFacts = agentRoleFactsResolver((id) => agents.getSpec(id));
412
+ const budget = deps.getBudgetPreflight?.();
413
+ const result = compileFleetRunPreview({
414
+ workspaceRoot,
415
+ name,
416
+ vars,
417
+ getAgentSpec: (agentId) => agents.getSpec(agentId),
418
+ roleFacts,
419
+ ...(budget ? { budget } : {}),
420
+ ...(deps.loadFleetSources ? { load: deps.loadFleetSources } : {}),
421
+ resolveRoute: (step) => {
422
+ const request: DispatchRequest = {
423
+ agentId: step.agentId,
424
+ executionRole: requestExecutionRole({ agentId: step.agentId, resolveFacts: roleFacts }),
425
+ task: step.task,
426
+ ...(step.scope === "readonly" ? { autonomy: "read-only" as const } : {}),
427
+ ...(step.target !== undefined ? { target: step.target } : {}),
428
+ ...(step.profile !== undefined ? { workerProfile: step.profile } : {}),
429
+ };
430
+ const resolution = deps.dispatch.preview?.(request);
431
+ if (!resolution) return null;
432
+ return {
433
+ targetId: resolution.targetId,
434
+ wireModelId: resolution.wireModelId,
435
+ nodeId: resolution.node.id,
436
+ };
437
+ },
438
+ });
439
+
440
+ deps.transitions.state = "fleet-run-approval";
441
+ deps.transitions.handle = (deps.openFleetRunApprovalOverlay ?? openFleetRunApprovalOverlay)(deps.tui, {
442
+ subject: result.ok ? { ok: true, preview: result.preview } : { ok: false, name, diagnostics: result.diagnostics },
443
+ columns: deps.terminal.columns,
444
+ onAccept: () => {
445
+ deps.closeOverlay();
446
+ if (result.ok) dispatchFleetRun(result.preview, agents);
447
+ },
448
+ onCancel: () => {
449
+ deps.closeOverlay();
450
+ deps.notify("info", `fleet ${name}: cancelled; nothing was dispatched`, "fleet-run:cancelled");
451
+ },
452
+ });
453
+ deps.requestRender();
454
+ };
455
+
456
+ const dispatchFleetRun = (preview: FleetRunPreview, agents: AgentsContract): void => {
457
+ const fleetRootId = `fleet-${randomBytes(6).toString("hex")}`;
458
+ const run = deps.runFleet ?? executeFleetRun;
459
+ deps.notify(
460
+ "info",
461
+ `fleet ${preview.name}: root=${fleetRootId} plan=${preview.planHash.slice(0, 12)} steps=${preview.plan.steps.length}`,
462
+ "fleet-run:started",
463
+ );
464
+ void run({
465
+ plan: preview.plan,
466
+ contractName: preview.name,
467
+ commands: preview.commands,
468
+ workspaceRoot: deps.getSessionMeta()?.cwd ?? process.cwd(),
469
+ fleetRootId,
470
+ dispatch: deps.dispatch,
471
+ agents: { getSpec: (agentId) => agents.getSpec(agentId) },
472
+ attributionEnabled: deps.getSettings?.().attribution.gitCommits ?? true,
473
+ vars: preview.vars,
474
+ onStepDispatched: (event) => {
475
+ deps.setFleetRunPhase?.(event.assignmentId, { wave: event.waveIndex, stepId: event.stepId });
476
+ },
477
+ onNotice: (text) => deps.stderr(`[fleet ${preview.name}] ${text}\n`),
478
+ })
479
+ .then((outcome) => {
480
+ deps.notify(
481
+ outcome.cleanRun ? "success" : "warning",
482
+ `fleet ${preview.name}: ${outcome.succeededStepCount}/${outcome.requiredStepCount} steps succeeded, cost $${outcome.totalCostUsd.toFixed(4)}`,
483
+ "fleet-run:settled",
484
+ );
485
+ })
486
+ .catch((error: unknown) => {
487
+ deps.notify(
488
+ "error",
489
+ `fleet ${preview.name}: ${error instanceof Error ? error.message : String(error)}`,
490
+ "fleet-run:failed",
491
+ );
492
+ });
493
+ };
494
+
269
495
  return {
270
496
  openCost,
271
497
  openContextView,
@@ -276,5 +502,7 @@ export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): Ov
276
502
  openMemory,
277
503
  openView,
278
504
  toggleDispatchBoard,
505
+ openSideQuestion,
506
+ startFleetRun,
279
507
  };
280
508
  }
@@ -25,12 +25,28 @@ export type OverlayState =
25
25
  | "prompts"
26
26
  | "extensions"
27
27
  | "interop"
28
- | "skills-hub";
28
+ | "skills-hub"
29
+ | "side-question"
30
+ | "handoff-review"
31
+ | "fleet-run-approval";
29
32
 
30
33
  export interface PermissionOverlayKeyDeps {
31
34
  cancelPermission: () => void;
32
35
  confirmPermission: () => void;
33
36
  stopTurnFromPermission: () => void;
37
+ /**
38
+ * Whether the composer holds text. Enter must not allow a parked call while
39
+ * it does (issue #186): an operator who typed a message and pressed the
40
+ * habitual send key meant to send, and on a safety rail the ambiguous key
41
+ * resolves away from "allow". Absent means an empty composer.
42
+ */
43
+ composerHasDraft?: () => boolean;
44
+ /**
45
+ * Deliver a draft-editing key to the composer while the prompt owns input,
46
+ * so the draft that makes Enter inert can be cleared without first denying
47
+ * the call. Only the deletion keys in `isDraftEditKey` arrive here.
48
+ */
49
+ editDraft?: (data: string) => void;
34
50
  }
35
51
 
36
52
  export interface DispatchBoardOverlayKeyDeps {
@@ -39,6 +55,8 @@ export interface DispatchBoardOverlayKeyDeps {
39
55
  selectNextDispatch: () => void;
40
56
  steerSelectedDispatch: () => void;
41
57
  cancelSelectedDispatch: () => void;
58
+ /** Open or close the selected run's worker-progress detail. */
59
+ toggleSelectedDispatchDetail: () => void;
42
60
  }
43
61
 
44
62
  interface CloseOverlayKeyDeps {
@@ -55,10 +73,29 @@ export function isEscapeKey(data: string): boolean {
55
73
  return matchesKey(data, "escape") && !isKeyRelease(data);
56
74
  }
57
75
 
76
+ const DRAFT_EDIT_KEYS = ["backspace", "delete", "ctrl+u", "ctrl+w", "alt+backspace", "ctrl+k"] as const;
77
+
78
+ /**
79
+ * The keys that only ever remove text from the composer. They are the one
80
+ * class of input a pending permission prompt lets through, because they
81
+ * cannot type a command, cannot submit, and are the way out of the inert-Enter
82
+ * state a draft puts the prompt in.
83
+ */
84
+ function isDraftEditKey(data: string): boolean {
85
+ return !isKeyRelease(data) && DRAFT_EDIT_KEYS.some((key) => matchesKey(data, key));
86
+ }
87
+
58
88
  /** Pure permission overlay key router: returns true when the input was consumed. */
59
89
  export function routePermissionOverlayKey(data: string, deps: PermissionOverlayKeyDeps): boolean {
60
90
  if (matchesKey(data, "enter") && !isKeyRelease(data)) {
61
- deps.confirmPermission();
91
+ // Consumed either way: with a draft the press changes nothing, and the
92
+ // composer rail says what clears it. Letting it fall through would hand
93
+ // Enter to the editor, which is the send path this guard exists to block.
94
+ if (!(deps.composerHasDraft?.() ?? false)) deps.confirmPermission();
95
+ return true;
96
+ }
97
+ if (deps.editDraft && isDraftEditKey(data)) {
98
+ deps.editDraft(data);
62
99
  return true;
63
100
  }
64
101
  if (isEscapeKey(data)) {
@@ -98,6 +135,12 @@ export function routeDispatchBoardOverlayKey(data: string, deps: DispatchBoardOv
98
135
  deps.cancelSelectedDispatch();
99
136
  return true;
100
137
  }
138
+ // Worker progress is expanded detail the operator asks for. The default list
139
+ // stays compact so a fan-out of scouts costs one card each.
140
+ if (matchesKey(data, "enter")) {
141
+ deps.toggleSelectedDispatchDetail();
142
+ return true;
143
+ }
101
144
  return false;
102
145
  }
103
146
 
@@ -180,6 +223,19 @@ export function routeOverlayKey(
180
223
  routeCostOverlayKey(data, deps);
181
224
  return true;
182
225
  }
226
+ // Esc is the only key the side-question overlay answers: it aborts a round
227
+ // that is still streaming and closes one that has settled. Everything else is
228
+ // swallowed so a keystroke cannot reach the composer behind it.
229
+ if (overlayState === "side-question") {
230
+ routeCostOverlayKey(data, deps);
231
+ return true;
232
+ }
233
+ // The handoff review overlay owns Enter, `e`, the arrows, and Esc itself, so
234
+ // every key goes to its own focus box rather than through the router.
235
+ if (overlayState === "handoff-review") return false;
236
+ // The fleet-run approval overlay owns Enter, the arrows, and Esc itself, so
237
+ // every key goes to its own focus box rather than through the router.
238
+ if (overlayState === "fleet-run-approval") return false;
183
239
  if (overlayState === "context-reset") return false;
184
240
  if (overlayState === "tasks") return false;
185
241
  if (overlayState === "decisions") return false;