@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
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import { resolve } from "node:path";
|
|
1
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
2
2
|
import type { DispatchPlanTaskResolution, DispatchRequest } from "../domains/dispatch/contract.js";
|
|
3
3
|
import type { ExecutionPlan } from "../domains/dispatch/execution-plan.js";
|
|
4
4
|
import { gateDeciderAgentId } from "../domains/dispatch/execution-role.js";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
COUNCIL_JUDGE_PROMPT,
|
|
7
|
+
JUDGE_GATE_PROMPT,
|
|
8
|
+
REVIEWER_GATE_PROMPT,
|
|
9
|
+
renderCouncilVoteMemberTask,
|
|
10
|
+
} from "../domains/dispatch/gate-role-prompts.js";
|
|
11
|
+
import { normalizeDispatchIntent } from "../domains/dispatch/intent.js";
|
|
12
|
+
import { renderDispatchReviewerTask } from "../domains/dispatch/intent-requirements.js";
|
|
6
13
|
import { approvedRouteCandidates } from "../domains/dispatch/route-approval.js";
|
|
7
14
|
import { defaultRoutingIntent } from "../domains/dispatch/routing-intent.js";
|
|
8
15
|
import type { DispatchFailoverMode } from "../domains/dispatch/validation.js";
|
|
@@ -29,12 +36,15 @@ import {
|
|
|
29
36
|
} from "./dispatch-scout-admission.js";
|
|
30
37
|
import type {
|
|
31
38
|
DispatchCompeteSettings,
|
|
39
|
+
DispatchCouncilSettings,
|
|
32
40
|
DispatchExecutionSnapshot,
|
|
33
41
|
DispatchMode,
|
|
34
42
|
DispatchReviewSettings,
|
|
35
43
|
DispatchToolDeps,
|
|
36
44
|
} from "./dispatch-types.js";
|
|
37
45
|
import type { ToolSpec } from "./registry.js";
|
|
46
|
+
import { gitCheckoutRoot } from "./task-worktree.js";
|
|
47
|
+
import { discoverDeclaredChecks } from "./verify/scripts.js";
|
|
38
48
|
|
|
39
49
|
/**
|
|
40
50
|
* The six identity-sensitive stores which bind synchronous admission to the
|
|
@@ -68,18 +78,67 @@ const COMPETE_SINGLE_TASK_MESSAGE = "dispatch: compete requires exactly one task
|
|
|
68
78
|
const COMPETE_NO_REVIEW_MESSAGE = "dispatch: compete has its own judge and cannot combine with review";
|
|
69
79
|
const COMPETE_MIN_CANDIDATES = 2;
|
|
70
80
|
const COMPETE_MAX_CANDIDATES = 4;
|
|
81
|
+
const COUNCIL_LABEL = /^[a-z][a-z0-9_-]{0,31}$/u;
|
|
71
82
|
|
|
72
|
-
function
|
|
73
|
-
|
|
83
|
+
function councilSettingsFromArgs(
|
|
84
|
+
args: Record<string, unknown>,
|
|
85
|
+
deps: DispatchToolDeps,
|
|
86
|
+
): { ok: true; council: DispatchCouncilSettings } | { ok: false; message: string } {
|
|
87
|
+
const rosterName = stringArg(args, "roster");
|
|
88
|
+
const hasMembers = Object.hasOwn(args, "members");
|
|
89
|
+
if ((rosterName === undefined) === !hasMembers) {
|
|
90
|
+
return { ok: false, message: "dispatch: council requires exactly one of roster or members" };
|
|
91
|
+
}
|
|
92
|
+
let members: DispatchCouncilSettings["members"];
|
|
93
|
+
if (rosterName !== undefined) {
|
|
94
|
+
const roster = deps.getWorkerRosters?.()[rosterName];
|
|
95
|
+
if (roster === undefined) return { ok: false, message: `council_roster_unknown: ${rosterName}` };
|
|
96
|
+
members = roster.members.map((member) => ({ ...member }));
|
|
97
|
+
} else {
|
|
98
|
+
if (!Array.isArray(args.members)) return { ok: false, message: "council_members_out_of_range" };
|
|
99
|
+
members = [];
|
|
100
|
+
for (const raw of args.members) {
|
|
101
|
+
if (!isRecord(raw)) return { ok: false, message: "council_members_out_of_range" };
|
|
102
|
+
const label = stringArg(raw, "label");
|
|
103
|
+
const target = stringArg(raw, "target");
|
|
104
|
+
if (label === undefined || target === undefined || !COUNCIL_LABEL.test(label)) {
|
|
105
|
+
return { ok: false, message: "council_members_out_of_range" };
|
|
106
|
+
}
|
|
107
|
+
const model = stringArg(raw, "model");
|
|
108
|
+
const thinking = stringArg(raw, "thinking");
|
|
109
|
+
members.push({ label, target, ...(model ? { model } : {}), ...(thinking ? { thinking } : {}) });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (members.length < 2 || members.length > 5) return { ok: false, message: "council_members_out_of_range" };
|
|
113
|
+
const labels = new Set<string>();
|
|
114
|
+
for (const member of members) {
|
|
115
|
+
if (labels.has(member.label)) return { ok: false, message: `council_member_label_duplicate: ${member.label}` };
|
|
116
|
+
labels.add(member.label);
|
|
117
|
+
}
|
|
118
|
+
const synthesis = args.synthesis === undefined ? "none" : args.synthesis;
|
|
119
|
+
if (synthesis !== "none" && synthesis !== "judge" && synthesis !== "vote") {
|
|
120
|
+
return { ok: false, message: "dispatch: synthesis must be none, judge, or vote" };
|
|
121
|
+
}
|
|
122
|
+
if (args.judge !== undefined && synthesis !== "judge") {
|
|
123
|
+
return { ok: false, message: "council_synthesis_requires_judge_settings" };
|
|
124
|
+
}
|
|
125
|
+
const rounds = args.rounds === undefined ? 1 : args.rounds;
|
|
126
|
+
if (!Number.isInteger(rounds) || Number(rounds) < 1 || Number(rounds) > 3) {
|
|
127
|
+
return { ok: false, message: "dispatch: rounds must be an integer 1..3" };
|
|
128
|
+
}
|
|
129
|
+
const judge = isRecord(args.judge)
|
|
130
|
+
? Object.fromEntries(
|
|
131
|
+
["agent", "model", "target", "node"].flatMap((key) => {
|
|
132
|
+
const value = stringArg(args.judge as Record<string, unknown>, key);
|
|
133
|
+
return value === undefined ? [] : [[key, value]];
|
|
134
|
+
}),
|
|
135
|
+
)
|
|
136
|
+
: undefined;
|
|
137
|
+
return { ok: true, council: { members, synthesis, rounds: Number(rounds), ...(judge ? { judge } : {}) } };
|
|
74
138
|
}
|
|
75
139
|
|
|
76
|
-
function
|
|
77
|
-
return
|
|
78
|
-
`Review the work of builder run ${builderRunId} (review cycle ${cycle}).`,
|
|
79
|
-
"The builder's final answer is provided as input data; verify it against the workspace, do not trust it blindly.",
|
|
80
|
-
"Original task the builder was given:",
|
|
81
|
-
originalTask,
|
|
82
|
-
].join("\n\n");
|
|
140
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
141
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
83
142
|
}
|
|
84
143
|
|
|
85
144
|
function reviewSettingsFromArgs(
|
|
@@ -165,6 +224,28 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
165
224
|
approvedAuthorities: ["read-only", "verification", "artifact-write", "workspace-edit"],
|
|
166
225
|
authorityBasis: deps.getAutonomy?.() === "full-auto" ? "full-auto-policy" : "operator-plan-approval",
|
|
167
226
|
},
|
|
227
|
+
resolveIntent(rawIntent, cwd) {
|
|
228
|
+
const discovery = discoverDeclaredChecks(cwd);
|
|
229
|
+
if (!discovery.ok) return { ok: false, message: `verification_catalog_invalid: ${discovery.reason}` };
|
|
230
|
+
const declared = discovery.sources.flatMap((source) => source.checks);
|
|
231
|
+
const byId = new Map(declared.map((check) => [check.id, check]));
|
|
232
|
+
const normalized = normalizeDispatchIntent(rawIntent, byId);
|
|
233
|
+
if (!normalized.ok) return { ok: false, message: `${normalized.reason}: ${normalized.message}` };
|
|
234
|
+
return {
|
|
235
|
+
ok: true,
|
|
236
|
+
intent: normalized.intent,
|
|
237
|
+
resolvedVerification: normalized.intent.verification.map((entry) => {
|
|
238
|
+
const check = byId.get(entry.check);
|
|
239
|
+
if (check === undefined) throw new Error(`resolved verification check '${entry.check}' disappeared`);
|
|
240
|
+
return {
|
|
241
|
+
check: entry.check,
|
|
242
|
+
argv: [...check.command],
|
|
243
|
+
cwd: resolve(process.cwd(), check.cwd),
|
|
244
|
+
timeoutMs: entry.timeoutMs,
|
|
245
|
+
};
|
|
246
|
+
}),
|
|
247
|
+
};
|
|
248
|
+
},
|
|
168
249
|
});
|
|
169
250
|
const trustExecution = (args: Record<string, unknown>, snapshot: DispatchExecutionSnapshot) => {
|
|
170
251
|
state.trustedExecutionSnapshots.set(args, deepFreeze(structuredClone(snapshot)));
|
|
@@ -172,11 +253,15 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
172
253
|
};
|
|
173
254
|
const dispatchExecutionSnapshot = (
|
|
174
255
|
args: Record<string, unknown>,
|
|
175
|
-
fields: Pick<
|
|
256
|
+
fields: Pick<
|
|
257
|
+
DispatchExecutionSnapshot & { kind: "dispatch" },
|
|
258
|
+
"requests" | "mode" | "review" | "compete" | "council"
|
|
259
|
+
>,
|
|
176
260
|
): DispatchExecutionSnapshot => ({
|
|
177
261
|
kind: "dispatch",
|
|
178
262
|
planView: describeDispatchPlan(args),
|
|
179
263
|
...fields,
|
|
264
|
+
writers: args.writers === 1 ? 1 : undefined,
|
|
180
265
|
detach: args.detach === true,
|
|
181
266
|
timeoutMs: timeoutMsArg(args),
|
|
182
267
|
maxOutputBytes: maxOutputBytesArg(args),
|
|
@@ -217,6 +302,9 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
217
302
|
): ResolvedPlanTask => {
|
|
218
303
|
const resolution = deps.dispatch.preview?.(request);
|
|
219
304
|
if (resolution === undefined) throw new Error("dispatch preview is unavailable");
|
|
305
|
+
if (request.resolvedVerification?.length && resolution.runtimeId === "claude-code") {
|
|
306
|
+
throw new Error("verification_unsupported_runtime: claude-code subprocess dispatch cannot run host verification");
|
|
307
|
+
}
|
|
220
308
|
const pinned = request.node !== undefined || request.target !== undefined || request.model !== undefined;
|
|
221
309
|
const approvedCandidates =
|
|
222
310
|
pinned || request.failover !== "approved"
|
|
@@ -229,6 +317,15 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
229
317
|
agent: resolution.agentId,
|
|
230
318
|
task: request.task,
|
|
231
319
|
...(request.briefing !== undefined ? { briefing: request.briefing } : {}),
|
|
320
|
+
...(request.worktree === true
|
|
321
|
+
? {
|
|
322
|
+
worktree: true as const,
|
|
323
|
+
apply: request.apply ?? "merge",
|
|
324
|
+
...(gitCheckoutRoot(resolve(request.cwd ?? process.cwd())) === null
|
|
325
|
+
? {}
|
|
326
|
+
: { worktreeDestination: gitCheckoutRoot(resolve(request.cwd ?? process.cwd())) as string }),
|
|
327
|
+
}
|
|
328
|
+
: {}),
|
|
232
329
|
target: resolution.targetId,
|
|
233
330
|
model: resolution.wireModelId,
|
|
234
331
|
node: resolution.node.id,
|
|
@@ -248,6 +345,10 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
248
345
|
...(failover === "approved" ? { allowedCandidates: approvedCandidates.map((candidate) => ({ ...candidate })) } : {}),
|
|
249
346
|
role,
|
|
250
347
|
position,
|
|
348
|
+
...(request.intent !== undefined ? { intent: structuredClone(request.intent) } : {}),
|
|
349
|
+
...(request.resolvedVerification !== undefined
|
|
350
|
+
? { resolvedVerification: request.resolvedVerification.map((check) => ({ ...check, argv: [...check.argv] })) }
|
|
351
|
+
: {}),
|
|
251
352
|
};
|
|
252
353
|
state.taskResolutions.set(task, resolution);
|
|
253
354
|
return task;
|
|
@@ -269,6 +370,8 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
269
370
|
if (topology === "fleet") return task.wave ?? index;
|
|
270
371
|
if (topology === "parallel" || topology === "detached") return 0;
|
|
271
372
|
if (topology === "compete") return task.role === "judge" ? 1 : 0;
|
|
373
|
+
if (topology === "council")
|
|
374
|
+
return task.role === "synthesis" ? (task.council?.rounds ?? 1) : (task.council?.round ?? 1) - 1;
|
|
272
375
|
return index;
|
|
273
376
|
};
|
|
274
377
|
const markReservedPlan = (args: Record<string, unknown>, artifact: ResolvedDispatchPlanArtifact) => {
|
|
@@ -286,7 +389,7 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
286
389
|
};
|
|
287
390
|
});
|
|
288
391
|
const reservation = deps.dispatch.reservations.prepare({
|
|
289
|
-
topology: artifact.topology === "fleet" ? "parallel" : artifact.topology,
|
|
392
|
+
topology: artifact.topology === "fleet" || artifact.topology === "council" ? "parallel" : artifact.topology,
|
|
290
393
|
tasks,
|
|
291
394
|
});
|
|
292
395
|
try {
|
|
@@ -333,6 +436,7 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
333
436
|
mode: "parallel",
|
|
334
437
|
review: undefined,
|
|
335
438
|
compete: undefined,
|
|
439
|
+
council: undefined,
|
|
336
440
|
}),
|
|
337
441
|
);
|
|
338
442
|
state.trustedExecutionPlans.set(marked, deepFreeze(structuredClone(prepared.executionPlan)));
|
|
@@ -375,10 +479,13 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
375
479
|
}
|
|
376
480
|
const parsed = parseRequests(args);
|
|
377
481
|
if (!parsed.ok) return shapeRejection(args, parsed.message);
|
|
378
|
-
if (
|
|
482
|
+
if (
|
|
483
|
+
args.mode !== undefined &&
|
|
484
|
+
!["parallel", "sequential", "pipeline", "compete", "council"].includes(String(args.mode))
|
|
485
|
+
) {
|
|
379
486
|
return shapeRejection(
|
|
380
487
|
args,
|
|
381
|
-
`dispatch: mode must be parallel, sequential, pipeline, or
|
|
488
|
+
`dispatch: mode must be parallel, sequential, pipeline, compete, or council; got '${String(args.mode)}'`,
|
|
382
489
|
);
|
|
383
490
|
}
|
|
384
491
|
const mode: DispatchMode =
|
|
@@ -388,7 +495,30 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
388
495
|
? "pipeline"
|
|
389
496
|
: args.mode === "compete"
|
|
390
497
|
? "compete"
|
|
391
|
-
: "
|
|
498
|
+
: args.mode === "council"
|
|
499
|
+
? "council"
|
|
500
|
+
: "parallel";
|
|
501
|
+
if (args.writers !== undefined && args.writers !== 1) {
|
|
502
|
+
return shapeRejection(args, "dispatch: writers must be 1 when present");
|
|
503
|
+
}
|
|
504
|
+
if (args.writers === 1 && mode !== "parallel") {
|
|
505
|
+
return shapeRejection(args, "dispatch: writers is supported only for parallel dispatch");
|
|
506
|
+
}
|
|
507
|
+
const parentCheckout = gitCheckoutRoot(process.cwd());
|
|
508
|
+
for (const request of parsed.requests) {
|
|
509
|
+
if (request.worktree !== true) continue;
|
|
510
|
+
if (mode === "compete") return shapeRejection(args, "worktree_compete_incompatible");
|
|
511
|
+
if (parentCheckout === null) return shapeRejection(args, "worktree_non_git_checkout");
|
|
512
|
+
const spec = deps.getAgentSpecs().find((candidate) => candidate.id === request.agentId);
|
|
513
|
+
if (spec?.capabilityClass === "read-only") return shapeRejection(args, "worktree_read_only_agent");
|
|
514
|
+
if (request.cwd !== undefined) {
|
|
515
|
+
const requested = resolve(request.cwd);
|
|
516
|
+
const rel = relative(parentCheckout, requested);
|
|
517
|
+
if (rel === ".." || rel.startsWith("../") || isAbsolute(rel)) {
|
|
518
|
+
return shapeRejection(args, "worktree_cwd_outside_checkout");
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
392
522
|
const reviewResult = reviewSettingsFromArgs(args);
|
|
393
523
|
if (!reviewResult.ok) return shapeRejection(args, reviewResult.message);
|
|
394
524
|
if (reviewResult.review !== undefined) {
|
|
@@ -401,13 +531,28 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
401
531
|
if (mode === "compete" && (parsed.requests.length !== 1 || parsed.requests[0] === undefined)) {
|
|
402
532
|
return shapeRejection(args, COMPETE_SINGLE_TASK_MESSAGE);
|
|
403
533
|
}
|
|
534
|
+
if (mode === "compete" && parsed.requests.some((request) => (request.resolvedVerification?.length ?? 0) > 0)) {
|
|
535
|
+
return shapeRejection(args, "verification_unsupported_for_mode: compete requests cannot run host verification");
|
|
536
|
+
}
|
|
404
537
|
const competeResult = mode === "compete" ? competeSettingsFromArgs(args) : undefined;
|
|
405
538
|
if (competeResult !== undefined && !competeResult.ok) return shapeRejection(args, competeResult.message);
|
|
539
|
+
const councilResult = mode === "council" ? councilSettingsFromArgs(args, deps) : undefined;
|
|
540
|
+
if (councilResult !== undefined && !councilResult.ok) return shapeRejection(args, councilResult.message);
|
|
541
|
+
if (mode === "council") {
|
|
542
|
+
if (parsed.requests.length !== 1 || parsed.requests[0] === undefined) {
|
|
543
|
+
return shapeRejection(args, "dispatch: council requires exactly one task");
|
|
544
|
+
}
|
|
545
|
+
if (reviewResult.review !== undefined) return shapeRejection(args, "dispatch: council cannot combine with review");
|
|
546
|
+
if ((parsed.requests[0].resolvedVerification?.length ?? 0) > 0) {
|
|
547
|
+
return shapeRejection(args, "council_verification_unsupported");
|
|
548
|
+
}
|
|
549
|
+
}
|
|
406
550
|
const snapshotFields = {
|
|
407
551
|
requests: parsed.requests,
|
|
408
552
|
mode,
|
|
409
553
|
review: reviewResult.review,
|
|
410
554
|
compete: competeResult?.ok === true ? competeResult.compete : undefined,
|
|
555
|
+
council: councilResult?.ok === true ? councilResult.council : undefined,
|
|
411
556
|
};
|
|
412
557
|
if (deps.dispatch.preview === undefined) {
|
|
413
558
|
const marked = markPrepared(args);
|
|
@@ -432,7 +577,7 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
432
577
|
{
|
|
433
578
|
agentId: gateDeciderAgentId(reviewResult.review.reviewer),
|
|
434
579
|
executionRole: "reviewer",
|
|
435
|
-
task:
|
|
580
|
+
task: renderDispatchReviewerTask(base.task, subject.runId, cycle, base.intent),
|
|
436
581
|
systemPrompt: REVIEWER_GATE_PROMPT,
|
|
437
582
|
autonomy: "read-only",
|
|
438
583
|
gate: { role: "reviewer", group: "plan-preview", cycle, subjects: [subject] },
|
|
@@ -480,6 +625,81 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
480
625
|
1,
|
|
481
626
|
),
|
|
482
627
|
);
|
|
628
|
+
} else if (mode === "council") {
|
|
629
|
+
const council = councilResult?.ok === true ? councilResult.council : undefined;
|
|
630
|
+
const base = parsed.requests[0];
|
|
631
|
+
if (council === undefined || base === undefined)
|
|
632
|
+
throw new Error("dispatch: council admission settings are unavailable");
|
|
633
|
+
const subjects: Array<{ runId: string; digest: null }> = [];
|
|
634
|
+
for (let round = 1; round <= council.rounds; round += 1) {
|
|
635
|
+
for (const [index, member] of council.members.entries()) {
|
|
636
|
+
const memberRequest: DispatchRequest = {
|
|
637
|
+
...base,
|
|
638
|
+
executionRole: "researcher",
|
|
639
|
+
autonomy: "read-only",
|
|
640
|
+
toolProfile: "council-read-only",
|
|
641
|
+
// The runner reuses this composed task from the resolved plan, so
|
|
642
|
+
// the approval artifact the plan hash binds shows the operator the
|
|
643
|
+
// exact ballot the member is asked to cast.
|
|
644
|
+
...(council.synthesis === "vote"
|
|
645
|
+
? {
|
|
646
|
+
task: renderCouncilVoteMemberTask(base.task),
|
|
647
|
+
resultContractOverride: { kind: "council-ballot" as const },
|
|
648
|
+
}
|
|
649
|
+
: {}),
|
|
650
|
+
target: member.target,
|
|
651
|
+
...(member.model ? { model: member.model } : {}),
|
|
652
|
+
...(member.thinking ? { thinkingLevel: member.thinking as NonNullable<DispatchRequest["thinkingLevel"]> } : {}),
|
|
653
|
+
gate: { role: "member", group: "plan-preview", cycle: round },
|
|
654
|
+
};
|
|
655
|
+
let task: ResolvedPlanTask;
|
|
656
|
+
try {
|
|
657
|
+
task = resolveTask(memberRequest, "member", (round - 1) * council.members.length + index + 1);
|
|
658
|
+
} catch (error) {
|
|
659
|
+
return shapeRejection(
|
|
660
|
+
args,
|
|
661
|
+
`council_member_target_unknown: ${member.label}: ${error instanceof Error ? error.message : String(error)}`,
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
if (task.node !== "local" || task.nodeKind !== "local") {
|
|
665
|
+
return shapeRejection(args, `council_member_remote_node: ${member.label}`);
|
|
666
|
+
}
|
|
667
|
+
task.council = {
|
|
668
|
+
label: member.label,
|
|
669
|
+
...(member.color ? { color: member.color } : {}),
|
|
670
|
+
round,
|
|
671
|
+
rounds: council.rounds,
|
|
672
|
+
synthesis: council.synthesis,
|
|
673
|
+
};
|
|
674
|
+
if (round === council.rounds) subjects.push({ runId: `plan-member-${member.label}`, digest: null });
|
|
675
|
+
tasks.push(task);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (council.synthesis === "judge") {
|
|
679
|
+
const judgeRequest: DispatchRequest = {
|
|
680
|
+
agentId: council.judge?.agent ?? base.agentId,
|
|
681
|
+
executionRole: "judge",
|
|
682
|
+
task: "Plan-time capability check for council synthesis.",
|
|
683
|
+
systemPrompt: COUNCIL_JUDGE_PROMPT,
|
|
684
|
+
autonomy: "read-only",
|
|
685
|
+
toolProfile: "council-read-only",
|
|
686
|
+
gate: { role: "synthesis", group: "plan-preview", cycle: council.rounds, subjects },
|
|
687
|
+
...(council.judge?.node ? { node: council.judge.node } : {}),
|
|
688
|
+
...(council.judge?.model ? { model: council.judge.model } : {}),
|
|
689
|
+
...(council.judge?.target ? { target: council.judge.target } : {}),
|
|
690
|
+
};
|
|
691
|
+
const judgeTask = resolveTask(judgeRequest, "synthesis", 1);
|
|
692
|
+
if (judgeTask.node !== "local" || judgeTask.nodeKind !== "local") {
|
|
693
|
+
return shapeRejection(args, "council_member_remote_node: judge");
|
|
694
|
+
}
|
|
695
|
+
judgeTask.council = {
|
|
696
|
+
label: "synthesis",
|
|
697
|
+
round: council.rounds,
|
|
698
|
+
rounds: council.rounds,
|
|
699
|
+
synthesis: council.synthesis,
|
|
700
|
+
};
|
|
701
|
+
tasks.push(judgeTask);
|
|
702
|
+
}
|
|
483
703
|
} else {
|
|
484
704
|
for (const [index, request] of parsed.requests.entries()) tasks.push(resolveTask(request, "task", index + 1));
|
|
485
705
|
}
|
|
@@ -490,7 +710,20 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
490
710
|
topology === "compete" ||
|
|
491
711
|
tasks.some((task) => task.node !== "local" || task.failover === "approved");
|
|
492
712
|
if (!planScale) {
|
|
493
|
-
const marked =
|
|
713
|
+
const marked = parsed.requests.some((request) => request.intent !== undefined)
|
|
714
|
+
? markPrepared(
|
|
715
|
+
withResolvedDispatchPlan(args, {
|
|
716
|
+
version: 3,
|
|
717
|
+
topology,
|
|
718
|
+
source: null,
|
|
719
|
+
maxWorkers: null,
|
|
720
|
+
onFailure: null,
|
|
721
|
+
tasks,
|
|
722
|
+
costCeilingUsd: resolvedCostCeiling(),
|
|
723
|
+
deadlineMs: null,
|
|
724
|
+
}),
|
|
725
|
+
)
|
|
726
|
+
: markPrepared(args);
|
|
494
727
|
return trustExecution(marked, dispatchExecutionSnapshot(marked, snapshotFields));
|
|
495
728
|
}
|
|
496
729
|
const marked = markReservedPlan(args, {
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
/** Pure model-argument parsing; every returned DispatchRequest has a concrete agent id. */
|
|
2
2
|
|
|
3
|
+
import { pathBoundaryCovers } from "../core/path-boundary.js";
|
|
3
4
|
import type { AgentAutomationAuthority } from "../domains/agents/spec.js";
|
|
4
5
|
import { type AgentTaskType, classifyAgentTask } from "../domains/dispatch/agent-candidates.js";
|
|
5
6
|
import { cloneDispatchBudgetRequest } from "../domains/dispatch/budget-envelope.js";
|
|
6
7
|
import type { DispatchRequest } from "../domains/dispatch/contract.js";
|
|
7
8
|
import { type AgentRoleFactsResolver, requestExecutionRole } from "../domains/dispatch/execution-role.js";
|
|
9
|
+
import type { DispatchIntent } from "../domains/dispatch/intent.js";
|
|
8
10
|
import { parseRoutingIntent } from "../domains/dispatch/routing-intent.js";
|
|
9
11
|
import { DISPATCH_BRIEFING_MAX_BYTES, type JobThinkingLevel } from "../domains/dispatch/validation.js";
|
|
10
12
|
import { isToolProfileName, TOOL_PROFILE_NAMES } from "./profiles.js";
|
|
11
13
|
|
|
12
14
|
const DEFAULT_AGENT_ID = "coder";
|
|
15
|
+
/**
|
|
16
|
+
* The agent a council seats when the caller names none. Council admission pins
|
|
17
|
+
* every member to the `council-read-only` tool profile, and `coder` requires a
|
|
18
|
+
* write tool, so the ordinary default would fail admission for every member.
|
|
19
|
+
* `researcher` is the builtin read-only answerer.
|
|
20
|
+
*/
|
|
21
|
+
const COUNCIL_DEFAULT_AGENT_ID = "researcher";
|
|
13
22
|
|
|
14
23
|
/**
|
|
15
24
|
* Baseline recipe per task shape for `agent:"auto"`.
|
|
@@ -69,6 +78,12 @@ export interface DispatchArgumentParserOptions {
|
|
|
69
78
|
approvedAuthorities: ReadonlyArray<AgentAutomationAuthority>;
|
|
70
79
|
authorityBasis: "operator-plan-approval" | "full-auto-policy";
|
|
71
80
|
};
|
|
81
|
+
resolveIntent?: (
|
|
82
|
+
rawIntent: unknown,
|
|
83
|
+
cwd: string | undefined,
|
|
84
|
+
) =>
|
|
85
|
+
| { ok: true; intent: DispatchIntent; resolvedVerification: NonNullable<DispatchRequest["resolvedVerification"]> }
|
|
86
|
+
| { ok: false; message: string };
|
|
72
87
|
}
|
|
73
88
|
|
|
74
89
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -100,7 +115,8 @@ function dispatchRequestFromArgs(
|
|
|
100
115
|
const task = stringArg(args, "task");
|
|
101
116
|
if (!task) return { ok: false, message: "missing task (pass list:true to see available agents)" };
|
|
102
117
|
if (Object.hasOwn(args, "agent_id")) return { ok: false, message: "agent_id is unsupported; use agent" };
|
|
103
|
-
const requestedAgent =
|
|
118
|
+
const requestedAgent =
|
|
119
|
+
stringArg(args, "agent") ?? (args.mode === "council" ? COUNCIL_DEFAULT_AGENT_ID : DEFAULT_AGENT_ID);
|
|
104
120
|
const auto = requestedAgent === "auto";
|
|
105
121
|
const agentId = auto ? autoBaselineAgentId(task, options.hasAgent) : requestedAgent;
|
|
106
122
|
const request: DispatchRequest = {
|
|
@@ -150,6 +166,47 @@ function dispatchRequestFromArgs(
|
|
|
150
166
|
request.requiredCapabilities = routing.intent.requiredCapabilities;
|
|
151
167
|
const cwd = stringArg(args, "cwd");
|
|
152
168
|
if (cwd) request.cwd = cwd;
|
|
169
|
+
if (args.worktree !== undefined) {
|
|
170
|
+
if (args.worktree !== true) return { ok: false, message: "worktree must be true when present" };
|
|
171
|
+
request.worktree = true;
|
|
172
|
+
}
|
|
173
|
+
if (args.apply !== undefined) {
|
|
174
|
+
if (args.apply !== "merge" && args.apply !== "preserve")
|
|
175
|
+
return { ok: false, message: "apply must be merge or preserve" };
|
|
176
|
+
if (request.worktree !== true) return { ok: false, message: "apply requires worktree: true" };
|
|
177
|
+
request.apply = args.apply;
|
|
178
|
+
}
|
|
179
|
+
if (Object.hasOwn(args, "gate") && isRecord(args.intent) && Object.hasOwn(args.intent, "verification")) {
|
|
180
|
+
return { ok: false, message: "gate_and_intent_verification_conflict: gate cannot combine with intent.verification" };
|
|
181
|
+
}
|
|
182
|
+
let rawIntent = args.intent;
|
|
183
|
+
if (Object.hasOwn(args, "gate")) {
|
|
184
|
+
if (typeof args.gate !== "string" || args.gate.trim().length === 0) {
|
|
185
|
+
return { ok: false, message: "gate must be a non-empty declared check id" };
|
|
186
|
+
}
|
|
187
|
+
rawIntent = { ...(isRecord(rawIntent) ? rawIntent : {}), verification: [{ check: args.gate.trim() }] };
|
|
188
|
+
}
|
|
189
|
+
if (rawIntent !== undefined) {
|
|
190
|
+
if (options.resolveIntent === undefined) return { ok: false, message: "intent resolver is unavailable" };
|
|
191
|
+
const resolved = options.resolveIntent(rawIntent, cwd);
|
|
192
|
+
if (!resolved.ok) return resolved;
|
|
193
|
+
request.intent = resolved.intent;
|
|
194
|
+
if (resolved.resolvedVerification.length > 0) request.resolvedVerification = resolved.resolvedVerification;
|
|
195
|
+
if (resolved.intent.writeRoots.length > 0) {
|
|
196
|
+
const legacy = Array.isArray(args.writeRoots)
|
|
197
|
+
? args.writeRoots.filter((entry): entry is string => typeof entry === "string")
|
|
198
|
+
: undefined;
|
|
199
|
+
if (legacy !== undefined) {
|
|
200
|
+
const left = [...new Set(legacy)].sort();
|
|
201
|
+
if (JSON.stringify(left) !== JSON.stringify(resolved.intent.writeRoots)) {
|
|
202
|
+
return { ok: false, message: "intent_write_roots_contradiction" };
|
|
203
|
+
}
|
|
204
|
+
request.writeRoots = legacy;
|
|
205
|
+
} else {
|
|
206
|
+
request.writeRoots = resolved.intent.writeRoots;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
153
210
|
if ("persona" in args && args.persona !== undefined) {
|
|
154
211
|
if (typeof args.persona !== "string") return { ok: false, message: "persona must be a string" };
|
|
155
212
|
const persona = args.persona.trim();
|
|
@@ -205,8 +262,34 @@ export function dispatchRequestsFromArgs(
|
|
|
205
262
|
for (let index = 0; index < tasks.length; index += 1) {
|
|
206
263
|
const item = tasks[index];
|
|
207
264
|
const itemArgs: Record<string, unknown> = isRecord(item) ? { ...shared, ...item } : { ...shared, task: item };
|
|
265
|
+
const sharedIntent = isRecord(shared.intent) ? shared.intent : null;
|
|
266
|
+
const itemIntent = isRecord(item) && Object.hasOwn(item, "intent") && isRecord(item.intent) ? item.intent : null;
|
|
267
|
+
if (sharedIntent !== null && itemIntent !== null) itemArgs.intent = { ...sharedIntent, ...itemIntent };
|
|
208
268
|
const parsed = dispatchRequestFromArgs(itemArgs, options);
|
|
209
269
|
if (!parsed.ok) return { ok: false, message: `dispatch: task ${index + 1}: ${parsed.message}` };
|
|
270
|
+
if (sharedIntent !== null && itemIntent !== null) {
|
|
271
|
+
if (options.resolveIntent === undefined) {
|
|
272
|
+
return { ok: false, message: `dispatch: task ${index + 1}: intent resolver is unavailable` };
|
|
273
|
+
}
|
|
274
|
+
const ceiling = options.resolveIntent(sharedIntent, parsed.request.cwd);
|
|
275
|
+
if (!ceiling.ok) return { ok: false, message: `dispatch: task ${index + 1}: ${ceiling.message}` };
|
|
276
|
+
const pathCeilings = {
|
|
277
|
+
readRoots: [...ceiling.intent.readRoots, ...ceiling.intent.writeRoots],
|
|
278
|
+
writeRoots: ceiling.intent.writeRoots,
|
|
279
|
+
relevantPaths: [...ceiling.intent.readRoots, ...ceiling.intent.writeRoots, ...ceiling.intent.relevantPaths],
|
|
280
|
+
};
|
|
281
|
+
for (const field of ["readRoots", "writeRoots", "relevantPaths"] as const) {
|
|
282
|
+
const outside = parsed.request.intent?.[field].find(
|
|
283
|
+
(candidate) => !pathBoundaryCovers(pathCeilings[field], candidate),
|
|
284
|
+
);
|
|
285
|
+
if (outside !== undefined) {
|
|
286
|
+
return {
|
|
287
|
+
ok: false,
|
|
288
|
+
message: `dispatch: task ${index + 1}: intent_scope_widening: ${field} entry '${outside}' is outside the top-level intent ceiling`,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
210
293
|
requests.push(parsed.request);
|
|
211
294
|
}
|
|
212
295
|
return { ok: true, requests };
|