@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.
- package/CHANGELOG.md +65 -2
- package/CONTRIBUTING.md +6 -6
- package/README.md +16 -5
- package/dist/{acp-S5R4RR5B.js → acp-SK4MD6MM.js} +11 -11
- package/dist/{agents-P6DMMVZY.js → agents-2FN2K6ME.js} +33 -26
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-2XCZLPKS.js → auth-QIYZWM5I.js} +15 -15
- package/dist/{chunk-EKMEHE4H.js → chunk-33YXPOE3.js} +2 -3
- package/dist/{chunk-VAWWTKDP.js → chunk-3HAPLH5M.js} +11 -11
- package/dist/{chunk-YCWGATWI.js → chunk-465YSENW.js} +3 -3
- package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
- package/dist/{chunk-UZHIZC5S.js → chunk-4DWFMQDR.js} +61 -76
- package/dist/{chunk-ZWMF7253.js → chunk-5C3AQNDW.js} +328 -9
- package/dist/{chunk-22NAGB7X.js → chunk-5C77SEEY.js} +5 -94
- package/dist/{chunk-WPQLXFOZ.js → chunk-5FR74PWO.js} +3 -2
- package/dist/{chunk-35MKKU5R.js → chunk-5UJ6ECTS.js} +18 -10
- package/dist/{chunk-BRXQQJFP.js → chunk-6M7VS3J3.js} +571 -50
- package/dist/{chunk-QQK64KLB.js → chunk-6TUKSZVF.js} +141 -23
- package/dist/{chunk-N4CZJQRK.js → chunk-AB4XIIVB.js} +8 -6
- package/dist/{chunk-KRPY7NTG.js → chunk-BMWK7ZIZ.js} +14 -20
- package/dist/{chunk-4BPJXDWC.js → chunk-C4JBQ5SR.js} +30 -14
- package/dist/{chunk-ZYKPLLNQ.js → chunk-CEYBNUGC.js} +821 -83
- package/dist/{chunk-VEZEGCGW.js → chunk-D4MDIG46.js} +20 -18
- package/dist/chunk-DJNLUABN.js +843 -0
- package/dist/{chunk-BP4OYD6A.js → chunk-DMD2AGVS.js} +21 -2
- package/dist/{chunk-KOHPCX4K.js → chunk-DOOEX22V.js} +2 -2
- package/dist/chunk-DQA7QLMD.js +123 -0
- package/dist/chunk-DR52UMZW.js +21 -0
- package/dist/{chunk-3HZ5RWN2.js → chunk-EBEFWSGL.js} +9 -7
- package/dist/{chunk-EDRHSCIE.js → chunk-EELBMBT6.js} +128 -13
- package/dist/{chunk-HV5X7OR2.js → chunk-EOOQZZDE.js} +16 -14
- package/dist/{chunk-WR67VIZY.js → chunk-FOT2FX5J.js} +63 -5
- package/dist/{chunk-BPGS2WCQ.js → chunk-GEYXPTRF.js} +2 -1
- package/dist/{chunk-FYYLNIL5.js → chunk-GH5622CP.js} +2 -2
- package/dist/{chunk-BEY543CS.js → chunk-GOXNB3AO.js} +5 -2
- package/dist/chunk-GWS3VEIW.js +195 -0
- package/dist/{chunk-G4BMMOKF.js → chunk-HVDIIIQW.js} +2 -2
- package/dist/chunk-HWUFFB6L.js +83 -0
- package/dist/{chunk-X6COSD2O.js → chunk-J7PIKKWC.js} +8 -436
- package/dist/{chunk-NILBFAPG.js → chunk-JNXPYBB4.js} +2 -2
- package/dist/{chunk-4VP4KH3K.js → chunk-JRIO5UD2.js} +4 -4
- package/dist/{chunk-K6WL7QZT.js → chunk-JTSEDYVQ.js} +7 -7
- package/dist/{chunk-QKMUKYO7.js → chunk-KCMKRQX4.js} +236 -84
- package/dist/chunk-KZ2H5X4G.js +1026 -0
- package/dist/{chunk-A2GZF7DC.js → chunk-LADCF22A.js} +13 -13
- package/dist/chunk-LCGCVYZ4.js +57 -0
- package/dist/chunk-M4AKACEO.js +382 -0
- package/dist/{chunk-POHLU5DW.js → chunk-M6L6IDJG.js} +3 -3
- package/dist/{chunk-4JUF2NNX.js → chunk-MXI6J5JF.js} +7 -7
- package/dist/{chunk-X4RCMKVQ.js → chunk-NDINPTJ4.js} +2 -2
- package/dist/{chunk-TTNYS3EA.js → chunk-OB5HIGJY.js} +1 -1
- package/dist/{chunk-7RXG6QRZ.js → chunk-OBMAI2DP.js} +61 -840
- package/dist/{chunk-5M54SPOL.js → chunk-ODFEOB4F.js} +161 -5
- package/dist/chunk-PD3MESLB.js +242 -0
- package/dist/{chunk-ED4KHGC3.js → chunk-PPAMZ32Z.js} +9 -2
- package/dist/{chunk-VMNQ6OZA.js → chunk-QCTRSGHQ.js} +963 -786
- package/dist/chunk-RVG5JXAL.js +41 -0
- package/dist/{chunk-RD5U66HV.js → chunk-SROCI7ZU.js} +7 -7
- package/dist/{chunk-MFFY33HR.js → chunk-THKY7CD7.js} +466 -205
- package/dist/{chunk-34475P3I.js → chunk-TSHXZTOQ.js} +5 -4
- package/dist/{chunk-PCZJO5TI.js → chunk-UFQ3F4FW.js} +13 -178
- package/dist/{chunk-AD2SYQYC.js → chunk-UHXRNZ2J.js} +121 -3
- package/dist/chunk-UND3GU2L.js +103 -0
- package/dist/{chunk-QQL5RT5M.js → chunk-UUANF5CR.js} +2323 -2114
- package/dist/{chunk-VJWL6YS5.js → chunk-UUVG37B4.js} +2 -2
- package/dist/chunk-UVDSQ6LW.js +472 -0
- package/dist/{chunk-QWU7ZBO7.js → chunk-VQNODYQ4.js} +215 -56
- package/dist/chunk-VREKEFLL.js +37 -0
- package/dist/{chunk-2TZWSW76.js → chunk-WHGPSPT5.js} +2 -2
- package/dist/{chunk-TW3WDMVS.js → chunk-WHJYKASB.js} +2 -2
- package/dist/{chunk-MEQ45TQ4.js → chunk-WJHBC77E.js} +21 -7
- package/dist/{chunk-HXG4IURW.js → chunk-X2KV5FXT.js} +2 -2
- package/dist/{chunk-YHZX5GEU.js → chunk-XAKHZX5N.js} +2 -2
- package/dist/{chunk-2LZI5CAG.js → chunk-XEGB6BCN.js} +228 -36
- package/dist/{chunk-E25LMLRW.js → chunk-YD734TPH.js} +2 -2
- package/dist/{verifiers-4UUM6TEE.js → chunk-YTYFXUI3.js} +121 -372
- package/dist/{chunk-3JLKSKD7.js → chunk-ZGH7FGS5.js} +17 -7
- package/dist/{chunk-VSNATDE6.js → chunk-ZZMN5OM4.js} +2 -2
- package/dist/cli/index.js +34 -32
- package/dist/{clio-J5JIOIDS.js → clio-WBVQEBKO.js} +7 -7
- package/dist/{code-nav-AXCXSBHX.js → code-nav-FGGFIE7L.js} +7 -7
- package/dist/codewiki/build-worker.js +4 -4
- package/dist/{components-KELWS457.js → components-F7OEATSO.js} +5 -5
- package/dist/{config-OEBMIN2U.js → config-TRBL3RCF.js} +48 -41
- package/dist/{configure-PUQOSIXQ.js → configure-OLCVPHNM.js} +17 -17
- package/dist/{context-URSXPBCK.js → context-MJIJ6GOX.js} +12 -12
- package/dist/{context-EKDCKUUZ.js → context-WFPKQSM6.js} +26 -9
- package/dist/{context-MGSE4Z2T.js → context-XEWE3MOJ.js} +44 -37
- package/dist/{context-clear-KDAJRNUK.js → context-clear-KNOS2JPB.js} +44 -37
- package/dist/{context-index-BZ4UYMTC.js → context-index-SSR5ECNE.js} +3 -3
- package/dist/{context-working-set-SBKMPPI2.js → context-working-set-EUXAZI6N.js} +14 -13
- package/dist/{dispatch-runner-MSWN72NK.js → dispatch-runner-B7MTOVKL.js} +321 -60
- package/dist/{docs-2C2LTVT2.js → docs-FLJTIDSE.js} +5 -5
- package/dist/{doctor-7BSE27PJ.js → doctor-RN4YKO2X.js} +15 -15
- package/dist/{eval-IZGDOO4H.js → eval-RUBJVSNQ.js} +52 -236
- package/dist/{evidence-SR7WXB5B.js → evidence-JZNBUOQZ.js} +39 -33
- package/dist/{evolve-K7VE2CBX.js → evolve-FJVC4KKI.js} +39 -33
- package/dist/{extensions-QVDOHDGJ.js → extensions-IQL36S7K.js} +5 -5
- package/dist/{fleet-7XMJNQNF.js → fleet-BDKYJFCP.js} +243 -370
- package/dist/fleet-commands-ZFIWZSB3.js +70 -0
- package/dist/fleet-graph-Y6HPXIVF.js +125 -0
- package/dist/fleet-new-RDVJLHHH.js +48 -0
- package/dist/{fleet-preflight-AQNAH644.js → fleet-preflight-BHSNPBMH.js} +2 -2
- package/dist/fleet-validate-BIYREGIK.js +79 -0
- package/dist/{init-JGNPAYXT.js → init-LQUB5COQ.js} +57 -48
- package/dist/library-NJAHIGG4.js +217 -0
- package/dist/memory-OG6HOYKM.js +472 -0
- package/dist/{models-ZMMLFJNN.js → models-5ZG5XY7J.js} +23 -22
- package/dist/{monitor-2F3T5KHP.js → monitor-TJ7AMTGB.js} +69 -35
- package/dist/{orchestrator-ORHT43JB.js → orchestrator-WZYB54DM.js} +4868 -1189
- package/dist/{paths-UXLN5YYZ.js → paths-XUC7GS6E.js} +5 -5
- package/dist/{reset-NXGTYNUO.js → reset-PXQT45IY.js} +8 -8
- package/dist/{run-RF4WJGMT.js → run-FQ74YF62.js} +82 -62
- package/dist/{share-UT3W6E4M.js → share-FW7SVCL3.js} +34 -10
- package/dist/{skills-PSACKC5Q.js → skills-7E7IRB3R.js} +25 -9
- package/dist/{skills-eval-WJSI55RZ.js → skills-eval-LI75W6OK.js} +43 -35
- package/dist/{targets-PIIRAOYS.js → targets-4CIFKCTW.js} +27 -24
- package/dist/{terminal-lease-ULWXWNVY.js → terminal-lease-WUZY7ZV5.js} +5 -4
- package/dist/{uninstall-FZCQCDKC.js → uninstall-7FV7IP4E.js} +5 -5
- package/dist/{upgrade-346TZ6AV.js → upgrade-K2HVIVMQ.js} +21 -20
- package/dist/{usage-6KKXR32N.js → usage-GTZELZQX.js} +159 -59
- package/dist/verifiers-RLAHT27O.js +336 -0
- package/dist/{verify-X5HDROLA.js → verify-BX3BRKH5.js} +7 -6
- package/dist/{wiki-generate-7STOCIFZ.js → wiki-generate-ASIFASCN.js} +58 -48
- package/dist/worker/entry.js +98 -84
- package/dist/{workspace-G4ZWUIPR.js → workspace-ZJ6BFM3Q.js} +4 -4
- package/docs/README.md +4 -3
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-placement.md +1 -2
- package/docs/artifact-versions.md +10 -6
- package/docs/built-in-agents.md +26 -2
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +90 -8
- package/docs/configuration-and-targets.md +90 -2
- package/docs/context-engine.md +4 -2
- package/docs/context-working-set.md +4 -4
- package/docs/development-pipeline.md +1 -1
- package/docs/dispatch-architecture-rationale.md +1 -1
- package/docs/documentation-coverage.md +4 -4
- package/docs/documentation-guide.md +4 -4
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +4 -45
- package/docs/evidence-and-memory.md +70 -10
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +4 -1
- package/docs/extensions-and-sharing.md +6 -2
- package/docs/fleet-demo-runbook.md +2 -2
- package/docs/fleet-dispatch.md +224 -11
- package/docs/git-commit-provenance.md +2 -2
- package/docs/glossary.md +1 -1
- package/docs/installation-and-lifecycle.md +2 -2
- package/docs/middleware-and-components.md +20 -2
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +55 -8
- package/docs/proactive-memory.md +26 -16
- package/docs/prompt-envelope-and-tools.md +4 -2
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +83 -65
- package/docs/resource-library.md +59 -0
- package/docs/safety-model.md +29 -7
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +37 -1
- package/docs/skills-marketplace.md +16 -3
- package/docs/tool-usage.md +14 -7
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +38 -4
- package/docs/worker-dispatch-mechanics.md +3 -3
- package/package.json +7 -4
- package/src/cli/agents.ts +2 -3
- package/src/cli/argv.ts +14 -1
- package/src/cli/fleet-commands.ts +37 -0
- package/src/cli/fleet-graph.ts +102 -0
- package/src/cli/fleet-new.ts +36 -0
- package/src/cli/fleet-preflight.ts +121 -0
- package/src/cli/fleet-validate.ts +30 -0
- package/src/cli/fleet.ts +188 -335
- package/src/cli/index.ts +4 -2
- package/src/cli/library.ts +190 -0
- package/src/cli/memory.ts +272 -10
- package/src/cli/modes/json-stream.ts +2 -2
- package/src/cli/modes/print.ts +12 -1
- package/src/cli/run.ts +22 -2
- package/src/cli/share.ts +13 -1
- package/src/cli/targets.ts +12 -3
- package/src/cli/usage.ts +160 -20
- package/src/core/bus-events.ts +7 -0
- package/src/core/commit-attribution.ts +4 -4
- package/src/core/config.ts +130 -0
- package/src/core/defaults.ts +81 -0
- package/src/core/response-model-id.ts +134 -0
- package/src/core/toml.ts +62 -0
- package/src/core/workspace-files.ts +0 -1
- package/src/domains/agents/builtins/architect.md +2 -1
- package/src/domains/agents/builtins/oracle.md +33 -0
- package/src/domains/agents/catalog.ts +18 -5
- package/src/domains/agents/fleet-contract.ts +278 -16
- package/src/domains/agents/index.ts +14 -0
- package/src/domains/agents/recipe.ts +54 -14
- package/src/domains/agents/result-contract.ts +242 -5
- package/src/domains/config/classify.ts +4 -0
- package/src/domains/context/bootstrap.ts +36 -27
- package/src/domains/context/project-metadata.ts +19 -63
- package/src/domains/context/prompt-context.ts +8 -0
- package/src/domains/context/working-set/policies/index.ts +3 -4
- package/src/domains/dispatch/active-route-planner.ts +14 -0
- package/src/domains/dispatch/backoff.ts +2 -1
- package/src/domains/dispatch/budget-envelope.ts +396 -0
- package/src/domains/dispatch/capability-match.ts +1 -0
- package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
- package/src/domains/dispatch/contract.ts +36 -0
- package/src/domains/dispatch/delegation-plan.ts +167 -0
- package/src/domains/dispatch/execution-plan.ts +76 -5
- package/src/domains/dispatch/execution-role.ts +3 -1
- package/src/domains/dispatch/execution-scheduler.ts +183 -67
- package/src/domains/dispatch/extension.ts +339 -36
- package/src/domains/dispatch/fleet-gate.ts +14 -0
- package/src/domains/dispatch/fleet-plan.ts +63 -3
- package/src/domains/dispatch/fleet-run.ts +737 -0
- package/src/domains/dispatch/gate-role-prompts.ts +9 -0
- package/src/domains/dispatch/host-verification.ts +178 -0
- package/src/domains/dispatch/index.ts +38 -0
- package/src/domains/dispatch/intent.ts +159 -0
- package/src/domains/dispatch/orphan-recovery.ts +1 -0
- package/src/domains/dispatch/receipt-integrity.ts +12 -4
- package/src/domains/dispatch/state.ts +37 -3
- package/src/domains/dispatch/types.ts +61 -9
- package/src/domains/dispatch/validation.ts +80 -6
- package/src/domains/dispatch/worker-spawn.ts +14 -3
- package/src/domains/eval/metrics/evidence.ts +0 -116
- package/src/domains/eval/metrics/invariants.ts +1 -1
- package/src/domains/eval/runners/clio-run.ts +1 -10
- package/src/domains/eval/runners/external-command.ts +2 -29
- package/src/domains/eval/schema/suite.ts +0 -7
- package/src/domains/eval/suites/run.ts +1 -7
- package/src/domains/evidence/trust-status.ts +10 -1
- package/src/domains/memory/index.ts +22 -0
- package/src/domains/memory/operations.ts +58 -1
- package/src/domains/memory/promotion.ts +281 -0
- package/src/domains/memory/prompt-section.ts +25 -5
- package/src/domains/memory/proposal.ts +51 -7
- package/src/domains/memory/task-bank.ts +3 -2
- package/src/domains/memory/task-memory-handoff.ts +181 -24
- package/src/domains/memory/task-memory-policy.ts +3 -1
- package/src/domains/memory/types.ts +37 -0
- package/src/domains/memory/validate.ts +178 -0
- package/src/domains/middleware/index.ts +15 -0
- package/src/domains/middleware/memory-intervention.ts +35 -25
- package/src/domains/middleware/runtime.ts +6 -0
- package/src/domains/middleware/skills-reminder.ts +19 -4
- package/src/domains/middleware/stalled-turn.ts +43 -1
- package/src/domains/middleware/types.ts +10 -0
- package/src/domains/middleware/watchdog.ts +281 -0
- package/src/domains/observability/contract.ts +9 -2
- package/src/domains/observability/cost.ts +31 -4
- package/src/domains/observability/extension.ts +2 -2
- package/src/domains/observability/index.ts +10 -0
- package/src/domains/observability/out-of-turn-usage.ts +223 -0
- package/src/domains/providers/index.ts +3 -0
- package/src/domains/providers/model-discovery.ts +9 -0
- package/src/domains/providers/runtime-resolution.ts +38 -1
- package/src/domains/providers/runtimes/common/probe-helpers.ts +97 -16
- package/src/domains/providers/types/context-window-slots.ts +18 -0
- package/src/domains/providers/types/runtime-descriptor.ts +3 -1
- package/src/domains/resources/index.ts +20 -0
- package/src/domains/resources/library.ts +326 -0
- package/src/domains/resources/skills/marketplace.ts +37 -12
- package/src/domains/safety/call-target.ts +211 -14
- package/src/domains/safety/decision-presentation.ts +268 -0
- package/src/domains/safety/redaction.ts +73 -0
- package/src/domains/session/context-ledger.ts +10 -1
- package/src/domains/session/decision-board.ts +4 -0
- package/src/domains/session/entries.ts +3 -0
- package/src/domains/session/handoff.ts +629 -0
- package/src/domains/session/history.ts +68 -19
- package/src/domains/session/usage.ts +24 -7
- package/src/domains/share/archive.ts +67 -2
- package/src/engine/acp/event-mapper.ts +7 -0
- package/src/engine/acp/server.ts +29 -2
- package/src/engine/apis/lmstudio.ts +25 -4
- package/src/engine/apis/openai-completions.ts +147 -22
- package/src/engine/claude/sdk-runtime.ts +8 -2
- package/src/engine/claude/tool-safety.ts +13 -0
- package/src/engine/loop-guard.ts +27 -3
- package/src/engine/worker-events.ts +4 -3
- package/src/engine/worker-runtime.ts +59 -54
- package/src/entry/orchestrator.ts +55 -1
- package/src/interactive/bus-notices.ts +26 -0
- package/src/interactive/chat-loop-messages.ts +22 -0
- package/src/interactive/chat-loop.ts +248 -1
- package/src/interactive/chat-renderer.ts +41 -3
- package/src/interactive/clio-editor.ts +44 -7
- package/src/interactive/context-overlay.ts +43 -5
- package/src/interactive/cost-overlay.ts +70 -11
- package/src/interactive/council-dispatch.ts +30 -0
- package/src/interactive/council-grid.ts +213 -0
- package/src/interactive/council.ts +99 -0
- package/src/interactive/dispatch-board.ts +471 -50
- package/src/interactive/fleet-run-preview.ts +307 -0
- package/src/interactive/footer/notifications.ts +219 -0
- package/src/interactive/footer/widgets.ts +13 -0
- package/src/interactive/handoff-round.ts +56 -0
- package/src/interactive/interactive-application.ts +49 -2
- package/src/interactive/interactive-event-projection.ts +9 -1
- package/src/interactive/interactive-input-runtime.ts +11 -1
- package/src/interactive/interactive-presentation.ts +11 -1
- package/src/interactive/interactive-slash-runtime.ts +52 -2
- package/src/interactive/interactive-subscriptions.ts +14 -2
- package/src/interactive/memory-overlay.ts +89 -4
- package/src/interactive/oracle.ts +179 -0
- package/src/interactive/overlay-ask-user-lifecycle.ts +7 -1
- package/src/interactive/overlay-frame.ts +5 -2
- package/src/interactive/overlay-general-openers.ts +230 -2
- package/src/interactive/overlay-key-routing.ts +58 -2
- package/src/interactive/overlay-lifecycle.ts +52 -5
- package/src/interactive/overlay-permission-lifecycle.ts +33 -8
- package/src/interactive/overlay-resource-openers.ts +11 -3
- package/src/interactive/overlay-session-lifecycle.ts +234 -2
- package/src/interactive/overlay-transitions.ts +11 -0
- package/src/interactive/overlays/ask-user.ts +74 -30
- package/src/interactive/overlays/decisions.ts +3 -1
- package/src/interactive/overlays/fleet-run-approval.ts +208 -0
- package/src/interactive/overlays/handoff-review.ts +185 -0
- package/src/interactive/overlays/library-install-confirm.ts +151 -0
- package/src/interactive/overlays/list-overlay.ts +168 -2
- package/src/interactive/overlays/settings.ts +101 -4
- package/src/interactive/overlays/side-question.ts +139 -0
- package/src/interactive/overlays/skills-hub.ts +401 -15
- package/src/interactive/permission-hint.ts +35 -0
- package/src/interactive/permission-overlay.ts +95 -45
- package/src/interactive/renderers/tool-execution.ts +19 -49
- package/src/interactive/session-last-turn.ts +8 -1
- package/src/interactive/session-usage-reseed.ts +36 -10
- package/src/interactive/side-question.ts +171 -0
- package/src/interactive/slash-commands.ts +434 -7
- package/src/interactive/slash-spec.ts +19 -6
- package/src/interactive/status/summary.ts +5 -0
- package/src/interactive/status/types.ts +5 -0
- package/src/interactive/terminal-lease.ts +1 -0
- package/src/interactive/theme/tokens.ts +30 -0
- package/src/interactive/turn-context.ts +96 -23
- package/src/interactive/turn-middleware.ts +16 -1
- package/src/interactive/turn-runtime.ts +37 -8
- package/src/interactive/turn-state.ts +3 -0
- package/src/interactive/watchdog-run.ts +75 -0
- package/src/interactive/worker-progress.ts +440 -0
- package/src/interactive/worker-share.ts +56 -1
- package/src/interactive/worker-stream.ts +58 -110
- package/src/tools/agent-tools.ts +28 -3
- package/src/tools/ask-user.ts +21 -1
- package/src/tools/bootstrap.ts +3 -0
- package/src/tools/compete-worktrees.ts +13 -79
- package/src/tools/context/index.ts +2 -2
- package/src/tools/dispatch-admission.ts +242 -8
- package/src/tools/dispatch-arguments.ts +65 -1
- package/src/tools/dispatch-event-text.ts +19 -0
- package/src/tools/dispatch-plan.ts +136 -6
- package/src/tools/dispatch-runner.ts +319 -13
- package/src/tools/dispatch-types.ts +20 -1
- package/src/tools/dispatch.ts +96 -3
- package/src/tools/monitor.ts +31 -0
- package/src/tools/profiles.ts +18 -4
- package/src/tools/registry.ts +15 -5
- package/src/tools/result-disposition.ts +156 -0
- package/src/tools/result-shaping.ts +59 -1
- package/src/tools/task-worktree.ts +238 -0
- package/src/tools/verify/authoring.ts +116 -55
- package/src/tools/verify/scripts.ts +62 -0
- package/src/tools/worker-evidence.ts +21 -1
- package/src/worker/spec-contract.ts +44 -3
- package/dist/chunk-EFADSJET.js +0 -18
- package/dist/chunk-HC4CLZ2Y.js +0 -68
- package/dist/memory-4ALKDJ4Q.js +0 -246
- package/src/domains/eval/metrics/chaos-stream.ts +0 -93
|
@@ -9,6 +9,14 @@ import {
|
|
|
9
9
|
import type { SafeEventBus } from "../core/event-bus.js";
|
|
10
10
|
import { rawDurationMs } from "../core/timers.js";
|
|
11
11
|
import type { AgentAudience } from "../domains/agents/spec.js";
|
|
12
|
+
import {
|
|
13
|
+
cloneRunToolBudgetEnvelope,
|
|
14
|
+
formatBudgetPolicy,
|
|
15
|
+
formatBudgetReasons,
|
|
16
|
+
formatBudgetRequest,
|
|
17
|
+
formatEffectiveBudget,
|
|
18
|
+
type RunToolBudgetEnvelope,
|
|
19
|
+
} from "../domains/dispatch/budget-envelope.js";
|
|
12
20
|
import type { DispatchSnapshot } from "../domains/dispatch/contract.js";
|
|
13
21
|
import {
|
|
14
22
|
type DispatchRequestOrigin,
|
|
@@ -25,8 +33,15 @@ import {
|
|
|
25
33
|
} from "../domains/observability/index.js";
|
|
26
34
|
import type { CostProvenance } from "../domains/providers/index.js";
|
|
27
35
|
import { sanitizeCallTargetText } from "../domains/safety/call-target.js";
|
|
28
|
-
import { type Component, truncateToWidth, visibleWidth } from "../engine/tui.js";
|
|
36
|
+
import { type Component, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../engine/tui.js";
|
|
29
37
|
import { formatWorkerContextMeter } from "./context-meter.js";
|
|
38
|
+
import {
|
|
39
|
+
COUNCIL_SYNTHESIS_LABEL,
|
|
40
|
+
type CouncilGroupView,
|
|
41
|
+
type CouncilMemberView,
|
|
42
|
+
councilGroupBody,
|
|
43
|
+
councilIslandLines,
|
|
44
|
+
} from "./council-grid.js";
|
|
30
45
|
import { formatFooterTokens } from "./footer-panel.js";
|
|
31
46
|
import {
|
|
32
47
|
type ClioTheme,
|
|
@@ -42,6 +57,13 @@ import {
|
|
|
42
57
|
screenTitle,
|
|
43
58
|
spinnerFrame,
|
|
44
59
|
} from "./theme/index.js";
|
|
60
|
+
import {
|
|
61
|
+
createWorkerProgressFold,
|
|
62
|
+
type WorkerAction,
|
|
63
|
+
type WorkerProgressFold,
|
|
64
|
+
type WorkerProgressSnapshot,
|
|
65
|
+
} from "./worker-progress.js";
|
|
66
|
+
import type { WorkerReceiptReader } from "./worker-stream.js";
|
|
45
67
|
|
|
46
68
|
export type DispatchBoardStatus =
|
|
47
69
|
| Extract<RunStatus, "running" | "completed" | "failed" | "stale" | "dead">
|
|
@@ -72,6 +94,8 @@ export interface DispatchBoardRow {
|
|
|
72
94
|
wireModelId: string;
|
|
73
95
|
/** Sanitized, one-line summary of the task assigned to this run. */
|
|
74
96
|
taskSummary?: string;
|
|
97
|
+
/** Immutable budget admission provenance for this run. */
|
|
98
|
+
budget?: RunToolBudgetEnvelope;
|
|
75
99
|
status: DispatchBoardStatus;
|
|
76
100
|
elapsedMs: number;
|
|
77
101
|
tokenCount: number;
|
|
@@ -86,6 +110,9 @@ export interface DispatchBoardRow {
|
|
|
86
110
|
node?: string;
|
|
87
111
|
/** Review/compete gate badge (role + cycle). */
|
|
88
112
|
gate?: { role: string; cycle: number };
|
|
113
|
+
council?: { group: string; label: string; color?: string; round: number };
|
|
114
|
+
/** Orchestrator-executed declared verification status. */
|
|
115
|
+
hostVerification?: "verified" | "rejected" | "skipped";
|
|
89
116
|
/** Dead-node failover hops recorded on this run's chain. */
|
|
90
117
|
rerouteCount?: number;
|
|
91
118
|
/** Assignment retry attempts observed on the assignment event stream. */
|
|
@@ -94,10 +121,17 @@ export interface DispatchBoardRow {
|
|
|
94
121
|
contextWindow?: number;
|
|
95
122
|
/** Last assistant message's input+cacheRead+output: current context occupancy. */
|
|
96
123
|
lastContextTokens?: number;
|
|
97
|
-
/** Tool currently executing in the worker; null between calls. */
|
|
124
|
+
/** Tool currently executing in the worker; null between calls. Projected from `progress`. */
|
|
98
125
|
currentTool?: string | null;
|
|
99
|
-
/** Recently finished tools, newest first, bounded. */
|
|
126
|
+
/** Recently finished tools, newest first, bounded. Projected from `progress`. */
|
|
100
127
|
recentTools?: ReadonlyArray<string>;
|
|
128
|
+
/**
|
|
129
|
+
* The canonical worker-progress projection for this run: the bounded answer
|
|
130
|
+
* tail, the phase, and the redacted action descriptors. The same projection
|
|
131
|
+
* the transcript's worker block reads, so opening a run here shows what the
|
|
132
|
+
* worker is doing rather than a richer spinner.
|
|
133
|
+
*/
|
|
134
|
+
progress?: WorkerProgressSnapshot;
|
|
101
135
|
/**
|
|
102
136
|
* Id of the receipt sealed for this run (`receipts/<runId>.json`, the id
|
|
103
137
|
* `clio-coder trace` takes). Set only once a terminal dispatch event has
|
|
@@ -110,8 +144,39 @@ export interface DispatchBoardRow {
|
|
|
110
144
|
retry?: DispatchRetryPresentation;
|
|
111
145
|
/** Most recent worker delivery acknowledgement for an operator steer. */
|
|
112
146
|
steerAcknowledgement?: DispatchSteerAcknowledgement;
|
|
147
|
+
/**
|
|
148
|
+
* Where this run sits in a fleet plan: the wave the step was scheduled in
|
|
149
|
+
* and the step id it executes. Present only for runs a fleet plan
|
|
150
|
+
* dispatched, so a `/run`, a delegation, or a dispatch-tool run leaves the
|
|
151
|
+
* board's phase column empty rather than claiming a position it has none of.
|
|
152
|
+
*/
|
|
153
|
+
phase?: DispatchBoardPhase;
|
|
113
154
|
}
|
|
114
155
|
|
|
156
|
+
/** One fleet plan position: the wave index and the step the run executes. */
|
|
157
|
+
export interface DispatchBoardPhase {
|
|
158
|
+
wave: number;
|
|
159
|
+
stepId: string;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The phase cell, or null for a run that is not a fleet step. Fixed width so
|
|
164
|
+
* the column lines up across rows; a long step id is clipped with a marker
|
|
165
|
+
* rather than pushing the row's other facts out of position.
|
|
166
|
+
*/
|
|
167
|
+
export function formatDispatchPhaseCell(
|
|
168
|
+
phase: DispatchBoardPhase | undefined,
|
|
169
|
+
width = PHASE_COLUMN_WIDTH,
|
|
170
|
+
): string | null {
|
|
171
|
+
if (phase === undefined) return null;
|
|
172
|
+
return truncateToWidth(`w${phase.wave} ${phase.stepId}`, Math.max(1, width), "…", false);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Columns the compact island reserves for the phase cell when it can afford them. */
|
|
176
|
+
export const PHASE_COLUMN_WIDTH = 12;
|
|
177
|
+
/** Below this much room for the agent label, the compact row drops the column entirely. */
|
|
178
|
+
const PHASE_COLUMN_MIN_LABEL_WIDTH = 8;
|
|
179
|
+
|
|
115
180
|
/** Live HTTP/SDK rows are the only rows whose runtime can consume operator guidance. */
|
|
116
181
|
export function isDispatchBoardRowSteerable(row: DispatchBoardRow): boolean {
|
|
117
182
|
return (
|
|
@@ -126,7 +191,10 @@ export function isDispatchBoardRowCancellable(row: DispatchBoardRow): boolean {
|
|
|
126
191
|
}
|
|
127
192
|
|
|
128
193
|
interface DispatchBoardEntry
|
|
129
|
-
extends Omit<
|
|
194
|
+
extends Omit<
|
|
195
|
+
DispatchBoardRow,
|
|
196
|
+
"elapsedMs" | "recentTools" | "lastContextTokens" | "currentTool" | "retry" | "progress"
|
|
197
|
+
> {
|
|
130
198
|
sequence: number;
|
|
131
199
|
enqueuedAtMs: number;
|
|
132
200
|
startedAtMs: number | null;
|
|
@@ -139,15 +207,19 @@ interface DispatchBoardEntry
|
|
|
139
207
|
finishedAtMs: number | null;
|
|
140
208
|
durationMs: number | null;
|
|
141
209
|
lastContextTokens: number;
|
|
142
|
-
|
|
143
|
-
|
|
210
|
+
/**
|
|
211
|
+
* The one fold that reads this run's worker events. Tool activity and the
|
|
212
|
+
* answer tail are its output, so the board no longer interprets the stream
|
|
213
|
+
* alongside the transcript.
|
|
214
|
+
*/
|
|
215
|
+
progress: WorkerProgressFold;
|
|
144
216
|
retry?: DispatchRetryPresentation;
|
|
145
217
|
}
|
|
146
218
|
|
|
147
|
-
/** Bound on the recent-tool trail rendered on a card. */
|
|
148
|
-
const RECENT_TOOL_TRAIL_LIMIT = 4;
|
|
149
219
|
/** Keep raw task text out of long-lived TUI rows and bound hostile/user-sized input. */
|
|
150
220
|
const TASK_SUMMARY_MAX_WIDTH = 240;
|
|
221
|
+
/** Rows of worker prose an expanded card shows before it defers to `/view`. */
|
|
222
|
+
const WORKER_PROGRESS_CARD_ROWS = 6;
|
|
151
223
|
|
|
152
224
|
interface WorkerEventShape {
|
|
153
225
|
type?: unknown;
|
|
@@ -433,11 +505,92 @@ function retryCountdown(retry: DispatchRetryPresentation, now = Date.now()): str
|
|
|
433
505
|
return remainingMs === 0 ? "now" : `in ${formatCompactMs(remainingMs)}`;
|
|
434
506
|
}
|
|
435
507
|
|
|
508
|
+
/**
|
|
509
|
+
* One action as a phrase: the tool name, then the verb and object of its
|
|
510
|
+
* redacted descriptor. The descriptor was bounded and scrubbed at the worker
|
|
511
|
+
* seam, so this composes text and never inspects an argument.
|
|
512
|
+
*/
|
|
513
|
+
function actionPhrase(action: WorkerAction): string {
|
|
514
|
+
const descriptor = action.descriptor;
|
|
515
|
+
if (descriptor === undefined) return action.tool;
|
|
516
|
+
const object = descriptor.object === undefined ? "" : ` ${descriptor.object}${descriptor.truncated ? "…" : ""}`;
|
|
517
|
+
return `${action.tool} ${descriptor.verb}${object}`;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** The phase word an expanded card names, or null for a phase that says nothing new. */
|
|
521
|
+
function progressPhaseUnit(theme: ClioTheme, progress: WorkerProgressSnapshot): string | null {
|
|
522
|
+
switch (progress.phase) {
|
|
523
|
+
case "thinking":
|
|
524
|
+
// The phase only. Reasoning content never reaches an operator surface.
|
|
525
|
+
return theme.fg("reason", `${GLYPH.phaseThinking} thinking`);
|
|
526
|
+
case "writing":
|
|
527
|
+
return theme.fg("accent", `${GLYPH.phaseWriting} writing`);
|
|
528
|
+
case "tool":
|
|
529
|
+
return theme.fg("action", `${GLYPH.phaseTool} tool`);
|
|
530
|
+
case "waiting":
|
|
531
|
+
return theme.fg("info", `${GLYPH.phaseWaiting} waiting`);
|
|
532
|
+
case "settled":
|
|
533
|
+
case "starting":
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** The `doing` row: the phase, then the running call or the most recent one, with its object. */
|
|
539
|
+
function progressActionLine(theme: ClioTheme, progress: WorkerProgressSnapshot, contentWidth: number): string | null {
|
|
540
|
+
const phase = progressPhaseUnit(theme, progress);
|
|
541
|
+
const current = progress.currentAction;
|
|
542
|
+
const recent = progress.recentActions[0];
|
|
543
|
+
const units: string[] = [];
|
|
544
|
+
if (phase !== null) units.push(phase);
|
|
545
|
+
if (current !== null) units.push(theme.fg("muted", actionPhrase(current)));
|
|
546
|
+
else if (recent !== undefined) units.push(theme.fg("dim", `last ${actionPhrase(recent)}`));
|
|
547
|
+
if (units.length === 0) return null;
|
|
548
|
+
return cardUnitsLine(theme, "doing", units, contentWidth);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* The `answer` block: the newest rows of the worker's bounded prose on a rail,
|
|
553
|
+
* then what is not shown and where to read it. Wrapping happens before the row
|
|
554
|
+
* cap, so the block is at most {@link WORKER_PROGRESS_CARD_ROWS} rows tall
|
|
555
|
+
* whatever the terminal width, and a streaming answer cannot make the card grow
|
|
556
|
+
* under the operator.
|
|
557
|
+
*/
|
|
558
|
+
function progressAnswerLines(
|
|
559
|
+
theme: ClioTheme,
|
|
560
|
+
progress: WorkerProgressSnapshot,
|
|
561
|
+
runId: string,
|
|
562
|
+
contentWidth: number,
|
|
563
|
+
): string[] {
|
|
564
|
+
if (progress.tailText.length === 0) return [];
|
|
565
|
+
const gutter = CARD_KV_KEY_WIDTH + 1;
|
|
566
|
+
const railWidth = Math.max(1, contentWidth - gutter - 2);
|
|
567
|
+
const wrapped: string[] = [];
|
|
568
|
+
for (const line of progress.tailText.split("\n")) {
|
|
569
|
+
for (const row of wrapTextWithAnsi(sanitizeCallTargetText(line), railWidth)) wrapped.push(row);
|
|
570
|
+
}
|
|
571
|
+
const shown = wrapped.slice(Math.max(0, wrapped.length - WORKER_PROGRESS_CARD_ROWS));
|
|
572
|
+
const hiddenRows = wrapped.length - shown.length;
|
|
573
|
+
const rail = theme.fg("dim", `${GLYPH.rail} `);
|
|
574
|
+
const body = shown.map((row) => `${rail}${theme.fg("muted", row)}`);
|
|
575
|
+
const hiddenLines = progress.droppedLines + hiddenRows;
|
|
576
|
+
if (hiddenLines > 0 || progress.droppedBytes > 0) {
|
|
577
|
+
const facts = [
|
|
578
|
+
...(hiddenLines > 0 ? [`${hiddenLines} more line${hiddenLines === 1 ? "" : "s"}`] : []),
|
|
579
|
+
...(progress.droppedBytes > 0 ? [`${progress.droppedBytes} bytes outran the view`] : []),
|
|
580
|
+
`/view dispatch:${runId}`,
|
|
581
|
+
];
|
|
582
|
+
body.push(`${rail}${theme.fg("dim", truncateToWidth(facts.join(" · "), railWidth, "…", false))}`);
|
|
583
|
+
}
|
|
584
|
+
// The key labels the block once and the rest hangs under it, which is the
|
|
585
|
+
// card's key-value grammar applied to a body rather than to one value.
|
|
586
|
+
return body.map((row, index) => `${index === 0 ? cardKvKey(theme, "answer") : " ".repeat(gutter)}${row}`);
|
|
587
|
+
}
|
|
588
|
+
|
|
436
589
|
export function renderDispatchCard(
|
|
437
590
|
row: DispatchBoardRow,
|
|
438
591
|
width: number,
|
|
439
592
|
evidence?: RunEvidencePresentation,
|
|
440
|
-
options: { selected?: boolean } = {},
|
|
593
|
+
options: { selected?: boolean; expanded?: boolean } = {},
|
|
441
594
|
): string[] {
|
|
442
595
|
const theme = clioTheme();
|
|
443
596
|
const contentWidth = Math.max(0, width - 4);
|
|
@@ -498,14 +651,42 @@ export function renderDispatchCard(
|
|
|
498
651
|
`${theme.fg("dim", "ttft")} ${theme.fg("muted", ttft)}`,
|
|
499
652
|
`${theme.fg("dim", "cost")} ${theme.fg("muted", cost)}`,
|
|
500
653
|
];
|
|
654
|
+
const hostVerification = theme.fg("info", `host_verification=${row.hostVerification ?? "not_requested"}`);
|
|
501
655
|
const contextUnit = formatWorkerContextMeter(row.lastContextTokens ?? 0, row.contextWindow, theme);
|
|
656
|
+
// The phase column: a fleet step says which wave it belongs to and which
|
|
657
|
+
// step it is; every other run leaves the cell empty rather than inventing a
|
|
658
|
+
// position, so the column reads as "not a fleet step" and not as wave zero.
|
|
659
|
+
const phaseCell = formatDispatchPhaseCell(row.phase, contentWidth);
|
|
502
660
|
const bodyLines = [
|
|
503
661
|
cardUnitsLine(theme, "run", [theme.fg("dim", row.runId)], contentWidth),
|
|
662
|
+
cardUnitsLine(
|
|
663
|
+
theme,
|
|
664
|
+
"phase",
|
|
665
|
+
[phaseCell === null ? theme.fg("dim", "—") : theme.fg("info", phaseCell)],
|
|
666
|
+
contentWidth,
|
|
667
|
+
),
|
|
504
668
|
targetLine,
|
|
505
669
|
...(row.taskSummary
|
|
506
670
|
? [truncateToWidth(`${cardKvKey(theme, "task")}${theme.fg("muted", row.taskSummary)}`, contentWidth, "…", false)]
|
|
507
671
|
: []),
|
|
508
672
|
cardUnitsLine(theme, "status", statusUnits, contentWidth),
|
|
673
|
+
cardUnitsLine(theme, "evidence", [hostVerification], contentWidth),
|
|
674
|
+
...(row.budget !== undefined
|
|
675
|
+
? [
|
|
676
|
+
truncateToWidth(
|
|
677
|
+
`${cardKvKey(theme, "policy")}${theme.fg("muted", `${formatBudgetPolicy(row.budget)}; requested ${formatBudgetRequest(row.budget)}`)}`,
|
|
678
|
+
contentWidth,
|
|
679
|
+
"…",
|
|
680
|
+
false,
|
|
681
|
+
),
|
|
682
|
+
truncateToWidth(
|
|
683
|
+
`${cardKvKey(theme, "budget")}${theme.fg("muted", `${formatEffectiveBudget(row.budget)}; reason ${formatBudgetReasons(row.budget)}`)}`,
|
|
684
|
+
contentWidth,
|
|
685
|
+
"…",
|
|
686
|
+
false,
|
|
687
|
+
),
|
|
688
|
+
]
|
|
689
|
+
: []),
|
|
509
690
|
cardUnitsLine(theme, "telemetry", [up, down, total, ...(contextUnit !== null ? [contextUnit] : [])], contentWidth),
|
|
510
691
|
];
|
|
511
692
|
if (row.retry) {
|
|
@@ -533,6 +714,13 @@ export function renderDispatchCard(
|
|
|
533
714
|
];
|
|
534
715
|
bodyLines.push(cardUnitsLine(theme, "tools", toolUnits, contentWidth));
|
|
535
716
|
}
|
|
717
|
+
// Live worker progress is expanded detail, never a default row: five running
|
|
718
|
+
// scouts must stay five compact cards until the operator opens one.
|
|
719
|
+
if (options.expanded === true && row.progress) {
|
|
720
|
+
const doing = progressActionLine(theme, row.progress, contentWidth);
|
|
721
|
+
if (doing !== null) bodyLines.push(doing);
|
|
722
|
+
bodyLines.push(...progressAnswerLines(theme, row.progress, row.runId, contentWidth));
|
|
723
|
+
}
|
|
536
724
|
if (row.steerAcknowledgement) {
|
|
537
725
|
bodyLines.push(
|
|
538
726
|
cardUnitsLine(
|
|
@@ -580,11 +768,25 @@ function renderTaskIslandRow(row: DispatchBoardRow, width: number): string[] {
|
|
|
580
768
|
visibleWidth(presentation.label) +
|
|
581
769
|
3 +
|
|
582
770
|
visibleWidth(elapsed);
|
|
583
|
-
|
|
771
|
+
// The phase column rides the compact row only when the row can still show a
|
|
772
|
+
// readable agent label beside it. TASK_ISLAND_WIDTH is fixed, so a row that
|
|
773
|
+
// cannot afford the cell drops the column here and shows the phase in the
|
|
774
|
+
// expanded card instead of clipping the label to make room.
|
|
775
|
+
const phaseCell = formatDispatchPhaseCell(row.phase);
|
|
776
|
+
const phaseColumn = phaseCell === null ? "" : `${dot}${theme.fg("info", padAnsi(phaseCell, PHASE_COLUMN_WIDTH))}`;
|
|
777
|
+
const phaseChrome = phaseCell === null ? 0 : PHASE_COLUMN_WIDTH + 3;
|
|
778
|
+
const labelWidth = width - labelChrome - phaseChrome;
|
|
779
|
+
const showPhase = phaseCell !== null && labelWidth >= PHASE_COLUMN_MIN_LABEL_WIDTH;
|
|
780
|
+
const clampedLabel = truncateToWidth(
|
|
781
|
+
agentLabel,
|
|
782
|
+
Math.max(1, showPhase ? labelWidth : width - labelChrome),
|
|
783
|
+
"…",
|
|
784
|
+
false,
|
|
785
|
+
);
|
|
584
786
|
|
|
585
787
|
// The agent label drops its accent color for plain bold; the status word is
|
|
586
788
|
// the only status-colored element on the row, with dim middot separators.
|
|
587
|
-
const line1 = `${glyph} ${rowPrefix.text}${theme.paint(clampedLabel, { bold: true })}${dot}${statusStr}${dot}${theme.fg("muted", elapsed)}`;
|
|
789
|
+
const line1 = `${glyph} ${rowPrefix.text}${theme.paint(clampedLabel, { bold: true })}${dot}${statusStr}${dot}${theme.fg("muted", elapsed)}${showPhase ? phaseColumn : ""}`;
|
|
588
790
|
|
|
589
791
|
const elapsedSec = row.elapsedMs / 1000;
|
|
590
792
|
const tokensPerSec = elapsedSec > 0.1 ? Math.round(row.outputTokens / elapsedSec) : 0;
|
|
@@ -605,11 +807,138 @@ function renderTaskIslandRow(row: DispatchBoardRow, width: number): string[] {
|
|
|
605
807
|
return [padAnsi(line1, width), ...(task ? [padAnsi(task, width)] : []), padAnsi(telemetry, width)];
|
|
606
808
|
}
|
|
607
809
|
|
|
810
|
+
/**
|
|
811
|
+
* One member column's facts, drawn from the row the board already keeps. The
|
|
812
|
+
* route, status, and answer tail are the same values the run's own card shows,
|
|
813
|
+
* so a council column and a council card can never disagree.
|
|
814
|
+
*/
|
|
815
|
+
function councilMemberView(
|
|
816
|
+
row: DispatchBoardRow,
|
|
817
|
+
council: NonNullable<DispatchBoardRow["council"]>,
|
|
818
|
+
): CouncilMemberView {
|
|
819
|
+
const presentation = dispatchStatusPresentation(row.status, {
|
|
820
|
+
compact: true,
|
|
821
|
+
...(row.status === "running" ? { tick: Math.floor(Date.now() / 100) } : {}),
|
|
822
|
+
});
|
|
823
|
+
return {
|
|
824
|
+
runId: row.runId,
|
|
825
|
+
label: council.label,
|
|
826
|
+
...(council.color !== undefined ? { color: council.color } : {}),
|
|
827
|
+
round: council.round,
|
|
828
|
+
route: `${row.targetId}/${row.wireModelId}`,
|
|
829
|
+
status: { glyph: presentation.glyph, label: presentation.label, token: presentation.token },
|
|
830
|
+
tailText: sanitizeCallTargetText(row.progress?.tailText ?? ""),
|
|
831
|
+
droppedLines: row.progress?.droppedLines ?? 0,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Fold one council group's rows into the view the grid renders.
|
|
837
|
+
*
|
|
838
|
+
* A council of N members over R rounds seals N x R member runs, and the
|
|
839
|
+
* operator is looking at a council, not at its history: each label keeps its
|
|
840
|
+
* newest round, so the group shows one column per member however many rounds
|
|
841
|
+
* it ran. Columns are ordered by label so a repaint cannot reshuffle them under
|
|
842
|
+
* the cursor while runs settle in whatever order they finish.
|
|
843
|
+
*/
|
|
844
|
+
function foldCouncilGroup(
|
|
845
|
+
group: string,
|
|
846
|
+
rows: ReadonlyArray<DispatchBoardRow>,
|
|
847
|
+
selectedRunId?: string | null,
|
|
848
|
+
): CouncilGroupView {
|
|
849
|
+
const newest = new Map<string, DispatchBoardRow>();
|
|
850
|
+
let synthesisRow: DispatchBoardRow | null = null;
|
|
851
|
+
let rank = Number.POSITIVE_INFINITY;
|
|
852
|
+
let elapsedMs = 0;
|
|
853
|
+
let round = 1;
|
|
854
|
+
for (const row of rows) {
|
|
855
|
+
const council = row.council;
|
|
856
|
+
if (council === undefined) continue;
|
|
857
|
+
rank = Math.min(rank, STATUS_ORDER[row.status]);
|
|
858
|
+
elapsedMs = Math.max(elapsedMs, row.elapsedMs);
|
|
859
|
+
if (council.label === COUNCIL_SYNTHESIS_LABEL) {
|
|
860
|
+
synthesisRow = row;
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
round = Math.max(round, council.round);
|
|
864
|
+
const previous = newest.get(council.label);
|
|
865
|
+
if (previous === undefined || (previous.council?.round ?? 0) <= council.round) newest.set(council.label, row);
|
|
866
|
+
}
|
|
867
|
+
const aggregate = (Object.entries(STATUS_ORDER) as Array<[DispatchBoardStatus, number]>).find(
|
|
868
|
+
([, value]) => value === rank,
|
|
869
|
+
);
|
|
870
|
+
const presentation = dispatchStatusPresentation(aggregate?.[0] ?? "running", {
|
|
871
|
+
compact: true,
|
|
872
|
+
...(aggregate?.[0] === "running" ? { tick: Math.floor(Date.now() / 100) } : {}),
|
|
873
|
+
});
|
|
874
|
+
const members = [...newest.entries()]
|
|
875
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
876
|
+
// biome-ignore lint/style/noNonNullAssertion: every entry was inserted with its own council badge.
|
|
877
|
+
.map(([, row]) => councilMemberView(row, row.council!));
|
|
878
|
+
const selectedInGroup =
|
|
879
|
+
selectedRunId != null && rows.some((row) => row.runId === selectedRunId) ? selectedRunId : undefined;
|
|
880
|
+
return {
|
|
881
|
+
group,
|
|
882
|
+
members,
|
|
883
|
+
// biome-ignore lint/style/noNonNullAssertion: the synthesis row was matched on its own council badge.
|
|
884
|
+
synthesis: synthesisRow === null ? null : councilMemberView(synthesisRow, synthesisRow.council!),
|
|
885
|
+
status: { glyph: presentation.glyph, label: presentation.label, token: presentation.token },
|
|
886
|
+
round,
|
|
887
|
+
elapsed: formatCompactMs(elapsedMs),
|
|
888
|
+
...(selectedInGroup !== undefined ? { selectedRunId: selectedInGroup } : {}),
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/** A board entry: either one ordinary run, or one whole council in the position of its first row. */
|
|
893
|
+
export type DispatchBoardItem = { kind: "run"; row: DispatchBoardRow } | { kind: "council"; group: CouncilGroupView };
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* Board order with councils folded in place. A council occupies the position of
|
|
897
|
+
* its first row and its remaining rows are consumed, so the board never shows a
|
|
898
|
+
* member both inside its group and beside it.
|
|
899
|
+
*/
|
|
900
|
+
export function dispatchBoardItems(
|
|
901
|
+
rows: ReadonlyArray<DispatchBoardRow>,
|
|
902
|
+
selectedRunId?: string | null,
|
|
903
|
+
): DispatchBoardItem[] {
|
|
904
|
+
const byGroup = new Map<string, DispatchBoardRow[]>();
|
|
905
|
+
for (const row of rows) {
|
|
906
|
+
const group = row.council?.group;
|
|
907
|
+
if (group === undefined) continue;
|
|
908
|
+
const bucket = byGroup.get(group);
|
|
909
|
+
if (bucket === undefined) byGroup.set(group, [row]);
|
|
910
|
+
else bucket.push(row);
|
|
911
|
+
}
|
|
912
|
+
const emitted = new Set<string>();
|
|
913
|
+
const items: DispatchBoardItem[] = [];
|
|
914
|
+
for (const row of rows) {
|
|
915
|
+
const group = row.council?.group;
|
|
916
|
+
if (group === undefined) {
|
|
917
|
+
items.push({ kind: "run", row });
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
if (emitted.has(group)) continue;
|
|
921
|
+
emitted.add(group);
|
|
922
|
+
items.push({ kind: "council", group: foldCouncilGroup(group, byGroup.get(group) ?? [row], selectedRunId) });
|
|
923
|
+
}
|
|
924
|
+
return items;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/** The framed council card: the member grid or stack, with the synthesis run full width under it. */
|
|
928
|
+
export function renderCouncilCard(group: CouncilGroupView, width: number): string[] {
|
|
929
|
+
const theme = clioTheme();
|
|
930
|
+
const contentWidth = Math.max(0, width - 4);
|
|
931
|
+
const title = `${theme.fg("info", "council")} ${theme.paint(group.group, { bold: true })}`;
|
|
932
|
+
return frame(theme, title, councilGroupBody(theme, group, contentWidth), width, { rightMeta: group.elapsed });
|
|
933
|
+
}
|
|
934
|
+
|
|
608
935
|
export function formatDispatchBoardLines(
|
|
609
936
|
rows: ReadonlyArray<DispatchBoardRow>,
|
|
610
937
|
width = 76,
|
|
611
938
|
observability?: ObservabilitySnapshot,
|
|
612
939
|
selectedRunId?: string | null,
|
|
940
|
+
/** Whether the selected row renders its worker-progress detail. Off by default. */
|
|
941
|
+
detailExpanded = false,
|
|
613
942
|
): string[] {
|
|
614
943
|
if (rows.length === 0) {
|
|
615
944
|
const theme = clioTheme();
|
|
@@ -620,10 +949,15 @@ export function formatDispatchBoardLines(
|
|
|
620
949
|
});
|
|
621
950
|
}
|
|
622
951
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
952
|
+
// A council is one question asked of several members, so its rows render as
|
|
953
|
+
// one card holding the whole group rather than as unrelated neighbours.
|
|
954
|
+
const cards = dispatchBoardItems(rows, selectedRunId).map((item) =>
|
|
955
|
+
item.kind === "council"
|
|
956
|
+
? renderCouncilCard(item.group, width)
|
|
957
|
+
: renderDispatchCard(item.row, width, deriveRunEvidenceState(observability, item.row.runId), {
|
|
958
|
+
selected: item.row.runId === selectedRunId,
|
|
959
|
+
expanded: detailExpanded && item.row.runId === selectedRunId,
|
|
960
|
+
}),
|
|
627
961
|
);
|
|
628
962
|
const body: string[] = [];
|
|
629
963
|
for (const card of cards) {
|
|
@@ -648,6 +982,9 @@ export interface DispatchBoardView extends Component {
|
|
|
648
982
|
selectPrevious(): void;
|
|
649
983
|
selectNext(): void;
|
|
650
984
|
resetSelection(): void;
|
|
985
|
+
/** Open or close the selected row's worker-progress detail. */
|
|
986
|
+
toggleDetail(): void;
|
|
987
|
+
detailExpanded(): boolean;
|
|
651
988
|
}
|
|
652
989
|
|
|
653
990
|
export function createDispatchBoardView(
|
|
@@ -655,6 +992,9 @@ export function createDispatchBoardView(
|
|
|
655
992
|
observability: () => ObservabilitySnapshot | undefined,
|
|
656
993
|
): DispatchBoardView {
|
|
657
994
|
let selectedRunId: string | null = null;
|
|
995
|
+
// Detail is a property of the operator's attention, not of a run, so it
|
|
996
|
+
// follows the cursor rather than pinning to the row it was opened on.
|
|
997
|
+
let expanded = false;
|
|
658
998
|
|
|
659
999
|
const normalizeSelection = (currentRows: ReadonlyArray<DispatchBoardRow>): DispatchBoardRow | null => {
|
|
660
1000
|
if (currentRows.length === 0) {
|
|
@@ -679,7 +1019,7 @@ export function createDispatchBoardView(
|
|
|
679
1019
|
render(width: number): string[] {
|
|
680
1020
|
const currentRows = rows();
|
|
681
1021
|
normalizeSelection(currentRows);
|
|
682
|
-
return formatDispatchBoardLines(currentRows, Math.max(1, width), observability(), selectedRunId);
|
|
1022
|
+
return formatDispatchBoardLines(currentRows, Math.max(1, width), observability(), selectedRunId, expanded);
|
|
683
1023
|
},
|
|
684
1024
|
selectedRow(): DispatchBoardRow | null {
|
|
685
1025
|
return normalizeSelection(rows());
|
|
@@ -692,30 +1032,44 @@ export function createDispatchBoardView(
|
|
|
692
1032
|
},
|
|
693
1033
|
resetSelection(): void {
|
|
694
1034
|
selectedRunId = null;
|
|
1035
|
+
expanded = false;
|
|
695
1036
|
normalizeSelection(rows());
|
|
696
1037
|
},
|
|
1038
|
+
toggleDetail(): void {
|
|
1039
|
+
expanded = !expanded;
|
|
1040
|
+
},
|
|
1041
|
+
detailExpanded(): boolean {
|
|
1042
|
+
return expanded;
|
|
1043
|
+
},
|
|
697
1044
|
invalidate(): void {},
|
|
698
1045
|
};
|
|
699
1046
|
}
|
|
700
1047
|
|
|
701
1048
|
export function formatTaskIslandLines(rows: ReadonlyArray<DispatchBoardRow>, maxRows = 4): string[] {
|
|
702
|
-
|
|
1049
|
+
// Councils are folded before the row cap, so a five-member council costs the
|
|
1050
|
+
// island one card and never crowds out the runs beside it.
|
|
1051
|
+
const items = dispatchBoardItems(rows);
|
|
1052
|
+
const visibleItems = items.slice(0, Math.max(1, maxRows));
|
|
703
1053
|
const body: string[] = [];
|
|
704
1054
|
|
|
705
|
-
if (
|
|
1055
|
+
if (visibleItems.length === 0) {
|
|
706
1056
|
const theme = clioTheme();
|
|
707
1057
|
body.push(theme.fg("dim", "No active fleet runs."));
|
|
708
1058
|
body.push(theme.fg("dim", "Use /run or /delegate to spawn agents."));
|
|
709
1059
|
} else {
|
|
710
|
-
for (let i = 0; i <
|
|
711
|
-
const
|
|
712
|
-
if (!
|
|
1060
|
+
for (let i = 0; i < visibleItems.length; i++) {
|
|
1061
|
+
const item = visibleItems[i];
|
|
1062
|
+
if (!item) continue;
|
|
713
1063
|
if (i > 0) {
|
|
714
1064
|
body.push(innerDivider(clioTheme(), TASK_ISLAND_WIDTH));
|
|
715
1065
|
}
|
|
716
|
-
body.push(
|
|
1066
|
+
body.push(
|
|
1067
|
+
...(item.kind === "council"
|
|
1068
|
+
? councilIslandLines(clioTheme(), item.group, TASK_ISLAND_WIDTH)
|
|
1069
|
+
: renderTaskIslandRow(item.row, TASK_ISLAND_WIDTH)),
|
|
1070
|
+
);
|
|
717
1071
|
}
|
|
718
|
-
const hidden =
|
|
1072
|
+
const hidden = items.length - visibleItems.length;
|
|
719
1073
|
if (hidden > 0) {
|
|
720
1074
|
body.push(innerDivider(clioTheme(), TASK_ISLAND_WIDTH));
|
|
721
1075
|
body.push(clioTheme().fg("dim", `+ ${hidden} more`));
|
|
@@ -781,6 +1135,21 @@ function parseGateBadge(value: unknown): { role: string; cycle: number } | undef
|
|
|
781
1135
|
return { role: record.role, cycle };
|
|
782
1136
|
}
|
|
783
1137
|
|
|
1138
|
+
function parseCouncilBadge(value: unknown): DispatchBoardRow["council"] | undefined {
|
|
1139
|
+
if (typeof value !== "object" || value === null) return undefined;
|
|
1140
|
+
const record = value as { group?: unknown; label?: unknown; color?: unknown; round?: unknown };
|
|
1141
|
+
const group = parseNonEmptyString(record.group);
|
|
1142
|
+
const label = parseNonEmptyString(record.label);
|
|
1143
|
+
const round = parsePositiveInt(record.round);
|
|
1144
|
+
if (group === undefined || label === undefined || round === undefined) return undefined;
|
|
1145
|
+
return {
|
|
1146
|
+
group,
|
|
1147
|
+
label,
|
|
1148
|
+
...(typeof record.color === "string" && record.color.length > 0 ? { color: record.color } : {}),
|
|
1149
|
+
round,
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
|
|
784
1153
|
function parseFiniteNumberOrZero(value: unknown): number {
|
|
785
1154
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
786
1155
|
}
|
|
@@ -879,6 +1248,7 @@ function readRunningSnapshot(snapshot: DispatchSnapshot): Map<
|
|
|
879
1248
|
costUsd: number;
|
|
880
1249
|
costProvenance: CostProvenance;
|
|
881
1250
|
outcomePhase: string;
|
|
1251
|
+
budget?: RunToolBudgetEnvelope;
|
|
882
1252
|
}
|
|
883
1253
|
> {
|
|
884
1254
|
const running = new Map<
|
|
@@ -890,12 +1260,14 @@ function readRunningSnapshot(snapshot: DispatchSnapshot): Map<
|
|
|
890
1260
|
costUsd: number;
|
|
891
1261
|
costProvenance: CostProvenance;
|
|
892
1262
|
outcomePhase: string;
|
|
1263
|
+
budget?: RunToolBudgetEnvelope;
|
|
893
1264
|
}
|
|
894
1265
|
>();
|
|
895
1266
|
try {
|
|
896
1267
|
for (const value of snapshot.running) {
|
|
897
1268
|
const runId = parseRunId(value.runId);
|
|
898
1269
|
if (!runId) continue;
|
|
1270
|
+
const budget = cloneRunToolBudgetEnvelope(value.budget);
|
|
899
1271
|
running.set(runId, {
|
|
900
1272
|
inputTokens: parseFiniteNumberOrZero(value.tokens.input),
|
|
901
1273
|
outputTokens: parseFiniteNumberOrZero(value.tokens.output),
|
|
@@ -903,6 +1275,7 @@ function readRunningSnapshot(snapshot: DispatchSnapshot): Map<
|
|
|
903
1275
|
costUsd: parseFiniteNumberOrZero(value.costUsd),
|
|
904
1276
|
costProvenance: value.costProvenance ?? "unknown",
|
|
905
1277
|
outcomePhase: value.outcomePhase,
|
|
1278
|
+
...(budget !== undefined ? { budget } : {}),
|
|
906
1279
|
});
|
|
907
1280
|
}
|
|
908
1281
|
} catch {
|
|
@@ -920,7 +1293,9 @@ function resolveElapsedMs(entry: DispatchBoardEntry, now: number): number {
|
|
|
920
1293
|
|
|
921
1294
|
function toRow(entry: DispatchBoardEntry, now: number): DispatchBoardRow {
|
|
922
1295
|
const retry = entry.retry;
|
|
1296
|
+
const progress = entry.progress.snapshot();
|
|
923
1297
|
return {
|
|
1298
|
+
progress,
|
|
924
1299
|
runId: entry.runId,
|
|
925
1300
|
agentId: entry.agentId,
|
|
926
1301
|
...(entry.agentAudience !== undefined ? { agentAudience: entry.agentAudience } : {}),
|
|
@@ -930,6 +1305,7 @@ function toRow(entry: DispatchBoardEntry, now: number): DispatchBoardRow {
|
|
|
930
1305
|
targetId: entry.targetId,
|
|
931
1306
|
wireModelId: entry.wireModelId,
|
|
932
1307
|
...(entry.taskSummary !== undefined ? { taskSummary: entry.taskSummary } : {}),
|
|
1308
|
+
...(entry.budget !== undefined ? { budget: entry.budget } : {}),
|
|
933
1309
|
status: retry ? "retrying" : entry.status,
|
|
934
1310
|
elapsedMs: resolveElapsedMs(entry, now),
|
|
935
1311
|
tokenCount: entry.tokenCount,
|
|
@@ -941,13 +1317,18 @@ function toRow(entry: DispatchBoardEntry, now: number): DispatchBoardRow {
|
|
|
941
1317
|
...(entry.outcomeDetail !== undefined ? { outcomeDetail: entry.outcomeDetail } : {}),
|
|
942
1318
|
...(entry.node !== undefined ? { node: entry.node } : {}),
|
|
943
1319
|
...(entry.gate !== undefined ? { gate: { ...entry.gate } } : {}),
|
|
1320
|
+
...(entry.council !== undefined ? { council: { ...entry.council } } : {}),
|
|
1321
|
+
...(entry.hostVerification !== undefined ? { hostVerification: entry.hostVerification } : {}),
|
|
944
1322
|
...(entry.rerouteCount !== undefined ? { rerouteCount: entry.rerouteCount } : {}),
|
|
945
1323
|
...(entry.failoverHops !== undefined ? { failoverHops: entry.failoverHops } : {}),
|
|
946
1324
|
...(entry.contextWindow !== undefined ? { contextWindow: entry.contextWindow } : {}),
|
|
947
1325
|
lastContextTokens: entry.lastContextTokens,
|
|
948
1326
|
...(entry.receiptId !== undefined ? { receiptId: entry.receiptId } : {}),
|
|
949
|
-
|
|
950
|
-
|
|
1327
|
+
...(entry.phase !== undefined ? { phase: { ...entry.phase } } : {}),
|
|
1328
|
+
// Both tool fields are views of the one projection, so the compact card and
|
|
1329
|
+
// the expanded detail can never disagree about what is running.
|
|
1330
|
+
currentTool: retry ? null : (progress.currentAction?.tool ?? null),
|
|
1331
|
+
recentTools: progress.recentActions.map((action) => action.tool),
|
|
951
1332
|
...(retry ? { retry: { ...retry } } : {}),
|
|
952
1333
|
...(entry.steerAcknowledgement ? { steerAcknowledgement: { ...entry.steerAcknowledgement } } : {}),
|
|
953
1334
|
};
|
|
@@ -964,7 +1345,7 @@ function sortEntries(a: DispatchBoardEntry, b: DispatchBoardEntry): number {
|
|
|
964
1345
|
return a.sequence - b.sequence;
|
|
965
1346
|
}
|
|
966
1347
|
|
|
967
|
-
function pruneEntries(entries: Map<string, DispatchBoardEntry
|
|
1348
|
+
function pruneEntries(entries: Map<string, DispatchBoardEntry>, onEvict?: (runId: string) => void): void {
|
|
968
1349
|
if (entries.size <= MAX_DISPATCH_BOARD_ROWS) return;
|
|
969
1350
|
const terminalEntries = [...entries.values()]
|
|
970
1351
|
.filter((entry) => !entry.retry && isTerminalStatus(entry.status))
|
|
@@ -974,22 +1355,49 @@ function pruneEntries(entries: Map<string, DispatchBoardEntry>): void {
|
|
|
974
1355
|
for (const entry of evictionQueue) {
|
|
975
1356
|
if (entries.size <= MAX_DISPATCH_BOARD_ROWS) break;
|
|
976
1357
|
entries.delete(entry.runId);
|
|
1358
|
+
onEvict?.(entry.runId);
|
|
977
1359
|
}
|
|
978
1360
|
}
|
|
979
1361
|
|
|
980
1362
|
export function createDispatchBoardStore(
|
|
981
1363
|
bus: SafeEventBus,
|
|
982
1364
|
snapshot?: () => DispatchSnapshot,
|
|
1365
|
+
/**
|
|
1366
|
+
* Sealed terminal facts for a finished run. Settlement replaces the
|
|
1367
|
+
* provisional live tail with the receipt's answer where one can be read; the
|
|
1368
|
+
* run's own durable message stands in where it cannot.
|
|
1369
|
+
*/
|
|
1370
|
+
readReceipt?: WorkerReceiptReader,
|
|
983
1371
|
): {
|
|
984
1372
|
rows(): ReadonlyArray<DispatchBoardRow>;
|
|
985
1373
|
activeRows(): ReadonlyArray<DispatchBoardRow>;
|
|
986
1374
|
reconcile(): void;
|
|
1375
|
+
/**
|
|
1376
|
+
* Record the fleet-plan position of a run the caller just dispatched. The
|
|
1377
|
+
* board never infers a position: a run carries one only because whoever
|
|
1378
|
+
* dispatched the plan said so, which is why a non-fleet run can never grow
|
|
1379
|
+
* a phase cell.
|
|
1380
|
+
*/
|
|
1381
|
+
setFleetPhase(runId: string, phase: DispatchBoardPhase): void;
|
|
987
1382
|
unsubscribe(): void;
|
|
988
1383
|
} {
|
|
989
1384
|
const entries = new Map<string, DispatchBoardEntry>();
|
|
1385
|
+
// Positions arrive from the fleet-run path, which may hand one over before
|
|
1386
|
+
// the run's first lifecycle event creates the entry.
|
|
1387
|
+
const fleetPhases = new Map<string, DispatchBoardPhase>();
|
|
990
1388
|
let nextSequence = 0;
|
|
991
1389
|
let reconciledAtMs = Date.now();
|
|
992
1390
|
|
|
1391
|
+
/**
|
|
1392
|
+
* Seal a run's projection on the sealed receipt's answer. Only for the
|
|
1393
|
+
* terminal bus events: the dispatch domain writes `receipts/<runId>.json`
|
|
1394
|
+
* before it publishes one, so this is the first moment the file exists.
|
|
1395
|
+
*/
|
|
1396
|
+
const settleFromReceipt = (entry: DispatchBoardEntry): void => {
|
|
1397
|
+
const text = readReceipt?.(entry.runId)?.text;
|
|
1398
|
+
entry.progress.settle(typeof text === "string" && text.trim().length > 0 ? text : undefined);
|
|
1399
|
+
};
|
|
1400
|
+
|
|
993
1401
|
// Payloads arrive typed off the bus, but the board keeps its runtime
|
|
994
1402
|
// parsing (parse* helpers) because events are not validated at runtime.
|
|
995
1403
|
const upsertBase = (
|
|
@@ -1004,9 +1412,12 @@ export function createDispatchBoardStore(
|
|
|
1004
1412
|
const requestOrigin = parseRequestOrigin(raw.requestOrigin, previous?.requestOrigin);
|
|
1005
1413
|
const node = parseNonEmptyString(raw.node) ?? previous?.node;
|
|
1006
1414
|
const gate = parseGateBadge(raw.gate) ?? previous?.gate;
|
|
1415
|
+
const council = parseCouncilBadge(raw.council) ?? previous?.council;
|
|
1007
1416
|
const rerouteCount = parsePositiveInt(raw.rerouteCount) ?? previous?.rerouteCount;
|
|
1008
1417
|
const contextWindow = parsePositiveInt(raw.contextWindow) ?? previous?.contextWindow;
|
|
1009
1418
|
const taskSummary = parseTaskSummary(raw, previous?.taskSummary);
|
|
1419
|
+
const budget = cloneRunToolBudgetEnvelope(raw.budget) ?? previous?.budget;
|
|
1420
|
+
const phase = fleetPhases.get(runId) ?? previous?.phase;
|
|
1010
1421
|
const entry: DispatchBoardEntry = {
|
|
1011
1422
|
runId,
|
|
1012
1423
|
agentId: parseText(raw.agentId, previous?.agentId ?? "-"),
|
|
@@ -1017,6 +1428,7 @@ export function createDispatchBoardStore(
|
|
|
1017
1428
|
targetId: parseText(raw.targetId, previous?.targetId ?? "-"),
|
|
1018
1429
|
wireModelId: parseText(raw.wireModelId, previous?.wireModelId ?? "-"),
|
|
1019
1430
|
...(taskSummary !== undefined ? { taskSummary } : {}),
|
|
1431
|
+
...(budget !== undefined ? { budget } : {}),
|
|
1020
1432
|
status,
|
|
1021
1433
|
tokenCount: previous?.tokenCount ?? 0,
|
|
1022
1434
|
costUsd: previous?.costUsd ?? 0,
|
|
@@ -1033,17 +1445,19 @@ export function createDispatchBoardStore(
|
|
|
1033
1445
|
outcomeDetail: previous?.outcomeDetail ?? null,
|
|
1034
1446
|
...(node !== undefined ? { node } : {}),
|
|
1035
1447
|
...(gate !== undefined ? { gate } : {}),
|
|
1448
|
+
...(council !== undefined ? { council } : {}),
|
|
1449
|
+
...(previous?.hostVerification !== undefined ? { hostVerification: previous.hostVerification } : {}),
|
|
1036
1450
|
...(rerouteCount !== undefined ? { rerouteCount } : {}),
|
|
1037
1451
|
...(contextWindow !== undefined ? { contextWindow } : {}),
|
|
1038
1452
|
lastContextTokens: previous?.lastContextTokens ?? 0,
|
|
1039
1453
|
...(previous?.receiptId !== undefined ? { receiptId: previous.receiptId } : {}),
|
|
1040
|
-
|
|
1041
|
-
|
|
1454
|
+
...(phase !== undefined ? { phase } : {}),
|
|
1455
|
+
progress: previous?.progress ?? createWorkerProgressFold(),
|
|
1042
1456
|
...(previous?.retry ? { retry: { ...previous.retry } } : {}),
|
|
1043
1457
|
...(previous?.steerAcknowledgement ? { steerAcknowledgement: { ...previous.steerAcknowledgement } } : {}),
|
|
1044
1458
|
};
|
|
1045
1459
|
entries.set(runId, entry);
|
|
1046
|
-
pruneEntries(entries);
|
|
1460
|
+
pruneEntries(entries, (evicted) => fleetPhases.delete(evicted));
|
|
1047
1461
|
return entry;
|
|
1048
1462
|
};
|
|
1049
1463
|
|
|
@@ -1074,7 +1488,8 @@ export function createDispatchBoardStore(
|
|
|
1074
1488
|
entry.costUsd = parseFiniteNumber(payload.costUsd, entry.costUsd);
|
|
1075
1489
|
entry.costProvenance = payload.costProvenance ?? "unknown";
|
|
1076
1490
|
entry.outcomeDetail = null;
|
|
1077
|
-
entry.
|
|
1491
|
+
if (payload.hostVerification !== undefined) entry.hostVerification = payload.hostVerification;
|
|
1492
|
+
settleFromReceipt(entry);
|
|
1078
1493
|
// A terminal dispatch event is published only after the run's receipt is
|
|
1079
1494
|
// sealed at receipts/<runId>.json, so the run id is the receipt id here.
|
|
1080
1495
|
entry.receiptId = entry.runId;
|
|
@@ -1102,7 +1517,8 @@ export function createDispatchBoardStore(
|
|
|
1102
1517
|
entry.costUsd = parseFiniteNumber(payload.costUsd, entry.costUsd);
|
|
1103
1518
|
entry.costProvenance = payload.costProvenance ?? "unknown";
|
|
1104
1519
|
entry.outcomeDetail = resolveFailureDetail(payload, entry.outcomeDetail);
|
|
1105
|
-
entry.
|
|
1520
|
+
if (payload.hostVerification !== undefined) entry.hostVerification = payload.hostVerification;
|
|
1521
|
+
settleFromReceipt(entry);
|
|
1106
1522
|
// A denied retry never reached a run, so no receipt was sealed for it;
|
|
1107
1523
|
// every other failure finalized through recordReceipt like a success.
|
|
1108
1524
|
if (payload.reason !== "retry_denied") entry.receiptId = entry.runId;
|
|
@@ -1126,7 +1542,7 @@ export function createDispatchBoardStore(
|
|
|
1126
1542
|
// wind down, so the history row can become terminal immediately.
|
|
1127
1543
|
entry.status = "aborted";
|
|
1128
1544
|
entry.finishedAtMs = Date.now();
|
|
1129
|
-
entry.
|
|
1545
|
+
entry.progress.settle();
|
|
1130
1546
|
} else {
|
|
1131
1547
|
if (isTerminalStatus(entry.status) && !wasRetrying) return;
|
|
1132
1548
|
entry.status = "cancelling";
|
|
@@ -1148,7 +1564,10 @@ export function createDispatchBoardStore(
|
|
|
1148
1564
|
entry.status = status;
|
|
1149
1565
|
if (status === "dead") {
|
|
1150
1566
|
entry.finishedAtMs ??= Date.now();
|
|
1151
|
-
|
|
1567
|
+
// A worker-side terminal signal arrives before any receipt is
|
|
1568
|
+
// sealed, so the run's own last durable message is the best truth
|
|
1569
|
+
// there is; the terminal bus event replaces it with the sealed one.
|
|
1570
|
+
entry.progress.settle();
|
|
1152
1571
|
delete entry.retry;
|
|
1153
1572
|
}
|
|
1154
1573
|
return;
|
|
@@ -1159,7 +1578,9 @@ export function createDispatchBoardStore(
|
|
|
1159
1578
|
entry.failoverHops = (entry.failoverHops ?? 0) + 1;
|
|
1160
1579
|
entry.status = "running";
|
|
1161
1580
|
entry.finishedAtMs = null;
|
|
1162
|
-
|
|
1581
|
+
// The projection keeps the tail the operator is reading and drops the
|
|
1582
|
+
// finished attempt's live state, exactly as the transcript block does.
|
|
1583
|
+
entry.progress.restart();
|
|
1163
1584
|
delete entry.retry;
|
|
1164
1585
|
return;
|
|
1165
1586
|
}
|
|
@@ -1183,20 +1604,10 @@ export function createDispatchBoardStore(
|
|
|
1183
1604
|
entry.ttftMs = Math.round(performance.now() - entry.startedAtClockMs);
|
|
1184
1605
|
}
|
|
1185
1606
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
}
|
|
1191
|
-
if (type === "clio_tool_finish") {
|
|
1192
|
-
const payload = (workerEvent as { payload?: { tool?: unknown } }).payload;
|
|
1193
|
-
const tool = parseNonEmptyString(payload?.tool);
|
|
1194
|
-
entry.currentTool = null;
|
|
1195
|
-
if (tool !== undefined) {
|
|
1196
|
-
entry.recentTools.unshift(tool);
|
|
1197
|
-
if (entry.recentTools.length > RECENT_TOOL_TRAIL_LIMIT) entry.recentTools.length = RECENT_TOOL_TRAIL_LIMIT;
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1607
|
+
// The one fold that reads worker prose and tool activity. Everything the
|
|
1608
|
+
// board says about what a worker is saying or touching comes from here,
|
|
1609
|
+
// so the board and the transcript block cannot tell two stories.
|
|
1610
|
+
entry.progress.observe(payload.event);
|
|
1200
1611
|
if (type === "clio_steer_received") {
|
|
1201
1612
|
const steerPayload = (workerEvent as { payload?: { chars?: unknown } }).payload;
|
|
1202
1613
|
entry.steerAcknowledgement = {
|
|
@@ -1221,7 +1632,10 @@ export function createDispatchBoardStore(
|
|
|
1221
1632
|
if (!status) return;
|
|
1222
1633
|
entry.status = status;
|
|
1223
1634
|
entry.finishedAtMs ??= Date.now();
|
|
1224
|
-
|
|
1635
|
+
// The worker says it is done before the domain seals its receipt. Settle
|
|
1636
|
+
// on the durable message now so the card stops reading as live; the
|
|
1637
|
+
// terminal bus event seals it on the receipt shortly after.
|
|
1638
|
+
entry.progress.settle();
|
|
1225
1639
|
delete entry.retry;
|
|
1226
1640
|
}
|
|
1227
1641
|
}),
|
|
@@ -1270,7 +1684,8 @@ export function createDispatchBoardStore(
|
|
|
1270
1684
|
now,
|
|
1271
1685
|
);
|
|
1272
1686
|
if (entry) {
|
|
1273
|
-
|
|
1687
|
+
// A waiting retry has no live worker; the row projection already
|
|
1688
|
+
// renders a retrying row with no current tool.
|
|
1274
1689
|
entry.retry = { ...projection.retry };
|
|
1275
1690
|
if (entry.taskSummary === undefined && projection.taskSummary !== undefined) {
|
|
1276
1691
|
entry.taskSummary = projection.taskSummary;
|
|
@@ -1286,6 +1701,7 @@ export function createDispatchBoardStore(
|
|
|
1286
1701
|
entry.tokenCount = live.tokenCount;
|
|
1287
1702
|
entry.costUsd = live.costUsd;
|
|
1288
1703
|
entry.costProvenance = live.costProvenance;
|
|
1704
|
+
if (live.budget !== undefined) entry.budget = live.budget;
|
|
1289
1705
|
if (live.outcomePhase === "aborting") {
|
|
1290
1706
|
if (entry.status !== "completed" && entry.status !== "aborted") {
|
|
1291
1707
|
entry.status = "cancelling";
|
|
@@ -1310,6 +1726,11 @@ export function createDispatchBoardStore(
|
|
|
1310
1726
|
return projectRows(true);
|
|
1311
1727
|
},
|
|
1312
1728
|
reconcile,
|
|
1729
|
+
setFleetPhase(runId: string, phase: DispatchBoardPhase): void {
|
|
1730
|
+
fleetPhases.set(runId, phase);
|
|
1731
|
+
const entry = entries.get(runId);
|
|
1732
|
+
if (entry) entry.phase = { ...phase };
|
|
1733
|
+
},
|
|
1313
1734
|
unsubscribe() {
|
|
1314
1735
|
if (closed) return;
|
|
1315
1736
|
closed = true;
|