@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.
- package/CHANGELOG.md +59 -0
- package/README.md +22 -6
- package/dist/{acp-2BEHC4DL.js → acp-U67UHUK2.js} +14 -14
- package/dist/{agents-LNNFTM53.js → agents-YU6SGALZ.js} +42 -35
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-KXXFI2VS.js → auth-5ZPJOIVG.js} +32 -24
- package/dist/builtins-C6JMZVV6.js +17 -0
- package/dist/{chunk-E25LMLRW.js → chunk-26LEYJZH.js} +2 -2
- package/dist/{chunk-TSHXZTOQ.js → chunk-2HEJ2F35.js} +5 -5
- package/dist/{chunk-PBTHKCPN.js → chunk-2HFZQUHL.js} +7 -7
- package/dist/{chunk-FO5ZOVUY.js → chunk-3BINW3FP.js} +5 -5
- package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
- package/dist/{chunk-CKXWIANG.js → chunk-4SPRNWDE.js} +18 -16
- package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
- package/dist/{chunk-XF5N4U5A.js → chunk-5DHKRSMQ.js} +9 -8
- package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
- package/dist/{chunk-EKY57CSP.js → chunk-5H3GB5BO.js} +68 -772
- package/dist/{chunk-6XXKFVSN.js → chunk-5Q2VVUKB.js} +4 -4
- package/dist/{chunk-DJVECN66.js → chunk-7RFXX52T.js} +295 -3705
- package/dist/{chunk-ZXF4XRKW.js → chunk-7RGZWPB6.js} +158 -8
- package/dist/{chunk-FYYLNIL5.js → chunk-A2NJGIB3.js} +2 -2
- package/dist/{chunk-XXQNGV4M.js → chunk-A3WNZD3P.js} +548 -72
- package/dist/{chunk-VEZEGCGW.js → chunk-B5CSFE7B.js} +23 -21
- package/dist/{chunk-2SFS6XQE.js → chunk-DGSYXYMX.js} +3 -2
- package/dist/chunk-DR52UMZW.js +21 -0
- package/dist/{chunk-ZWLZP4ZT.js → chunk-DYIM5TJT.js} +98 -12
- package/dist/{chunk-QKMUKYO7.js → chunk-E77JEWSD.js} +270 -125
- package/dist/{chunk-LYF7OHWH.js → chunk-EMYUUSFG.js} +12 -467
- package/dist/{chunk-4VP4KH3K.js → chunk-EQ63NRB7.js} +8 -8
- package/dist/chunk-FBVTI2TJ.js +518 -0
- package/dist/{chunk-R46L2BIR.js → chunk-FHJEP5SW.js} +19 -25
- package/dist/{chunk-WR67VIZY.js → chunk-GN57SG4G.js} +66 -8
- package/dist/{chunk-RD5U66HV.js → chunk-GPIEI3LY.js} +9 -9
- package/dist/{chunk-PCZJO5TI.js → chunk-GU2UIAFZ.js} +13 -178
- package/dist/chunk-GWS3VEIW.js +195 -0
- package/dist/chunk-H7IXIC72.js +103 -0
- package/dist/{chunk-3BPUFZDL.js → chunk-HLE42MG7.js} +3 -3
- package/dist/{chunk-24I7BN55.js → chunk-IFBNV6H6.js} +3 -3
- package/dist/{chunk-QNQHSOLF.js → chunk-IGWKHNIQ.js} +114 -55
- package/dist/chunk-IIZWH4XA.js +172 -0
- package/dist/{chunk-AD2SYQYC.js → chunk-IJ7RPIYJ.js} +124 -6
- package/dist/chunk-J3YUBZWY.js +382 -0
- package/dist/{chunk-5JGRAMKL.js → chunk-JOZYP4GM.js} +8 -6
- package/dist/{chunk-OH3TOQTB.js → chunk-K4XHGFR5.js} +751 -15
- package/dist/{chunk-EYPA3EGJ.js → chunk-KTYTFRMB.js} +178 -14
- package/dist/chunk-LU7P4LHA.js +33 -0
- package/dist/chunk-ME6CCNFO.js +108 -0
- package/dist/chunk-MXKJU4JB.js +1100 -0
- package/dist/{chunk-RY3LY4J5.js → chunk-N22QMJKY.js} +21 -18
- package/dist/chunk-NMPKI6XL.js +3006 -0
- package/dist/chunk-NUGM5KR6.js +165 -0
- package/dist/{chunk-22NAGB7X.js → chunk-P43ETTHK.js} +5 -94
- package/dist/{chunk-NILBFAPG.js → chunk-PMDBGQSJ.js} +2 -2
- package/dist/chunk-PT7HYKEM.js +165 -0
- package/dist/chunk-RVG5JXAL.js +41 -0
- package/dist/{chunk-GEYXPTRF.js → chunk-RWSI4YD7.js} +2 -2
- package/dist/{chunk-4BPJXDWC.js → chunk-TANS5ZJS.js} +35 -19
- package/dist/{verifiers-NCBTHHN2.js → chunk-TB5666IT.js} +67 -324
- package/dist/{chunk-QM3F2GKX.js → chunk-TLQJPP24.js} +7927 -8088
- package/dist/{chunk-G7MUEIGA.js → chunk-TT36MB5S.js} +2 -1
- package/dist/chunk-TTHACPOM.js +961 -0
- package/dist/{chunk-6US73PDB.js → chunk-TYPGUK6W.js} +7 -7
- package/dist/{chunk-MFFY33HR.js → chunk-U6MBIEMB.js} +554 -209
- package/dist/chunk-VAWNZU7Z.js +242 -0
- package/dist/{chunk-IR4CFBFN.js → chunk-VCBR6CU7.js} +12 -12
- package/dist/{chunk-WHJYKASB.js → chunk-VHN4MY6O.js} +2 -2
- package/dist/{chunk-XYDYPRZI.js → chunk-VWZOAB7K.js} +10 -10
- package/dist/{chunk-ZRGEBJ4T.js → chunk-WLFILSD5.js} +48 -48
- package/dist/{chunk-K7T3E2SR.js → chunk-WNIJTQQK.js} +12 -11
- package/dist/{chunk-CYQKWTG3.js → chunk-WSB3FPX7.js} +68 -20
- package/dist/{chunk-KHSFENX2.js → chunk-WWCZ5F23.js} +116 -14
- package/dist/{chunk-XE2VEJHX.js → chunk-WXY7KU3G.js} +2 -2
- package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
- package/dist/{chunk-KOHPCX4K.js → chunk-XWSF374K.js} +5 -5
- package/dist/{chunk-CJUB2JJ2.js → chunk-YS5VLNH5.js} +10 -10
- package/dist/{chunk-ZZMN5OM4.js → chunk-ZNLWCMVZ.js} +2 -2
- package/dist/{chunk-WHGPSPT5.js → chunk-ZVJ5BLO2.js} +2 -2
- package/dist/cli/index.js +33 -31
- package/dist/{clio-M2KGYUFZ.js → clio-QVTYJ57A.js} +10 -10
- package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +5 -5
- package/dist/codewiki/build-worker.js +4 -4
- package/dist/{components-5TTYYX6G.js → components-ZFA3SAER.js} +9 -9
- package/dist/{config-XUUYQIWO.js → config-LW5IJFQN.js} +65 -55
- package/dist/{configure-IHJ7YOMV.js → configure-7XIZCOU4.js} +29 -24
- package/dist/{context-75MIWW3U.js → context-L3WL3X7K.js} +54 -43
- package/dist/{context-ZQ7SIFJV.js → context-N52ZA626.js} +30 -14
- package/dist/{context-74JLXAWD.js → context-Y6Y7QPR6.js} +12 -12
- package/dist/{context-clear-GYKWNUML.js → context-clear-MBQRLSDQ.js} +54 -43
- package/dist/{context-index-SSR5ECNE.js → context-index-HVMFQHK3.js} +5 -5
- package/dist/{context-working-set-UX5KEP4J.js → context-working-set-GS6DSO7F.js} +18 -18
- package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-22ZCNOM3.js} +367 -79
- package/dist/{docs-6FZSCG5B.js → docs-7LQ23DLM.js} +9 -9
- package/dist/{doctor-SVJ5BZCW.js → doctor-M7YEDGAE.js} +27 -23
- package/dist/{eval-CG6LLBLD.js → eval-BEC2WHDA.js} +73 -20
- package/dist/{evidence-ZYFIEN42.js → evidence-REJUMSKM.js} +70 -59
- package/dist/{evolve-QGEXEMDW.js → evolve-PY5ZBA5K.js} +50 -39
- package/dist/{extensions-ADGNCJJD.js → extensions-HVKU65YU.js} +7 -7
- package/dist/{fleet-S5R4ZOQY.js → fleet-7WZEWRFA.js} +236 -377
- package/dist/fleet-commands-UVHWM76J.js +70 -0
- package/dist/fleet-graph-6ULH7PES.js +125 -0
- package/dist/fleet-new-RDVJLHHH.js +48 -0
- package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-J53T6CCE.js} +5 -5
- package/dist/fleet-validate-72PC4SLA.js +79 -0
- package/dist/{init-5DRU55YR.js → init-OG3TPGQG.js} +69 -57
- package/dist/library-CNTMPLRF.js +217 -0
- package/dist/{memory-7YKKR6UC.js → memory-6IS7F275.js} +52 -41
- package/dist/{models-ZPOLRU2C.js → models-ENRJDA5W.js} +37 -31
- package/dist/{monitor-US5F5YGZ.js → monitor-XLDVO7TN.js} +79 -42
- package/dist/{orchestrator-E2AL4T5N.js → orchestrator-6KSPYRHA.js} +4126 -762
- package/dist/{paths-E7KYAQWE.js → paths-DBXMZMDU.js} +6 -6
- package/dist/registry-LG64LTF4.js +11 -0
- package/dist/{reset-KZ652EK6.js → reset-RZ4ER727.js} +12 -12
- package/dist/{run-SRNBKDWD.js → run-Y2CNK5RU.js} +89 -76
- package/dist/{share-CGZE33UP.js → share-A55GYP6Z.js} +37 -13
- package/dist/{skills-S2X4DLY5.js → skills-ALC5J6AT.js} +29 -13
- package/dist/{skills-eval-W2GGIC4R.js → skills-eval-JPBEBYQU.js} +60 -48
- package/dist/support-MIETYA5E.js +38 -0
- package/dist/{targets-54SWINWB.js → targets-VGNXIR3S.js} +46 -36
- package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WOBR64YA.js} +4 -4
- package/dist/{uninstall-BVLWXKBT.js → uninstall-ZJF5H5ZN.js} +9 -9
- package/dist/{upgrade-JKAR27XC.js → upgrade-FUSUAGHR.js} +31 -28
- package/dist/{usage-MSAWCLX4.js → usage-N4MKVHKD.js} +140 -68
- package/dist/verifiers-YAWOJ3H2.js +336 -0
- package/dist/{verify-X5HDROLA.js → verify-LTDHYBGY.js} +10 -9
- package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-6M7GHTBJ.js} +75 -62
- package/dist/worker/entry.js +67 -57
- package/docs/README.md +3 -2
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-versions.md +11 -6
- package/docs/built-in-agents.md +26 -2
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +83 -3
- package/docs/configuration-and-targets.md +86 -4
- package/docs/context-engine.md +1 -1
- package/docs/development-pipeline.md +1 -1
- package/docs/dispatch-architecture-rationale.md +1 -1
- package/docs/documentation-coverage.md +3 -3
- package/docs/documentation-guide.md +3 -3
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +1 -1
- package/docs/evidence-and-memory.md +74 -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 +228 -16
- package/docs/git-commit-provenance.md +2 -2
- package/docs/glossary.md +22 -2
- package/docs/installation-and-lifecycle.md +2 -2
- package/docs/middleware-and-components.md +2 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +56 -9
- package/docs/proactive-memory.md +1 -1
- 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 +2 -2
- 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 +1 -1
- package/docs/worker-dispatch-mechanics.md +3 -3
- package/package.json +1 -2
- package/src/cli/argv.ts +5 -0
- package/src/cli/configure.ts +107 -23
- package/src/cli/doctor.ts +5 -1
- package/src/cli/evidence.ts +30 -25
- 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 +111 -0
- package/src/cli/fleet-validate.ts +30 -0
- package/src/cli/fleet.ts +173 -335
- package/src/cli/index.ts +3 -1
- package/src/cli/library.ts +190 -0
- package/src/cli/share.ts +13 -1
- package/src/cli/shared.ts +1 -0
- package/src/cli/targets.ts +4 -1
- package/src/cli/usage.ts +111 -19
- package/src/cli/validate-model.ts +60 -5
- package/src/core/bus-events.ts +29 -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/path-boundary.ts +100 -0
- package/src/domains/agents/builtins/architect.md +1 -0
- package/src/domains/agents/builtins/oracle.md +33 -0
- package/src/domains/agents/catalog.ts +13 -1
- package/src/domains/agents/extension.ts +2 -11
- package/src/domains/agents/fleet-contract.ts +304 -24
- package/src/domains/agents/index.ts +14 -0
- package/src/domains/agents/recipe.ts +7 -1
- package/src/domains/agents/registry.ts +73 -5
- package/src/domains/agents/result-contract.ts +360 -15
- package/src/domains/agents/write-boundary.ts +15 -50
- package/src/domains/config/classify.ts +4 -0
- package/src/domains/context/project-rules.ts +51 -1
- package/src/domains/dispatch/active-route-planner.ts +14 -0
- package/src/domains/dispatch/assignment-reconcile.ts +22 -5
- package/src/domains/dispatch/assignment-store.ts +151 -14
- package/src/domains/dispatch/backoff.ts +2 -1
- 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 +48 -0
- package/src/domains/dispatch/delegation-plan.ts +164 -0
- package/src/domains/dispatch/execution-plan.ts +76 -5
- package/src/domains/dispatch/execution-role.ts +12 -2
- package/src/domains/dispatch/execution-scheduler.ts +183 -67
- package/src/domains/dispatch/extension.ts +412 -74
- 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 +791 -0
- package/src/domains/dispatch/gate-role-prompts.ts +47 -0
- package/src/domains/dispatch/host-verification.ts +178 -0
- package/src/domains/dispatch/index.ts +41 -1
- package/src/domains/dispatch/intent-requirements.ts +40 -0
- package/src/domains/dispatch/intent.ts +235 -0
- package/src/domains/dispatch/path-scope.ts +370 -0
- package/src/domains/dispatch/receipt-integrity.ts +9 -4
- package/src/domains/dispatch/state.ts +36 -3
- package/src/domains/dispatch/types.ts +64 -12
- package/src/domains/dispatch/validation.ts +69 -6
- package/src/domains/dispatch/write-boundary-enforcer.ts +45 -0
- package/src/domains/dispatch/write-boundary.ts +201 -22
- package/src/domains/eval/metrics/evidence.ts +79 -2
- package/src/domains/eval/runners/clio-run.ts +12 -2
- package/src/domains/evidence/build.ts +69 -11
- package/src/domains/evidence/index.ts +21 -0
- package/src/domains/evidence/provenance.ts +46 -11
- package/src/domains/evidence/trust-projection.ts +274 -0
- package/src/domains/evidence/trust-status.ts +155 -18
- package/src/domains/evidence/types.ts +4 -0
- package/src/domains/extensions/discovery.ts +88 -1
- package/src/domains/extensions/resources.ts +20 -8
- package/src/domains/extensions/state.ts +6 -2
- package/src/domains/extensions/types.ts +4 -1
- package/src/domains/lifecycle/doctor.ts +140 -1
- package/src/domains/middleware/index.ts +15 -0
- package/src/domains/middleware/watchdog.ts +281 -0
- package/src/domains/observability/contract.ts +3 -1
- package/src/domains/observability/cost.ts +12 -1
- 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/prompts/contract.ts +3 -5
- package/src/domains/providers/extension.ts +30 -2
- package/src/domains/resources/common-loader.ts +3 -0
- package/src/domains/resources/index.ts +20 -0
- package/src/domains/resources/library.ts +326 -0
- package/src/domains/resources/prompts/loader.ts +119 -14
- package/src/domains/resources/skills/marketplace.ts +37 -12
- package/src/domains/safety/policy-engine.ts +5 -5
- package/src/domains/safety/run-effects.ts +64 -1
- package/src/domains/safety/scope.ts +7 -12
- package/src/domains/session/handoff.ts +629 -0
- package/src/domains/share/archive.ts +67 -2
- package/src/engine/acp/server.ts +4 -1
- package/src/engine/prompt-templates.ts +18 -1
- package/src/engine/worker-runtime.ts +6 -3
- package/src/entry/orchestrator.ts +37 -0
- package/src/interactive/bus-notices.ts +26 -0
- package/src/interactive/chat-loop.ts +235 -1
- package/src/interactive/chat-renderer.ts +22 -0
- package/src/interactive/cost-overlay.ts +31 -3
- 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 +311 -17
- package/src/interactive/fleet-run-preview.ts +307 -0
- package/src/interactive/footer/notifications.ts +219 -0
- package/src/interactive/handoff-round.ts +56 -0
- package/src/interactive/interactive-application.ts +43 -1
- package/src/interactive/interactive-event-projection.ts +23 -1
- package/src/interactive/interactive-slash-runtime.ts +52 -2
- package/src/interactive/interactive-subscriptions.ts +14 -2
- package/src/interactive/oracle.ts +179 -0
- package/src/interactive/overlay-ask-user-lifecycle.ts +6 -0
- package/src/interactive/overlay-general-openers.ts +190 -1
- package/src/interactive/overlay-key-routing.ts +17 -1
- package/src/interactive/overlay-lifecycle.ts +41 -1
- package/src/interactive/overlay-permission-lifecycle.ts +10 -0
- package/src/interactive/overlay-resource-openers.ts +11 -3
- package/src/interactive/overlay-session-lifecycle.ts +234 -2
- 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 +221 -30
- package/src/interactive/overlays/side-question.ts +139 -0
- package/src/interactive/overlays/skills-hub.ts +401 -15
- package/src/interactive/side-question.ts +171 -0
- package/src/interactive/slash-commands.ts +439 -7
- package/src/interactive/slash-spec.ts +19 -6
- package/src/interactive/theme/tokens.ts +30 -0
- package/src/interactive/turn-middleware.ts +15 -1
- package/src/interactive/view/artifacts.ts +42 -9
- package/src/interactive/view/view-overlay.ts +15 -3
- package/src/interactive/watchdog-run.ts +75 -0
- package/src/interactive/worker-receipts.ts +14 -2
- package/src/interactive/worker-share.ts +56 -1
- package/src/interactive/worker-stream.ts +15 -0
- package/src/tools/bootstrap.ts +3 -0
- package/src/tools/compete-worktrees.ts +13 -79
- package/src/tools/dispatch-admission.ts +251 -18
- package/src/tools/dispatch-arguments.ts +84 -1
- package/src/tools/dispatch-plan.ts +165 -6
- package/src/tools/dispatch-runner.ts +364 -23
- package/src/tools/dispatch-types.ts +20 -1
- package/src/tools/dispatch.ts +72 -2
- package/src/tools/monitor.ts +29 -0
- package/src/tools/profiles.ts +18 -4
- package/src/tools/task-worktree.ts +238 -0
- package/src/tools/verify/authoring.ts +61 -1
- package/src/tools/verify/scripts.ts +62 -0
- package/src/tools/worker-evidence.ts +21 -14
- package/src/worker/spec-contract.ts +3 -1
- package/dist/chunk-HC4CLZ2Y.js +0 -68
|
@@ -3,7 +3,9 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { BusChannels } from "../core/bus-events.js";
|
|
5
5
|
import type { SafeEventBus } from "../core/event-bus.js";
|
|
6
|
+
import { parseJsonObjectPayload } from "../core/json-payload.js";
|
|
6
7
|
import { clioStateDir } from "../core/xdg.js";
|
|
8
|
+
import type { CouncilReport, CouncilReportMember } from "../domains/agents/result-contract.js";
|
|
7
9
|
import { closeAgentLedger, openAgentLedger, renderAgentLedgerBoard } from "../domains/dispatch/agent-ledger-store.js";
|
|
8
10
|
import type { DetachedBatchRun } from "../domains/dispatch/batch-store.js";
|
|
9
11
|
import type { AbortReason, DispatchContract, DispatchRequest } from "../domains/dispatch/contract.js";
|
|
@@ -32,15 +34,19 @@ import {
|
|
|
32
34
|
} from "../domains/dispatch/gate-decisions.js";
|
|
33
35
|
import {
|
|
34
36
|
COMPETE_STANCES,
|
|
37
|
+
COUNCIL_JUDGE_PROMPT,
|
|
35
38
|
type CompeteStance,
|
|
36
39
|
JUDGE_GATE_PROMPT,
|
|
37
40
|
REVIEWER_GATE_PROMPT,
|
|
41
|
+
renderCouncilVoteMemberTask,
|
|
38
42
|
} from "../domains/dispatch/gate-role-prompts.js";
|
|
43
|
+
import { renderDispatchReviewerTask } from "../domains/dispatch/intent-requirements.js";
|
|
39
44
|
import { UNVERIFIABLE_RECEIPT_VERIFICATION } from "../domains/dispatch/receipt-findings.js";
|
|
40
45
|
import { type ReceiptIntegrityResult, verifyReceiptIntegrity } from "../domains/dispatch/receipt-integrity.js";
|
|
41
46
|
import { explainRouteDecision } from "../domains/dispatch/routing-intent.js";
|
|
42
47
|
import type { RunGateProvenance, RunGateSubjectRef, RunPlanProvenance, RunReceipt } from "../domains/dispatch/types.js";
|
|
43
48
|
import { extractRunProvenance, provenanceCompactSuffix } from "../domains/evidence/provenance.js";
|
|
49
|
+
import { summarizeTrustStatus } from "../domains/evidence/trust-projection.js";
|
|
44
50
|
import { adaptRunReceiptTrustStatus } from "../domains/evidence/trust-status.js";
|
|
45
51
|
import type { AutonomyLevel } from "../domains/safety/autonomy.js";
|
|
46
52
|
import {
|
|
@@ -71,7 +77,12 @@ import {
|
|
|
71
77
|
type DispatchRunEventRegistry,
|
|
72
78
|
} from "./dispatch-run-events.js";
|
|
73
79
|
import { runScoutContinuationPlan, scoutPlanAuthorityGranted, scoutTransitionDetail } from "./dispatch-scout.js";
|
|
74
|
-
import type {
|
|
80
|
+
import type {
|
|
81
|
+
DispatchCompeteSettings,
|
|
82
|
+
DispatchCouncilSettings,
|
|
83
|
+
DispatchReviewSettings,
|
|
84
|
+
DispatchToolDeps,
|
|
85
|
+
} from "./dispatch-types.js";
|
|
75
86
|
import type { ToolInvokeOptions, ToolResult, ToolResultDetails } from "./registry.js";
|
|
76
87
|
import { truncateUtf8 } from "./truncate-utf8.js";
|
|
77
88
|
import {
|
|
@@ -487,7 +498,10 @@ function formatDispatchOutput(
|
|
|
487
498
|
// Evidence confidence comes from the sealed receipt. A receipt that fails
|
|
488
499
|
// integrity cannot be read as evidence at all.
|
|
489
500
|
const verification = run.integrity.ok ? receipt.verification : UNVERIFIABLE_RECEIPT_VERIFICATION;
|
|
490
|
-
const trustStatus = adaptRunReceiptTrustStatus(
|
|
501
|
+
const trustStatus = adaptRunReceiptTrustStatus(
|
|
502
|
+
{ ...receipt, verification: verification },
|
|
503
|
+
{ integrity: run.integrity },
|
|
504
|
+
);
|
|
491
505
|
const evidenceSuffix = ` ${receiptEvidenceLabels(receipt, verification, run.integrity).join(" ")}`;
|
|
492
506
|
const routingSuffix =
|
|
493
507
|
run.integrity.ok && receipt.routeDecision !== undefined && receipt.routingIntent !== undefined
|
|
@@ -566,8 +580,13 @@ function dispatchDetails(
|
|
|
566
580
|
// sealed receipt read-only, plus the integrity check so a tampered
|
|
567
581
|
// receipt is machine-visible here too.
|
|
568
582
|
verification: integrity.ok ? receipt.verification : UNVERIFIABLE_RECEIPT_VERIFICATION,
|
|
583
|
+
hostVerification: integrity.ok ? (receipt.hostVerification ?? null) : null,
|
|
569
584
|
receiptIntegrity: integrity,
|
|
570
585
|
trustStatus,
|
|
586
|
+
// The bounded projection sits shallow and flat on purpose: a
|
|
587
|
+
// depth-capped wire (ACP rawOutput) keeps it whole while the
|
|
588
|
+
// canonical status's nested references fall off the end.
|
|
589
|
+
trust: summarizeTrustStatus(trustStatus),
|
|
571
590
|
...(receipt.outcome !== undefined && receipt.outcome !== "succeeded"
|
|
572
591
|
? { outcome: receipt.outcome, outcomeDetail: receipt.outcomeDetail ?? null }
|
|
573
592
|
: {}),
|
|
@@ -620,15 +639,6 @@ function competeCorrelation(
|
|
|
620
639
|
return gateCorrelationOf(subject, decider);
|
|
621
640
|
}
|
|
622
641
|
|
|
623
|
-
function reviewerTask(originalTask: string, builderRunId: string, cycle: number): string {
|
|
624
|
-
return [
|
|
625
|
-
`Review the work of builder run ${builderRunId} (review cycle ${cycle}).`,
|
|
626
|
-
"The builder's final answer is provided as input data; verify it against the workspace, do not trust it blindly.",
|
|
627
|
-
"Original task the builder was given:",
|
|
628
|
-
originalTask,
|
|
629
|
-
].join("\n\n");
|
|
630
|
-
}
|
|
631
|
-
|
|
632
642
|
type ReviewGateSettings = DispatchReviewSettings;
|
|
633
643
|
|
|
634
644
|
const REVIEW_SINGLE_TASK_MESSAGE =
|
|
@@ -775,7 +785,7 @@ async function runReviewGated(
|
|
|
775
785
|
const reviewerRequest: DispatchRequest = {
|
|
776
786
|
agentId: gateDeciderAgentId(review.reviewer),
|
|
777
787
|
executionRole: "reviewer",
|
|
778
|
-
task:
|
|
788
|
+
task: renderDispatchReviewerTask(base.task, builder.receipt.runId, cycle, base.intent),
|
|
779
789
|
systemPrompt: REVIEWER_GATE_PROMPT,
|
|
780
790
|
autonomy: "read-only",
|
|
781
791
|
gate: { role: "reviewer", group, cycle, subjects: [subjectRef(builder.receipt)] },
|
|
@@ -1782,6 +1792,267 @@ function competeResult(
|
|
|
1782
1792
|
};
|
|
1783
1793
|
}
|
|
1784
1794
|
|
|
1795
|
+
const COUNCIL_BRIEFING_MAX_BYTES = 8 * 1024;
|
|
1796
|
+
const COUNCIL_BRIEFING_TRUNCATION = "\n[council peer briefing truncated]";
|
|
1797
|
+
|
|
1798
|
+
/**
|
|
1799
|
+
* The verdict a council answer casts, lower-cased.
|
|
1800
|
+
*
|
|
1801
|
+
* A tally groups by the exact string, so folding case is the difference
|
|
1802
|
+
* between two members who both said yes agreeing and splitting into a
|
|
1803
|
+
* `no_majority` the operator has to read past. Folding happens here, at the one
|
|
1804
|
+
* place a verdict is ever read, so the reported member verdict and the tally
|
|
1805
|
+
* key can never disagree.
|
|
1806
|
+
*/
|
|
1807
|
+
function councilVerdict(text: string): string | undefined {
|
|
1808
|
+
const parsed = parseJsonObjectPayload(text);
|
|
1809
|
+
return parsed.ok && typeof parsed.value.verdict === "string" && parsed.value.verdict.trim().length > 0
|
|
1810
|
+
? parsed.value.verdict.trim().toLowerCase()
|
|
1811
|
+
: undefined;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
function councilJudgeText(text: string): string {
|
|
1815
|
+
const parsed = parseJsonObjectPayload(text);
|
|
1816
|
+
return parsed.ok && typeof parsed.value.text === "string" && parsed.value.text.trim().length > 0
|
|
1817
|
+
? parsed.value.text.trim()
|
|
1818
|
+
: text;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
function boundedCouncilBriefing(parts: ReadonlyArray<string>): string {
|
|
1822
|
+
return truncateUtf8(parts.join("\n\n"), COUNCIL_BRIEFING_MAX_BYTES, COUNCIL_BRIEFING_TRUNCATION);
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
async function runCouncil(
|
|
1826
|
+
deps: RegisteredDispatchToolDeps,
|
|
1827
|
+
base: DispatchRequest,
|
|
1828
|
+
council: DispatchCouncilSettings,
|
|
1829
|
+
timeoutMs: number | undefined,
|
|
1830
|
+
signal: AbortSignal | undefined,
|
|
1831
|
+
): Promise<{ report: CouncilReport; runs: CompletedRun[]; group: string }> {
|
|
1832
|
+
const group = newGateGroupId("council");
|
|
1833
|
+
const runs: CompletedRun[] = [];
|
|
1834
|
+
let prior = new Map<string, CompletedRun>();
|
|
1835
|
+
const runMember = async (request: DispatchRequest): Promise<CompletedRun> => {
|
|
1836
|
+
if (signal?.aborted) throw new Error("council dispatch aborted");
|
|
1837
|
+
const handle = await deps.dispatch.dispatch(request);
|
|
1838
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1839
|
+
if (timeoutMs !== undefined) {
|
|
1840
|
+
timer = setTimeout(
|
|
1841
|
+
() => deps.dispatch.abort(handle.runId, { cause: "timeout", detail: `timed out after ${timeoutMs}ms` }),
|
|
1842
|
+
timeoutMs,
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
const abort = (): void => deps.dispatch.abort(handle.runId);
|
|
1846
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
1847
|
+
try {
|
|
1848
|
+
const [summary, receipt] = await Promise.all([
|
|
1849
|
+
consumeGateSensitiveDispatchEvents(deps, handle.runId, request.agentId, handle.events),
|
|
1850
|
+
handle.finalPromise,
|
|
1851
|
+
]);
|
|
1852
|
+
return completeRun(deps, receipt, summary);
|
|
1853
|
+
} finally {
|
|
1854
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
1855
|
+
signal?.removeEventListener("abort", abort);
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
|
|
1859
|
+
for (let round = 1; round <= council.rounds; round += 1) {
|
|
1860
|
+
const settled = await Promise.all(
|
|
1861
|
+
council.members.map(async (member, index) => {
|
|
1862
|
+
const peerParts: string[] = base.briefing === undefined ? [] : [`[original briefing] ${base.briefing}`];
|
|
1863
|
+
if (round > 1) {
|
|
1864
|
+
for (const peer of council.members) {
|
|
1865
|
+
if (peer.label === member.label) continue;
|
|
1866
|
+
const previous = prior.get(peer.label);
|
|
1867
|
+
if (previous === undefined || isPipelineStepFailure(previous.receipt)) {
|
|
1868
|
+
peerParts.push(`[${peer.label}] failed in prior round; no answer contributed.`);
|
|
1869
|
+
} else {
|
|
1870
|
+
peerParts.push(`[${peer.label}] ${normalizedAssistantText(previous.summary)}`);
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
const position = (round - 1) * council.members.length + index + 1;
|
|
1875
|
+
const pinned = council.resolvedTasks?.find((task) => task.role === "member" && task.position === position);
|
|
1876
|
+
let voteTask = base.task;
|
|
1877
|
+
if (council.synthesis === "vote") {
|
|
1878
|
+
if (pinned !== undefined) voteTask = pinned.task;
|
|
1879
|
+
else if (council.resolvedTasks !== undefined)
|
|
1880
|
+
throw new Error(`dispatch: resolved council plan has no member task at position ${position}`);
|
|
1881
|
+
else voteTask = renderCouncilVoteMemberTask(base.task);
|
|
1882
|
+
}
|
|
1883
|
+
const request: DispatchRequest = {
|
|
1884
|
+
...base,
|
|
1885
|
+
executionRole: "researcher",
|
|
1886
|
+
autonomy: "read-only",
|
|
1887
|
+
toolProfile: "council-read-only",
|
|
1888
|
+
// A vote is a majority over the members' verdict fields, so a vote
|
|
1889
|
+
// council asks each member for one and seals the ballot it asked
|
|
1890
|
+
// for. The seated recipe is untouched: the ask is a task suffix and
|
|
1891
|
+
// the postcondition is an override, so the member is still the
|
|
1892
|
+
// agent the operator seated, and any recipe can be voted with.
|
|
1893
|
+
// Admission already composed the suffix into each resolved member
|
|
1894
|
+
// task, so reuse its exact bytes. The fallback supports dispatch
|
|
1895
|
+
// contracts that have no resolved admission artifact.
|
|
1896
|
+
...(council.synthesis === "vote"
|
|
1897
|
+
? {
|
|
1898
|
+
task: voteTask,
|
|
1899
|
+
resultContractOverride: { kind: "council-ballot" as const },
|
|
1900
|
+
}
|
|
1901
|
+
: {}),
|
|
1902
|
+
target: member.target,
|
|
1903
|
+
...(member.model ? { model: member.model } : {}),
|
|
1904
|
+
...(member.thinking ? { thinkingLevel: member.thinking as NonNullable<DispatchRequest["thinkingLevel"]> } : {}),
|
|
1905
|
+
gate: {
|
|
1906
|
+
role: "member",
|
|
1907
|
+
group,
|
|
1908
|
+
cycle: round,
|
|
1909
|
+
...(round > 1
|
|
1910
|
+
? {
|
|
1911
|
+
subjects: [...prior.entries()]
|
|
1912
|
+
.filter(([label]) => label !== member.label)
|
|
1913
|
+
.map(([, run]) => subjectRef(run.receipt)),
|
|
1914
|
+
}
|
|
1915
|
+
: {}),
|
|
1916
|
+
},
|
|
1917
|
+
council: { group, label: member.label, ...(member.color ? { color: member.color } : {}), round },
|
|
1918
|
+
...(round > 1
|
|
1919
|
+
? { briefing: boundedCouncilBriefing(peerParts) }
|
|
1920
|
+
: base.briefing !== undefined
|
|
1921
|
+
? { briefing: base.briefing }
|
|
1922
|
+
: {}),
|
|
1923
|
+
};
|
|
1924
|
+
return [
|
|
1925
|
+
member.label,
|
|
1926
|
+
await runMember(withResolvedPlanTaskPin(request, pinned, { pinTask: false, pinBriefing: false })),
|
|
1927
|
+
] as const;
|
|
1928
|
+
}),
|
|
1929
|
+
);
|
|
1930
|
+
prior = new Map(settled);
|
|
1931
|
+
runs.push(...settled.map(([, run]) => run));
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
const members: CouncilReportMember[] = runs.map((run) => {
|
|
1935
|
+
const text = normalizedAssistantText(run.summary);
|
|
1936
|
+
const failed = isPipelineStepFailure(run.receipt);
|
|
1937
|
+
const verdict = councilVerdict(text);
|
|
1938
|
+
return {
|
|
1939
|
+
label: run.receipt.council?.label ?? "member",
|
|
1940
|
+
runId: run.receipt.runId,
|
|
1941
|
+
round: run.receipt.council?.round ?? 1,
|
|
1942
|
+
// A ballot's prose lives in its `text` field, so the report carries the
|
|
1943
|
+
// member's answer rather than its wire envelope, the same way the judge
|
|
1944
|
+
// line already does. A member answering any other shape has no `text`
|
|
1945
|
+
// key and keeps its whole answer.
|
|
1946
|
+
answer: councilJudgeText(text),
|
|
1947
|
+
...(verdict !== undefined ? { verdict } : {}),
|
|
1948
|
+
...(failed ? { failed: { reason: pipelineFailureReason(run.receipt) } } : {}),
|
|
1949
|
+
};
|
|
1950
|
+
});
|
|
1951
|
+
const finalMembers = members.filter((member) => member.round === council.rounds && member.failed === undefined);
|
|
1952
|
+
const finalReports = members.filter((member) => member.round === council.rounds);
|
|
1953
|
+
let synthesis: CouncilReport["synthesis"] = { kind: council.synthesis };
|
|
1954
|
+
if (council.synthesis === "vote") {
|
|
1955
|
+
const tally: Record<string, number> = {};
|
|
1956
|
+
for (const member of finalMembers)
|
|
1957
|
+
if (member.verdict !== undefined) tally[member.verdict] = (tally[member.verdict] ?? 0) + 1;
|
|
1958
|
+
const ranked = Object.entries(tally).sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]));
|
|
1959
|
+
const winner = ranked[0];
|
|
1960
|
+
const verdict =
|
|
1961
|
+
winner === undefined ? "no_verdict_field" : winner[1] > finalMembers.length / 2 ? winner[0] : "no_majority";
|
|
1962
|
+
synthesis = { kind: "vote", verdict, tally };
|
|
1963
|
+
} else if (council.synthesis === "judge") {
|
|
1964
|
+
const finalRuns = [...prior.values()];
|
|
1965
|
+
const judgeRequest: DispatchRequest = {
|
|
1966
|
+
agentId: council.judge?.agent ?? base.agentId,
|
|
1967
|
+
executionRole: "judge",
|
|
1968
|
+
task: `Synthesize the council answers for this task:\n\n${base.task}`,
|
|
1969
|
+
briefing: boundedCouncilBriefing(
|
|
1970
|
+
finalReports.map((member) =>
|
|
1971
|
+
member.failed === undefined
|
|
1972
|
+
? `[${member.label}] ${member.answer}`
|
|
1973
|
+
: `[${member.label}] failed; no final answer contributed. Reason: ${member.failed.reason}`,
|
|
1974
|
+
),
|
|
1975
|
+
),
|
|
1976
|
+
systemPrompt: COUNCIL_JUDGE_PROMPT,
|
|
1977
|
+
autonomy: "read-only",
|
|
1978
|
+
toolProfile: "council-read-only",
|
|
1979
|
+
gate: { role: "synthesis", group, cycle: council.rounds, subjects: finalRuns.map((run) => subjectRef(run.receipt)) },
|
|
1980
|
+
council: { group, label: "synthesis", round: council.rounds },
|
|
1981
|
+
...(council.judge?.target ? { target: council.judge.target } : {}),
|
|
1982
|
+
...(council.judge?.model ? { model: council.judge.model } : {}),
|
|
1983
|
+
...(council.judge?.node ? { node: council.judge.node } : {}),
|
|
1984
|
+
...(base.plan ? { plan: base.plan } : {}),
|
|
1985
|
+
...(base.parentToolCallId ? { parentToolCallId: base.parentToolCallId } : {}),
|
|
1986
|
+
};
|
|
1987
|
+
const pinned = council.resolvedTasks?.find((task) => task.role === "synthesis");
|
|
1988
|
+
const judgeRun = await runMember(
|
|
1989
|
+
withResolvedPlanTaskPin(judgeRequest, pinned, { pinTask: false, pinBriefing: false }),
|
|
1990
|
+
);
|
|
1991
|
+
runs.push(judgeRun);
|
|
1992
|
+
const rawText = normalizedAssistantText(judgeRun.summary);
|
|
1993
|
+
const text = councilJudgeText(rawText);
|
|
1994
|
+
const verdict = councilVerdict(rawText);
|
|
1995
|
+
synthesis = {
|
|
1996
|
+
kind: "judge",
|
|
1997
|
+
text,
|
|
1998
|
+
...(verdict !== undefined ? { verdict } : {}),
|
|
1999
|
+
judgeRunId: judgeRun.receipt.runId,
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
if (prior.size > 0 && deps.dispatch.sealCouncilSynthesis !== undefined) {
|
|
2003
|
+
const finalRuns = [...prior.values()];
|
|
2004
|
+
const template = finalRuns[0];
|
|
2005
|
+
if (template === undefined) throw new Error("council synthesis has no final member receipt");
|
|
2006
|
+
// The sealed text is the whole council-report, not the synthesis alone:
|
|
2007
|
+
// /share <synthesis runId> reads this receipt to bring every final
|
|
2008
|
+
// member's labelled answer and the synthesis line into the main context.
|
|
2009
|
+
// A judge council seals it on the same terms: the judge run's own
|
|
2010
|
+
// receipt holds only its {"verdict","text"} payload, which carries no
|
|
2011
|
+
// member answers and no roster labels for the main agent to act on.
|
|
2012
|
+
const text = JSON.stringify({ members, synthesis } satisfies CouncilReport);
|
|
2013
|
+
const receipt = await deps.dispatch.sealCouncilSynthesis({
|
|
2014
|
+
group,
|
|
2015
|
+
round: council.rounds,
|
|
2016
|
+
kind: council.synthesis,
|
|
2017
|
+
text,
|
|
2018
|
+
subjects: finalRuns.map((run) => subjectRef(run.receipt)),
|
|
2019
|
+
template: template.receipt,
|
|
2020
|
+
});
|
|
2021
|
+
runs.push(
|
|
2022
|
+
completeRun(deps, receipt, {
|
|
2023
|
+
count: 0,
|
|
2024
|
+
types: [],
|
|
2025
|
+
lastAssistantText: text,
|
|
2026
|
+
terminalAttemptRunId: receipt.runId,
|
|
2027
|
+
}),
|
|
2028
|
+
);
|
|
2029
|
+
}
|
|
2030
|
+
return { report: { members, synthesis }, runs, group };
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
function councilResult(deps: DispatchToolDeps, outcome: Awaited<ReturnType<typeof runCouncil>>): ToolResult {
|
|
2034
|
+
const sections = outcome.report.members
|
|
2035
|
+
.filter((member) => member.round === Math.max(...outcome.report.members.map((entry) => entry.round)))
|
|
2036
|
+
.map((member) => {
|
|
2037
|
+
const run = outcome.runs.find((entry) => entry.receipt.runId === member.runId);
|
|
2038
|
+
return `[${member.label}] ${run?.receipt.targetId ?? "unknown"}/${run?.receipt.wireModelId ?? "unknown"}\n${member.answer}`;
|
|
2039
|
+
});
|
|
2040
|
+
sections.push(
|
|
2041
|
+
`[synthesis] ${outcome.report.synthesis.kind}\n${outcome.report.synthesis.text ?? outcome.report.synthesis.verdict ?? "none"}`,
|
|
2042
|
+
);
|
|
2043
|
+
const details: ToolResultDetails = {
|
|
2044
|
+
...dispatchDetails(deps, "council", outcome.runs),
|
|
2045
|
+
council: { group: outcome.group, ...outcome.report },
|
|
2046
|
+
};
|
|
2047
|
+
const finalRound = Math.max(...outcome.report.members.map((member) => member.round));
|
|
2048
|
+
const failed =
|
|
2049
|
+
outcome.report.members.some((member) => member.round === finalRound && member.failed !== undefined) ||
|
|
2050
|
+
outcome.runs.some((run) => run.receipt.gate?.role === "synthesis" && isPipelineStepFailure(run.receipt));
|
|
2051
|
+
return failed
|
|
2052
|
+
? { kind: "error", message: sections.join("\n\n"), details }
|
|
2053
|
+
: { kind: "ok", output: sections.join("\n\n"), details };
|
|
2054
|
+
}
|
|
2055
|
+
|
|
1785
2056
|
export async function runDispatchTool(
|
|
1786
2057
|
inputDeps: DispatchToolDeps,
|
|
1787
2058
|
admissionState: DispatchAdmissionState,
|
|
@@ -1856,6 +2127,7 @@ export async function runDispatchTool(
|
|
|
1856
2127
|
const maxOutputBytes = snapshot.maxOutputBytes;
|
|
1857
2128
|
const timeoutMs = snapshot.timeoutMs;
|
|
1858
2129
|
let review = snapshot.review === undefined ? undefined : structuredClone(snapshot.review);
|
|
2130
|
+
let council = snapshot.council === undefined ? undefined : structuredClone(snapshot.council);
|
|
1859
2131
|
|
|
1860
2132
|
// Plan-scale calls are either approved at supervised admission or run
|
|
1861
2133
|
// unstopped at full-auto; every run seals the same plan hash.
|
|
@@ -1888,7 +2160,8 @@ export async function runDispatchTool(
|
|
|
1888
2160
|
};
|
|
1889
2161
|
}
|
|
1890
2162
|
if (resolvedPlan !== null) {
|
|
1891
|
-
const primaryRole =
|
|
2163
|
+
const primaryRole =
|
|
2164
|
+
review !== undefined ? "builder" : mode === "compete" ? "candidate" : mode === "council" ? "member" : "task";
|
|
1892
2165
|
const primaryTasks = resolvedPlan.tasks.filter((task) => task.role === primaryRole);
|
|
1893
2166
|
const executionTasks = primaryRole === "task" ? primaryTasks : primaryTasks.slice(0, 1);
|
|
1894
2167
|
if (executionTasks.length !== requests.length) {
|
|
@@ -1924,6 +2197,7 @@ export async function runDispatchTool(
|
|
|
1924
2197
|
resolvedTasks: resolvedPlan.tasks,
|
|
1925
2198
|
};
|
|
1926
2199
|
}
|
|
2200
|
+
if (council !== undefined) council = { ...council, resolvedTasks: resolvedPlan.tasks };
|
|
1927
2201
|
}
|
|
1928
2202
|
if (planView.planScale) {
|
|
1929
2203
|
const plan: RunPlanProvenance = {
|
|
@@ -1955,15 +2229,17 @@ export async function runDispatchTool(
|
|
|
1955
2229
|
? "a Scout dependency plan drives its stages from this turn"
|
|
1956
2230
|
: mode === "compete"
|
|
1957
2231
|
? "compete holds its judge gate in this turn"
|
|
1958
|
-
:
|
|
1959
|
-
? "
|
|
1960
|
-
:
|
|
1961
|
-
? "a
|
|
1962
|
-
:
|
|
1963
|
-
?
|
|
1964
|
-
:
|
|
1965
|
-
?
|
|
1966
|
-
:
|
|
2232
|
+
: mode === "council"
|
|
2233
|
+
? "council holds its rounds and synthesis in this turn"
|
|
2234
|
+
: review !== undefined
|
|
2235
|
+
? "a review gate holds its cycle state in this turn"
|
|
2236
|
+
: mode === "pipeline" && requests.length > 1
|
|
2237
|
+
? "a pipeline threads each step's output through this turn"
|
|
2238
|
+
: timeoutMs !== undefined
|
|
2239
|
+
? `this call set timeout_ms=${timeoutMs} and nobody would be left to enforce the deadline`
|
|
2240
|
+
: deps.dispatch.detached === undefined
|
|
2241
|
+
? "detached batch records are unavailable in this context"
|
|
2242
|
+
: null;
|
|
1967
2243
|
const background = createBackgroundSwitch();
|
|
1968
2244
|
// Review and Scout both execute under mode=parallel, so the operator-facing
|
|
1969
2245
|
// label names the topology they actually asked for.
|
|
@@ -1994,6 +2270,7 @@ export async function runDispatchTool(
|
|
|
1994
2270
|
topology: executionPlan.topology,
|
|
1995
2271
|
rootTask: executionPlan.rootTask,
|
|
1996
2272
|
maxWorkers: executionPlan.maxWorkers,
|
|
2273
|
+
...(executionPlan.writers === 1 ? { writers: 1 as const } : {}),
|
|
1997
2274
|
onFailure: executionPlan.onFailure,
|
|
1998
2275
|
steps: requireAgentSteps(executionPlan.steps).map((step) => ({
|
|
1999
2276
|
...step,
|
|
@@ -2085,6 +2362,17 @@ export async function runDispatchTool(
|
|
|
2085
2362
|
}
|
|
2086
2363
|
}
|
|
2087
2364
|
|
|
2365
|
+
if (mode === "council") {
|
|
2366
|
+
if (requests.length !== 1 || requests[0] === undefined || council === undefined) {
|
|
2367
|
+
return { kind: "error", message: "dispatch: trusted council settings are unavailable" };
|
|
2368
|
+
}
|
|
2369
|
+
try {
|
|
2370
|
+
return councilResult(deps, await runCouncil(deps, requests[0], council, timeoutMs, options?.signal));
|
|
2371
|
+
} catch (err) {
|
|
2372
|
+
return { kind: "error", message: dispatchErrorMessage(err) };
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2088
2376
|
if (review !== undefined) {
|
|
2089
2377
|
if (requests.length !== 1 || requests[0] === undefined) {
|
|
2090
2378
|
return { kind: "error", message: REVIEW_SINGLE_TASK_MESSAGE };
|
|
@@ -2111,7 +2399,10 @@ export async function runDispatchTool(
|
|
|
2111
2399
|
// plain sequential and no pipeline-input message is sent.
|
|
2112
2400
|
runs = await runSequential(deps, requests, mode, timeoutMs, options?.signal, background);
|
|
2113
2401
|
} else {
|
|
2114
|
-
const batch =
|
|
2402
|
+
const batch =
|
|
2403
|
+
snapshot.writers === 1
|
|
2404
|
+
? await runWriterLimitedBatch(deps, requests, timeoutMs, options?.signal)
|
|
2405
|
+
: await runBatch(deps, requests, timeoutMs, options?.signal, background);
|
|
2115
2406
|
runs = batch.runs;
|
|
2116
2407
|
board = batch.board;
|
|
2117
2408
|
}
|
|
@@ -2370,3 +2661,53 @@ async function runBatch(
|
|
|
2370
2661
|
if (!backgrounded) await closeAgentLedger(ledgerId);
|
|
2371
2662
|
}
|
|
2372
2663
|
}
|
|
2664
|
+
|
|
2665
|
+
/** Admit one checkout writer at a time while every read-only peer remains concurrent. */
|
|
2666
|
+
async function runWriterLimitedBatch(
|
|
2667
|
+
deps: RegisteredDispatchToolDeps,
|
|
2668
|
+
requests: ReadonlyArray<DispatchRequest>,
|
|
2669
|
+
timeoutMs: number | undefined,
|
|
2670
|
+
signal: AbortSignal | undefined,
|
|
2671
|
+
): Promise<BatchOutcome> {
|
|
2672
|
+
const specs = new Map(deps.getAgentSpecs().map((spec) => [spec.id, spec]));
|
|
2673
|
+
const readers = requests.filter((request) => specs.get(request.agentId)?.capabilityClass === "read-only");
|
|
2674
|
+
const writers = requests.filter((request) => specs.get(request.agentId)?.capabilityClass !== "read-only");
|
|
2675
|
+
const ledgerId = newGateGroupId("ledger");
|
|
2676
|
+
await openAgentLedger(ledgerId);
|
|
2677
|
+
const activeIds = new Set<string>();
|
|
2678
|
+
const completed = new Map<DispatchRequest, CompletedRun>();
|
|
2679
|
+
const start = async (request: DispatchRequest): Promise<CompletedRun> => {
|
|
2680
|
+
const ledgered = { ...request, ledger: { id: ledgerId, sequence: 0 } };
|
|
2681
|
+
const handle = await deps.dispatch.dispatch(ledgered);
|
|
2682
|
+
activeIds.add(handle.runId);
|
|
2683
|
+
const registered = deps.runEvents.registerSingle(handle, request.agentId, fallbackProgressBus(deps));
|
|
2684
|
+
const value = await registered.completion;
|
|
2685
|
+
activeIds.delete(handle.runId);
|
|
2686
|
+
const run = completeRun(deps, value.receipt, value.summary);
|
|
2687
|
+
completed.set(request, run);
|
|
2688
|
+
return run;
|
|
2689
|
+
};
|
|
2690
|
+
const abort = (bySignal: boolean): void => {
|
|
2691
|
+
const reason = bySignal ? undefined : ({ cause: "timeout", detail: `timed out after ${timeoutMs}ms` } as const);
|
|
2692
|
+
for (const runId of activeIds) deps.dispatch.abort(runId, reason);
|
|
2693
|
+
};
|
|
2694
|
+
const onSignalAbort = (): void => abort(true);
|
|
2695
|
+
const timer = timeoutMs === undefined ? null : setTimeout(() => abort(false), timeoutMs);
|
|
2696
|
+
signal?.addEventListener("abort", onSignalAbort, { once: true });
|
|
2697
|
+
try {
|
|
2698
|
+
const readerPromises = readers.map((request) => start(request));
|
|
2699
|
+
for (const writer of writers) await start(writer);
|
|
2700
|
+
await Promise.all(readerPromises);
|
|
2701
|
+
return {
|
|
2702
|
+
runs: requests.flatMap((request) => {
|
|
2703
|
+
const run = completed.get(request);
|
|
2704
|
+
return run === undefined ? [] : [run];
|
|
2705
|
+
}),
|
|
2706
|
+
board: renderAgentLedgerBoard(ledgerId),
|
|
2707
|
+
};
|
|
2708
|
+
} finally {
|
|
2709
|
+
if (timer !== null) clearTimeout(timer);
|
|
2710
|
+
signal?.removeEventListener("abort", onSignalAbort);
|
|
2711
|
+
await closeAgentLedger(ledgerId);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
@@ -8,7 +8,23 @@ import type { DispatchBackgroundRegistry } from "./dispatch-background.js";
|
|
|
8
8
|
import type { DispatchPlanView, ResolvedDispatchPlanArtifact } from "./dispatch-plan.js";
|
|
9
9
|
import type { DispatchRunEventRegistry } from "./dispatch-run-events.js";
|
|
10
10
|
|
|
11
|
-
export type DispatchMode = "parallel" | "sequential" | "pipeline" | "compete";
|
|
11
|
+
export type DispatchMode = "parallel" | "sequential" | "pipeline" | "compete" | "council";
|
|
12
|
+
|
|
13
|
+
export interface DispatchCouncilMember {
|
|
14
|
+
label: string;
|
|
15
|
+
target: string;
|
|
16
|
+
model?: string;
|
|
17
|
+
thinking?: string;
|
|
18
|
+
color?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DispatchCouncilSettings {
|
|
22
|
+
members: DispatchCouncilMember[];
|
|
23
|
+
synthesis: "none" | "judge" | "vote";
|
|
24
|
+
rounds: number;
|
|
25
|
+
judge?: DispatchCompeteSettings["judge"];
|
|
26
|
+
resolvedTasks?: ReadonlyArray<ResolvedDispatchPlanArtifact["tasks"][number]>;
|
|
27
|
+
}
|
|
12
28
|
|
|
13
29
|
export interface DispatchReviewSettings {
|
|
14
30
|
reviewer?: string;
|
|
@@ -33,8 +49,10 @@ export interface DispatchRunExecutionSnapshot {
|
|
|
33
49
|
readonly planView: DispatchPlanView;
|
|
34
50
|
readonly requests: ReadonlyArray<DispatchRequest>;
|
|
35
51
|
readonly mode: DispatchMode;
|
|
52
|
+
readonly writers: 1 | undefined;
|
|
36
53
|
readonly review: DispatchReviewSettings | undefined;
|
|
37
54
|
readonly compete: DispatchCompeteSettings | undefined;
|
|
55
|
+
readonly council: DispatchCouncilSettings | undefined;
|
|
38
56
|
readonly detach: boolean;
|
|
39
57
|
readonly timeoutMs: number | undefined;
|
|
40
58
|
readonly maxOutputBytes: number;
|
|
@@ -74,4 +92,5 @@ export interface DispatchToolDeps {
|
|
|
74
92
|
getAgentRoleFacts?: AgentRoleFactsResolver;
|
|
75
93
|
getAutonomy?: () => AutonomyLevel;
|
|
76
94
|
getCostCeilingUsd?: () => number;
|
|
95
|
+
getWorkerRosters?: () => Readonly<Record<string, { members: ReadonlyArray<DispatchCouncilMember> }>>;
|
|
77
96
|
}
|
package/src/tools/dispatch.ts
CHANGED
|
@@ -25,6 +25,16 @@ export type { DispatchToolDeps } from "./dispatch-types.js";
|
|
|
25
25
|
|
|
26
26
|
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
27
27
|
|
|
28
|
+
const CouncilMemberSchema = Type.Object(
|
|
29
|
+
{
|
|
30
|
+
label: Type.String({ pattern: "^[a-z][a-z0-9_-]{0,31}$" }),
|
|
31
|
+
target: Type.String(),
|
|
32
|
+
model: Type.Optional(Type.String()),
|
|
33
|
+
thinking: Type.Optional(StringEnum(THINKING_LEVELS)),
|
|
34
|
+
},
|
|
35
|
+
{ additionalProperties: false },
|
|
36
|
+
);
|
|
37
|
+
|
|
28
38
|
const DispatchBudgetPhaseSchema = Type.Object(
|
|
29
39
|
{
|
|
30
40
|
toolCalls: Type.Integer({ minimum: 1 }),
|
|
@@ -46,6 +56,34 @@ const DispatchBudgetSchema = Type.Object(
|
|
|
46
56
|
},
|
|
47
57
|
);
|
|
48
58
|
|
|
59
|
+
const DispatchVerificationSchema = Type.Array(
|
|
60
|
+
Type.Object(
|
|
61
|
+
{
|
|
62
|
+
check: Type.String({ description: "Declared verification check id. This is not a shell command." }),
|
|
63
|
+
timeout_ms: Type.Optional(
|
|
64
|
+
Type.Integer({ minimum: 1, description: "Requested timeout within the declared check bounds." }),
|
|
65
|
+
),
|
|
66
|
+
},
|
|
67
|
+
{ additionalProperties: false },
|
|
68
|
+
),
|
|
69
|
+
{ maxItems: 8 },
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const DispatchIntentSchema = Type.Object(
|
|
73
|
+
{
|
|
74
|
+
read_roots: Type.Optional(Type.Array(Type.String(), { maxItems: 32 })),
|
|
75
|
+
write_roots: Type.Optional(Type.Array(Type.String(), { maxItems: 32 })),
|
|
76
|
+
relevant_paths: Type.Optional(Type.Array(Type.String(), { maxItems: 32 })),
|
|
77
|
+
expected_outputs: Type.Optional(Type.Array(Type.String(), { maxItems: 32 })),
|
|
78
|
+
verification: Type.Optional(DispatchVerificationSchema),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
additionalProperties: false,
|
|
82
|
+
description:
|
|
83
|
+
"Typed repository-relative path and output intent. Verification checks are declared ids from package scripts or .clio-coder/verifiers.yaml, not shell commands.",
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
|
|
49
87
|
/**
|
|
50
88
|
* Stable, lightweight dispatch surface. Admission remains synchronous so the
|
|
51
89
|
* policy decision and provisional reservation are bound to the exact argument
|
|
@@ -131,17 +169,42 @@ export function createDispatchTool(
|
|
|
131
169
|
model: Type.Optional(Type.String()),
|
|
132
170
|
node: Type.Optional(Type.String({ description: "Fleet node pin: local or a fleet.nodes id." })),
|
|
133
171
|
cwd: Type.Optional(Type.String()),
|
|
172
|
+
worktree: Type.Optional(Type.Literal(true, { description: "Run this writer in an isolated git worktree." })),
|
|
173
|
+
apply: Type.Optional(
|
|
174
|
+
StringEnum(["merge", "preserve"], { description: "Merge successful work by default, or preserve its branch." }),
|
|
175
|
+
),
|
|
176
|
+
intent: Type.Optional(DispatchIntentSchema),
|
|
177
|
+
gate: Type.Optional(
|
|
178
|
+
Type.String({
|
|
179
|
+
description: "Shorthand for one intent.verification declared check id. This is not a shell command.",
|
|
180
|
+
}),
|
|
181
|
+
),
|
|
134
182
|
}),
|
|
135
183
|
]),
|
|
136
184
|
{ description: "Tasks to dispatch; a single object or string is accepted and wrapped." },
|
|
137
185
|
),
|
|
138
186
|
),
|
|
139
187
|
mode: Type.Optional(
|
|
140
|
-
StringEnum(["parallel", "sequential", "pipeline", "compete"], {
|
|
188
|
+
StringEnum(["parallel", "sequential", "pipeline", "compete", "council"], {
|
|
141
189
|
description:
|
|
142
190
|
"Run tasks concurrently (default), one at a time, as a pipeline where each task receives the previous task's output as input data, or as a compete where N candidates build the same single task in scratch worktrees and a judge picks the winner.",
|
|
143
191
|
}),
|
|
144
192
|
),
|
|
193
|
+
roster: Type.Optional(Type.String({ description: "Configured workers.rosters name for council mode." })),
|
|
194
|
+
members: Type.Optional(Type.Array(CouncilMemberSchema, { minItems: 2, maxItems: 5 })),
|
|
195
|
+
synthesis: Type.Optional(StringEnum(["none", "judge", "vote"] as const)),
|
|
196
|
+
rounds: Type.Optional(Type.Integer({ minimum: 1, maximum: 3 })),
|
|
197
|
+
writers: Type.Optional(
|
|
198
|
+
Type.Literal(1, {
|
|
199
|
+
description: "Serialize writer admission in declared task order while readers continue concurrently.",
|
|
200
|
+
}),
|
|
201
|
+
),
|
|
202
|
+
worktree: Type.Optional(
|
|
203
|
+
Type.Literal(true, { description: "Run a singular writer task in an isolated git worktree." }),
|
|
204
|
+
),
|
|
205
|
+
apply: Type.Optional(
|
|
206
|
+
StringEnum(["merge", "preserve"], { description: "Merge successful work by default, or preserve its branch." }),
|
|
207
|
+
),
|
|
145
208
|
detach: Type.Optional(
|
|
146
209
|
Type.Boolean({
|
|
147
210
|
description:
|
|
@@ -200,7 +263,8 @@ export function createDispatchTool(
|
|
|
200
263
|
),
|
|
201
264
|
agent: Type.Optional(
|
|
202
265
|
Type.String({
|
|
203
|
-
description:
|
|
266
|
+
description:
|
|
267
|
+
"Default agent recipe for string tasks, or auto for bounded agent selection (default coder; researcher for mode council).",
|
|
204
268
|
}),
|
|
205
269
|
),
|
|
206
270
|
briefing: Type.Optional(
|
|
@@ -208,6 +272,12 @@ export function createDispatchTool(
|
|
|
208
272
|
description: `Separate bounded parent context/data for task, or the shared default for tasks; never worker instructions and never a task replacement. Max ${DISPATCH_BRIEFING_MAX_BYTES} UTF-8 bytes.`,
|
|
209
273
|
}),
|
|
210
274
|
),
|
|
275
|
+
intent: Type.Optional(DispatchIntentSchema),
|
|
276
|
+
gate: Type.Optional(
|
|
277
|
+
Type.String({
|
|
278
|
+
description: "Shorthand for one intent.verification declared check id. This is not a shell command.",
|
|
279
|
+
}),
|
|
280
|
+
),
|
|
211
281
|
persona: Type.Optional(
|
|
212
282
|
Type.String({ description: "Default ad-hoc specialist persona for dispatched tasks, max 8000 chars." }),
|
|
213
283
|
),
|