@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
@@ -1,3 +1,4 @@
1
+ import type { LibraryEntryKind } from "../domains/resources/index.js";
1
2
  import { appendNotice } from "./command-output.js";
2
3
  import { createOverlayAskUserLifecycle, type OverlayAskUserLifecycle } from "./overlay-ask-user-lifecycle.js";
3
4
  import { createOverlayAuthLifecycle } from "./overlay-auth-lifecycle.js";
@@ -12,7 +13,9 @@ import {
12
13
  createPermissionOverlayBody,
13
14
  PERMISSION_OVERLAY_WIDTH,
14
15
  permissionOverlayHint,
16
+ permissionOverlayPlacement,
15
17
  permissionOverlayTitle,
18
+ permissionOverlayTone,
16
19
  } from "./permission-overlay.js";
17
20
 
18
21
  export * from "./overlay-key-routing.js";
@@ -24,6 +27,7 @@ import type { SettingsCenterRowId, SettingsSectionId } from "./overlays/settings
24
27
  // application composition root no longer owns the mutable overlay state.
25
28
  export type OverlayLifecycleApplicationDeps = Pick<
26
29
  import("./interactive-application.js").InteractiveDeps,
30
+ | "agents"
27
31
  | "bus"
28
32
  | "chat"
29
33
  | "commitSetting"
@@ -40,12 +44,14 @@ export type OverlayLifecycleApplicationDeps = Pick<
40
44
  | "observability"
41
45
  | "onContextClear"
42
46
  | "onForkSession"
47
+ | "onNewSession"
43
48
  | "onResumeSession"
44
49
  | "onSelectModel"
45
50
  | "providers"
46
51
  | "readSessionEntries"
47
52
  | "registerAskUserHandler"
48
53
  | "resources"
54
+ | "scheduling"
49
55
  | "session"
50
56
  | "stateDir"
51
57
  | "supersedeDecision"
@@ -73,17 +79,30 @@ export interface OverlayLifecycleRuntimeDeps {
73
79
  notify: (level: import("./interactive-subscriptions.js").InteractiveNoticeLevel, text: string, key?: string) => void;
74
80
  terminal: Pick<import("../engine/tui.js").ProcessTerminal, "columns">;
75
81
  dispatchBoard: ReturnType<typeof import("./dispatch-board.js").createDispatchBoardView>;
82
+ /** Record a dispatched fleet step's plan position for the board's phase column. */
83
+ setFleetRunPhase?: (runId: string, phase: { wave: number; stepId: string }) => void;
76
84
  chatPanel: import("./chat-panel.js").ChatPanel;
77
85
  /** Clears the transcript and every view folded alongside it; the session overlays call it before a replay. */
78
86
  resetTranscript: () => void;
87
+ /**
88
+ * Stop and restart the terminal around a child process that owns the screen.
89
+ * `/handoff` uses it for the external editor, the same way the composer's
90
+ * own `$EDITOR` opener does.
91
+ */
92
+ suspendTerminal: <T>(run: () => T) => T;
79
93
  io: import("./slash-commands.js").RunIo;
80
94
  readStructuredEntries: (sessionId: string) => import("../domains/session/index.js").SessionEntry[];
81
95
  announceTaskMemorySeedOffer: () => void;
82
96
  /** Rescopes the footer's last-turn line when a session overlay changes the branch. */
83
97
  setLastTurnSummary?: (summary: import("./status/index.js").TurnSummary | null) => void;
84
98
  keybindings: ReturnType<typeof import("./keybinding-manager.js").createKeybindingManager>;
85
- editor: Pick<import("./clio-editor.js").ClioEditor, "getText" | "setText">;
99
+ editor: Pick<import("./clio-editor.js").ClioEditor, "getText" | "render" | "setText">;
86
100
  getSlashContext: () => import("./slash-commands.js").SlashCommandContext;
101
+ /**
102
+ * A worker permission or ask_user request parked waiting for the operator.
103
+ * Wired to the desktop notification; absent hosts simply do not notify.
104
+ */
105
+ onOperatorParked?: () => void;
87
106
  showOverlayFrame?: typeof showClioOverlayFrame;
88
107
  openAuthDialog?: typeof import("./overlays/auth-dialog.js").openAuthDialog;
89
108
  openAskUserOverlay?: typeof import("./overlays/ask-user.js").openAskUserOverlay;
@@ -125,6 +144,11 @@ export interface OverlayLifecycleController {
125
144
  openDecisionsOverlayState(): void;
126
145
  openMemoryOverlayState(): void;
127
146
  openViewOverlayState(initialFilter?: string): void;
147
+ /** `/btw <question>`: one side-question round rendered in its own overlay. */
148
+ openSideQuestionOverlayState(question: string): void;
149
+ /** `/handoff <goal>`: extract, review, and seed a successor session. */
150
+ startHandoffState(goal: string): void;
151
+ startFleetRunState(name: string, vars: Readonly<Record<string, string>>): void;
128
152
  openModelOverlayState(): void;
129
153
  openSettingsOverlayState(section?: SettingsSectionId, rowId?: SettingsCenterRowId): void;
130
154
  openResumeOverlayState(): void;
@@ -132,7 +156,7 @@ export interface OverlayLifecycleController {
132
156
  openMessagePickerOverlayState(): void;
133
157
  openHelpOverlayState(query?: string): void;
134
158
  openAgentsOverlayState(): void;
135
- openSkillsHubState(): void;
159
+ openSkillsHubState(tab?: LibraryEntryKind): void;
136
160
  openPromptsOverlayState(): void;
137
161
  openExtensionsOverlayState(): void;
138
162
  openInteropOverlayState(): void;
@@ -194,6 +218,7 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
194
218
  cancelPendingAskUser: () => overlayAskUser?.cancelPending() ?? false,
195
219
  finishAuth: (dismiss) => overlayAuth.finish(dismiss),
196
220
  onPermissionOverlayClosed: () => overlayPermission?.onPermissionOverlayClosed(),
221
+ onOverlayClosed: () => overlayPermission?.retryPending(),
197
222
  });
198
223
  const closeOverlay = overlayTransitions.close;
199
224
 
@@ -227,10 +252,13 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
227
252
  if (overlayTransitions.state !== "closed") return false;
228
253
  overlayTransitions.state = "permission-confirm";
229
254
  overlayTransitions.handle = showOverlayFrame(tui, createPermissionOverlayBody(view), {
230
- anchor: "center",
255
+ ...permissionOverlayPlacement(tui, editor, footer.view),
231
256
  width: PERMISSION_OVERLAY_WIDTH,
232
- title: permissionOverlayTitle(),
233
- footerHint: (innerWidth) => permissionOverlayHint(innerWidth),
257
+ title: permissionOverlayTitle(view),
258
+ tone: permissionOverlayTone(view),
259
+ // Read per frame: the footer names what Enter does right now, and
260
+ // that depends on whether the composer holds a draft.
261
+ footerHint: (innerWidth) => permissionOverlayHint(innerWidth, editor.getText().length > 0),
234
262
  });
235
263
  tui.requestRender();
236
264
  return true;
@@ -247,6 +275,7 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
247
275
  source: "stream_cancel",
248
276
  auditReason: "operator stopped the turn at a permission prompt",
249
277
  }),
278
+ ...(deps.onOperatorParked ? { onOperatorParked: deps.onOperatorParked } : {}),
250
279
  });
251
280
 
252
281
  overlayAskUser = createOverlayAskUserLifecycle({
@@ -264,6 +293,7 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
264
293
  requestRender: () => tui.requestRender(),
265
294
  ...(deps.app.registerAskUserHandler ? { registerHandler: deps.app.registerAskUserHandler } : {}),
266
295
  ...(openAskUserOverlayFactory ? { openAskUserOverlay: openAskUserOverlayFactory } : {}),
296
+ ...(deps.onOperatorParked ? { onOperatorParked: deps.onOperatorParked } : {}),
267
297
  });
268
298
 
269
299
  const overlayModelSelectors = createOverlayModelSelectors({
@@ -312,6 +342,10 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
312
342
  getSlashNotice: () => deps.getSlashContext().notice,
313
343
  ...(deps.app.onResumeSession ? { onResumeSession: deps.app.onResumeSession } : {}),
314
344
  ...(deps.app.onForkSession ? { onForkSession: deps.app.onForkSession } : {}),
345
+ ...(deps.app.onNewSession ? { onNewSession: deps.app.onNewSession } : {}),
346
+ ...(deps.app.getDecisionBoard ? { getDecisionBoard: deps.app.getDecisionBoard } : {}),
347
+ terminal: deps.terminal,
348
+ suspendTerminal: deps.suspendTerminal,
315
349
  announceTaskMemorySeedOffer,
316
350
  sessionUsage: deps.app.observability,
317
351
  ...(setLastTurnSummary ? { setLastTurnSummary } : {}),
@@ -325,6 +359,7 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
325
359
  ...(openCwdFallbackOverlayFactory ? { openCwdFallbackOverlay: openCwdFallbackOverlayFactory } : {}),
326
360
  });
327
361
 
362
+ const scheduling = deps.app.scheduling;
328
363
  const overlayGeneralOpeners = createOverlayGeneralOpeners({
329
364
  tui,
330
365
  transitions: overlayTransitions,
@@ -351,6 +386,7 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
351
386
  stateDir: deps.app.stateDir,
352
387
  getSessionMeta: () => deps.app.session?.current() ?? null,
353
388
  ...(deps.app.readSessionEntries ? { readSessionEntries: deps.app.readSessionEntries } : {}),
389
+ ...(deps.app.getSettings ? { getSettings: deps.app.getSettings } : {}),
354
390
  terminal,
355
391
  dispatchBoard,
356
392
  startDispatchBoardTicker: () => interactiveTickers.startDispatchBoardTicker(),
@@ -363,6 +399,11 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
363
399
  ...(openDecisionsOverlayFactory ? { openDecisionsOverlay: openDecisionsOverlayFactory } : {}),
364
400
  ...(openMemoryOverlayFactory ? { openMemoryOverlay: openMemoryOverlayFactory } : {}),
365
401
  ...(openViewOverlayFactory ? { openViewOverlay: openViewOverlayFactory } : {}),
402
+ askSideQuestion: (question, options) => deps.app.chat.askSideQuestion(question, options),
403
+ ...(deps.app.agents ? { agents: deps.app.agents } : {}),
404
+ ...(scheduling ? { getBudgetPreflight: () => scheduling.preflight() } : {}),
405
+ isTurnInFlight: () => deps.app.chat.isStreaming(),
406
+ ...(deps.setFleetRunPhase ? { setFleetRunPhase: deps.setFleetRunPhase } : {}),
366
407
  });
367
408
 
368
409
  const openResumeOverlayState = overlaySessions.openResume;
@@ -376,6 +417,9 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
376
417
  const openDecisionsOverlayState = overlayGeneralOpeners.openDecisions;
377
418
  const openMemoryOverlayState = overlayGeneralOpeners.openMemory;
378
419
  const openViewOverlayState = overlayGeneralOpeners.openView;
420
+ const openSideQuestionOverlayState = overlayGeneralOpeners.openSideQuestion;
421
+ const startFleetRunState = overlayGeneralOpeners.startFleetRun;
422
+ const startHandoffState = overlaySessions.startHandoff;
379
423
  const toggleDispatchBoardOverlay = overlayGeneralOpeners.toggleDispatchBoard;
380
424
 
381
425
  return {
@@ -395,6 +439,9 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
395
439
  openDecisionsOverlayState,
396
440
  openMemoryOverlayState,
397
441
  openViewOverlayState,
442
+ openSideQuestionOverlayState,
443
+ startFleetRunState,
444
+ startHandoffState,
398
445
  openModelOverlayState: overlayModelSelectors.openModelOverlayState,
399
446
  openSettingsOverlayState: overlayModelSelectors.openSettingsOverlayState,
400
447
  openResumeOverlayState,
@@ -2,9 +2,11 @@ import { BusChannels, type PermissionRequestedPayload } from "../core/bus-events
2
2
  import type { SafeEventBus } from "../core/event-bus.js";
3
3
  import { ToolNames } from "../core/tool-names.js";
4
4
  import type { DispatchContract } from "../domains/dispatch/contract.js";
5
- import type { ClassifierCall } from "../domains/safety/action-classifier.js";
5
+ import type { ActionClass, ClassifierCall } from "../domains/safety/action-classifier.js";
6
6
  import { sanitizeCallTargetText } from "../domains/safety/call-target.js";
7
7
  import type { SafetyDecision } from "../domains/safety/contract.js";
8
+ import { decisionActionClass } from "../domains/safety/decision-presentation.js";
9
+ import { askUserExposure } from "../tools/ask-user.js";
8
10
  import type { PermissionRequiredMeta, ToolRegistry } from "../tools/registry.js";
9
11
  import { approvalParkedNotice, autonomyDeniedNotice, workerEscalationNotice } from "./bus-notices.js";
10
12
  import type { ToolApprovalStateEvent } from "./chat-loop.js";
@@ -29,7 +31,7 @@ interface WorkerEscalationEntry {
29
31
  requestId: string;
30
32
  agentId: string;
31
33
  tool: string;
32
- actionClass: string;
34
+ actionClass: ActionClass;
33
35
  axis: ApprovalRequestView["axis"];
34
36
  reason: string;
35
37
  target?: string;
@@ -48,6 +50,12 @@ export interface OverlayPermissionLifecycleDeps {
48
50
  requestRender(): void;
49
51
  /** End the in-flight run, carrying the text the operator will see. */
50
52
  stopActiveTurn(reason: string): void;
53
+ /**
54
+ * A worker permission request parked waiting for the operator. Fired once per
55
+ * request, on the same dedup the operator-facing surfaces use, so a
56
+ * re-presented dialog does not notify twice.
57
+ */
58
+ onOperatorParked?(): void;
51
59
  }
52
60
 
53
61
  export interface OverlayPermissionLifecycle {
@@ -60,6 +68,14 @@ export interface OverlayPermissionLifecycle {
60
68
  */
61
69
  stopTurn(): void;
62
70
  onPermissionOverlayClosed(): void;
71
+ /**
72
+ * Re-present whatever is still parked once no overlay holds the screen. A
73
+ * request that arrived while `/context` or a picker was open could only be
74
+ * announced, and nothing re-attempted the dialog when that overlay closed:
75
+ * the transcript said awaiting approval, the footer said confirm, and the
76
+ * operator had no key to press (issue #186).
77
+ */
78
+ retryPending(): void;
63
79
  dispose(): void;
64
80
  }
65
81
 
@@ -114,7 +130,7 @@ function mainApprovalRequestView(
114
130
  (axisFromDecision.kind === "net"
115
131
  ? { kind: "net" as const, ruleId: axisFromDecision.ruleId }
116
132
  : { kind: "autonomy" as const, level: autonomy });
117
- const target = describeCallTarget(call.args);
133
+ const target = describeCallTarget(call.tool, call.args);
118
134
  return {
119
135
  requestId: meta?.requestId ?? "permission-pending",
120
136
  tool: call.tool,
@@ -126,6 +142,7 @@ function mainApprovalRequestView(
126
142
  ...(call.tool === ToolNames.Dispatch && decision.kind === "ask"
127
143
  ? { artifact: { kind: "dispatch-plan" as const, text: decision.rejection.detail } }
128
144
  : {}),
145
+ ...(call.tool === ToolNames.AskUser ? { exposure: askUserExposure(call.args) } : {}),
129
146
  ...(target.length > 0 ? { target } : {}),
130
147
  ...(queueDepth !== undefined && queueDepth > 1 ? { queueDepth } : {}),
131
148
  };
@@ -141,7 +158,7 @@ function workerEscalationEntry(payload: PermissionRequestedPayload, autonomy: st
141
158
  requestId: payload.requestId,
142
159
  agentId: typeof payload.agentId === "string" ? payload.agentId : "worker",
143
160
  tool: typeof payload.tool === "string" ? payload.tool : "unknown",
144
- actionClass: typeof payload.actionClass === "string" ? payload.actionClass : "unknown",
161
+ actionClass: decisionActionClass(payload.actionClass),
145
162
  axis:
146
163
  axisViewFromId(typeof payload.axis === "string" ? payload.axis : undefined, autonomy) ??
147
164
  (typeof payload.ruleId === "string"
@@ -173,6 +190,8 @@ export function createOverlayPermissionLifecycle(deps: OverlayPermissionLifecycl
173
190
  let pendingWorker: Pick<WorkerEscalationEntry, "agentId" | "requestId" | "runId"> | null = null;
174
191
  const workerQueue: WorkerEscalationEntry[] = [];
175
192
  const announcedRequestIds = new Set<string>();
193
+ /** Worker escalations already surfaced, so one request parks the operator once. */
194
+ const announcedWorkerRequestIds = new Set<string>();
176
195
  let confirmed = false;
177
196
  let stopping = false;
178
197
 
@@ -188,6 +207,12 @@ export function createOverlayPermissionLifecycle(deps: OverlayPermissionLifecycl
188
207
  const next = workerQueue.shift();
189
208
  if (next) openWorker(next);
190
209
  };
210
+ const retryPending = (): void => {
211
+ maybeOpenWorker();
212
+ if (deps.getOverlayState() === "closed" && deps.toolRegistry?.hasParkedCalls()) {
213
+ deps.toolRegistry.renotifyHead();
214
+ }
215
+ };
191
216
 
192
217
  const unsubscribePermission =
193
218
  deps.toolRegistry?.onPermissionRequired((call, decision, meta) => {
@@ -228,8 +253,10 @@ export function createOverlayPermissionLifecycle(deps: OverlayPermissionLifecycl
228
253
  const unsubscribeWorker = deps.bus.on(BusChannels.PermissionRequested, (payload) => {
229
254
  const entry = workerEscalationEntry(payload, deps.getAutonomy());
230
255
  if (!entry) return;
256
+ if (!markPermissionRequestSurfaced(announcedWorkerRequestIds, entry.requestId)) return;
231
257
  const notice = workerEscalationNotice(payload);
232
258
  if (notice !== null) deps.appendNotice(notice.level, notice.text);
259
+ deps.onOperatorParked?.();
233
260
  workerQueue.push(entry);
234
261
  maybeOpenWorker();
235
262
  });
@@ -310,10 +337,7 @@ export function createOverlayPermissionLifecycle(deps: OverlayPermissionLifecycl
310
337
  workerQueue.length = 0;
311
338
  return;
312
339
  }
313
- maybeOpenWorker();
314
- if (deps.getOverlayState() === "closed" && deps.toolRegistry?.hasParkedCalls()) {
315
- deps.toolRegistry.renotifyHead();
316
- }
340
+ retryPending();
317
341
  };
318
342
 
319
343
  return {
@@ -336,6 +360,7 @@ export function createOverlayPermissionLifecycle(deps: OverlayPermissionLifecycl
336
360
  );
337
361
  },
338
362
  onPermissionOverlayClosed,
363
+ retryPending,
339
364
  dispose: () => {
340
365
  unsubscribePermission();
341
366
  unsubscribeWorker();
@@ -1,4 +1,4 @@
1
- import type { ResourcesContract } from "../domains/resources/index.js";
1
+ import type { LibraryEntryKind, ResourcesContract } from "../domains/resources/index.js";
2
2
  import { installSkill as installMarketplaceSkill } from "../domains/resources/skills/marketplace.js";
3
3
  import type { TUI } from "../engine/tui.js";
4
4
  import type { ClioEditor } from "./clio-editor.js";
@@ -32,7 +32,7 @@ export interface OverlayResourceOpenersDeps {
32
32
  export interface OverlayResourceOpeners {
33
33
  openHelpOverlayState(query?: string): void;
34
34
  openAgentsOverlayState(): void;
35
- openSkillsHubState(): void;
35
+ openSkillsHubState(tab?: LibraryEntryKind): void;
36
36
  openPromptsOverlayState(): void;
37
37
  openExtensionsOverlayState(): void;
38
38
  openInteropOverlayState(): void;
@@ -61,10 +61,18 @@ export function createOverlayResourceOpeners(deps: OverlayResourceOpenersDeps):
61
61
  deps.tui.requestRender();
62
62
  };
63
63
 
64
- const openSkillsHubState = (): void => {
64
+ const openSkillsHubState = (tab?: LibraryEntryKind): void => {
65
65
  if (deps.transitions.state !== "closed") return;
66
66
  deps.transitions.state = "skills-hub";
67
67
  deps.transitions.handle = openSkills(deps.tui, {
68
+ ...(tab ? { initialTab: tab } : {}),
69
+ // A fleet's `use` is its approval preview, which is a surface of its own.
70
+ // The hub closes first so the preview owns the overlay slot, exactly as
71
+ // `/fleet run <name>` typed into the composer would.
72
+ openFleetRun: (name) => {
73
+ deps.closeOverlay();
74
+ deps.getSlashContext().startFleetRun?.(name, {});
75
+ },
68
76
  listSkills: () => deps.resources?.skills(process.cwd()) ?? { items: [], diagnostics: [] },
69
77
  setEditorText: (text) => {
70
78
  deps.editor.setText(text);
@@ -1,14 +1,29 @@
1
1
  import { resolveSessionCwd } from "../domains/session/cwd-fallback.js";
2
+ import type { DecisionLedgerEntry } from "../domains/session/entries.js";
3
+ import {
4
+ buildHandoffReadLedger,
5
+ HANDOFF_NOTE_CUSTOM_TYPE,
6
+ HANDOFF_SEED_CUSTOM_TYPE,
7
+ type HandoffNoteData,
8
+ type HandoffSeedData,
9
+ mergeHandoffDecisions,
10
+ parseHandoffExtraction,
11
+ renderHandoffDocument,
12
+ validateHandoffFiles,
13
+ validateHandoffGoal,
14
+ } from "../domains/session/handoff.js";
2
15
  import type { SessionContract, SessionEntry } from "../domains/session/index.js";
3
16
  import type { TUI } from "../engine/tui.js";
4
17
  import type { ChatLoop } from "./chat-loop.js";
5
18
  import type { ChatPanel } from "./chat-panel.js";
6
19
  import { rehydrateChatPanelFromTurns } from "./chat-renderer.js";
7
20
  import { emitCommandNotice } from "./command-fallbacks.js";
21
+ import { editTextExternally, resolveExternalEditor } from "./external-editor.js";
8
22
  import type { InteractiveNoticeLevel } from "./interactive-subscriptions.js";
9
23
  import { buildModelReplayAgentMessagesFromTurns } from "./model-session-replay.js";
10
24
  import type { OverlayTransitions } from "./overlay-transitions.js";
11
25
  import { openCwdFallbackOverlay } from "./overlays/cwd-fallback.js";
26
+ import { openHandoffReviewOverlay } from "./overlays/handoff-review.js";
12
27
  import { openMessagePickerOverlay } from "./overlays/message-picker.js";
13
28
  import { openSessionOverlay } from "./overlays/session-selector.js";
14
29
  import { openTreeOverlay } from "./overlays/tree-selector.js";
@@ -22,7 +37,7 @@ export interface OverlaySessionLifecycleDeps {
22
37
  tui: TUI;
23
38
  transitions: OverlayTransitions;
24
39
  session?: SessionContract;
25
- chat: Pick<ChatLoop, "cancel" | "isStreaming" | "resetForSession" | "whenSettled">;
40
+ chat: Pick<ChatLoop, "cancel" | "isStreaming" | "resetForSession" | "whenSettled" | "extractHandoff">;
26
41
  chatPanel: ChatPanel;
27
42
  /** The one transcript reset: the panel plus every view folded alongside it. */
28
43
  resetTranscript(): void;
@@ -30,6 +45,18 @@ export interface OverlaySessionLifecycleDeps {
30
45
  getSlashNotice(): SlashCommandContext["notice"];
31
46
  onResumeSession?(sessionId: string): void;
32
47
  onForkSession?(parentTurnId: string): void;
48
+ /**
49
+ * Mint a fresh session, the same hook `/new` uses. `/handoff` needs the one
50
+ * session-creation path the orchestrator owns rather than a second one.
51
+ */
52
+ onNewSession?(): void;
53
+ /** The session's settled decision board, which wins over extracted decisions. */
54
+ getDecisionBoard?(): ReadonlyArray<DecisionLedgerEntry>;
55
+ /** Stop and restart the terminal around an external editor child process. */
56
+ suspendTerminal?<T>(run: () => T): T;
57
+ resolveEditor?: typeof resolveExternalEditor;
58
+ editExternally?: typeof editTextExternally;
59
+ openHandoffReviewOverlay?: typeof openHandoffReviewOverlay;
33
60
  announceTaskMemorySeedOffer(): void;
34
61
  /**
35
62
  * Running usage totals, reseeded whenever the session under them changes.
@@ -45,6 +72,8 @@ export interface OverlaySessionLifecycleDeps {
45
72
  setLastTurnSummary?(summary: TurnSummary | null): void;
46
73
  refreshFooter(): void;
47
74
  requestRender(): void;
75
+ /** Live terminal width, so the handoff review box tracks the window. */
76
+ terminal?: { columns: number };
48
77
  stderr(text: string): void;
49
78
  notify(level: InteractiveNoticeLevel, text: string, key?: string): void;
50
79
  openSessionOverlay?: typeof openSessionOverlay;
@@ -57,6 +86,8 @@ export interface OverlaySessionLifecycle {
57
86
  openResume(): void;
58
87
  openTree(): void;
59
88
  openMessagePicker(): void;
89
+ /** `/handoff <goal>`: extract, review, and seed a successor session. */
90
+ startHandoff(goal: string): void;
60
91
  }
61
92
 
62
93
  /**
@@ -327,5 +358,206 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
327
358
  deps.requestRender();
328
359
  }
329
360
 
330
- return { openResume, openTree, openMessagePicker: openMessagePickerState };
361
+ /**
362
+ * `/handoff <goal>`: carry this session's working state into a fresh one.
363
+ *
364
+ * A handoff is a session operation. It writes no memory promotion candidate,
365
+ * touches no memory record, and never calls the task-memory bank; the only
366
+ * things it writes are one seed entry plus replayed skill activations in the
367
+ * new session and one terminal note in the old one, and it writes none of
368
+ * them until the operator has accepted the document.
369
+ */
370
+ function startHandoff(goal: string): void {
371
+ if (deps.transitions.state !== "closed") return;
372
+ const notice = deps.getSlashNotice();
373
+ const verdict = validateHandoffGoal(goal);
374
+ if (!verdict.ok) {
375
+ emitCommandNotice(notice, "warn", "handoff", verdict.reason);
376
+ return;
377
+ }
378
+ if (!deps.session) {
379
+ emitCommandNotice(notice, "error", "handoff", "session contract unavailable");
380
+ return;
381
+ }
382
+ if (!deps.onNewSession) {
383
+ emitCommandNotice(notice, "error", "handoff", "no session-creation path is wired in this session");
384
+ return;
385
+ }
386
+ // Refused, never queued. The document describes a session that has
387
+ // stopped; a turn still in flight is about to change what it would say.
388
+ if (deps.chat.isStreaming()) {
389
+ emitCommandNotice(
390
+ notice,
391
+ "warn",
392
+ "handoff",
393
+ "a turn is in flight; /handoff cannot summarize a session that is still moving",
394
+ );
395
+ return;
396
+ }
397
+ const session = deps.session;
398
+ const fromSessionId = session.current()?.id ?? null;
399
+ if (fromSessionId === null) {
400
+ emitCommandNotice(notice, "warn", "handoff", "no current session to hand off; start one with /new or /resume");
401
+ return;
402
+ }
403
+ void runHandoffExtraction(session, fromSessionId, verdict.goal);
404
+ }
405
+
406
+ async function runHandoffExtraction(session: SessionContract, fromSessionId: string, goal: string): Promise<void> {
407
+ const notice = deps.getSlashNotice();
408
+ const outcome = await deps.chat.extractHandoff(goal);
409
+ if (outcome.status !== "answered") {
410
+ const reason = outcome.status === "aborted" ? "the extraction round was cancelled" : outcome.reason;
411
+ emitCommandNotice(notice, outcome.status === "failed" ? "error" : "warn", "handoff", reason);
412
+ return;
413
+ }
414
+ const parsed = parseHandoffExtraction(outcome.text);
415
+ if (!parsed.ok) {
416
+ emitCommandNotice(notice, "error", "handoff", parsed.reason);
417
+ return;
418
+ }
419
+ const meta = session.current();
420
+ const cwd = typeof meta?.cwd === "string" && meta.cwd.length > 0 ? meta.cwd : null;
421
+ const entries = deps.readStructuredEntries(fromSessionId);
422
+ // The ledger is what this session's own tool calls touched, folded through
423
+ // the active path so an abandoned `/tree` branch is not evidence.
424
+ const ledger = buildHandoffReadLedger(entries, { cwd, leafTurnId: meta?.pinnedLeafTurnId ?? null });
425
+ const files = validateHandoffFiles(parsed.result.extraction.files, ledger, cwd);
426
+ const decisions = mergeHandoffDecisions(parsed.result.extraction.decisions, deps.getDecisionBoard?.() ?? []);
427
+ const document = renderHandoffDocument({
428
+ goal,
429
+ fromSessionId,
430
+ decisions,
431
+ facts: parsed.result.extraction.facts,
432
+ files: files.kept,
433
+ droppedFiles: files.dropped,
434
+ commands: parsed.result.extraction.commands,
435
+ openQuestions: parsed.result.extraction.openQuestions,
436
+ truncations: parsed.result.truncations,
437
+ });
438
+ openHandoffReview(session, fromSessionId, goal, document);
439
+ }
440
+
441
+ /** Hand the document to `$EDITOR`, or say why it could not go. */
442
+ function editHandoffDocument(current: string): string | null {
443
+ const resolve = deps.resolveEditor ?? resolveExternalEditor;
444
+ const edit = deps.editExternally ?? editTextExternally;
445
+ const command = resolve();
446
+ if (!command) {
447
+ deps.notify("warning", "handoff: no external editor configured; set VISUAL or EDITOR", "handoff:no-editor");
448
+ return null;
449
+ }
450
+ const suspend = deps.suspendTerminal ?? (<T>(run: () => T): T => run());
451
+ const result = suspend(() => edit(current, command));
452
+ if (result.ok) return result.text ?? current;
453
+ if (result.error) deps.notify("warning", `handoff: ${result.error}`, "handoff:editor-failed");
454
+ return null;
455
+ }
456
+
457
+ function openHandoffReview(session: SessionContract, fromSessionId: string, goal: string, document: string): void {
458
+ if (deps.transitions.state !== "closed") return;
459
+ const openReview = deps.openHandoffReviewOverlay ?? openHandoffReviewOverlay;
460
+ deps.transitions.state = "handoff-review";
461
+ deps.transitions.handle = openReview(deps.tui, {
462
+ document,
463
+ goal,
464
+ columns: deps.terminal?.columns ?? 80,
465
+ onEdit: editHandoffDocument,
466
+ onAccept: (reviewed) => {
467
+ deps.transitions.close();
468
+ const text = reviewed.trim();
469
+ if (text.length === 0) {
470
+ deps.notify("warning", "handoff: the reviewed document was empty; nothing was written", "handoff:empty");
471
+ return;
472
+ }
473
+ seedHandoffSession(session, fromSessionId, goal, text);
474
+ },
475
+ // Esc. Nothing has been written yet, so cancel really is free. The
476
+ // overlay settles itself on cancel and answers no further key, so the
477
+ // transition has to close here or the review stays on screen inert.
478
+ onCancel: () => {
479
+ deps.transitions.close();
480
+ deps.notify("info", "handoff cancelled; nothing was written", "handoff:cancelled");
481
+ },
482
+ });
483
+ deps.requestRender();
484
+ }
485
+
486
+ /**
487
+ * Mint the successor session and open it on the reviewed document.
488
+ *
489
+ * Order matters. The old session's terminal note is appended while it is
490
+ * still current, because an append always lands in the current session. Then
491
+ * the new session is minted through the one creation path the orchestrator
492
+ * owns, the document goes in as bounded data labelled by its origin, and the
493
+ * old session's skill activations are replayed so loaded skills carry
494
+ * forward. The old session is otherwise untouched.
495
+ */
496
+ function seedHandoffSession(session: SessionContract, fromSessionId: string, goal: string, document: string): void {
497
+ const notice = deps.getSlashNotice();
498
+ const activations = session.current()?.skillActivations ?? [];
499
+ let toSessionId: string;
500
+ try {
501
+ deps.onNewSession?.();
502
+ const minted = session.current()?.id ?? null;
503
+ if (minted === null || minted === fromSessionId) throw new Error("the new session was not created");
504
+ toSessionId = minted;
505
+ session.appendEntry({
506
+ kind: "custom",
507
+ parentTurnId: null,
508
+ customType: HANDOFF_SEED_CUSTOM_TYPE,
509
+ display: true,
510
+ data: { fromSessionId, goal, document } satisfies HandoffSeedData,
511
+ });
512
+ for (const activation of activations) session.recordSkillActivation(activation);
513
+ } catch (error) {
514
+ emitCommandNotice(
515
+ notice,
516
+ "error",
517
+ "handoff",
518
+ `could not seed the new session: ${error instanceof Error ? error.message : String(error)}`,
519
+ );
520
+ return;
521
+ }
522
+ // The note names the target, so it can only be written once the target
523
+ // exists. Appends land in the current session, so the old session is made
524
+ // current for exactly one append and then handed back. A failure here
525
+ // costs the note and nothing else: the successor session is already
526
+ // seeded and is where the operator continues.
527
+ try {
528
+ session.switchBranch(fromSessionId);
529
+ session.appendEntry({
530
+ kind: "custom",
531
+ parentTurnId: null,
532
+ customType: HANDOFF_NOTE_CUSTOM_TYPE,
533
+ display: true,
534
+ data: { toSessionId, goal } satisfies HandoffNoteData,
535
+ });
536
+ } catch (error) {
537
+ deps.stderr(`[/handoff] handoff note failed: ${error instanceof Error ? error.message : String(error)}\n`);
538
+ } finally {
539
+ try {
540
+ session.switchBranch(toSessionId);
541
+ } catch (error) {
542
+ deps.stderr(
543
+ `[/handoff] could not return to the new session: ${error instanceof Error ? error.message : String(error)}\n`,
544
+ );
545
+ }
546
+ }
547
+ try {
548
+ const turns = deps.readStructuredEntries(toSessionId);
549
+ deps.resetTranscript();
550
+ rehydrateChatPanelFromTurns(deps.chatPanel, turns);
551
+ deps.chat.resetForSession(null, buildModelReplayAgentMessagesFromTurns(turns));
552
+ rescopeToBranch(session, turns, null);
553
+ } catch (error) {
554
+ deps.stderr(`[/handoff] seeding replay failed: ${error instanceof Error ? error.message : String(error)}\n`);
555
+ deps.chat.resetForSession(null);
556
+ }
557
+ emitCommandNotice(notice, "info", "handoff", `handed off to session ${toSessionId}`);
558
+ deps.refreshFooter();
559
+ deps.requestRender();
560
+ }
561
+
562
+ return { openResume, openTree, openMessagePicker: openMessagePickerState, startHandoff };
331
563
  }
@@ -9,6 +9,12 @@ export interface OverlayTransitionsDeps {
9
9
  cancelPendingAskUser: () => boolean;
10
10
  finishAuth: (dismiss: boolean) => void;
11
11
  onPermissionOverlayClosed: () => void;
12
+ /**
13
+ * Runs after any overlay has closed and the state is back to `closed`. The
14
+ * permission lifecycle uses it to re-present a call that parked while this
15
+ * overlay held the screen; nothing else re-attempts that dialog.
16
+ */
17
+ onOverlayClosed?: () => void;
12
18
  }
13
19
 
14
20
  export interface OverlayTransitions {
@@ -26,7 +32,11 @@ export function createOverlayTransitions(deps: OverlayTransitionsDeps): OverlayT
26
32
  return state;
27
33
  },
28
34
  set state(next) {
35
+ const closing = state !== "closed" && next === "closed";
29
36
  state = next;
37
+ // The ask-user and auth lifecycles leave by assigning the state rather
38
+ // than through `close`, so the hook fires here for them.
39
+ if (closing) deps.onOverlayClosed?.();
30
40
  },
31
41
  get handle() {
32
42
  return handle;
@@ -50,6 +60,7 @@ export function createOverlayTransitions(deps: OverlayTransitionsDeps): OverlayT
50
60
  handle?.hide();
51
61
  handle = null;
52
62
  if (leaving === "permission-confirm") deps.onPermissionOverlayClosed();
63
+ else deps.onOverlayClosed?.();
53
64
  deps.renderContextIsland();
54
65
  deps.renderTaskIsland();
55
66
  deps.requestRender();