@iowarp/clio-coder 0.3.6 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +16 -5
- package/dist/{acp-2BEHC4DL.js → acp-SK4MD6MM.js} +10 -10
- package/dist/{agents-LNNFTM53.js → agents-2FN2K6ME.js} +30 -25
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-KXXFI2VS.js → auth-QIYZWM5I.js} +13 -13
- package/dist/{chunk-KHSFENX2.js → chunk-3HAPLH5M.js} +10 -10
- package/dist/{chunk-24I7BN55.js → chunk-465YSENW.js} +2 -2
- package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
- package/dist/{chunk-CYQKWTG3.js → chunk-4DWFMQDR.js} +4 -4
- package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
- package/dist/{chunk-22NAGB7X.js → chunk-5C77SEEY.js} +5 -94
- package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
- package/dist/{chunk-K7T3E2SR.js → chunk-5UJ6ECTS.js} +10 -9
- package/dist/{chunk-6US73PDB.js → chunk-6M7VS3J3.js} +5 -5
- package/dist/{chunk-CJUB2JJ2.js → chunk-6TUKSZVF.js} +5 -5
- package/dist/{chunk-5JGRAMKL.js → chunk-AB4XIIVB.js} +8 -6
- package/dist/{chunk-R46L2BIR.js → chunk-BMWK7ZIZ.js} +14 -20
- package/dist/{chunk-4BPJXDWC.js → chunk-C4JBQ5SR.js} +30 -14
- package/dist/{chunk-XXQNGV4M.js → chunk-CEYBNUGC.js} +243 -63
- package/dist/{chunk-VEZEGCGW.js → chunk-D4MDIG46.js} +20 -18
- package/dist/chunk-DJNLUABN.js +843 -0
- package/dist/{chunk-RY3LY4J5.js → chunk-DMD2AGVS.js} +5 -4
- package/dist/{chunk-KOHPCX4K.js → chunk-DOOEX22V.js} +2 -2
- package/dist/chunk-DQA7QLMD.js +123 -0
- package/dist/chunk-DR52UMZW.js +21 -0
- package/dist/{chunk-XF5N4U5A.js → chunk-EBEFWSGL.js} +6 -5
- package/dist/{chunk-EYPA3EGJ.js → chunk-EELBMBT6.js} +120 -13
- package/dist/{chunk-CKXWIANG.js → chunk-EOOQZZDE.js} +16 -14
- package/dist/{chunk-WR67VIZY.js → chunk-FOT2FX5J.js} +63 -5
- package/dist/{chunk-FYYLNIL5.js → chunk-GH5622CP.js} +2 -2
- package/dist/chunk-GWS3VEIW.js +195 -0
- package/dist/{chunk-LYF7OHWH.js → chunk-J7PIKKWC.js} +8 -463
- package/dist/{chunk-NILBFAPG.js → chunk-JNXPYBB4.js} +2 -2
- package/dist/{chunk-4VP4KH3K.js → chunk-JRIO5UD2.js} +4 -4
- package/dist/{chunk-6XXKFVSN.js → chunk-JTSEDYVQ.js} +7 -7
- package/dist/{chunk-QKMUKYO7.js → chunk-KCMKRQX4.js} +236 -84
- package/dist/chunk-KZ2H5X4G.js +1026 -0
- package/dist/{chunk-QNQHSOLF.js → chunk-LADCF22A.js} +12 -12
- package/dist/chunk-M4AKACEO.js +382 -0
- package/dist/{chunk-XYDYPRZI.js → chunk-MXI6J5JF.js} +7 -7
- package/dist/{chunk-G7MUEIGA.js → chunk-OB5HIGJY.js} +1 -1
- package/dist/{chunk-EKY57CSP.js → chunk-OBMAI2DP.js} +61 -767
- package/dist/chunk-PD3MESLB.js +242 -0
- package/dist/{chunk-ZRGEBJ4T.js → chunk-QCTRSGHQ.js} +21 -21
- package/dist/chunk-RVG5JXAL.js +41 -0
- package/dist/{chunk-RD5U66HV.js → chunk-SROCI7ZU.js} +7 -7
- package/dist/{chunk-MFFY33HR.js → chunk-THKY7CD7.js} +466 -205
- package/dist/{chunk-PCZJO5TI.js → chunk-UFQ3F4FW.js} +13 -178
- package/dist/{chunk-AD2SYQYC.js → chunk-UHXRNZ2J.js} +121 -3
- package/dist/chunk-UND3GU2L.js +103 -0
- package/dist/{chunk-QM3F2GKX.js → chunk-UUANF5CR.js} +2247 -2096
- package/dist/chunk-UVDSQ6LW.js +472 -0
- package/dist/{chunk-DJVECN66.js → chunk-VQNODYQ4.js} +14 -14
- package/dist/{chunk-3BPUFZDL.js → chunk-VREKEFLL.js} +3 -3
- package/dist/{chunk-PBTHKCPN.js → chunk-WJHBC77E.js} +6 -6
- package/dist/{chunk-XE2VEJHX.js → chunk-X2KV5FXT.js} +2 -2
- package/dist/{chunk-ZXF4XRKW.js → chunk-XEGB6BCN.js} +157 -7
- package/dist/{chunk-E25LMLRW.js → chunk-YD734TPH.js} +2 -2
- package/dist/{verifiers-NCBTHHN2.js → chunk-YTYFXUI3.js} +65 -322
- package/dist/{chunk-OH3TOQTB.js → chunk-ZGH7FGS5.js} +13 -7
- package/dist/cli/index.js +32 -30
- package/dist/{clio-M2KGYUFZ.js → clio-WBVQEBKO.js} +7 -7
- package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +3 -3
- package/dist/{components-5TTYYX6G.js → components-F7OEATSO.js} +4 -4
- package/dist/{config-XUUYQIWO.js → config-TRBL3RCF.js} +34 -29
- package/dist/{configure-IHJ7YOMV.js → configure-OLCVPHNM.js} +15 -15
- package/dist/{context-74JLXAWD.js → context-MJIJ6GOX.js} +11 -11
- package/dist/{context-ZQ7SIFJV.js → context-WFPKQSM6.js} +19 -3
- package/dist/{context-75MIWW3U.js → context-XEWE3MOJ.js} +31 -26
- package/dist/{context-clear-GYKWNUML.js → context-clear-KNOS2JPB.js} +31 -26
- package/dist/{context-working-set-UX5KEP4J.js → context-working-set-EUXAZI6N.js} +8 -8
- package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-B7MTOVKL.js} +313 -53
- package/dist/{docs-6FZSCG5B.js → docs-FLJTIDSE.js} +4 -4
- package/dist/{doctor-SVJ5BZCW.js → doctor-RN4YKO2X.js} +14 -14
- package/dist/{eval-CG6LLBLD.js → eval-RUBJVSNQ.js} +8 -7
- package/dist/{evidence-ZYFIEN42.js → evidence-JZNBUOQZ.js} +30 -25
- package/dist/{evolve-QGEXEMDW.js → evolve-FJVC4KKI.js} +30 -25
- package/dist/{extensions-ADGNCJJD.js → extensions-IQL36S7K.js} +4 -4
- package/dist/{fleet-S5R4ZOQY.js → fleet-BDKYJFCP.js} +214 -360
- package/dist/fleet-commands-ZFIWZSB3.js +70 -0
- package/dist/fleet-graph-Y6HPXIVF.js +125 -0
- package/dist/fleet-new-RDVJLHHH.js +48 -0
- package/dist/fleet-validate-BIYREGIK.js +79 -0
- package/dist/{init-5DRU55YR.js → init-LQUB5COQ.js} +44 -37
- package/dist/library-NJAHIGG4.js +217 -0
- package/dist/{memory-7YKKR6UC.js → memory-OG6HOYKM.js} +31 -26
- package/dist/{models-ZPOLRU2C.js → models-5ZG5XY7J.js} +21 -20
- package/dist/{monitor-US5F5YGZ.js → monitor-TJ7AMTGB.js} +49 -30
- package/dist/{orchestrator-E2AL4T5N.js → orchestrator-WZYB54DM.js} +3827 -581
- package/dist/{paths-E7KYAQWE.js → paths-XUC7GS6E.js} +4 -4
- package/dist/{reset-KZ652EK6.js → reset-PXQT45IY.js} +7 -7
- package/dist/{run-SRNBKDWD.js → run-FQ74YF62.js} +53 -45
- package/dist/{share-CGZE33UP.js → share-FW7SVCL3.js} +33 -9
- package/dist/{skills-S2X4DLY5.js → skills-7E7IRB3R.js} +24 -8
- package/dist/{skills-eval-W2GGIC4R.js → skills-eval-LI75W6OK.js} +34 -27
- package/dist/{targets-54SWINWB.js → targets-4CIFKCTW.js} +23 -22
- package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WUZY7ZV5.js} +4 -4
- package/dist/{uninstall-BVLWXKBT.js → uninstall-7FV7IP4E.js} +4 -4
- package/dist/{upgrade-JKAR27XC.js → upgrade-K2HVIVMQ.js} +20 -19
- package/dist/{usage-MSAWCLX4.js → usage-GTZELZQX.js} +116 -49
- package/dist/verifiers-RLAHT27O.js +336 -0
- package/dist/{verify-X5HDROLA.js → verify-BX3BRKH5.js} +7 -6
- package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-ASIFASCN.js} +45 -37
- package/dist/worker/entry.js +38 -35
- 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 +10 -6
- package/docs/built-in-agents.md +26 -2
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +82 -2
- package/docs/configuration-and-targets.md +79 -2
- 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 +5 -5
- 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 +197 -10
- package/docs/git-commit-provenance.md +2 -2
- package/docs/glossary.md +1 -1
- package/docs/installation-and-lifecycle.md +2 -2
- package/docs/middleware-and-components.md +2 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +55 -8
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +1 -1
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +79 -64
- 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 -1
- package/src/cli/fleet-commands.ts +37 -0
- package/src/cli/fleet-graph.ts +102 -0
- package/src/cli/fleet-new.ts +36 -0
- package/src/cli/fleet-preflight.ts +121 -0
- package/src/cli/fleet-validate.ts +30 -0
- package/src/cli/fleet.ts +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/usage.ts +111 -19
- package/src/core/bus-events.ts +4 -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/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/fleet-contract.ts +278 -16
- package/src/domains/agents/index.ts +14 -0
- package/src/domains/agents/result-contract.ts +235 -1
- package/src/domains/config/classify.ts +4 -0
- package/src/domains/dispatch/active-route-planner.ts +14 -0
- 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 +34 -0
- package/src/domains/dispatch/delegation-plan.ts +167 -0
- package/src/domains/dispatch/execution-plan.ts +76 -5
- package/src/domains/dispatch/execution-role.ts +3 -1
- package/src/domains/dispatch/execution-scheduler.ts +183 -67
- package/src/domains/dispatch/extension.ts +258 -9
- package/src/domains/dispatch/fleet-gate.ts +14 -0
- package/src/domains/dispatch/fleet-plan.ts +63 -3
- package/src/domains/dispatch/fleet-run.ts +737 -0
- package/src/domains/dispatch/gate-role-prompts.ts +9 -0
- package/src/domains/dispatch/host-verification.ts +178 -0
- package/src/domains/dispatch/index.ts +38 -0
- package/src/domains/dispatch/intent.ts +159 -0
- package/src/domains/dispatch/receipt-integrity.ts +8 -4
- package/src/domains/dispatch/state.ts +36 -3
- package/src/domains/dispatch/types.ts +51 -6
- package/src/domains/dispatch/validation.ts +66 -6
- package/src/domains/evidence/trust-status.ts +10 -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/resources/index.ts +20 -0
- package/src/domains/resources/library.ts +326 -0
- package/src/domains/resources/skills/marketplace.ts +37 -12
- package/src/domains/session/handoff.ts +629 -0
- package/src/domains/share/archive.ts +67 -2
- 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 +260 -16
- 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 +9 -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 +101 -4
- 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 +432 -5
- 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/watchdog-run.ts +75 -0
- package/src/interactive/worker-share.ts +56 -1
- package/src/interactive/worker-stream.ts +7 -0
- package/src/tools/bootstrap.ts +3 -0
- package/src/tools/compete-worktrees.ts +13 -79
- package/src/tools/dispatch-admission.ts +242 -8
- package/src/tools/dispatch-arguments.ts +57 -1
- package/src/tools/dispatch-plan.ts +136 -6
- package/src/tools/dispatch-runner.ts +319 -13
- package/src/tools/dispatch-types.ts +20 -1
- package/src/tools/dispatch.ts +72 -2
- package/src/tools/monitor.ts +16 -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 +2 -1
- package/src/worker/spec-contract.ts +1 -0
- package/dist/chunk-HC4CLZ2Y.js +0 -68
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one fleet-run execution path.
|
|
3
|
+
*
|
|
4
|
+
* A compiled fleet plan is driven here and nowhere else, so every surface that
|
|
5
|
+
* starts a fleet gets identical admission, autonomy, receipts, gate decisions,
|
|
6
|
+
* write-boundary enforcement, and durable ledger rows. The operator surface
|
|
7
|
+
* that calls it owns presentation only: this module writes nothing to stdout
|
|
8
|
+
* and reports progress through callbacks.
|
|
9
|
+
*
|
|
10
|
+
* Everything the run needs is passed in. The module resolves no contract by
|
|
11
|
+
* itself, loads no domain, and reads no configuration, which is what lets a
|
|
12
|
+
* headless CLI invocation and an interactive approval share it.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { attributeCommitMessage } from "../../core/commit-attribution.js";
|
|
19
|
+
import { clioStateDir } from "../../core/xdg.js";
|
|
20
|
+
import type { FleetCommandRegistry } from "../agents/fleet-commands.js";
|
|
21
|
+
import type { FleetContract } from "../agents/fleet-contract.js";
|
|
22
|
+
import { resultContractAuthorship } from "../agents/result-contract.js";
|
|
23
|
+
import type { AgentSpec } from "../agents/spec.js";
|
|
24
|
+
import { runCodeStep } from "./code-step.js";
|
|
25
|
+
import { codeStepDir, writeCodeStepRecord } from "./code-step-store.js";
|
|
26
|
+
import type { DispatchContract, DispatchRequest } from "./contract.js";
|
|
27
|
+
import { buildDelegationProposalBriefing, validateDelegationPlan } from "./delegation-plan.js";
|
|
28
|
+
import {
|
|
29
|
+
type ExecutionPlan,
|
|
30
|
+
type ExecutionPlanCodeStep,
|
|
31
|
+
type ExecutionPlanStep,
|
|
32
|
+
executionPlanAncestors,
|
|
33
|
+
spliceExecutionPlan,
|
|
34
|
+
} from "./execution-plan.js";
|
|
35
|
+
import { gateRouteCorrelation, requestExecutionRole } from "./execution-role.js";
|
|
36
|
+
import { type ExecutionPlanResult, type ExecutionStepResult, executePlan } from "./execution-scheduler.js";
|
|
37
|
+
import { deriveFleetCommitAttribution } from "./fleet-commit-attribution.js";
|
|
38
|
+
import { gateBaselineFailure, gateFailureLines } from "./fleet-gate.js";
|
|
39
|
+
import {
|
|
40
|
+
decideReviewGate,
|
|
41
|
+
type GateDecisionCorrelation,
|
|
42
|
+
materializePendingGateDecision,
|
|
43
|
+
stagePendingGateDecision,
|
|
44
|
+
} from "./gate-decisions.js";
|
|
45
|
+
import { verifyReceiptIntegrity } from "./receipt-integrity.js";
|
|
46
|
+
import { type FleetRunRecord, writeFleetRun } from "./state.js";
|
|
47
|
+
import type { RunReceipt } from "./types.js";
|
|
48
|
+
import { createWriteBoundaryEnforcer } from "./write-boundary-enforcer.js";
|
|
49
|
+
|
|
50
|
+
/** Owner id for a plan with no model runs; it holds nothing to release. */
|
|
51
|
+
const NO_WORKER_RESERVATION = "fleet-no-worker-reservation";
|
|
52
|
+
|
|
53
|
+
/** Recipe lookup for the agents a plan names; the agents domain owns the registry. */
|
|
54
|
+
export interface FleetRunAgentAccess {
|
|
55
|
+
getSpec(agentId: string): AgentSpec | null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** One step reaching a runnable state, reported the moment its identity exists. */
|
|
59
|
+
export interface FleetRunStepEvent {
|
|
60
|
+
stepId: string;
|
|
61
|
+
kind: ExecutionPlanStep["kind"];
|
|
62
|
+
/** Index of the plan wave this step belongs to. */
|
|
63
|
+
waveIndex: number;
|
|
64
|
+
/** Assignment id for an agent step, or the deterministic run id for a code step. */
|
|
65
|
+
assignmentId: string;
|
|
66
|
+
agentId?: string;
|
|
67
|
+
commandId?: string;
|
|
68
|
+
/** True when this event projects a result retained from an earlier run. */
|
|
69
|
+
replayed?: true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One settled step, after its receipt or deterministic report is sealed. */
|
|
73
|
+
export interface FleetRunStepOutcome extends FleetRunStepEvent {
|
|
74
|
+
succeeded: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Why a step failed after its own receipt or report succeeded: a gate whose
|
|
77
|
+
* baseline was green (`gate_not_discriminating`), a missing gate path or
|
|
78
|
+
* command, or a delegation plan the validator refused (`delegation_plan_*`).
|
|
79
|
+
*/
|
|
80
|
+
failureReason?: string;
|
|
81
|
+
/** Terminal run id, which is the receipt id for an agent step. */
|
|
82
|
+
terminalRunId: string;
|
|
83
|
+
costUsd: number;
|
|
84
|
+
/** The sealed agent receipt when this was an agent step. */
|
|
85
|
+
receipt?: RunReceipt;
|
|
86
|
+
/** The durable code-step record when this was a deterministic step. */
|
|
87
|
+
codeStep?: Awaited<ReturnType<typeof runCodeStep>>["record"];
|
|
88
|
+
/** The durable code-step record path when this was a deterministic step. */
|
|
89
|
+
recordPath?: string;
|
|
90
|
+
/** The scheduler result retained for a replayed step. */
|
|
91
|
+
result?: ExecutionStepResult;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface FleetResumeStepDiff {
|
|
95
|
+
index: number;
|
|
96
|
+
prior: unknown;
|
|
97
|
+
current: unknown;
|
|
98
|
+
priorId: string;
|
|
99
|
+
currentId: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export type FleetResumeRefusal =
|
|
103
|
+
| { ok: false; reason: "fleet-name"; priorFleet: string; currentFleet: string }
|
|
104
|
+
| { ok: false; reason: "vars" }
|
|
105
|
+
| {
|
|
106
|
+
ok: false;
|
|
107
|
+
reason: "plan-hash";
|
|
108
|
+
priorHash: string;
|
|
109
|
+
currentHash: string;
|
|
110
|
+
diff: ReadonlyArray<FleetResumeStepDiff>;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export type FleetResumePlan = { ok: true; replayed: ReadonlyMap<string, ExecutionStepResult> } | FleetResumeRefusal;
|
|
114
|
+
|
|
115
|
+
function sortedVars(vars: Readonly<Record<string, string>>): string {
|
|
116
|
+
return JSON.stringify(Object.entries(vars).sort());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function stepIdentity(step: unknown): string {
|
|
120
|
+
if (typeof step === "string") return step;
|
|
121
|
+
if (typeof step === "object" && step !== null && "id" in step && typeof step.id === "string") return step.id;
|
|
122
|
+
return "(missing)";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Validate a prior record and retain its longest ordered successful prefix. */
|
|
126
|
+
export function planFleetResume(
|
|
127
|
+
record: FleetRunRecord,
|
|
128
|
+
plan: ExecutionPlan,
|
|
129
|
+
contract: Pick<FleetContract, "name">,
|
|
130
|
+
vars: Readonly<Record<string, string>>,
|
|
131
|
+
): FleetResumePlan {
|
|
132
|
+
if (record.fleet !== contract.name) {
|
|
133
|
+
return { ok: false, reason: "fleet-name", priorFleet: record.fleet, currentFleet: contract.name };
|
|
134
|
+
}
|
|
135
|
+
if (sortedVars(record.vars) !== sortedVars(vars)) return { ok: false, reason: "vars" };
|
|
136
|
+
if (record.planHash !== plan.hash) {
|
|
137
|
+
const priorSteps = record.planSteps ?? record.stepIds;
|
|
138
|
+
const width = Math.max(priorSteps.length, plan.steps.length);
|
|
139
|
+
const diff: FleetResumeStepDiff[] = [];
|
|
140
|
+
for (let index = 0; index < width; index += 1) {
|
|
141
|
+
const prior = priorSteps[index] ?? "(missing)";
|
|
142
|
+
const current = plan.steps[index] ?? "(missing)";
|
|
143
|
+
if (JSON.stringify(prior) === JSON.stringify(current)) continue;
|
|
144
|
+
diff.push({ index, prior, current, priorId: stepIdentity(prior), currentId: stepIdentity(current) });
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
reason: "plan-hash",
|
|
149
|
+
priorHash: record.planHash,
|
|
150
|
+
currentHash: plan.hash,
|
|
151
|
+
diff,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const priorByStep = new Map(record.steps.map((entry) => [entry.stepId, entry.result]));
|
|
155
|
+
const replayed = new Map<string, ExecutionStepResult>();
|
|
156
|
+
for (const step of plan.steps) {
|
|
157
|
+
if (step.kind === "agent" && step.plan !== undefined) break;
|
|
158
|
+
const result = priorByStep.get(step.id);
|
|
159
|
+
if (result === undefined || !result.succeeded || !result.integrityValid) break;
|
|
160
|
+
replayed.set(step.id, result);
|
|
161
|
+
}
|
|
162
|
+
return { ok: true, replayed };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface ExecuteFleetRunInput {
|
|
166
|
+
plan: ExecutionPlan;
|
|
167
|
+
/** Contract name, used for the deterministic commit subject only. */
|
|
168
|
+
contractName: string;
|
|
169
|
+
/** Registered command bindings; required when the plan carries code steps. */
|
|
170
|
+
commands: FleetCommandRegistry | null;
|
|
171
|
+
workspaceRoot: string;
|
|
172
|
+
/** Lineage root every step in this run shares. */
|
|
173
|
+
fleetRootId: string;
|
|
174
|
+
dispatch: DispatchContract;
|
|
175
|
+
agents: FleetRunAgentAccess;
|
|
176
|
+
/** Whether a commit node stamps Clio's attribution trailers. */
|
|
177
|
+
attributionEnabled: boolean;
|
|
178
|
+
/** Variables rendered into the contract body and sealed into the run record. */
|
|
179
|
+
vars?: Readonly<Record<string, string>>;
|
|
180
|
+
/** A validated prior run and the successful prefix retained from it. */
|
|
181
|
+
resume?: { record: FleetRunRecord; replayed: ReadonlyMap<string, ExecutionStepResult> };
|
|
182
|
+
onStepDispatched?: (event: FleetRunStepEvent) => void;
|
|
183
|
+
onStepSettled?: (outcome: FleetRunStepOutcome) => void;
|
|
184
|
+
/** Operator-facing lines the caller renders wherever it shows the run. */
|
|
185
|
+
onNotice?: (text: string, kind?: "gate" | "write-boundary") => void;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface FleetRunOutcome {
|
|
189
|
+
rootId: string;
|
|
190
|
+
planHash: string;
|
|
191
|
+
result: ExecutionPlanResult;
|
|
192
|
+
receipts: ReadonlyArray<RunReceipt>;
|
|
193
|
+
totalCostUsd: number;
|
|
194
|
+
/** Steps that ran unconditionally, excluding nodes a resolved loop skipped. */
|
|
195
|
+
requiredStepCount: number;
|
|
196
|
+
succeededStepCount: number;
|
|
197
|
+
resolvedLoopCount: number;
|
|
198
|
+
/** True when every required step succeeded and every loop converged. */
|
|
199
|
+
cleanRun: boolean;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Sealed independence facts between the work under review and its reviewer. */
|
|
203
|
+
function fleetGateCorrelation(subject: RunReceipt, decider: RunReceipt): GateDecisionCorrelation {
|
|
204
|
+
const facts = (receipt: RunReceipt) => ({
|
|
205
|
+
agentId: receipt.agentId,
|
|
206
|
+
targetId: receipt.targetId,
|
|
207
|
+
wireModelId: receipt.wireModelId,
|
|
208
|
+
runtimeId: receipt.runtimeId,
|
|
209
|
+
nodeId: receipt.node?.id ?? "local",
|
|
210
|
+
});
|
|
211
|
+
const { agent, target, modelFamily, runtime, node, independent } = gateRouteCorrelation(
|
|
212
|
+
facts(subject),
|
|
213
|
+
facts(decider),
|
|
214
|
+
);
|
|
215
|
+
return { agent, target, modelFamily, runtime, node, independent };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function fleetPlanWaveIndex(plan: ExecutionPlan, stepId: string): number {
|
|
219
|
+
return plan.waves.findIndex((wave) => wave.includes(stepId));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Run a compiled fleet plan to completion.
|
|
224
|
+
*
|
|
225
|
+
* The caller has already compiled the plan, resolved every agent, and passed
|
|
226
|
+
* preflight; this drives the scheduler and seals the durable artifacts. It
|
|
227
|
+
* throws only when the scheduler itself cannot proceed, so a red step is a
|
|
228
|
+
* result rather than an exception.
|
|
229
|
+
*/
|
|
230
|
+
export async function executeFleetRun(input: ExecuteFleetRunInput): Promise<FleetRunOutcome> {
|
|
231
|
+
const { dispatch, agents, fleetRootId, workspaceRoot } = input;
|
|
232
|
+
let livePlan = input.plan;
|
|
233
|
+
const replayed = input.resume?.replayed ?? new Map<string, ExecutionStepResult>();
|
|
234
|
+
const notice = (text: string, kind?: "gate" | "write-boundary"): void => input.onNotice?.(text, kind);
|
|
235
|
+
const fleetRunRecord: FleetRunRecord = {
|
|
236
|
+
version: 1,
|
|
237
|
+
id: fleetRootId,
|
|
238
|
+
fleet: input.contractName,
|
|
239
|
+
planHash: livePlan.hash,
|
|
240
|
+
stepIds: livePlan.steps.map((step) => step.id),
|
|
241
|
+
planSteps: livePlan.steps.map((step) => structuredClone(step)),
|
|
242
|
+
vars: { ...(input.vars ?? {}) },
|
|
243
|
+
startedAt: new Date().toISOString(),
|
|
244
|
+
endedAt: null,
|
|
245
|
+
resumedFrom: input.resume?.record.id ?? null,
|
|
246
|
+
steps: [...replayed].map(([stepId, result]) => ({ stepId, result })),
|
|
247
|
+
dynamicPlans: [],
|
|
248
|
+
};
|
|
249
|
+
await writeFleetRun(fleetRunRecord);
|
|
250
|
+
const boundaryEnforcer = createWriteBoundaryEnforcer({
|
|
251
|
+
root: workspaceRoot,
|
|
252
|
+
rootId: fleetRootId,
|
|
253
|
+
boundaryFor: (stepId) => livePlan.steps.find((step) => step.id === stepId)?.writes,
|
|
254
|
+
onVerdict(verdict, path) {
|
|
255
|
+
if (verdict.reason === null) return;
|
|
256
|
+
notice(`write boundary ${verdict.window}: ${verdict.detail ?? verdict.reason} record=${path}`, "write-boundary");
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
const receipts: RunReceipt[] = [];
|
|
260
|
+
const receiptsByStep = new Map<string, RunReceipt>();
|
|
261
|
+
for (const [stepId, replayedResult] of replayed) {
|
|
262
|
+
try {
|
|
263
|
+
const receipt = JSON.parse(
|
|
264
|
+
readFileSync(join(clioStateDir(), "receipts", `${replayedResult.terminalRunId}.json`), "utf8"),
|
|
265
|
+
) as RunReceipt;
|
|
266
|
+
receiptsByStep.set(stepId, receipt);
|
|
267
|
+
} catch {
|
|
268
|
+
// Deterministic results and unavailable historical receipts need no receipt projection.
|
|
269
|
+
}
|
|
270
|
+
const step = livePlan.steps.find((entry) => entry.id === stepId);
|
|
271
|
+
if (step === undefined) continue;
|
|
272
|
+
const event: FleetRunStepEvent = {
|
|
273
|
+
stepId,
|
|
274
|
+
kind: step.kind,
|
|
275
|
+
waveIndex: fleetPlanWaveIndex(livePlan, stepId),
|
|
276
|
+
assignmentId: replayedResult.assignmentId,
|
|
277
|
+
...(step.kind === "agent" ? { agentId: step.agentId } : { commandId: step.commandId }),
|
|
278
|
+
replayed: true,
|
|
279
|
+
};
|
|
280
|
+
input.onStepDispatched?.(event);
|
|
281
|
+
input.onStepSettled?.({
|
|
282
|
+
...event,
|
|
283
|
+
succeeded: true,
|
|
284
|
+
terminalRunId: replayedResult.terminalRunId,
|
|
285
|
+
costUsd: receiptsByStep.get(stepId)?.costUsd ?? 0,
|
|
286
|
+
result: replayedResult,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const planStep = (stepId: string) => livePlan.steps.find((entry) => entry.id === stepId);
|
|
290
|
+
|
|
291
|
+
// Freshness is coordinator-owned execution state. Worker prose cannot set
|
|
292
|
+
// either flag: a successful deterministic verification sets the first, and
|
|
293
|
+
// an integrity-valid independent gate decision sets the second. Any later
|
|
294
|
+
// workspace agent invalidates both before a commit may read them.
|
|
295
|
+
let validationFresh = false;
|
|
296
|
+
let independentReviewFresh = false;
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* The commit message is the words of the agent that produced the work, and
|
|
300
|
+
* only that agent's. Candidates are consulted newest first; when none of
|
|
301
|
+
* them authored one, code writes a deterministic line rather than inventing
|
|
302
|
+
* a sentence or reusing somebody else's.
|
|
303
|
+
*/
|
|
304
|
+
const commitMessageFor = (
|
|
305
|
+
step: ExecutionPlanCodeStep,
|
|
306
|
+
priorResults: ReadonlyMap<string, { output: string }>,
|
|
307
|
+
): string => {
|
|
308
|
+
for (const candidate of step.commitFrom ?? []) {
|
|
309
|
+
const source = planStep(candidate);
|
|
310
|
+
const result = priorResults.get(candidate);
|
|
311
|
+
if (source === undefined || source.kind !== "agent" || result === undefined) continue;
|
|
312
|
+
const resultContract = agents.getSpec(source.agentId)?.resultContract ?? null;
|
|
313
|
+
if (resultContract === null) continue;
|
|
314
|
+
const authored = resultContractAuthorship(resultContract, result.output);
|
|
315
|
+
if (authored.commitMessage !== null) return authored.commitMessage;
|
|
316
|
+
if (authored.summary !== null) return `clio(${fleetRootId}): ${authored.summary}`;
|
|
317
|
+
}
|
|
318
|
+
return `clio(${fleetRootId}): ${input.contractName} ${step.id}`;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
let result: ExecutionPlanResult;
|
|
322
|
+
try {
|
|
323
|
+
result = await executePlan(livePlan, {
|
|
324
|
+
preflight(step) {
|
|
325
|
+
const request: DispatchRequest = {
|
|
326
|
+
agentId: step.agentId,
|
|
327
|
+
executionRole: step.executionRole,
|
|
328
|
+
task: step.task,
|
|
329
|
+
cwd: workspaceRoot,
|
|
330
|
+
requestOrigin: "user",
|
|
331
|
+
lineage: {
|
|
332
|
+
parentRunId: input.resume?.record.id ?? fleetRootId,
|
|
333
|
+
rootRunId: fleetRootId,
|
|
334
|
+
attempt: 0,
|
|
335
|
+
depth: 1,
|
|
336
|
+
},
|
|
337
|
+
...(step.scope === "readonly" ? { autonomy: "read-only" as const } : {}),
|
|
338
|
+
...(step.target !== undefined ? { target: step.target } : {}),
|
|
339
|
+
...(step.profile !== undefined ? { workerProfile: step.profile } : {}),
|
|
340
|
+
};
|
|
341
|
+
const resolution = dispatch.preview?.(request);
|
|
342
|
+
if (!resolution) throw new Error(`fleet preflight cannot resolve step '${step.id}'`);
|
|
343
|
+
return { step, costUpperBoundUsd: resolution.costUpperBoundUsd, nodeId: resolution.node.id };
|
|
344
|
+
},
|
|
345
|
+
reserve(reservedPlan, admissions) {
|
|
346
|
+
// A fleet of deterministic steps admits no worker and holds no
|
|
347
|
+
// capacity. The reservation authority refuses an empty reservation,
|
|
348
|
+
// correctly: there is nothing to reserve, so nothing is asked of it.
|
|
349
|
+
if (admissions.length === 0) return { ownerId: NO_WORKER_RESERVATION };
|
|
350
|
+
const reservation = dispatch.reservations?.prepare({
|
|
351
|
+
topology: "parallel",
|
|
352
|
+
tasks: admissions.map((admission) => ({
|
|
353
|
+
memberId: admission.step.id,
|
|
354
|
+
wave: fleetPlanWaveIndex(reservedPlan, admission.step.id),
|
|
355
|
+
resolution: dispatch.preview?.({
|
|
356
|
+
agentId: admission.step.agentId,
|
|
357
|
+
executionRole: admission.step.executionRole,
|
|
358
|
+
task: admission.step.task,
|
|
359
|
+
...(admission.step.target !== undefined ? { target: admission.step.target } : {}),
|
|
360
|
+
...(admission.step.profile !== undefined ? { workerProfile: admission.step.profile } : {}),
|
|
361
|
+
}) as NonNullable<ReturnType<NonNullable<DispatchContract["preview"]>>>,
|
|
362
|
+
})),
|
|
363
|
+
});
|
|
364
|
+
if (!reservation) throw new Error("fleet whole-plan reservation is unavailable");
|
|
365
|
+
return { ownerId: reservation.ownerId };
|
|
366
|
+
},
|
|
367
|
+
async run(step, handoffs, reservation, ledger) {
|
|
368
|
+
// A loop repair is attempt n of the same logical work, so it enters
|
|
369
|
+
// the run ledger as one: recovery evidence, not a fresh observation.
|
|
370
|
+
const attempt = step.loop?.role === "repair" ? step.loop.attempt : 0;
|
|
371
|
+
const parentReceipt = step.planParentId === undefined ? undefined : receiptsByStep.get(step.planParentId);
|
|
372
|
+
if (step.planParentId !== undefined && parentReceipt === undefined) {
|
|
373
|
+
throw new Error(`fleet dynamic step '${step.id}' has no terminal plan-step receipt`);
|
|
374
|
+
}
|
|
375
|
+
const request: DispatchRequest = {
|
|
376
|
+
agentId: step.agentId,
|
|
377
|
+
executionRole: step.executionRole,
|
|
378
|
+
task: step.task,
|
|
379
|
+
cwd: workspaceRoot,
|
|
380
|
+
predecessorHandoffs: handoffs,
|
|
381
|
+
requestOrigin: "user",
|
|
382
|
+
lineage: {
|
|
383
|
+
parentRunId: parentReceipt?.runId ?? input.resume?.record.id ?? fleetRootId,
|
|
384
|
+
rootRunId: fleetRootId,
|
|
385
|
+
attempt,
|
|
386
|
+
depth: parentReceipt === undefined ? 1 : 2,
|
|
387
|
+
},
|
|
388
|
+
reservation,
|
|
389
|
+
...(ledger !== undefined ? { ledger } : {}),
|
|
390
|
+
...(step.loop?.role === "check"
|
|
391
|
+
? {
|
|
392
|
+
gate: {
|
|
393
|
+
role: "reviewer" as const,
|
|
394
|
+
group: `${fleetRootId}:${step.loop.loopId}`,
|
|
395
|
+
cycle: step.loop.attempt,
|
|
396
|
+
subjects: handoffs.map((handoff) => ({
|
|
397
|
+
runId: handoff.terminalRunId,
|
|
398
|
+
digest: handoff.receiptDigest,
|
|
399
|
+
})),
|
|
400
|
+
},
|
|
401
|
+
}
|
|
402
|
+
: {}),
|
|
403
|
+
...(step.scope === "readonly" ? { autonomy: "read-only" as const } : {}),
|
|
404
|
+
...(step.target !== undefined ? { target: step.target } : {}),
|
|
405
|
+
...(step.profile !== undefined ? { workerProfile: step.profile } : {}),
|
|
406
|
+
...(step.plan !== undefined
|
|
407
|
+
? { resultContractOverride: { kind: "delegation-plan" as const } }
|
|
408
|
+
: step.gate !== undefined
|
|
409
|
+
? { resultContractOverride: { kind: "artifact-report" as const } }
|
|
410
|
+
: {}),
|
|
411
|
+
...(step.gate !== undefined ? { fleetGateReceipt: { path: step.gate.path } } : {}),
|
|
412
|
+
};
|
|
413
|
+
if (step.plan?.proposals === true) {
|
|
414
|
+
const proposals: Array<{ agent: string; output: string }> = [];
|
|
415
|
+
for (const agentId of step.plan.roster) {
|
|
416
|
+
const proposal = await dispatch.dispatch({
|
|
417
|
+
agentId,
|
|
418
|
+
executionRole: "researcher",
|
|
419
|
+
task: step.task,
|
|
420
|
+
cwd: workspaceRoot,
|
|
421
|
+
requestOrigin: "user",
|
|
422
|
+
autonomy: "read-only",
|
|
423
|
+
lineage: { parentRunId: fleetRootId, rootRunId: fleetRootId, attempt: 0, depth: 1 },
|
|
424
|
+
resultContractOverride: { kind: "artifact-report" },
|
|
425
|
+
...(step.target !== undefined ? { target: step.target } : {}),
|
|
426
|
+
...(step.profile !== undefined ? { workerProfile: step.profile } : {}),
|
|
427
|
+
});
|
|
428
|
+
void (async () => {
|
|
429
|
+
for await (const _event of proposal.events) {
|
|
430
|
+
/* Proposal events are drained while their final answers are collected. */
|
|
431
|
+
}
|
|
432
|
+
})().catch(() => {});
|
|
433
|
+
const receipt = await proposal.finalPromise;
|
|
434
|
+
const answer = receipt.output?.state === "final" ? receipt.output.text : "[proposal failed]";
|
|
435
|
+
proposals.push({ agent: agentId, output: answer });
|
|
436
|
+
}
|
|
437
|
+
request.briefing = buildDelegationProposalBriefing(proposals);
|
|
438
|
+
}
|
|
439
|
+
const handle = await dispatch.dispatch(request);
|
|
440
|
+
const waveIndex = fleetPlanWaveIndex(livePlan, step.id);
|
|
441
|
+
input.onStepDispatched?.({
|
|
442
|
+
stepId: step.id,
|
|
443
|
+
kind: "agent",
|
|
444
|
+
waveIndex,
|
|
445
|
+
assignmentId: handle.runId,
|
|
446
|
+
agentId: step.agentId,
|
|
447
|
+
});
|
|
448
|
+
void (async () => {
|
|
449
|
+
for await (const _event of handle.events) {
|
|
450
|
+
/* background display drain */
|
|
451
|
+
}
|
|
452
|
+
})().catch(() => {});
|
|
453
|
+
return {
|
|
454
|
+
assignmentId: handle.runId,
|
|
455
|
+
result: handle.finalPromise.then(async (receipt) => {
|
|
456
|
+
if (step.scope === "workspace") {
|
|
457
|
+
validationFresh = false;
|
|
458
|
+
independentReviewFresh = false;
|
|
459
|
+
}
|
|
460
|
+
receipts.push(receipt);
|
|
461
|
+
receiptsByStep.set(step.id, receipt);
|
|
462
|
+
const envelope = dispatch.getRun(receipt.runId);
|
|
463
|
+
const integrityValid = envelope !== null && verifyReceiptIntegrity(receipt, envelope).ok;
|
|
464
|
+
const succeeded = receipt.exitCode === 0 && (receipt.outcome === undefined || receipt.outcome === "succeeded");
|
|
465
|
+
let stepSucceeded = succeeded;
|
|
466
|
+
let failureReason: string | undefined;
|
|
467
|
+
let gatePathHash: string | undefined;
|
|
468
|
+
let delegationPlanHash: string | undefined;
|
|
469
|
+
let delegationPlan: ExecutionStepResult["delegationPlan"];
|
|
470
|
+
if (step.gate !== undefined && succeeded) {
|
|
471
|
+
let gateBytes = "";
|
|
472
|
+
try {
|
|
473
|
+
gateBytes = readFileSync(join(workspaceRoot, step.gate.path), "utf8");
|
|
474
|
+
} catch {
|
|
475
|
+
gateBytes = "";
|
|
476
|
+
}
|
|
477
|
+
gatePathHash = createHash("sha256").update(gateBytes).digest("hex");
|
|
478
|
+
const command = input.commands?.commands.get(step.gate.commandId);
|
|
479
|
+
if (gateBytes.length === 0 || command === undefined) {
|
|
480
|
+
stepSucceeded = false;
|
|
481
|
+
failureReason = gateBytes.length === 0 ? "gate_path_missing" : "gate_command_missing";
|
|
482
|
+
} else {
|
|
483
|
+
const baseline = await runCodeStep({
|
|
484
|
+
stepId: `${step.id}.baseline`,
|
|
485
|
+
command,
|
|
486
|
+
workspaceRoot,
|
|
487
|
+
artifactDir: codeStepDir(fleetRootId),
|
|
488
|
+
substitutions: { path: step.gate.path },
|
|
489
|
+
});
|
|
490
|
+
const baselineFailure = gateBaselineFailure(baseline.report.passed);
|
|
491
|
+
if (baselineFailure !== null) {
|
|
492
|
+
stepSucceeded = false;
|
|
493
|
+
failureReason = baselineFailure;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (step.plan !== undefined && succeeded) {
|
|
498
|
+
let value: unknown = null;
|
|
499
|
+
try {
|
|
500
|
+
value = JSON.parse(receipt.output?.state === "final" ? receipt.output.text : "");
|
|
501
|
+
} catch {
|
|
502
|
+
value = null;
|
|
503
|
+
}
|
|
504
|
+
const validated = validateDelegationPlan({
|
|
505
|
+
value,
|
|
506
|
+
roster: step.plan.roster,
|
|
507
|
+
maxTasks: step.plan.maxTasks,
|
|
508
|
+
writes: step.writes ?? [],
|
|
509
|
+
});
|
|
510
|
+
if (!validated.ok) {
|
|
511
|
+
stepSucceeded = false;
|
|
512
|
+
failureReason = validated.reason;
|
|
513
|
+
} else {
|
|
514
|
+
delegationPlanHash = validated.hash;
|
|
515
|
+
delegationPlan = validated.plan;
|
|
516
|
+
fleetRunRecord.dynamicPlans?.push({ stepId: step.id, hash: validated.hash });
|
|
517
|
+
await writeFleetRun(fleetRunRecord);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// Settle only after the gate baseline and the delegation-plan
|
|
521
|
+
// validation have run: a receipt that succeeded on its own can
|
|
522
|
+
// still fail the step, and the operator must see that reason.
|
|
523
|
+
input.onStepSettled?.({
|
|
524
|
+
stepId: step.id,
|
|
525
|
+
kind: "agent",
|
|
526
|
+
waveIndex,
|
|
527
|
+
assignmentId: handle.runId,
|
|
528
|
+
agentId: step.agentId,
|
|
529
|
+
succeeded: stepSucceeded,
|
|
530
|
+
terminalRunId: receipt.runId,
|
|
531
|
+
costUsd: receipt.costUsd,
|
|
532
|
+
receipt,
|
|
533
|
+
...(failureReason !== undefined ? { failureReason } : {}),
|
|
534
|
+
});
|
|
535
|
+
notice(
|
|
536
|
+
`step ${step.id} ${step.agentId}: ${stepSucceeded ? "succeeded" : "failed"}${failureReason !== undefined ? ` reason=${failureReason}` : ""} assignment=${handle.runId} terminal-run=${receipt.runId} cost=$${receipt.costUsd.toFixed(4)}`,
|
|
537
|
+
);
|
|
538
|
+
return {
|
|
539
|
+
stepId: step.id,
|
|
540
|
+
assignmentId: handle.runId,
|
|
541
|
+
terminalRunId: receipt.runId,
|
|
542
|
+
receiptDigest: receipt.integrity.digest,
|
|
543
|
+
output: receipt.output?.state === "final" ? receipt.output.text : "",
|
|
544
|
+
succeeded: stepSucceeded,
|
|
545
|
+
integrityValid,
|
|
546
|
+
...(failureReason !== undefined ? { failureReason } : {}),
|
|
547
|
+
...(gatePathHash !== undefined ? { gatePathHash } : {}),
|
|
548
|
+
...(delegationPlanHash !== undefined ? { delegationPlanHash } : {}),
|
|
549
|
+
...(delegationPlan !== undefined ? { delegationPlan } : {}),
|
|
550
|
+
};
|
|
551
|
+
}),
|
|
552
|
+
};
|
|
553
|
+
},
|
|
554
|
+
async runCode(step, _handoffs, signal, priorResults) {
|
|
555
|
+
const command = input.commands?.commands.get(step.commandId);
|
|
556
|
+
if (command === undefined) throw new Error(`fleet code step '${step.id}' has no registered command`);
|
|
557
|
+
const isCommit = step.commitFrom !== undefined;
|
|
558
|
+
const evidence = isCommit
|
|
559
|
+
? deriveFleetCommitAttribution({ plan: livePlan, step, priorResults, validationFresh, independentReviewFresh })
|
|
560
|
+
: null;
|
|
561
|
+
if (!isCommit && step.scope === "workspace" && step.verification !== true) {
|
|
562
|
+
validationFresh = false;
|
|
563
|
+
independentReviewFresh = false;
|
|
564
|
+
}
|
|
565
|
+
const originalMessage = isCommit ? commitMessageFor(step, priorResults) : null;
|
|
566
|
+
const outcome = await runCodeStep({
|
|
567
|
+
stepId: step.id,
|
|
568
|
+
command,
|
|
569
|
+
workspaceRoot,
|
|
570
|
+
artifactDir: codeStepDir(fleetRootId),
|
|
571
|
+
signal,
|
|
572
|
+
...(step.gate !== undefined ? { substitutions: { path: step.gate.path } } : {}),
|
|
573
|
+
...(isCommit && evidence !== null && originalMessage !== null
|
|
574
|
+
? {
|
|
575
|
+
substitutions: {
|
|
576
|
+
commitMessage: attributeCommitMessage(originalMessage, evidence, input.attributionEnabled),
|
|
577
|
+
},
|
|
578
|
+
requireWorkspaceChanges: true,
|
|
579
|
+
commitAttribution: { enabled: input.attributionEnabled, evidence },
|
|
580
|
+
}
|
|
581
|
+
: {}),
|
|
582
|
+
});
|
|
583
|
+
if (step.verification === true) validationFresh = outcome.report.passed;
|
|
584
|
+
const recordPath = await writeCodeStepRecord(fleetRootId, outcome.record);
|
|
585
|
+
const waveIndex = fleetPlanWaveIndex(livePlan, step.id);
|
|
586
|
+
const event = {
|
|
587
|
+
stepId: step.id,
|
|
588
|
+
kind: "code" as const,
|
|
589
|
+
waveIndex,
|
|
590
|
+
assignmentId: outcome.record.runId,
|
|
591
|
+
commandId: command.id,
|
|
592
|
+
};
|
|
593
|
+
input.onStepDispatched?.(event);
|
|
594
|
+
input.onStepSettled?.({
|
|
595
|
+
...event,
|
|
596
|
+
succeeded: outcome.report.passed,
|
|
597
|
+
terminalRunId: outcome.record.runId,
|
|
598
|
+
costUsd: 0,
|
|
599
|
+
codeStep: outcome.record,
|
|
600
|
+
recordPath,
|
|
601
|
+
});
|
|
602
|
+
notice(
|
|
603
|
+
`step ${step.id} code:${command.id}: ${outcome.report.passed ? "passed" : "failed"} exit=${outcome.report.exitCode} ${outcome.record.durationMs}ms record=${recordPath}`,
|
|
604
|
+
);
|
|
605
|
+
return {
|
|
606
|
+
stepId: step.id,
|
|
607
|
+
assignmentId: outcome.record.runId,
|
|
608
|
+
terminalRunId: outcome.record.runId,
|
|
609
|
+
receiptDigest: outcome.record.reportDigest,
|
|
610
|
+
output: step.gate === undefined ? outcome.output : gateFailureLines(outcome.report.outputExcerpt),
|
|
611
|
+
succeeded: outcome.report.passed,
|
|
612
|
+
// The runner authored this report itself, so its provenance is
|
|
613
|
+
// valid by construction; only the command's verdict can be red.
|
|
614
|
+
integrityValid: true,
|
|
615
|
+
};
|
|
616
|
+
},
|
|
617
|
+
async decideLoop({ loop, step, attempt, terminalAttempt, result: stepResult }) {
|
|
618
|
+
// The coordinator's review policy: pass ends the loop, a failure with
|
|
619
|
+
// attempts left earns one repair, and the same failure at the bound is
|
|
620
|
+
// the terminal answer. `revise` is never a verdict the reviewed model
|
|
621
|
+
// authors.
|
|
622
|
+
const decider = receiptsByStep.get(step.id);
|
|
623
|
+
// The subject is the work under review: the most recent agent run
|
|
624
|
+
// upstream of this verification. A direct dependency is not enough,
|
|
625
|
+
// because a loop's declared dependency is another loop's deterministic
|
|
626
|
+
// check, which produced no receipt at all.
|
|
627
|
+
const ancestors = executionPlanAncestors(livePlan, step.id);
|
|
628
|
+
let subject: RunReceipt | undefined;
|
|
629
|
+
for (const [stepId, receipt] of receiptsByStep) {
|
|
630
|
+
if (ancestors.has(stepId)) subject = receipt;
|
|
631
|
+
}
|
|
632
|
+
if (decider === undefined || subject === undefined) {
|
|
633
|
+
return {
|
|
634
|
+
resolved: false,
|
|
635
|
+
findings: null,
|
|
636
|
+
needsDecision: `loop '${loop.id}' cycle ${attempt} has no sealed reviewer or subject receipt`,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
const correlation = fleetGateCorrelation(subject, decider);
|
|
640
|
+
const decided = decideReviewGate({
|
|
641
|
+
group: `${fleetRootId}:${loop.id}`,
|
|
642
|
+
cycle: attempt,
|
|
643
|
+
terminalCycle: terminalAttempt,
|
|
644
|
+
subjects: [{ runId: subject.runId, digest: subject.integrity.digest }],
|
|
645
|
+
decider: { runId: decider.runId, digest: decider.integrity.digest },
|
|
646
|
+
correlation,
|
|
647
|
+
output: stepResult.output,
|
|
648
|
+
});
|
|
649
|
+
independentReviewFresh = decided.verdict === "pass" && correlation.independent;
|
|
650
|
+
if (!replayed.has(step.id)) {
|
|
651
|
+
const staged = stagePendingGateDecision(decided.draft);
|
|
652
|
+
const { path: decisionPath } = materializePendingGateDecision(staged);
|
|
653
|
+
notice(
|
|
654
|
+
`loop ${loop.id} cycle ${attempt}: ${decided.draft.outcome}${decided.draft.detail === undefined ? "" : ` (${decided.draft.detail})`} decision=${decisionPath}`,
|
|
655
|
+
"gate",
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
return { resolved: decided.verdict === "pass", findings: decided.findings, needsDecision: decided.needsDecision };
|
|
659
|
+
},
|
|
660
|
+
beginWriteBoundary: (window, stepIds) => boundaryEnforcer.begin(window, stepIds),
|
|
661
|
+
verifyWriteBoundary: (window, stepIds) => boundaryEnforcer.verify(window, stepIds),
|
|
662
|
+
cancel: (assignmentId) => dispatch.abort(assignmentId),
|
|
663
|
+
release: (ownerId) => {
|
|
664
|
+
if (ownerId === NO_WORKER_RESERVATION) return;
|
|
665
|
+
dispatch.reservations?.rollbackUnconsumed(ownerId);
|
|
666
|
+
},
|
|
667
|
+
releaseUnconsumed: (ownerId) => {
|
|
668
|
+
if (ownerId === NO_WORKER_RESERVATION) return;
|
|
669
|
+
dispatch.reservations?.rollbackUnconsumed(ownerId);
|
|
670
|
+
},
|
|
671
|
+
onStepSettled: async (step, stepResult) => {
|
|
672
|
+
fleetRunRecord.steps.push({ stepId: step.id, result: stepResult });
|
|
673
|
+
await writeFleetRun(fleetRunRecord);
|
|
674
|
+
},
|
|
675
|
+
spliceAfter(currentPlan, step, stepResult) {
|
|
676
|
+
if (stepResult.delegationPlan === undefined) return null;
|
|
677
|
+
const tasks = stepResult.delegationPlan.tasks.map((task) => {
|
|
678
|
+
const spec = agents.getSpec(task.agent);
|
|
679
|
+
if (spec === null || spec.capabilityClass === "orchestration" || spec.capabilityClass === "internal") {
|
|
680
|
+
throw new Error(`delegation_plan_agent_unavailable:${task.agent}`);
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
id: task.id,
|
|
684
|
+
agentId: task.agent,
|
|
685
|
+
task: task.description,
|
|
686
|
+
dependencies: task.depends_on,
|
|
687
|
+
writes: task.writes,
|
|
688
|
+
...(step.target !== undefined ? { target: step.target } : {}),
|
|
689
|
+
...(step.profile !== undefined ? { profile: step.profile } : {}),
|
|
690
|
+
requestedAuthority: spec.capabilityClass,
|
|
691
|
+
approvedAuthority: spec.capabilityClass,
|
|
692
|
+
expectedResultContract: spec.resultContract.kind,
|
|
693
|
+
executionRole: requestExecutionRole({
|
|
694
|
+
agentId: task.agent,
|
|
695
|
+
resolveFacts: (id) => {
|
|
696
|
+
const candidate = agents.getSpec(id);
|
|
697
|
+
return candidate === null
|
|
698
|
+
? null
|
|
699
|
+
: {
|
|
700
|
+
capabilityClass: candidate.capabilityClass,
|
|
701
|
+
resultContractKind: candidate.resultContract.kind,
|
|
702
|
+
};
|
|
703
|
+
},
|
|
704
|
+
}),
|
|
705
|
+
};
|
|
706
|
+
});
|
|
707
|
+
livePlan = spliceExecutionPlan(currentPlan, step.id, tasks);
|
|
708
|
+
return livePlan;
|
|
709
|
+
},
|
|
710
|
+
replayed,
|
|
711
|
+
});
|
|
712
|
+
} catch (error) {
|
|
713
|
+
fleetRunRecord.endedAt = new Date().toISOString();
|
|
714
|
+
await writeFleetRun(fleetRunRecord);
|
|
715
|
+
throw error;
|
|
716
|
+
}
|
|
717
|
+
fleetRunRecord.endedAt = new Date().toISOString();
|
|
718
|
+
await writeFleetRun(fleetRunRecord);
|
|
719
|
+
|
|
720
|
+
// A loop is judged by whether it converged, not attempt by attempt: a red
|
|
721
|
+
// verification that a later attempt fixed is the loop working as declared,
|
|
722
|
+
// and a node a resolved loop made unnecessary never had to run at all.
|
|
723
|
+
const required = livePlan.steps.filter((step) => step.loop === undefined && !result.unneeded.includes(step.id));
|
|
724
|
+
const failedLoops = result.loops.filter((loop) => !loop.resolved);
|
|
725
|
+
const succeededStepCount = required.filter((step) => result.results.get(step.id)?.succeeded === true).length;
|
|
726
|
+
return {
|
|
727
|
+
rootId: fleetRootId,
|
|
728
|
+
planHash: input.plan.hash,
|
|
729
|
+
result,
|
|
730
|
+
receipts,
|
|
731
|
+
totalCostUsd: receipts.reduce((sum, receipt) => sum + receipt.costUsd, 0),
|
|
732
|
+
requiredStepCount: required.length,
|
|
733
|
+
succeededStepCount,
|
|
734
|
+
resolvedLoopCount: result.loops.length - failedLoops.length,
|
|
735
|
+
cleanRun: failedLoops.length === 0 && result.skipped.length === 0 && succeededStepCount === required.length,
|
|
736
|
+
};
|
|
737
|
+
}
|