@iowarp/clio-coder 0.3.6 → 0.3.8

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 (323) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +22 -6
  3. package/dist/{acp-2BEHC4DL.js → acp-U67UHUK2.js} +14 -14
  4. package/dist/{agents-LNNFTM53.js → agents-YU6SGALZ.js} +42 -35
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-KXXFI2VS.js → auth-5ZPJOIVG.js} +32 -24
  7. package/dist/builtins-C6JMZVV6.js +17 -0
  8. package/dist/{chunk-E25LMLRW.js → chunk-26LEYJZH.js} +2 -2
  9. package/dist/{chunk-TSHXZTOQ.js → chunk-2HEJ2F35.js} +5 -5
  10. package/dist/{chunk-PBTHKCPN.js → chunk-2HFZQUHL.js} +7 -7
  11. package/dist/{chunk-FO5ZOVUY.js → chunk-3BINW3FP.js} +5 -5
  12. package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
  13. package/dist/{chunk-CKXWIANG.js → chunk-4SPRNWDE.js} +18 -16
  14. package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
  15. package/dist/{chunk-XF5N4U5A.js → chunk-5DHKRSMQ.js} +9 -8
  16. package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
  17. package/dist/{chunk-EKY57CSP.js → chunk-5H3GB5BO.js} +68 -772
  18. package/dist/{chunk-6XXKFVSN.js → chunk-5Q2VVUKB.js} +4 -4
  19. package/dist/{chunk-DJVECN66.js → chunk-7RFXX52T.js} +295 -3705
  20. package/dist/{chunk-ZXF4XRKW.js → chunk-7RGZWPB6.js} +158 -8
  21. package/dist/{chunk-FYYLNIL5.js → chunk-A2NJGIB3.js} +2 -2
  22. package/dist/{chunk-XXQNGV4M.js → chunk-A3WNZD3P.js} +548 -72
  23. package/dist/{chunk-VEZEGCGW.js → chunk-B5CSFE7B.js} +23 -21
  24. package/dist/{chunk-2SFS6XQE.js → chunk-DGSYXYMX.js} +3 -2
  25. package/dist/chunk-DR52UMZW.js +21 -0
  26. package/dist/{chunk-ZWLZP4ZT.js → chunk-DYIM5TJT.js} +98 -12
  27. package/dist/{chunk-QKMUKYO7.js → chunk-E77JEWSD.js} +270 -125
  28. package/dist/{chunk-LYF7OHWH.js → chunk-EMYUUSFG.js} +12 -467
  29. package/dist/{chunk-4VP4KH3K.js → chunk-EQ63NRB7.js} +8 -8
  30. package/dist/chunk-FBVTI2TJ.js +518 -0
  31. package/dist/{chunk-R46L2BIR.js → chunk-FHJEP5SW.js} +19 -25
  32. package/dist/{chunk-WR67VIZY.js → chunk-GN57SG4G.js} +66 -8
  33. package/dist/{chunk-RD5U66HV.js → chunk-GPIEI3LY.js} +9 -9
  34. package/dist/{chunk-PCZJO5TI.js → chunk-GU2UIAFZ.js} +13 -178
  35. package/dist/chunk-GWS3VEIW.js +195 -0
  36. package/dist/chunk-H7IXIC72.js +103 -0
  37. package/dist/{chunk-3BPUFZDL.js → chunk-HLE42MG7.js} +3 -3
  38. package/dist/{chunk-24I7BN55.js → chunk-IFBNV6H6.js} +3 -3
  39. package/dist/{chunk-QNQHSOLF.js → chunk-IGWKHNIQ.js} +114 -55
  40. package/dist/chunk-IIZWH4XA.js +172 -0
  41. package/dist/{chunk-AD2SYQYC.js → chunk-IJ7RPIYJ.js} +124 -6
  42. package/dist/chunk-J3YUBZWY.js +382 -0
  43. package/dist/{chunk-5JGRAMKL.js → chunk-JOZYP4GM.js} +8 -6
  44. package/dist/{chunk-OH3TOQTB.js → chunk-K4XHGFR5.js} +751 -15
  45. package/dist/{chunk-EYPA3EGJ.js → chunk-KTYTFRMB.js} +178 -14
  46. package/dist/chunk-LU7P4LHA.js +33 -0
  47. package/dist/chunk-ME6CCNFO.js +108 -0
  48. package/dist/chunk-MXKJU4JB.js +1100 -0
  49. package/dist/{chunk-RY3LY4J5.js → chunk-N22QMJKY.js} +21 -18
  50. package/dist/chunk-NMPKI6XL.js +3006 -0
  51. package/dist/chunk-NUGM5KR6.js +165 -0
  52. package/dist/{chunk-22NAGB7X.js → chunk-P43ETTHK.js} +5 -94
  53. package/dist/{chunk-NILBFAPG.js → chunk-PMDBGQSJ.js} +2 -2
  54. package/dist/chunk-PT7HYKEM.js +165 -0
  55. package/dist/chunk-RVG5JXAL.js +41 -0
  56. package/dist/{chunk-GEYXPTRF.js → chunk-RWSI4YD7.js} +2 -2
  57. package/dist/{chunk-4BPJXDWC.js → chunk-TANS5ZJS.js} +35 -19
  58. package/dist/{verifiers-NCBTHHN2.js → chunk-TB5666IT.js} +67 -324
  59. package/dist/{chunk-QM3F2GKX.js → chunk-TLQJPP24.js} +7927 -8088
  60. package/dist/{chunk-G7MUEIGA.js → chunk-TT36MB5S.js} +2 -1
  61. package/dist/chunk-TTHACPOM.js +961 -0
  62. package/dist/{chunk-6US73PDB.js → chunk-TYPGUK6W.js} +7 -7
  63. package/dist/{chunk-MFFY33HR.js → chunk-U6MBIEMB.js} +554 -209
  64. package/dist/chunk-VAWNZU7Z.js +242 -0
  65. package/dist/{chunk-IR4CFBFN.js → chunk-VCBR6CU7.js} +12 -12
  66. package/dist/{chunk-WHJYKASB.js → chunk-VHN4MY6O.js} +2 -2
  67. package/dist/{chunk-XYDYPRZI.js → chunk-VWZOAB7K.js} +10 -10
  68. package/dist/{chunk-ZRGEBJ4T.js → chunk-WLFILSD5.js} +48 -48
  69. package/dist/{chunk-K7T3E2SR.js → chunk-WNIJTQQK.js} +12 -11
  70. package/dist/{chunk-CYQKWTG3.js → chunk-WSB3FPX7.js} +68 -20
  71. package/dist/{chunk-KHSFENX2.js → chunk-WWCZ5F23.js} +116 -14
  72. package/dist/{chunk-XE2VEJHX.js → chunk-WXY7KU3G.js} +2 -2
  73. package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
  74. package/dist/{chunk-KOHPCX4K.js → chunk-XWSF374K.js} +5 -5
  75. package/dist/{chunk-CJUB2JJ2.js → chunk-YS5VLNH5.js} +10 -10
  76. package/dist/{chunk-ZZMN5OM4.js → chunk-ZNLWCMVZ.js} +2 -2
  77. package/dist/{chunk-WHGPSPT5.js → chunk-ZVJ5BLO2.js} +2 -2
  78. package/dist/cli/index.js +33 -31
  79. package/dist/{clio-M2KGYUFZ.js → clio-QVTYJ57A.js} +10 -10
  80. package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +5 -5
  81. package/dist/codewiki/build-worker.js +4 -4
  82. package/dist/{components-5TTYYX6G.js → components-ZFA3SAER.js} +9 -9
  83. package/dist/{config-XUUYQIWO.js → config-LW5IJFQN.js} +65 -55
  84. package/dist/{configure-IHJ7YOMV.js → configure-7XIZCOU4.js} +29 -24
  85. package/dist/{context-75MIWW3U.js → context-L3WL3X7K.js} +54 -43
  86. package/dist/{context-ZQ7SIFJV.js → context-N52ZA626.js} +30 -14
  87. package/dist/{context-74JLXAWD.js → context-Y6Y7QPR6.js} +12 -12
  88. package/dist/{context-clear-GYKWNUML.js → context-clear-MBQRLSDQ.js} +54 -43
  89. package/dist/{context-index-SSR5ECNE.js → context-index-HVMFQHK3.js} +5 -5
  90. package/dist/{context-working-set-UX5KEP4J.js → context-working-set-GS6DSO7F.js} +18 -18
  91. package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-22ZCNOM3.js} +367 -79
  92. package/dist/{docs-6FZSCG5B.js → docs-7LQ23DLM.js} +9 -9
  93. package/dist/{doctor-SVJ5BZCW.js → doctor-M7YEDGAE.js} +27 -23
  94. package/dist/{eval-CG6LLBLD.js → eval-BEC2WHDA.js} +73 -20
  95. package/dist/{evidence-ZYFIEN42.js → evidence-REJUMSKM.js} +70 -59
  96. package/dist/{evolve-QGEXEMDW.js → evolve-PY5ZBA5K.js} +50 -39
  97. package/dist/{extensions-ADGNCJJD.js → extensions-HVKU65YU.js} +7 -7
  98. package/dist/{fleet-S5R4ZOQY.js → fleet-7WZEWRFA.js} +236 -377
  99. package/dist/fleet-commands-UVHWM76J.js +70 -0
  100. package/dist/fleet-graph-6ULH7PES.js +125 -0
  101. package/dist/fleet-new-RDVJLHHH.js +48 -0
  102. package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-J53T6CCE.js} +5 -5
  103. package/dist/fleet-validate-72PC4SLA.js +79 -0
  104. package/dist/{init-5DRU55YR.js → init-OG3TPGQG.js} +69 -57
  105. package/dist/library-CNTMPLRF.js +217 -0
  106. package/dist/{memory-7YKKR6UC.js → memory-6IS7F275.js} +52 -41
  107. package/dist/{models-ZPOLRU2C.js → models-ENRJDA5W.js} +37 -31
  108. package/dist/{monitor-US5F5YGZ.js → monitor-XLDVO7TN.js} +79 -42
  109. package/dist/{orchestrator-E2AL4T5N.js → orchestrator-6KSPYRHA.js} +4126 -762
  110. package/dist/{paths-E7KYAQWE.js → paths-DBXMZMDU.js} +6 -6
  111. package/dist/registry-LG64LTF4.js +11 -0
  112. package/dist/{reset-KZ652EK6.js → reset-RZ4ER727.js} +12 -12
  113. package/dist/{run-SRNBKDWD.js → run-Y2CNK5RU.js} +89 -76
  114. package/dist/{share-CGZE33UP.js → share-A55GYP6Z.js} +37 -13
  115. package/dist/{skills-S2X4DLY5.js → skills-ALC5J6AT.js} +29 -13
  116. package/dist/{skills-eval-W2GGIC4R.js → skills-eval-JPBEBYQU.js} +60 -48
  117. package/dist/support-MIETYA5E.js +38 -0
  118. package/dist/{targets-54SWINWB.js → targets-VGNXIR3S.js} +46 -36
  119. package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WOBR64YA.js} +4 -4
  120. package/dist/{uninstall-BVLWXKBT.js → uninstall-ZJF5H5ZN.js} +9 -9
  121. package/dist/{upgrade-JKAR27XC.js → upgrade-FUSUAGHR.js} +31 -28
  122. package/dist/{usage-MSAWCLX4.js → usage-N4MKVHKD.js} +140 -68
  123. package/dist/verifiers-YAWOJ3H2.js +336 -0
  124. package/dist/{verify-X5HDROLA.js → verify-LTDHYBGY.js} +10 -9
  125. package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-6M7GHTBJ.js} +75 -62
  126. package/dist/worker/entry.js +67 -57
  127. package/docs/README.md +3 -2
  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-versions.md +11 -6
  132. package/docs/built-in-agents.md +26 -2
  133. package/docs/capacity-and-scheduling.md +1 -1
  134. package/docs/commands-and-modes.md +83 -3
  135. package/docs/configuration-and-targets.md +86 -4
  136. package/docs/context-engine.md +1 -1
  137. package/docs/development-pipeline.md +1 -1
  138. package/docs/dispatch-architecture-rationale.md +1 -1
  139. package/docs/documentation-coverage.md +3 -3
  140. package/docs/documentation-guide.md +3 -3
  141. package/docs/eval-runner.md +1 -1
  142. package/docs/evals-internal.md +1 -1
  143. package/docs/evidence-and-memory.md +74 -10
  144. package/docs/evolution.md +1 -1
  145. package/docs/exit-codes-and-output.md +4 -1
  146. package/docs/extensions-and-sharing.md +6 -2
  147. package/docs/fleet-demo-runbook.md +2 -2
  148. package/docs/fleet-dispatch.md +228 -16
  149. package/docs/git-commit-provenance.md +2 -2
  150. package/docs/glossary.md +22 -2
  151. package/docs/installation-and-lifecycle.md +2 -2
  152. package/docs/middleware-and-components.md +2 -1
  153. package/docs/model-catalog.md +1 -1
  154. package/docs/observability.md +56 -9
  155. package/docs/proactive-memory.md +1 -1
  156. package/docs/prompt-envelope-and-tools.md +4 -2
  157. package/docs/provider-adapter-cookbook.md +1 -1
  158. package/docs/release-cut-checklist.md +83 -65
  159. package/docs/resource-library.md +59 -0
  160. package/docs/safety-model.md +2 -2
  161. package/docs/scientific-validation.md +3 -3
  162. package/docs/session-lifecycle.md +37 -1
  163. package/docs/skills-marketplace.md +16 -3
  164. package/docs/tool-usage.md +14 -7
  165. package/docs/trace-store.md +1 -1
  166. package/docs/troubleshooting.md +1 -1
  167. package/docs/tui-design.md +1 -1
  168. package/docs/worker-dispatch-mechanics.md +3 -3
  169. package/package.json +1 -2
  170. package/src/cli/argv.ts +5 -0
  171. package/src/cli/configure.ts +107 -23
  172. package/src/cli/doctor.ts +5 -1
  173. package/src/cli/evidence.ts +30 -25
  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 +111 -0
  178. package/src/cli/fleet-validate.ts +30 -0
  179. package/src/cli/fleet.ts +173 -335
  180. package/src/cli/index.ts +3 -1
  181. package/src/cli/library.ts +190 -0
  182. package/src/cli/share.ts +13 -1
  183. package/src/cli/shared.ts +1 -0
  184. package/src/cli/targets.ts +4 -1
  185. package/src/cli/usage.ts +111 -19
  186. package/src/cli/validate-model.ts +60 -5
  187. package/src/core/bus-events.ts +29 -0
  188. package/src/core/commit-attribution.ts +4 -4
  189. package/src/core/config.ts +130 -0
  190. package/src/core/defaults.ts +81 -0
  191. package/src/core/path-boundary.ts +100 -0
  192. package/src/domains/agents/builtins/architect.md +1 -0
  193. package/src/domains/agents/builtins/oracle.md +33 -0
  194. package/src/domains/agents/catalog.ts +13 -1
  195. package/src/domains/agents/extension.ts +2 -11
  196. package/src/domains/agents/fleet-contract.ts +304 -24
  197. package/src/domains/agents/index.ts +14 -0
  198. package/src/domains/agents/recipe.ts +7 -1
  199. package/src/domains/agents/registry.ts +73 -5
  200. package/src/domains/agents/result-contract.ts +360 -15
  201. package/src/domains/agents/write-boundary.ts +15 -50
  202. package/src/domains/config/classify.ts +4 -0
  203. package/src/domains/context/project-rules.ts +51 -1
  204. package/src/domains/dispatch/active-route-planner.ts +14 -0
  205. package/src/domains/dispatch/assignment-reconcile.ts +22 -5
  206. package/src/domains/dispatch/assignment-store.ts +151 -14
  207. package/src/domains/dispatch/backoff.ts +2 -1
  208. package/src/domains/dispatch/capability-match.ts +1 -0
  209. package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
  210. package/src/domains/dispatch/contract.ts +48 -0
  211. package/src/domains/dispatch/delegation-plan.ts +164 -0
  212. package/src/domains/dispatch/execution-plan.ts +76 -5
  213. package/src/domains/dispatch/execution-role.ts +12 -2
  214. package/src/domains/dispatch/execution-scheduler.ts +183 -67
  215. package/src/domains/dispatch/extension.ts +412 -74
  216. package/src/domains/dispatch/fleet-gate.ts +14 -0
  217. package/src/domains/dispatch/fleet-plan.ts +63 -3
  218. package/src/domains/dispatch/fleet-run.ts +791 -0
  219. package/src/domains/dispatch/gate-role-prompts.ts +47 -0
  220. package/src/domains/dispatch/host-verification.ts +178 -0
  221. package/src/domains/dispatch/index.ts +41 -1
  222. package/src/domains/dispatch/intent-requirements.ts +40 -0
  223. package/src/domains/dispatch/intent.ts +235 -0
  224. package/src/domains/dispatch/path-scope.ts +370 -0
  225. package/src/domains/dispatch/receipt-integrity.ts +9 -4
  226. package/src/domains/dispatch/state.ts +36 -3
  227. package/src/domains/dispatch/types.ts +64 -12
  228. package/src/domains/dispatch/validation.ts +69 -6
  229. package/src/domains/dispatch/write-boundary-enforcer.ts +45 -0
  230. package/src/domains/dispatch/write-boundary.ts +201 -22
  231. package/src/domains/eval/metrics/evidence.ts +79 -2
  232. package/src/domains/eval/runners/clio-run.ts +12 -2
  233. package/src/domains/evidence/build.ts +69 -11
  234. package/src/domains/evidence/index.ts +21 -0
  235. package/src/domains/evidence/provenance.ts +46 -11
  236. package/src/domains/evidence/trust-projection.ts +274 -0
  237. package/src/domains/evidence/trust-status.ts +155 -18
  238. package/src/domains/evidence/types.ts +4 -0
  239. package/src/domains/extensions/discovery.ts +88 -1
  240. package/src/domains/extensions/resources.ts +20 -8
  241. package/src/domains/extensions/state.ts +6 -2
  242. package/src/domains/extensions/types.ts +4 -1
  243. package/src/domains/lifecycle/doctor.ts +140 -1
  244. package/src/domains/middleware/index.ts +15 -0
  245. package/src/domains/middleware/watchdog.ts +281 -0
  246. package/src/domains/observability/contract.ts +3 -1
  247. package/src/domains/observability/cost.ts +12 -1
  248. package/src/domains/observability/extension.ts +2 -2
  249. package/src/domains/observability/index.ts +10 -0
  250. package/src/domains/observability/out-of-turn-usage.ts +223 -0
  251. package/src/domains/prompts/contract.ts +3 -5
  252. package/src/domains/providers/extension.ts +30 -2
  253. package/src/domains/resources/common-loader.ts +3 -0
  254. package/src/domains/resources/index.ts +20 -0
  255. package/src/domains/resources/library.ts +326 -0
  256. package/src/domains/resources/prompts/loader.ts +119 -14
  257. package/src/domains/resources/skills/marketplace.ts +37 -12
  258. package/src/domains/safety/policy-engine.ts +5 -5
  259. package/src/domains/safety/run-effects.ts +64 -1
  260. package/src/domains/safety/scope.ts +7 -12
  261. package/src/domains/session/handoff.ts +629 -0
  262. package/src/domains/share/archive.ts +67 -2
  263. package/src/engine/acp/server.ts +4 -1
  264. package/src/engine/prompt-templates.ts +18 -1
  265. package/src/engine/worker-runtime.ts +6 -3
  266. package/src/entry/orchestrator.ts +37 -0
  267. package/src/interactive/bus-notices.ts +26 -0
  268. package/src/interactive/chat-loop.ts +235 -1
  269. package/src/interactive/chat-renderer.ts +22 -0
  270. package/src/interactive/cost-overlay.ts +31 -3
  271. package/src/interactive/council-dispatch.ts +30 -0
  272. package/src/interactive/council-grid.ts +213 -0
  273. package/src/interactive/council.ts +99 -0
  274. package/src/interactive/dispatch-board.ts +311 -17
  275. package/src/interactive/fleet-run-preview.ts +307 -0
  276. package/src/interactive/footer/notifications.ts +219 -0
  277. package/src/interactive/handoff-round.ts +56 -0
  278. package/src/interactive/interactive-application.ts +43 -1
  279. package/src/interactive/interactive-event-projection.ts +23 -1
  280. package/src/interactive/interactive-slash-runtime.ts +52 -2
  281. package/src/interactive/interactive-subscriptions.ts +14 -2
  282. package/src/interactive/oracle.ts +179 -0
  283. package/src/interactive/overlay-ask-user-lifecycle.ts +6 -0
  284. package/src/interactive/overlay-general-openers.ts +190 -1
  285. package/src/interactive/overlay-key-routing.ts +17 -1
  286. package/src/interactive/overlay-lifecycle.ts +41 -1
  287. package/src/interactive/overlay-permission-lifecycle.ts +10 -0
  288. package/src/interactive/overlay-resource-openers.ts +11 -3
  289. package/src/interactive/overlay-session-lifecycle.ts +234 -2
  290. package/src/interactive/overlays/fleet-run-approval.ts +208 -0
  291. package/src/interactive/overlays/handoff-review.ts +185 -0
  292. package/src/interactive/overlays/library-install-confirm.ts +151 -0
  293. package/src/interactive/overlays/list-overlay.ts +168 -2
  294. package/src/interactive/overlays/settings.ts +221 -30
  295. package/src/interactive/overlays/side-question.ts +139 -0
  296. package/src/interactive/overlays/skills-hub.ts +401 -15
  297. package/src/interactive/side-question.ts +171 -0
  298. package/src/interactive/slash-commands.ts +439 -7
  299. package/src/interactive/slash-spec.ts +19 -6
  300. package/src/interactive/theme/tokens.ts +30 -0
  301. package/src/interactive/turn-middleware.ts +15 -1
  302. package/src/interactive/view/artifacts.ts +42 -9
  303. package/src/interactive/view/view-overlay.ts +15 -3
  304. package/src/interactive/watchdog-run.ts +75 -0
  305. package/src/interactive/worker-receipts.ts +14 -2
  306. package/src/interactive/worker-share.ts +56 -1
  307. package/src/interactive/worker-stream.ts +15 -0
  308. package/src/tools/bootstrap.ts +3 -0
  309. package/src/tools/compete-worktrees.ts +13 -79
  310. package/src/tools/dispatch-admission.ts +251 -18
  311. package/src/tools/dispatch-arguments.ts +84 -1
  312. package/src/tools/dispatch-plan.ts +165 -6
  313. package/src/tools/dispatch-runner.ts +364 -23
  314. package/src/tools/dispatch-types.ts +20 -1
  315. package/src/tools/dispatch.ts +72 -2
  316. package/src/tools/monitor.ts +29 -0
  317. package/src/tools/profiles.ts +18 -4
  318. package/src/tools/task-worktree.ts +238 -0
  319. package/src/tools/verify/authoring.ts +61 -1
  320. package/src/tools/verify/scripts.ts +62 -0
  321. package/src/tools/worker-evidence.ts +21 -14
  322. package/src/worker/spec-contract.ts +3 -1
  323. package/dist/chunk-HC4CLZ2Y.js +0 -68
@@ -24,6 +24,11 @@ import {
24
24
  type RunStatus,
25
25
  runKindSupportsLiveSteering,
26
26
  } from "../domains/dispatch/types.js";
27
+ import {
28
+ summarizeTrustStatus,
29
+ type TrustSummaryProjection,
30
+ type TrustVerdict,
31
+ } from "../domains/evidence/trust-projection.js";
27
32
  import {
28
33
  COST_NOT_MEASURED,
29
34
  costAggregateForAmount,
@@ -35,6 +40,13 @@ import type { CostProvenance } from "../domains/providers/index.js";
35
40
  import { sanitizeCallTargetText } from "../domains/safety/call-target.js";
36
41
  import { type Component, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../engine/tui.js";
37
42
  import { formatWorkerContextMeter } from "./context-meter.js";
43
+ import {
44
+ COUNCIL_SYNTHESIS_LABEL,
45
+ type CouncilGroupView,
46
+ type CouncilMemberView,
47
+ councilGroupBody,
48
+ councilIslandLines,
49
+ } from "./council-grid.js";
38
50
  import { formatFooterTokens } from "./footer-panel.js";
39
51
  import {
40
52
  type ClioTheme,
@@ -103,6 +115,16 @@ export interface DispatchBoardRow {
103
115
  node?: string;
104
116
  /** Review/compete gate badge (role + cycle). */
105
117
  gate?: { role: string; cycle: number };
118
+ council?: { group: string; label: string; color?: string; round: number };
119
+ /** Orchestrator-executed declared verification status. */
120
+ hostVerification?: "verified" | "rejected" | "skipped";
121
+ /**
122
+ * The canonical trust projection of the sealed receipt, read back from
123
+ * disk and authenticated against the ledger row. Absent while the run is
124
+ * live or when the receipt could not be read back, so the card never
125
+ * implies a verdict the terminal bus event cannot carry.
126
+ */
127
+ trust?: TrustSummaryProjection;
106
128
  /** Dead-node failover hops recorded on this run's chain. */
107
129
  rerouteCount?: number;
108
130
  /** Assignment retry attempts observed on the assignment event stream. */
@@ -134,8 +156,39 @@ export interface DispatchBoardRow {
134
156
  retry?: DispatchRetryPresentation;
135
157
  /** Most recent worker delivery acknowledgement for an operator steer. */
136
158
  steerAcknowledgement?: DispatchSteerAcknowledgement;
159
+ /**
160
+ * Where this run sits in a fleet plan: the wave the step was scheduled in
161
+ * and the step id it executes. Present only for runs a fleet plan
162
+ * dispatched, so a `/run`, a delegation, or a dispatch-tool run leaves the
163
+ * board's phase column empty rather than claiming a position it has none of.
164
+ */
165
+ phase?: DispatchBoardPhase;
166
+ }
167
+
168
+ /** One fleet plan position: the wave index and the step the run executes. */
169
+ export interface DispatchBoardPhase {
170
+ wave: number;
171
+ stepId: string;
172
+ }
173
+
174
+ /**
175
+ * The phase cell, or null for a run that is not a fleet step. Fixed width so
176
+ * the column lines up across rows; a long step id is clipped with a marker
177
+ * rather than pushing the row's other facts out of position.
178
+ */
179
+ export function formatDispatchPhaseCell(
180
+ phase: DispatchBoardPhase | undefined,
181
+ width = PHASE_COLUMN_WIDTH,
182
+ ): string | null {
183
+ if (phase === undefined) return null;
184
+ return truncateToWidth(`w${phase.wave} ${phase.stepId}`, Math.max(1, width), "…", false);
137
185
  }
138
186
 
187
+ /** Columns the compact island reserves for the phase cell when it can afford them. */
188
+ export const PHASE_COLUMN_WIDTH = 12;
189
+ /** Below this much room for the agent label, the compact row drops the column entirely. */
190
+ const PHASE_COLUMN_MIN_LABEL_WIDTH = 8;
191
+
139
192
  /** Live HTTP/SDK rows are the only rows whose runtime can consume operator guidance. */
140
193
  export function isDispatchBoardRowSteerable(row: DispatchBoardRow): boolean {
141
194
  return (
@@ -610,14 +663,34 @@ export function renderDispatchCard(
610
663
  `${theme.fg("dim", "ttft")} ${theme.fg("muted", ttft)}`,
611
664
  `${theme.fg("dim", "cost")} ${theme.fg("muted", cost)}`,
612
665
  ];
666
+ // The trust line is the canonical projection, styled by its verdict tier;
667
+ // a host check is folded into that projection as validation grounding, so
668
+ // it is named as a secondary fact and never as independent verification.
669
+ const trustUnits = [
670
+ row.trust === undefined
671
+ ? theme.fg("dim", isTerminalStatus(row.status) ? "trust: receipt not read back" : "trust: not sealed yet")
672
+ : theme.fg(trustVerdictToken(row.trust.verdict), `${trustVerdictGlyph(row.trust.verdict)} ${row.trust.text}`),
673
+ ...(row.hostVerification !== undefined ? [theme.fg("muted", `host checks ${row.hostVerification}`)] : []),
674
+ ];
613
675
  const contextUnit = formatWorkerContextMeter(row.lastContextTokens ?? 0, row.contextWindow, theme);
676
+ // The phase column: a fleet step says which wave it belongs to and which
677
+ // step it is; every other run leaves the cell empty rather than inventing a
678
+ // position, so the column reads as "not a fleet step" and not as wave zero.
679
+ const phaseCell = formatDispatchPhaseCell(row.phase, contentWidth);
614
680
  const bodyLines = [
615
681
  cardUnitsLine(theme, "run", [theme.fg("dim", row.runId)], contentWidth),
682
+ cardUnitsLine(
683
+ theme,
684
+ "phase",
685
+ [phaseCell === null ? theme.fg("dim", "—") : theme.fg("info", phaseCell)],
686
+ contentWidth,
687
+ ),
616
688
  targetLine,
617
689
  ...(row.taskSummary
618
690
  ? [truncateToWidth(`${cardKvKey(theme, "task")}${theme.fg("muted", row.taskSummary)}`, contentWidth, "…", false)]
619
691
  : []),
620
692
  cardUnitsLine(theme, "status", statusUnits, contentWidth),
693
+ cardUnitsLine(theme, "trust", trustUnits, contentWidth),
621
694
  ...(row.budget !== undefined
622
695
  ? [
623
696
  truncateToWidth(
@@ -715,11 +788,25 @@ function renderTaskIslandRow(row: DispatchBoardRow, width: number): string[] {
715
788
  visibleWidth(presentation.label) +
716
789
  3 +
717
790
  visibleWidth(elapsed);
718
- const clampedLabel = truncateToWidth(agentLabel, Math.max(1, width - labelChrome), "…", false);
791
+ // The phase column rides the compact row only when the row can still show a
792
+ // readable agent label beside it. TASK_ISLAND_WIDTH is fixed, so a row that
793
+ // cannot afford the cell drops the column here and shows the phase in the
794
+ // expanded card instead of clipping the label to make room.
795
+ const phaseCell = formatDispatchPhaseCell(row.phase);
796
+ const phaseColumn = phaseCell === null ? "" : `${dot}${theme.fg("info", padAnsi(phaseCell, PHASE_COLUMN_WIDTH))}`;
797
+ const phaseChrome = phaseCell === null ? 0 : PHASE_COLUMN_WIDTH + 3;
798
+ const labelWidth = width - labelChrome - phaseChrome;
799
+ const showPhase = phaseCell !== null && labelWidth >= PHASE_COLUMN_MIN_LABEL_WIDTH;
800
+ const clampedLabel = truncateToWidth(
801
+ agentLabel,
802
+ Math.max(1, showPhase ? labelWidth : width - labelChrome),
803
+ "…",
804
+ false,
805
+ );
719
806
 
720
807
  // The agent label drops its accent color for plain bold; the status word is
721
808
  // the only status-colored element on the row, with dim middot separators.
722
- const line1 = `${glyph} ${rowPrefix.text}${theme.paint(clampedLabel, { bold: true })}${dot}${statusStr}${dot}${theme.fg("muted", elapsed)}`;
809
+ const line1 = `${glyph} ${rowPrefix.text}${theme.paint(clampedLabel, { bold: true })}${dot}${statusStr}${dot}${theme.fg("muted", elapsed)}${showPhase ? phaseColumn : ""}`;
723
810
 
724
811
  const elapsedSec = row.elapsedMs / 1000;
725
812
  const tokensPerSec = elapsedSec > 0.1 ? Math.round(row.outputTokens / elapsedSec) : 0;
@@ -740,6 +827,131 @@ function renderTaskIslandRow(row: DispatchBoardRow, width: number): string[] {
740
827
  return [padAnsi(line1, width), ...(task ? [padAnsi(task, width)] : []), padAnsi(telemetry, width)];
741
828
  }
742
829
 
830
+ /**
831
+ * One member column's facts, drawn from the row the board already keeps. The
832
+ * route, status, and answer tail are the same values the run's own card shows,
833
+ * so a council column and a council card can never disagree.
834
+ */
835
+ function councilMemberView(
836
+ row: DispatchBoardRow,
837
+ council: NonNullable<DispatchBoardRow["council"]>,
838
+ ): CouncilMemberView {
839
+ const presentation = dispatchStatusPresentation(row.status, {
840
+ compact: true,
841
+ ...(row.status === "running" ? { tick: Math.floor(Date.now() / 100) } : {}),
842
+ });
843
+ return {
844
+ runId: row.runId,
845
+ label: council.label,
846
+ ...(council.color !== undefined ? { color: council.color } : {}),
847
+ round: council.round,
848
+ route: `${row.targetId}/${row.wireModelId}`,
849
+ status: { glyph: presentation.glyph, label: presentation.label, token: presentation.token },
850
+ tailText: sanitizeCallTargetText(row.progress?.tailText ?? ""),
851
+ droppedLines: row.progress?.droppedLines ?? 0,
852
+ };
853
+ }
854
+
855
+ /**
856
+ * Fold one council group's rows into the view the grid renders.
857
+ *
858
+ * A council of N members over R rounds seals N x R member runs, and the
859
+ * operator is looking at a council, not at its history: each label keeps its
860
+ * newest round, so the group shows one column per member however many rounds
861
+ * it ran. Columns are ordered by label so a repaint cannot reshuffle them under
862
+ * the cursor while runs settle in whatever order they finish.
863
+ */
864
+ function foldCouncilGroup(
865
+ group: string,
866
+ rows: ReadonlyArray<DispatchBoardRow>,
867
+ selectedRunId?: string | null,
868
+ ): CouncilGroupView {
869
+ const newest = new Map<string, DispatchBoardRow>();
870
+ let synthesisRow: DispatchBoardRow | null = null;
871
+ let rank = Number.POSITIVE_INFINITY;
872
+ let elapsedMs = 0;
873
+ let round = 1;
874
+ for (const row of rows) {
875
+ const council = row.council;
876
+ if (council === undefined) continue;
877
+ rank = Math.min(rank, STATUS_ORDER[row.status]);
878
+ elapsedMs = Math.max(elapsedMs, row.elapsedMs);
879
+ if (council.label === COUNCIL_SYNTHESIS_LABEL) {
880
+ synthesisRow = row;
881
+ continue;
882
+ }
883
+ round = Math.max(round, council.round);
884
+ const previous = newest.get(council.label);
885
+ if (previous === undefined || (previous.council?.round ?? 0) <= council.round) newest.set(council.label, row);
886
+ }
887
+ const aggregate = (Object.entries(STATUS_ORDER) as Array<[DispatchBoardStatus, number]>).find(
888
+ ([, value]) => value === rank,
889
+ );
890
+ const presentation = dispatchStatusPresentation(aggregate?.[0] ?? "running", {
891
+ compact: true,
892
+ ...(aggregate?.[0] === "running" ? { tick: Math.floor(Date.now() / 100) } : {}),
893
+ });
894
+ const members = [...newest.entries()]
895
+ .sort(([left], [right]) => left.localeCompare(right))
896
+ // biome-ignore lint/style/noNonNullAssertion: every entry was inserted with its own council badge.
897
+ .map(([, row]) => councilMemberView(row, row.council!));
898
+ const selectedInGroup =
899
+ selectedRunId != null && rows.some((row) => row.runId === selectedRunId) ? selectedRunId : undefined;
900
+ return {
901
+ group,
902
+ members,
903
+ // biome-ignore lint/style/noNonNullAssertion: the synthesis row was matched on its own council badge.
904
+ synthesis: synthesisRow === null ? null : councilMemberView(synthesisRow, synthesisRow.council!),
905
+ status: { glyph: presentation.glyph, label: presentation.label, token: presentation.token },
906
+ round,
907
+ elapsed: formatCompactMs(elapsedMs),
908
+ ...(selectedInGroup !== undefined ? { selectedRunId: selectedInGroup } : {}),
909
+ };
910
+ }
911
+
912
+ /** A board entry: either one ordinary run, or one whole council in the position of its first row. */
913
+ export type DispatchBoardItem = { kind: "run"; row: DispatchBoardRow } | { kind: "council"; group: CouncilGroupView };
914
+
915
+ /**
916
+ * Board order with councils folded in place. A council occupies the position of
917
+ * its first row and its remaining rows are consumed, so the board never shows a
918
+ * member both inside its group and beside it.
919
+ */
920
+ export function dispatchBoardItems(
921
+ rows: ReadonlyArray<DispatchBoardRow>,
922
+ selectedRunId?: string | null,
923
+ ): DispatchBoardItem[] {
924
+ const byGroup = new Map<string, DispatchBoardRow[]>();
925
+ for (const row of rows) {
926
+ const group = row.council?.group;
927
+ if (group === undefined) continue;
928
+ const bucket = byGroup.get(group);
929
+ if (bucket === undefined) byGroup.set(group, [row]);
930
+ else bucket.push(row);
931
+ }
932
+ const emitted = new Set<string>();
933
+ const items: DispatchBoardItem[] = [];
934
+ for (const row of rows) {
935
+ const group = row.council?.group;
936
+ if (group === undefined) {
937
+ items.push({ kind: "run", row });
938
+ continue;
939
+ }
940
+ if (emitted.has(group)) continue;
941
+ emitted.add(group);
942
+ items.push({ kind: "council", group: foldCouncilGroup(group, byGroup.get(group) ?? [row], selectedRunId) });
943
+ }
944
+ return items;
945
+ }
946
+
947
+ /** The framed council card: the member grid or stack, with the synthesis run full width under it. */
948
+ export function renderCouncilCard(group: CouncilGroupView, width: number): string[] {
949
+ const theme = clioTheme();
950
+ const contentWidth = Math.max(0, width - 4);
951
+ const title = `${theme.fg("info", "council")} ${theme.paint(group.group, { bold: true })}`;
952
+ return frame(theme, title, councilGroupBody(theme, group, contentWidth), width, { rightMeta: group.elapsed });
953
+ }
954
+
743
955
  export function formatDispatchBoardLines(
744
956
  rows: ReadonlyArray<DispatchBoardRow>,
745
957
  width = 76,
@@ -757,11 +969,15 @@ export function formatDispatchBoardLines(
757
969
  });
758
970
  }
759
971
 
760
- const cards = rows.map((row) =>
761
- renderDispatchCard(row, width, deriveRunEvidenceState(observability, row.runId), {
762
- selected: row.runId === selectedRunId,
763
- expanded: detailExpanded && row.runId === selectedRunId,
764
- }),
972
+ // A council is one question asked of several members, so its rows render as
973
+ // one card holding the whole group rather than as unrelated neighbours.
974
+ const cards = dispatchBoardItems(rows, selectedRunId).map((item) =>
975
+ item.kind === "council"
976
+ ? renderCouncilCard(item.group, width)
977
+ : renderDispatchCard(item.row, width, deriveRunEvidenceState(observability, item.row.runId), {
978
+ selected: item.row.runId === selectedRunId,
979
+ expanded: detailExpanded && item.row.runId === selectedRunId,
980
+ }),
765
981
  );
766
982
  const body: string[] = [];
767
983
  for (const card of cards) {
@@ -850,23 +1066,30 @@ export function createDispatchBoardView(
850
1066
  }
851
1067
 
852
1068
  export function formatTaskIslandLines(rows: ReadonlyArray<DispatchBoardRow>, maxRows = 4): string[] {
853
- const visibleRows = rows.slice(0, Math.max(1, maxRows));
1069
+ // Councils are folded before the row cap, so a five-member council costs the
1070
+ // island one card and never crowds out the runs beside it.
1071
+ const items = dispatchBoardItems(rows);
1072
+ const visibleItems = items.slice(0, Math.max(1, maxRows));
854
1073
  const body: string[] = [];
855
1074
 
856
- if (visibleRows.length === 0) {
1075
+ if (visibleItems.length === 0) {
857
1076
  const theme = clioTheme();
858
1077
  body.push(theme.fg("dim", "No active fleet runs."));
859
1078
  body.push(theme.fg("dim", "Use /run or /delegate to spawn agents."));
860
1079
  } else {
861
- for (let i = 0; i < visibleRows.length; i++) {
862
- const row = visibleRows[i];
863
- if (!row) continue;
1080
+ for (let i = 0; i < visibleItems.length; i++) {
1081
+ const item = visibleItems[i];
1082
+ if (!item) continue;
864
1083
  if (i > 0) {
865
1084
  body.push(innerDivider(clioTheme(), TASK_ISLAND_WIDTH));
866
1085
  }
867
- body.push(...renderTaskIslandRow(row, TASK_ISLAND_WIDTH));
1086
+ body.push(
1087
+ ...(item.kind === "council"
1088
+ ? councilIslandLines(clioTheme(), item.group, TASK_ISLAND_WIDTH)
1089
+ : renderTaskIslandRow(item.row, TASK_ISLAND_WIDTH)),
1090
+ );
868
1091
  }
869
- const hidden = rows.length - visibleRows.length;
1092
+ const hidden = items.length - visibleItems.length;
870
1093
  if (hidden > 0) {
871
1094
  body.push(innerDivider(clioTheme(), TASK_ISLAND_WIDTH));
872
1095
  body.push(clioTheme().fg("dim", `+ ${hidden} more`));
@@ -932,6 +1155,21 @@ function parseGateBadge(value: unknown): { role: string; cycle: number } | undef
932
1155
  return { role: record.role, cycle };
933
1156
  }
934
1157
 
1158
+ function parseCouncilBadge(value: unknown): DispatchBoardRow["council"] | undefined {
1159
+ if (typeof value !== "object" || value === null) return undefined;
1160
+ const record = value as { group?: unknown; label?: unknown; color?: unknown; round?: unknown };
1161
+ const group = parseNonEmptyString(record.group);
1162
+ const label = parseNonEmptyString(record.label);
1163
+ const round = parsePositiveInt(record.round);
1164
+ if (group === undefined || label === undefined || round === undefined) return undefined;
1165
+ return {
1166
+ group,
1167
+ label,
1168
+ ...(typeof record.color === "string" && record.color.length > 0 ? { color: record.color } : {}),
1169
+ round,
1170
+ };
1171
+ }
1172
+
935
1173
  function parseFiniteNumberOrZero(value: unknown): number {
936
1174
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
937
1175
  }
@@ -942,6 +1180,33 @@ function parseOptionalDetail(value: unknown): string | null {
942
1180
  return detail.length > 0 ? detail : null;
943
1181
  }
944
1182
 
1183
+ /** Only an authenticated independent pass earns the success token; sealed alone is never green. */
1184
+ function trustVerdictToken(verdict: TrustVerdict): ClioToken {
1185
+ switch (verdict) {
1186
+ case "reviewed":
1187
+ return "success";
1188
+ case "grounded":
1189
+ return "info";
1190
+ case "unverified":
1191
+ return "muted";
1192
+ case "compromised":
1193
+ return "error";
1194
+ default:
1195
+ return "dim";
1196
+ }
1197
+ }
1198
+
1199
+ function trustVerdictGlyph(verdict: TrustVerdict): string {
1200
+ switch (verdict) {
1201
+ case "reviewed":
1202
+ return GLYPH.ok;
1203
+ case "compromised":
1204
+ return GLYPH.error;
1205
+ default:
1206
+ return "◇";
1207
+ }
1208
+ }
1209
+
945
1210
  function terminalDetail(row: DispatchBoardRow): string | null {
946
1211
  if (row.status !== "failed" && row.status !== "dead" && row.status !== "aborted") return null;
947
1212
  return parseOptionalDetail(row.outcomeDetail);
@@ -1099,11 +1364,15 @@ function toRow(entry: DispatchBoardEntry, now: number): DispatchBoardRow {
1099
1364
  ...(entry.outcomeDetail !== undefined ? { outcomeDetail: entry.outcomeDetail } : {}),
1100
1365
  ...(entry.node !== undefined ? { node: entry.node } : {}),
1101
1366
  ...(entry.gate !== undefined ? { gate: { ...entry.gate } } : {}),
1367
+ ...(entry.council !== undefined ? { council: { ...entry.council } } : {}),
1368
+ ...(entry.hostVerification !== undefined ? { hostVerification: entry.hostVerification } : {}),
1369
+ ...(entry.trust !== undefined ? { trust: entry.trust } : {}),
1102
1370
  ...(entry.rerouteCount !== undefined ? { rerouteCount: entry.rerouteCount } : {}),
1103
1371
  ...(entry.failoverHops !== undefined ? { failoverHops: entry.failoverHops } : {}),
1104
1372
  ...(entry.contextWindow !== undefined ? { contextWindow: entry.contextWindow } : {}),
1105
1373
  lastContextTokens: entry.lastContextTokens,
1106
1374
  ...(entry.receiptId !== undefined ? { receiptId: entry.receiptId } : {}),
1375
+ ...(entry.phase !== undefined ? { phase: { ...entry.phase } } : {}),
1107
1376
  // Both tool fields are views of the one projection, so the compact card and
1108
1377
  // the expanded detail can never disagree about what is running.
1109
1378
  currentTool: retry ? null : (progress.currentAction?.tool ?? null),
@@ -1124,7 +1393,7 @@ function sortEntries(a: DispatchBoardEntry, b: DispatchBoardEntry): number {
1124
1393
  return a.sequence - b.sequence;
1125
1394
  }
1126
1395
 
1127
- function pruneEntries(entries: Map<string, DispatchBoardEntry>): void {
1396
+ function pruneEntries(entries: Map<string, DispatchBoardEntry>, onEvict?: (runId: string) => void): void {
1128
1397
  if (entries.size <= MAX_DISPATCH_BOARD_ROWS) return;
1129
1398
  const terminalEntries = [...entries.values()]
1130
1399
  .filter((entry) => !entry.retry && isTerminalStatus(entry.status))
@@ -1134,6 +1403,7 @@ function pruneEntries(entries: Map<string, DispatchBoardEntry>): void {
1134
1403
  for (const entry of evictionQueue) {
1135
1404
  if (entries.size <= MAX_DISPATCH_BOARD_ROWS) break;
1136
1405
  entries.delete(entry.runId);
1406
+ onEvict?.(entry.runId);
1137
1407
  }
1138
1408
  }
1139
1409
 
@@ -1150,9 +1420,19 @@ export function createDispatchBoardStore(
1150
1420
  rows(): ReadonlyArray<DispatchBoardRow>;
1151
1421
  activeRows(): ReadonlyArray<DispatchBoardRow>;
1152
1422
  reconcile(): void;
1423
+ /**
1424
+ * Record the fleet-plan position of a run the caller just dispatched. The
1425
+ * board never infers a position: a run carries one only because whoever
1426
+ * dispatched the plan said so, which is why a non-fleet run can never grow
1427
+ * a phase cell.
1428
+ */
1429
+ setFleetPhase(runId: string, phase: DispatchBoardPhase): void;
1153
1430
  unsubscribe(): void;
1154
1431
  } {
1155
1432
  const entries = new Map<string, DispatchBoardEntry>();
1433
+ // Positions arrive from the fleet-run path, which may hand one over before
1434
+ // the run's first lifecycle event creates the entry.
1435
+ const fleetPhases = new Map<string, DispatchBoardPhase>();
1156
1436
  let nextSequence = 0;
1157
1437
  let reconciledAtMs = Date.now();
1158
1438
 
@@ -1162,8 +1442,10 @@ export function createDispatchBoardStore(
1162
1442
  * before it publishes one, so this is the first moment the file exists.
1163
1443
  */
1164
1444
  const settleFromReceipt = (entry: DispatchBoardEntry): void => {
1165
- const text = readReceipt?.(entry.runId)?.text;
1445
+ const facts = readReceipt?.(entry.runId);
1446
+ const text = facts?.text;
1166
1447
  entry.progress.settle(typeof text === "string" && text.trim().length > 0 ? text : undefined);
1448
+ if (facts?.trust !== undefined) entry.trust = summarizeTrustStatus(facts.trust);
1167
1449
  };
1168
1450
 
1169
1451
  // Payloads arrive typed off the bus, but the board keeps its runtime
@@ -1180,10 +1462,12 @@ export function createDispatchBoardStore(
1180
1462
  const requestOrigin = parseRequestOrigin(raw.requestOrigin, previous?.requestOrigin);
1181
1463
  const node = parseNonEmptyString(raw.node) ?? previous?.node;
1182
1464
  const gate = parseGateBadge(raw.gate) ?? previous?.gate;
1465
+ const council = parseCouncilBadge(raw.council) ?? previous?.council;
1183
1466
  const rerouteCount = parsePositiveInt(raw.rerouteCount) ?? previous?.rerouteCount;
1184
1467
  const contextWindow = parsePositiveInt(raw.contextWindow) ?? previous?.contextWindow;
1185
1468
  const taskSummary = parseTaskSummary(raw, previous?.taskSummary);
1186
1469
  const budget = cloneRunToolBudgetEnvelope(raw.budget) ?? previous?.budget;
1470
+ const phase = fleetPhases.get(runId) ?? previous?.phase;
1187
1471
  const entry: DispatchBoardEntry = {
1188
1472
  runId,
1189
1473
  agentId: parseText(raw.agentId, previous?.agentId ?? "-"),
@@ -1211,16 +1495,19 @@ export function createDispatchBoardStore(
1211
1495
  outcomeDetail: previous?.outcomeDetail ?? null,
1212
1496
  ...(node !== undefined ? { node } : {}),
1213
1497
  ...(gate !== undefined ? { gate } : {}),
1498
+ ...(council !== undefined ? { council } : {}),
1499
+ ...(previous?.hostVerification !== undefined ? { hostVerification: previous.hostVerification } : {}),
1214
1500
  ...(rerouteCount !== undefined ? { rerouteCount } : {}),
1215
1501
  ...(contextWindow !== undefined ? { contextWindow } : {}),
1216
1502
  lastContextTokens: previous?.lastContextTokens ?? 0,
1217
1503
  ...(previous?.receiptId !== undefined ? { receiptId: previous.receiptId } : {}),
1504
+ ...(phase !== undefined ? { phase } : {}),
1218
1505
  progress: previous?.progress ?? createWorkerProgressFold(),
1219
1506
  ...(previous?.retry ? { retry: { ...previous.retry } } : {}),
1220
1507
  ...(previous?.steerAcknowledgement ? { steerAcknowledgement: { ...previous.steerAcknowledgement } } : {}),
1221
1508
  };
1222
1509
  entries.set(runId, entry);
1223
- pruneEntries(entries);
1510
+ pruneEntries(entries, (evicted) => fleetPhases.delete(evicted));
1224
1511
  return entry;
1225
1512
  };
1226
1513
 
@@ -1251,6 +1538,7 @@ export function createDispatchBoardStore(
1251
1538
  entry.costUsd = parseFiniteNumber(payload.costUsd, entry.costUsd);
1252
1539
  entry.costProvenance = payload.costProvenance ?? "unknown";
1253
1540
  entry.outcomeDetail = null;
1541
+ if (payload.hostVerification !== undefined) entry.hostVerification = payload.hostVerification;
1254
1542
  settleFromReceipt(entry);
1255
1543
  // A terminal dispatch event is published only after the run's receipt is
1256
1544
  // sealed at receipts/<runId>.json, so the run id is the receipt id here.
@@ -1279,6 +1567,7 @@ export function createDispatchBoardStore(
1279
1567
  entry.costUsd = parseFiniteNumber(payload.costUsd, entry.costUsd);
1280
1568
  entry.costProvenance = payload.costProvenance ?? "unknown";
1281
1569
  entry.outcomeDetail = resolveFailureDetail(payload, entry.outcomeDetail);
1570
+ if (payload.hostVerification !== undefined) entry.hostVerification = payload.hostVerification;
1282
1571
  settleFromReceipt(entry);
1283
1572
  // A denied retry never reached a run, so no receipt was sealed for it;
1284
1573
  // every other failure finalized through recordReceipt like a success.
@@ -1487,6 +1776,11 @@ export function createDispatchBoardStore(
1487
1776
  return projectRows(true);
1488
1777
  },
1489
1778
  reconcile,
1779
+ setFleetPhase(runId: string, phase: DispatchBoardPhase): void {
1780
+ fleetPhases.set(runId, phase);
1781
+ const entry = entries.get(runId);
1782
+ if (entry) entry.phase = { ...phase };
1783
+ },
1490
1784
  unsubscribe() {
1491
1785
  if (closed) return;
1492
1786
  closed = true;