@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
@@ -10,10 +10,12 @@ import type { DispatchContract } from "../domains/dispatch/contract.js";
10
10
  import type { ExtensionsContract } from "../domains/extensions/index.js";
11
11
  import type { InteropContract } from "../domains/interop/index.js";
12
12
  import type { TaskMemoryOperatorStatus } from "../domains/memory/index.js";
13
+ import { openDetachedBatchViews } from "../domains/middleware/index.js";
13
14
  import type { ObservabilityContract } from "../domains/observability/index.js";
14
15
  import type { ProvidersContract, ThinkingLevel } from "../domains/providers/index.js";
15
16
  import type { ResourcesContract } from "../domains/resources/index.js";
16
17
  import type { FleetNodeSnapshot } from "../domains/scheduling/cluster.js";
18
+ import type { SchedulingContract } from "../domains/scheduling/contract.js";
17
19
  import type { DecisionLedgerEntry } from "../domains/session/entries.js";
18
20
  import type { SessionContract, SessionEntry, TaskBoardSnapshot } from "../domains/session/index.js";
19
21
  import type { ShareContract } from "../domains/share/index.js";
@@ -26,8 +28,10 @@ import { APPLICATION_DOUBLE_TAP_MS, type ApplicationController } from "./applica
26
28
  import type { ChatLoop, ChatLoopEvent } from "./chat-loop.js";
27
29
  import { emitCommandNotice } from "./command-fallbacks.js";
28
30
  import { appendNotice } from "./command-output.js";
31
+ import { dispatchCouncilThroughRegistry } from "./council-dispatch.js";
29
32
  import { createDispatchSteering } from "./dispatch-steering.js";
30
33
  import { createEditorSubmitController } from "./editor-submit.js";
34
+ import { createInteractiveDesktopNotifications } from "./footer/notifications.js";
31
35
  import { createInteractiveEventProjection } from "./interactive-event-projection.js";
32
36
  import { createInteractiveInputRuntime } from "./interactive-input-runtime.js";
33
37
  import { createInteractivePresentation } from "./interactive-presentation.js";
@@ -108,6 +112,8 @@ export interface InteractiveDeps {
108
112
  providers: ProvidersContract;
109
113
  dispatch: DispatchContract;
110
114
  agents?: AgentsContract;
115
+ /** Session budget state `/fleet run` shows as the ceiling a plan is admitted under. */
116
+ scheduling?: SchedulingContract;
111
117
  observability: ObservabilityContract;
112
118
  chat: ChatLoop;
113
119
  /** Fired once after the first real TUI render transaction issued all of its terminal writes. */
@@ -482,10 +488,16 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
482
488
  * here for the same reason as the leader flag above.
483
489
  */
484
490
  let shutdownArmed = false;
491
+ /**
492
+ * Assigned after the presentation because it renders into it. The composer
493
+ * rail reads it through the optional chain before then as "no prompt".
494
+ */
495
+ let overlayLifecycle: OverlayLifecycleController;
485
496
  const presentation = createInteractivePresentation({
486
497
  bus: deps.bus,
487
498
  getLeaderArmed: () => leaderArmed,
488
499
  getShutdownArmed: () => shutdownArmed,
500
+ isAwaitingApproval: () => overlayLifecycle?.getState() === "permission-confirm",
489
501
  resolveVisibleEventSequence: (event) => visibleEventIngress.get(event)?.traceSequence ?? null,
490
502
  resolveStreamIngress: (event) => visibleEventIngress.get(event) ?? null,
491
503
  commitFrame: (reason) => shell.commitCurrentFrame(reason === "teardown" ? 300 : 30_000),
@@ -535,6 +547,16 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
535
547
  } = presentation;
536
548
  refreshPresentationFooter = () => footer.refresh();
537
549
  const agentProgress = createAgentProgress(terminal);
550
+ // Desktop notifications are a protocol write on the terminal owner, issued
551
+ // outside any render transaction, so the sequence carries frameId null and
552
+ // never lands inside a frame. A non-TTY process never emits one even when
553
+ // `CLIO_CODER_INTERACTIVE=1` forced the interactive surface on.
554
+ const desktopNotifications = createInteractiveDesktopNotifications({
555
+ write: (data) => terminal.write(data),
556
+ enabled: () => deps.getSettings?.().terminal.notify ?? false,
557
+ interactiveTty: () => process.stdout.isTTY === true,
558
+ getOpenBatches: () => openDetachedBatchViews(deps.dispatch),
559
+ });
538
560
  const busNoticeSink = {
539
561
  appendReplayBlock: (renderBlock: Parameters<typeof chatPanel.appendReplayBlock>[0]) =>
540
562
  chatRenderer.mutate(() => chatPanel.appendReplayBlock(renderBlock), "bus-notice"),
@@ -564,6 +586,7 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
564
586
  setLastTurnSummary: (summary) => presentation.setLastTurnSummary(summary),
565
587
  startTerminalProgress: () => agentProgress.start(),
566
588
  stopTerminalProgress: () => agentProgress.stop(),
589
+ onTurnEnded: () => desktopNotifications.turnEnded(),
567
590
  refreshLiveWorkspaceGit,
568
591
  refreshFooter: () => footer.refresh(),
569
592
  requestRender: () => tui.requestRender(),
@@ -581,6 +604,10 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
581
604
  // The overlay reads the report this process already produced at boot; it
582
605
  // never probes on a keystroke.
583
606
  const interop = deps.interop;
607
+ // `/council` reaches execution only through the tool registry, so the
608
+ // approval overlay this application already installs on it is the same one an
609
+ // operator-typed council parks.
610
+ const toolRegistry = deps.toolRegistry;
584
611
  const interopSurface = interop ? interopOverlaySurface(interop, (level, text) => notify(level, text)) : null;
585
612
  const slashRuntime = createInteractiveSlashRuntime({
586
613
  io,
@@ -620,8 +647,12 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
620
647
  readStructuredEntries,
621
648
  expandSubmit: (text) => expandInteractiveSubmitAsync(text, deps.resources),
622
649
  openAskUser: (questions, options) => openAskUserOverlayState(questions, options),
623
- openSkillsHub: () => openSkillsHubState(),
650
+ openSkillsHub: (tab) => openSkillsHubState(tab),
624
651
  openCost: () => openCostOverlayState(),
652
+ openSideQuestion: (question) => openSideQuestionOverlayState(question),
653
+ startHandoff: (goal) => startHandoffState(goal),
654
+ startFleetRun: (name, vars) => startFleetRunState(name, vars),
655
+ ...(toolRegistry ? { runCouncilDispatch: (args) => dispatchCouncilThroughRegistry(toolRegistry, args) } : {}),
625
656
  openContextView: () => openContextViewOverlayState(),
626
657
  openTasks: () => openTasksOverlayState(),
627
658
  openDecisions: () => openDecisionsOverlayState(),
@@ -668,7 +699,6 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
668
699
  });
669
700
  editor.onSubmit = editorSubmit.submitEditorText;
670
701
 
671
- let overlayLifecycle: OverlayLifecycleController;
672
702
  const interactiveTickers = createInteractiveTickers({
673
703
  tui,
674
704
  dispatchBoardStore,
@@ -700,6 +730,7 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
700
730
  notify,
701
731
  terminal,
702
732
  dispatchBoard,
733
+ setFleetRunPhase: (runId, phase) => dispatchBoardStore.setFleetPhase(runId, phase),
703
734
  chatPanel,
704
735
  resetTranscript,
705
736
  io,
@@ -709,11 +740,26 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
709
740
  keybindings,
710
741
  editor,
711
742
  getSlashContext: () => slashRuntime.context,
743
+ // One terminal owner, so the external editor a handoff review opens gets
744
+ // the screen the same way the composer's own `$EDITOR` opener does.
745
+ suspendTerminal: (run) => {
746
+ try {
747
+ tui.stop();
748
+ return run();
749
+ } finally {
750
+ tui.start();
751
+ tui.requestRender(true);
752
+ }
753
+ },
754
+ onOperatorParked: () => desktopNotifications.approvalParked(),
712
755
  });
713
756
  const {
714
757
  closeOverlay,
715
758
  openAskUserOverlayState,
716
759
  openCostOverlayState,
760
+ openSideQuestionOverlayState,
761
+ startHandoffState,
762
+ startFleetRunState,
717
763
  openContextViewOverlayState,
718
764
  openContextResetOverlayState,
719
765
  openTasksOverlayState,
@@ -805,6 +851,7 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
805
851
  renderContextIsland: interactiveTickers.renderContextIsland,
806
852
  requestRender: () => tui.requestRender(),
807
853
  notify,
854
+ onDispatchSettled: () => desktopNotifications.dispatchSettled(),
808
855
  // The reducer mutates one state object per assignment, so the panel is
809
856
  // handed that object rather than a copy: a streamed delta reaches the
810
857
  // screen by invalidating a cached render, not by rebuilding the entry.
@@ -77,6 +77,11 @@ export interface InteractiveEventProjectionDeps {
77
77
  setLastTurnSummary: (summary: TurnSummary) => void;
78
78
  startTerminalProgress: () => void;
79
79
  stopTerminalProgress: () => void;
80
+ /**
81
+ * One model turn reached its end. Wired to the desktop notification; absent
82
+ * on hosts that do not own a terminal to notify through.
83
+ */
84
+ onTurnEnded?: () => void;
80
85
  refreshLiveWorkspaceGit: (force: boolean) => void;
81
86
  refreshFooter: () => void;
82
87
  requestRender: () => void;
@@ -214,7 +219,10 @@ export function createInteractiveEventProjection(deps: InteractiveEventProjectio
214
219
  deps.chat.onEvent((event) => {
215
220
  const showProgress = deps.getSettings?.().terminal.showTerminalProgress ?? false;
216
221
  if (event.type === "agent_start" && showProgress) deps.startTerminalProgress();
217
- else if (event.type === "agent_end") deps.stopTerminalProgress();
222
+ else if (event.type === "agent_end") {
223
+ deps.stopTerminalProgress();
224
+ deps.onTurnEnded?.();
225
+ }
218
226
  }),
219
227
  deps.bus.on(BusChannels.RunAborted, () => {
220
228
  deps.stopTerminalProgress();
@@ -76,13 +76,14 @@ export interface InteractiveInputRuntimeDeps {
76
76
  dispatchBoard: {
77
77
  selectPrevious(): void;
78
78
  selectNext(): void;
79
+ toggleDetail(): void;
79
80
  };
80
81
  steerSelectedDispatch: () => void;
81
82
  cancelSelectedDispatch: () => void;
82
83
  cancelActiveEditorBash: () => boolean;
83
84
  isStreaming: () => boolean;
84
85
  cancelActiveRun: () => void;
85
- editor: { getText(): string; setText(text: string): void };
86
+ editor: { getText(): string; setText(text: string): void; handleInput?(data: string): void };
86
87
  editorSubmit: {
87
88
  openExternalEditorForInput(): void;
88
89
  queueFollowUpFromEditor(): void;
@@ -211,6 +212,11 @@ export function createInteractiveInputRuntime(deps: InteractiveInputRuntimeDeps)
211
212
  cancelPermission: () => deps.overlay.closeOverlay(),
212
213
  confirmPermission: () => deps.overlay.confirmPermission(),
213
214
  stopTurnFromPermission: () => deps.overlay.stopTurnFromPermission(),
215
+ composerHasDraft: () => deps.editor.getText().length > 0,
216
+ editDraft: (data) => {
217
+ deps.editor.handleInput?.(data);
218
+ deps.requestRender();
219
+ },
214
220
  closeOverlay: () => deps.overlay.closeOverlay(),
215
221
  selectPreviousDispatch: () => {
216
222
  deps.dispatchBoard.selectPrevious();
@@ -220,6 +226,10 @@ export function createInteractiveInputRuntime(deps: InteractiveInputRuntimeDeps)
220
226
  deps.dispatchBoard.selectNext();
221
227
  deps.requestRender();
222
228
  },
229
+ toggleSelectedDispatchDetail: () => {
230
+ deps.dispatchBoard.toggleDetail();
231
+ deps.requestRender();
232
+ },
223
233
  steerSelectedDispatch: deps.steerSelectedDispatch,
224
234
  cancelSelectedDispatch: deps.cancelSelectedDispatch,
225
235
  cancelAskUser: () => deps.overlay.cancelAskUser(),
@@ -39,6 +39,7 @@ import type { SmoothStreamingMode } from "./stream-pacer.js";
39
39
  import { processAutoPacingAllowed, resolveSmoothStreamingMode } from "./stream-pacing-policy.js";
40
40
  import { formatTargetLabel } from "./theme/index.js";
41
41
  import { createWelcomeDashboard, type WelcomeDashboardComponent } from "./welcome-dashboard.js";
42
+ import { readWorkerReceiptFacts } from "./worker-receipts.js";
42
43
  import type { WorkspaceFacts } from "./workspace-facts.js";
43
44
 
44
45
  export interface PresentationTickerHandle {
@@ -96,6 +97,8 @@ export interface InteractivePresentationDeps {
96
97
  getLeaderArmed?: () => boolean;
97
98
  /** Whether a Ctrl+C armed the double tap and its window is still open. */
98
99
  getShutdownArmed?: () => boolean;
100
+ /** Whether a permission prompt owns the keyboard, for the composer's CONFIRM rail. */
101
+ isAwaitingApproval?: () => boolean;
99
102
  getCwd?: () => string;
100
103
  resolveVisibleEventSequence?: (event: ChatLoopEvent) => number | null;
101
104
  resolveStreamIngress?: (
@@ -242,7 +245,13 @@ export function createInteractivePresentation(deps: InteractivePresentationDeps)
242
245
  bus: deps.bus,
243
246
  ...(deps.getSettings ? { getSettings: deps.getSettings } : {}),
244
247
  });
245
- const dispatchBoardStore = factories.createDispatchBoardStore(deps.bus, () => deps.dispatch.snapshot());
248
+ // The board settles a finished run on the same sealed receipt the transcript
249
+ // block reads, so the two surfaces agree on a worker's terminal answer.
250
+ const dispatchBoardStore = factories.createDispatchBoardStore(
251
+ deps.bus,
252
+ () => deps.dispatch.snapshot(),
253
+ (runId) => readWorkerReceiptFacts(runId),
254
+ );
246
255
  const contextActivityStore = factories.createContextActivityStore(deps.bus);
247
256
 
248
257
  const footerToolCounts = new Map<string, number>();
@@ -408,6 +417,7 @@ export function createInteractivePresentation(deps: InteractivePresentationDeps)
408
417
  );
409
418
  },
410
419
  isStreaming: () => deps.chat.isStreaming(),
420
+ ...(deps.isAwaitingApproval ? { isAwaitingApproval: deps.isAwaitingApproval } : {}),
411
421
  willEnterSteer: (text) => willEnterSteerActiveWork(deps, text),
412
422
  getSubmitKeyLabel: () => formatKeyLabel(keybindings.getKeys("tui.input.submit")[0], "Enter"),
413
423
  getNewlineKeyLabel: () => formatKeyLabel(keybindings.getKeys("tui.input.newLine")[0], "Shift+Enter"),
@@ -16,9 +16,12 @@ import {
16
16
  type ThinkingLevel,
17
17
  thinkingLevelChoiceLabel,
18
18
  } from "../domains/providers/index.js";
19
- import type { ResourcesContract } from "../domains/resources/index.js";
19
+ import type { LibraryEntryKind, ResourcesContract } from "../domains/resources/index.js";
20
20
  import { installSkill } from "../domains/resources/skills/marketplace.js";
21
+ import { foldDecisionBoard } from "../domains/session/decision-board.js";
21
22
  import type { SessionContract, SessionEntry } from "../domains/session/index.js";
23
+ import { foldTaskBoard } from "../domains/session/task-board.js";
24
+ import { filterEntriesToActivePath } from "../domains/session/tree/active-path.js";
22
25
  import type { ShareContract } from "../domains/share/index.js";
23
26
  import type { UserTasksStore } from "../domains/user-tasks/store.js";
24
27
  import { stripTerminalSequences } from "../engine/tui.js";
@@ -32,9 +35,11 @@ import { appendNotice, appendOperatorCommand } from "./command-output.js";
32
35
  import { runOperatorRecall } from "./context-recall-command.js";
33
36
  import { renderSessionHtml } from "./export-html/index.js";
34
37
  import { dateLocal } from "./format-time.js";
38
+ import type { OracleDigestSources } from "./oracle.js";
35
39
  import type { SettingsCenterRowId, SettingsSectionId } from "./overlays/settings.js";
36
40
  import {
37
41
  type ContextClearCommandOptions,
42
+ type CouncilDispatchOutcome,
38
43
  dispatchSlashCommand,
39
44
  type InitCommandOptions,
40
45
  parseSlashCommand,
@@ -108,8 +113,21 @@ export interface InteractiveSlashRuntimeDeps {
108
113
  session?: Pick<SessionContract, "tree" | "current" | "appendEntry">;
109
114
  expandSubmit: (text: string) => Promise<InteractiveSlashSubmitExpansion>;
110
115
  openAskUser: AskUserHandler;
111
- openSkillsHub: () => void;
116
+ openSkillsHub: (tab?: LibraryEntryKind) => void;
112
117
  openCost: () => void;
118
+ /** Run one `/btw` side question in its own overlay. */
119
+ openSideQuestion: (question: string) => void;
120
+ /** Run one `/handoff <goal>`: extract, review, and seed a successor session. */
121
+ startHandoff: (goal: string) => void;
122
+ /** Run one `/fleet run <name>`: approval preview first, dispatch only on accept. */
123
+ startFleetRun?: (name: string, vars: Readonly<Record<string, string>>) => void;
124
+ /**
125
+ * Run one `/council <task>`: the prepared dispatch-tool arguments go through
126
+ * the ordinary tool-admission path, so supervised autonomy parks the call and
127
+ * the approval overlay opens before any member runs. Absent on a host with no
128
+ * tool registry, in which case `/council` says so.
129
+ */
130
+ runCouncilDispatch?: (args: Readonly<Record<string, unknown>>) => Promise<CouncilDispatchOutcome>;
113
131
  openContextView: () => void;
114
132
  openTasks: () => void;
115
133
  openDecisions: () => void;
@@ -186,6 +204,29 @@ export function createInteractiveSlashRuntime(deps: InteractiveSlashRuntimeDeps)
186
204
  const resources = deps.resources;
187
205
  const userTasks = deps.userTasks;
188
206
 
207
+ /**
208
+ * The record `/oracle` briefs its advisor on. Entries are filtered to the
209
+ * active branch first, the same way the task board, compaction, and the
210
+ * finish contract read them (issue #94): after a `/tree` switch the raw file
211
+ * still holds the abandoned turns, and an unscoped fold would brief the
212
+ * advisor on decisions the operator walked away from.
213
+ */
214
+ const oracleBriefingSources = (): Omit<OracleDigestSources, "question"> => {
215
+ const sessionId = deps.chat.getSessionId();
216
+ if (sessionId === null) return { decisions: [], tasks: [], compactionSummary: null };
217
+ const leafTurnId = deps.session?.tree(sessionId).leafId ?? undefined;
218
+ const entries = filterEntriesToActivePath(deps.readStructuredEntries(sessionId), leafTurnId);
219
+ let compactionSummary: string | null = null;
220
+ for (const entry of entries) {
221
+ if (entry.kind === "compactionSummary") compactionSummary = entry.summary;
222
+ }
223
+ return {
224
+ decisions: foldDecisionBoard(entries),
225
+ tasks: foldTaskBoard(entries)?.tasks ?? [],
226
+ compactionSummary,
227
+ };
228
+ };
229
+
189
230
  const appendCommandNotice: SlashCommandContext["notice"] = (level, text) => {
190
231
  appendNotice(level, text, {
191
232
  appendReplayBlock: (renderBlock) => deps.chatPanel.appendReplayBlock(renderBlock),
@@ -342,6 +383,15 @@ export function createInteractiveSlashRuntime(deps: InteractiveSlashRuntimeDeps)
342
383
  : deps.share.importArchive(resolve(archivePath), importOptions);
343
384
  },
344
385
  openCost: deps.openCost,
386
+ openSideQuestion: deps.openSideQuestion,
387
+ // `/oracle` is refused, never queued, while a turn streams: the digest it
388
+ // packs describes the record as it stands.
389
+ isTurnInFlight: () => deps.chat.isStreaming(),
390
+ oracleBriefing: oracleBriefingSources,
391
+ startHandoff: deps.startHandoff,
392
+ ...(deps.startFleetRun ? { startFleetRun: deps.startFleetRun } : {}),
393
+ ...(deps.getSettings ? { getWorkerRosters: () => deps.getSettings?.().workers.rosters ?? {} } : {}),
394
+ ...(deps.runCouncilDispatch ? { runCouncilDispatch: deps.runCouncilDispatch } : {}),
345
395
  openContextView: deps.openContextView,
346
396
  openTasks: deps.openTasks,
347
397
  openDecisions: deps.openDecisions,
@@ -37,6 +37,12 @@ export interface InteractiveSubscriptionsDeps {
37
37
  recordWorkerRun?: (fields: WorkerRunEntryFields) => void;
38
38
  /** Sealed-receipt reader, injected by tests. Defaults to `<state>/receipts/<runId>.json`. */
39
39
  readWorkerReceipt?: WorkerReceiptReader;
40
+ /**
41
+ * A dispatch run reached a terminal state. The desktop notification uses it
42
+ * to look for a detached batch that settled with this run; nothing else
43
+ * derives state from it.
44
+ */
45
+ onDispatchSettled?: () => void;
40
46
  }
41
47
 
42
48
  export interface InteractiveSubscriptions {
@@ -89,8 +95,14 @@ export function createInteractiveSubscriptions(deps: InteractiveSubscriptionsDep
89
95
  }),
90
96
  ),
91
97
  deps.bus.on(BusChannels.RunAborted, folded(workers.aborted)),
92
- deps.bus.on(BusChannels.DispatchCompleted, folded(workers.completed)),
93
- deps.bus.on(BusChannels.DispatchFailed, folded(workers.failed)),
98
+ deps.bus.on(BusChannels.DispatchCompleted, (payload) => {
99
+ folded(workers.completed)(payload);
100
+ deps.onDispatchSettled?.();
101
+ }),
102
+ deps.bus.on(BusChannels.DispatchFailed, (payload) => {
103
+ folded(workers.failed)(payload);
104
+ deps.onDispatchSettled?.();
105
+ }),
94
106
  deps.bus.on(BusChannels.ContextActivity, () => {
95
107
  deps.refreshFooter();
96
108
  deps.renderContextIsland();
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  describeTaskMemoryActivity,
3
+ type MemoryProposalResult,
3
4
  type MemoryRecord,
4
5
  type TaskMemoryActivityEvent,
5
6
  type TaskMemoryEntry,
@@ -182,6 +183,7 @@ function memorySignature(status: TaskMemoryOperatorStatus, records: ReadonlyArra
182
183
 
183
184
  interface OpenMemoryOverlayOptions {
184
185
  onClose?: () => void;
186
+ onPromote?: (entry: TaskMemoryEntry, scope: "repo" | "global") => Promise<MemoryProposalResult>;
185
187
  }
186
188
 
187
189
  /** Master-detail memory view: status header, grouped list, scrollable detail pane. */
@@ -189,12 +191,18 @@ export class MemoryOverlayView implements Component {
189
191
  private readonly list: ListOverlayView;
190
192
  private signature: string | null = null;
191
193
  private renderMemo: { width: number; status: string; listLines: string[]; lines: string[] } | null = null;
194
+ private pendingGlobalEntryId: string | null = null;
195
+ private promotionMessage: { token: "muted" | "warning" | "success" | "error"; text: string } | null = null;
196
+ private promotionInFlight = false;
192
197
 
193
198
  constructor(
194
199
  private readonly getStatus: () => TaskMemoryOperatorStatus,
195
200
  private readonly getRecords: () => ReadonlyArray<MemoryRecord>,
196
201
  onClose: () => void,
197
202
  onChange: () => void,
203
+ private readonly promotion?: {
204
+ onPromote: (entry: TaskMemoryEntry, scope: "repo" | "global") => Promise<MemoryProposalResult>;
205
+ },
198
206
  ) {
199
207
  this.list = new ListOverlayView(
200
208
  {
@@ -203,6 +211,18 @@ export class MemoryOverlayView implements Component {
203
211
  filterable: true,
204
212
  layout: "split",
205
213
  emptyMessage: EMPTY_MESSAGE,
214
+ ...(promotion
215
+ ? {
216
+ hints: [
217
+ { key: "p", verb: "propose repo" },
218
+ { key: "g", verb: "propose global" },
219
+ ],
220
+ actions: {
221
+ p: (item: ListOverlayItem) => this.promote(item, "repo", onChange),
222
+ g: (item: ListOverlayItem) => this.promote(item, "global", onChange),
223
+ },
224
+ }
225
+ : {}),
206
226
  onClose,
207
227
  },
208
228
  onChange,
@@ -217,11 +237,21 @@ export class MemoryOverlayView implements Component {
217
237
  render(width: number): string[] {
218
238
  const status = this.sync();
219
239
  const statusLine = formatMemoryStatusLine(status, width);
240
+ const promotionLine =
241
+ this.promotionMessage === null
242
+ ? null
243
+ : fitUnits(clioTheme(), "", [clioTheme().fg(this.promotionMessage.token, this.promotionMessage.text)], width);
244
+ const statusKey = promotionLine === null ? statusLine : `${statusLine}\n${promotionLine}`;
220
245
  const listLines = this.list.render(width);
221
246
  const memo = this.renderMemo;
222
- if (memo && memo.width === width && memo.status === statusLine && memo.listLines === listLines) return memo.lines;
223
- const lines = [statusLine, rule(clioTheme(), width), ...listLines];
224
- this.renderMemo = { width, status: statusLine, listLines, lines };
247
+ if (memo && memo.width === width && memo.status === statusKey && memo.listLines === listLines) return memo.lines;
248
+ const lines = [
249
+ statusLine,
250
+ ...(promotionLine === null ? [] : [promotionLine]),
251
+ rule(clioTheme(), width),
252
+ ...listLines,
253
+ ];
254
+ this.renderMemo = { width, status: statusKey, listLines, lines };
225
255
  return lines;
226
256
  }
227
257
 
@@ -247,9 +277,63 @@ export class MemoryOverlayView implements Component {
247
277
  }
248
278
  return status;
249
279
  }
280
+
281
+ private promote(item: ListOverlayItem, scope: "repo" | "global", onChange: () => void): void {
282
+ if (this.promotion === undefined || this.promotionInFlight) return;
283
+ const entry = this.selectedBankEntry(item);
284
+ if (entry === null) {
285
+ this.pendingGlobalEntryId = null;
286
+ this.promotionMessage = { token: "warning", text: "select a knowledge or procedural task-bank entry" };
287
+ this.invalidate();
288
+ onChange();
289
+ return;
290
+ }
291
+ if (scope === "global" && this.pendingGlobalEntryId !== entry.id) {
292
+ this.pendingGlobalEntryId = entry.id;
293
+ this.promotionMessage = {
294
+ token: "warning",
295
+ text: `global scope broadens applicability for ${entry.id}; press g again to acknowledge`,
296
+ };
297
+ this.invalidate();
298
+ onChange();
299
+ return;
300
+ }
301
+ this.pendingGlobalEntryId = null;
302
+ this.promotionInFlight = true;
303
+ this.promotionMessage = { token: "muted", text: `proposing ${entry.id} with ${scope} scope` };
304
+ this.invalidate();
305
+ onChange();
306
+ void this.promotion
307
+ .onPromote(entry, scope)
308
+ .then((result) => {
309
+ this.promotionMessage = {
310
+ token: "success",
311
+ text: `${result.created ? "proposed" : "found existing"} ${result.record.id}; review, then run clio-coder memory approve ${result.record.id}`,
312
+ };
313
+ })
314
+ .catch((error: unknown) => {
315
+ this.promotionMessage = {
316
+ token: "error",
317
+ text: `promotion failed: ${error instanceof Error ? error.message : String(error)}`,
318
+ };
319
+ })
320
+ .finally(() => {
321
+ this.promotionInFlight = false;
322
+ this.invalidate();
323
+ onChange();
324
+ });
325
+ }
326
+
327
+ private selectedBankEntry(item: ListOverlayItem): TaskMemoryEntry | null {
328
+ if (!item.id.startsWith("bank:")) return null;
329
+ const entryId = item.id.slice("bank:".length);
330
+ const entry = bankEntries(this.getStatus().bank).find((candidate) => candidate.id === entryId);
331
+ if (entry === undefined || entry.kind === "status") return null;
332
+ return entry;
333
+ }
250
334
  }
251
335
 
252
- /** Mount the read-only durable-lessons and live task-bank view. */
336
+ /** Mount durable lessons and the live task bank with reviewed promotion actions. */
253
337
  export function openMemoryOverlay(
254
338
  tui: TUI,
255
339
  getStatus: () => TaskMemoryOperatorStatus,
@@ -261,6 +345,7 @@ export function openMemoryOverlay(
261
345
  getRecords,
262
346
  () => options.onClose?.(),
263
347
  () => tui.requestRender(),
348
+ options.onPromote ? { onPromote: options.onPromote } : undefined,
264
349
  );
265
350
  const handle = showClioOverlayFrame(tui, view, {
266
351
  anchor: "center",