@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
|
@@ -7,10 +7,21 @@ export type ResultContract =
|
|
|
7
7
|
| { kind: "architect-plan"; path: string }
|
|
8
8
|
| { kind: "scout-report" }
|
|
9
9
|
| { kind: "verifier-report" }
|
|
10
|
+
| { kind: "council-report" }
|
|
10
11
|
| { kind: "debugger-report" }
|
|
11
12
|
| { kind: "research-report" }
|
|
12
13
|
| { kind: "mutation-report" }
|
|
13
14
|
| { kind: "provenance-report" }
|
|
15
|
+
| { kind: "delegation-plan" }
|
|
16
|
+
/**
|
|
17
|
+
* The advisory answer `/oracle` asks for. It exists as its own kind because
|
|
18
|
+
* the answer shape is the whole point of the command: a verdict, the
|
|
19
|
+
* strongest challenge the advisor can mount, the evidence that would change
|
|
20
|
+
* its mind, and the prior decisions it read. Folding it onto
|
|
21
|
+
* `research-report` would ask for claims and evidence instead, and the
|
|
22
|
+
* challenge and the reversal condition would have nowhere to go.
|
|
23
|
+
*/
|
|
24
|
+
| { kind: "oracle-report" }
|
|
14
25
|
| { kind: "external-delegation" }
|
|
15
26
|
/**
|
|
16
27
|
* The run's postcondition is the artifact it left on disk, so its terminal
|
|
@@ -58,6 +69,7 @@ export interface ResultContractValidation {
|
|
|
58
69
|
scout?: ScoutResult;
|
|
59
70
|
/** Parsed Verifier data for the dispatch review-gate projection. */
|
|
60
71
|
verifier?: VerifierResult;
|
|
72
|
+
delegationPlan?: DelegationPlanResult;
|
|
61
73
|
reason?: string;
|
|
62
74
|
}
|
|
63
75
|
|
|
@@ -211,6 +223,159 @@ export interface VerifierResult {
|
|
|
211
223
|
checks: ReadonlyArray<VerifierCheck>;
|
|
212
224
|
}
|
|
213
225
|
|
|
226
|
+
export interface DelegationPlanResultTask {
|
|
227
|
+
id: string;
|
|
228
|
+
agent: string;
|
|
229
|
+
description: string;
|
|
230
|
+
depends_on: ReadonlyArray<string>;
|
|
231
|
+
writes: ReadonlyArray<string>;
|
|
232
|
+
mode?: "sequential" | "parallel";
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface DelegationPlanResult {
|
|
236
|
+
tasks: ReadonlyArray<DelegationPlanResultTask>;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function parseDelegationPlanResult(output: string | null): DelegationPlanResult | null {
|
|
240
|
+
const parsed = parseJson(output);
|
|
241
|
+
if (
|
|
242
|
+
!parsed.ok ||
|
|
243
|
+
!hasOnlyKeys(parsed.value, ["tasks"]) ||
|
|
244
|
+
!Array.isArray(parsed.value.tasks) ||
|
|
245
|
+
parsed.value.tasks.length === 0
|
|
246
|
+
)
|
|
247
|
+
return null;
|
|
248
|
+
const tasks: DelegationPlanResultTask[] = [];
|
|
249
|
+
for (const raw of parsed.value.tasks) {
|
|
250
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
251
|
+
const task = raw as Record<string, unknown>;
|
|
252
|
+
if (
|
|
253
|
+
!hasOnlyKeys(task, ["id", "agent", "description", "depends_on", "writes", "mode"]) ||
|
|
254
|
+
!string(task.id) ||
|
|
255
|
+
!string(task.agent) ||
|
|
256
|
+
!string(task.description) ||
|
|
257
|
+
!Array.isArray(task.depends_on) ||
|
|
258
|
+
task.depends_on.some((entry) => !string(entry)) ||
|
|
259
|
+
!Array.isArray(task.writes) ||
|
|
260
|
+
task.writes.some((entry) => !string(entry)) ||
|
|
261
|
+
(task.mode !== undefined && task.mode !== "sequential" && task.mode !== "parallel")
|
|
262
|
+
)
|
|
263
|
+
return null;
|
|
264
|
+
tasks.push({
|
|
265
|
+
id: task.id,
|
|
266
|
+
agent: task.agent,
|
|
267
|
+
description: task.description,
|
|
268
|
+
depends_on: [...task.depends_on] as string[],
|
|
269
|
+
writes: [...task.writes] as string[],
|
|
270
|
+
...(task.mode !== undefined ? { mode: task.mode as "sequential" | "parallel" } : {}),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return { tasks };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export interface CouncilReportMember {
|
|
277
|
+
label: string;
|
|
278
|
+
runId: string;
|
|
279
|
+
round: number;
|
|
280
|
+
answer: string;
|
|
281
|
+
verdict?: string;
|
|
282
|
+
failed?: { reason: string };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface CouncilReport {
|
|
286
|
+
members: CouncilReportMember[];
|
|
287
|
+
synthesis: {
|
|
288
|
+
kind: "none" | "judge" | "vote";
|
|
289
|
+
text?: string;
|
|
290
|
+
verdict?: string;
|
|
291
|
+
tally?: Record<string, number>;
|
|
292
|
+
judgeRunId?: string;
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function parseCouncilReport(output: string | null): CouncilReport | null {
|
|
297
|
+
if (output === null) return null;
|
|
298
|
+
const parsed = parseJsonObjectPayload(output);
|
|
299
|
+
if (!parsed.ok) return null;
|
|
300
|
+
const value = parsed.value;
|
|
301
|
+
if (!hasOnlyKeys(value, ["members", "synthesis"]) || !Array.isArray(value.members) || value.members.length === 0) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
const members: CouncilReportMember[] = [];
|
|
305
|
+
for (const raw of value.members) {
|
|
306
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
307
|
+
const member = raw as Record<string, unknown>;
|
|
308
|
+
if (
|
|
309
|
+
!hasOnlyKeys(member, ["label", "runId", "round", "answer", "verdict", "failed"]) ||
|
|
310
|
+
!string(member.label) ||
|
|
311
|
+
!string(member.runId) ||
|
|
312
|
+
!Number.isInteger(member.round) ||
|
|
313
|
+
Number(member.round) < 1 ||
|
|
314
|
+
typeof member.answer !== "string" ||
|
|
315
|
+
(member.verdict !== undefined && !string(member.verdict))
|
|
316
|
+
)
|
|
317
|
+
return null;
|
|
318
|
+
let failed: { reason: string } | undefined;
|
|
319
|
+
if (member.failed !== undefined) {
|
|
320
|
+
if (
|
|
321
|
+
member.failed === null ||
|
|
322
|
+
typeof member.failed !== "object" ||
|
|
323
|
+
Array.isArray(member.failed) ||
|
|
324
|
+
!hasOnlyKeys(member.failed as Record<string, unknown>, ["reason"]) ||
|
|
325
|
+
!string((member.failed as Record<string, unknown>).reason)
|
|
326
|
+
)
|
|
327
|
+
return null;
|
|
328
|
+
failed = { reason: (member.failed as { reason: string }).reason };
|
|
329
|
+
}
|
|
330
|
+
members.push({
|
|
331
|
+
label: member.label,
|
|
332
|
+
runId: member.runId,
|
|
333
|
+
round: Number(member.round),
|
|
334
|
+
answer: member.answer,
|
|
335
|
+
...(member.verdict !== undefined ? { verdict: member.verdict as string } : {}),
|
|
336
|
+
...(failed !== undefined ? { failed } : {}),
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
if (value.synthesis === null || typeof value.synthesis !== "object" || Array.isArray(value.synthesis)) return null;
|
|
340
|
+
const rawSynthesis = value.synthesis as Record<string, unknown>;
|
|
341
|
+
if (!hasOnlyKeys(rawSynthesis, ["kind", "text", "verdict", "tally", "judgeRunId"])) return null;
|
|
342
|
+
if (rawSynthesis.kind !== "none" && rawSynthesis.kind !== "judge" && rawSynthesis.kind !== "vote") return null;
|
|
343
|
+
for (const key of ["text", "verdict", "judgeRunId"] as const) {
|
|
344
|
+
if (rawSynthesis[key] !== undefined && !string(rawSynthesis[key])) return null;
|
|
345
|
+
}
|
|
346
|
+
let tally: Record<string, number> | undefined;
|
|
347
|
+
if (rawSynthesis.tally !== undefined) {
|
|
348
|
+
if (rawSynthesis.tally === null || typeof rawSynthesis.tally !== "object" || Array.isArray(rawSynthesis.tally))
|
|
349
|
+
return null;
|
|
350
|
+
tally = {};
|
|
351
|
+
for (const [key, count] of Object.entries(rawSynthesis.tally as Record<string, unknown>)) {
|
|
352
|
+
if (!key || !Number.isInteger(count) || Number(count) < 0) return null;
|
|
353
|
+
tally[key] = Number(count);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
members,
|
|
358
|
+
synthesis: {
|
|
359
|
+
kind: rawSynthesis.kind,
|
|
360
|
+
...(rawSynthesis.text !== undefined ? { text: rawSynthesis.text as string } : {}),
|
|
361
|
+
...(rawSynthesis.verdict !== undefined ? { verdict: rawSynthesis.verdict as string } : {}),
|
|
362
|
+
...(tally !== undefined ? { tally } : {}),
|
|
363
|
+
...(rawSynthesis.judgeRunId !== undefined ? { judgeRunId: rawSynthesis.judgeRunId as string } : {}),
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Parsed `oracle-report` payload. `/oracle` renders these four fields into the
|
|
370
|
+
* operator note it hands the main agent, so the shape is the answer shape.
|
|
371
|
+
*/
|
|
372
|
+
export interface OracleResult {
|
|
373
|
+
verdict: string;
|
|
374
|
+
challenge: string;
|
|
375
|
+
changesMyMind: string;
|
|
376
|
+
citedDecisions: ReadonlyArray<string>;
|
|
377
|
+
}
|
|
378
|
+
|
|
214
379
|
/**
|
|
215
380
|
* Parsed `code-report` payload. `passed` restates `exitCode === 0` so a
|
|
216
381
|
* downstream reader never has to know a runner's exit conventions, and
|
|
@@ -269,7 +434,7 @@ function success(
|
|
|
269
434
|
contract: ResultContract,
|
|
270
435
|
quality: ResultContractQuality,
|
|
271
436
|
value: unknown,
|
|
272
|
-
extra: Pick<ResultContractValidation, "scout" | "verifier" | "reason"> = {},
|
|
437
|
+
extra: Pick<ResultContractValidation, "scout" | "verifier" | "delegationPlan" | "reason"> = {},
|
|
273
438
|
): ResultContractValidation {
|
|
274
439
|
return {
|
|
275
440
|
conformance: "pass",
|
|
@@ -335,6 +500,7 @@ const RESULT_CONTRACT_KINDS: ReadonlyArray<ResultContract["kind"]> = [
|
|
|
335
500
|
"architect-plan",
|
|
336
501
|
"scout-report",
|
|
337
502
|
"verifier-report",
|
|
503
|
+
"council-report",
|
|
338
504
|
"debugger-report",
|
|
339
505
|
"research-report",
|
|
340
506
|
"mutation-report",
|
|
@@ -664,6 +830,47 @@ export function parseVerifierResult(output: string | null): VerifierResult | nul
|
|
|
664
830
|
return validation.verifier ?? null;
|
|
665
831
|
}
|
|
666
832
|
|
|
833
|
+
/**
|
|
834
|
+
* An advisory opinion is never a correctness signal about the route that
|
|
835
|
+
* produced it, so conformance is judged on the shape alone and quality stays
|
|
836
|
+
* `unmeasured`. `citedDecisions` may be empty: an honest advisor that found no
|
|
837
|
+
* settled decision bearing on the question must be able to say so.
|
|
838
|
+
*/
|
|
839
|
+
function validateOracle(contract: ResultContract, output: string | null): ResultContractValidation {
|
|
840
|
+
const parsed = parseJson(output);
|
|
841
|
+
if (!parsed.ok) return failure(contract, "unmeasured", parsed.reason);
|
|
842
|
+
const value = parsed.value;
|
|
843
|
+
if (
|
|
844
|
+
!hasOnlyKeys(value, ["verdict", "challenge", "changesMyMind", "citedDecisions"]) ||
|
|
845
|
+
!string(value.verdict) ||
|
|
846
|
+
!string(value.challenge) ||
|
|
847
|
+
!string(value.changesMyMind) ||
|
|
848
|
+
!Array.isArray(value.citedDecisions) ||
|
|
849
|
+
value.citedDecisions.some((entry) => !string(entry))
|
|
850
|
+
) {
|
|
851
|
+
return failure(
|
|
852
|
+
contract,
|
|
853
|
+
"unmeasured",
|
|
854
|
+
'Oracle result must carry verdict, challenge, changesMyMind, and a citedDecisions array of strings, for example {"verdict":"consistent with decision routing.target","challenge":"...","changesMyMind":"...","citedDecisions":["routing.target"]}',
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
return success(contract, "unmeasured", value);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/** Parse an oracle answer under the same schema its recipe contract enforces. */
|
|
861
|
+
export function parseOracleResult(output: string | null): OracleResult | null {
|
|
862
|
+
const validation = validateOracle({ kind: "oracle-report" }, output);
|
|
863
|
+
if (validation.conformance !== "pass") return null;
|
|
864
|
+
const parsed = parseJson(output);
|
|
865
|
+
if (!parsed.ok) return null;
|
|
866
|
+
return {
|
|
867
|
+
verdict: String(parsed.value.verdict),
|
|
868
|
+
challenge: String(parsed.value.challenge),
|
|
869
|
+
changesMyMind: String(parsed.value.changesMyMind),
|
|
870
|
+
citedDecisions: (parsed.value.citedDecisions as ReadonlyArray<string>).map((entry) => String(entry)),
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
|
|
667
874
|
/**
|
|
668
875
|
* A code step's result is always `unmeasured` for routing quality. A red suite
|
|
669
876
|
* is evidence about the repository, not about the route that ran the command,
|
|
@@ -1036,6 +1243,12 @@ export function validateResultContract(input: ResultContractValidationInput): Re
|
|
|
1036
1243
|
}
|
|
1037
1244
|
case "verifier-report":
|
|
1038
1245
|
return validateVerifier(input.contract, input.output);
|
|
1246
|
+
case "council-report": {
|
|
1247
|
+
const report = parseCouncilReport(input.output);
|
|
1248
|
+
return report === null
|
|
1249
|
+
? failure(input.contract, "fail", "council-report is malformed")
|
|
1250
|
+
: success(input.contract, "unmeasured", { council: report });
|
|
1251
|
+
}
|
|
1039
1252
|
case "debugger-report":
|
|
1040
1253
|
return validateDebugger(input.contract, input.output);
|
|
1041
1254
|
case "research-report":
|
|
@@ -1044,6 +1257,18 @@ export function validateResultContract(input: ResultContractValidationInput): Re
|
|
|
1044
1257
|
return validateMutation(input.contract, input);
|
|
1045
1258
|
case "provenance-report":
|
|
1046
1259
|
return validateProvenance(input.contract, input.output);
|
|
1260
|
+
case "delegation-plan": {
|
|
1261
|
+
const plan = parseDelegationPlanResult(input.output);
|
|
1262
|
+
return plan === null
|
|
1263
|
+
? failure(
|
|
1264
|
+
input.contract,
|
|
1265
|
+
"fail",
|
|
1266
|
+
"delegation-plan must carry tasks with id, agent, description, depends_on, writes, and optional mode",
|
|
1267
|
+
)
|
|
1268
|
+
: success(input.contract, "unmeasured", plan, { delegationPlan: plan });
|
|
1269
|
+
}
|
|
1270
|
+
case "oracle-report":
|
|
1271
|
+
return validateOracle(input.contract, input.output);
|
|
1047
1272
|
case "external-delegation":
|
|
1048
1273
|
return success(input.contract, "unmeasured", { external: true });
|
|
1049
1274
|
case "artifact-report":
|
|
@@ -1120,6 +1345,8 @@ function resultContractShape(contract: ResultContract): string {
|
|
|
1120
1345
|
return '{"findings":[{"claim":"what you observed","path":"src/file.ts","line":1}],"needsSplit":false,"proposedSubtasks":[]} or {"findings":[],"needsSplit":true,"proposedSubtasks":[{"id":"inspect","task":"Inspect the boundary","dependencies":[],"expectedResultContract":"scout-report","requestedAuthority":"read-only"}]} or, if you cannot produce a citation you are sure of, at least {"findings":[{"claim":"what you observed"}]}, which is accepted as an ungrounded lead rather than losing the run';
|
|
1121
1346
|
case "verifier-report":
|
|
1122
1347
|
return '{"verdict":"pass","checks":[{"name":"npm run typecheck","passed":true,"evidence":"exit 0"}]}';
|
|
1348
|
+
case "council-report":
|
|
1349
|
+
return '{"members":[{"label":"alpha","runId":"run-1","round":1,"answer":"..."}],"synthesis":{"kind":"none"}}';
|
|
1123
1350
|
case "debugger-report":
|
|
1124
1351
|
return '{"diagnosis":"...","reproduction":"reproduced","evidence":["..."]}';
|
|
1125
1352
|
case "research-report":
|
|
@@ -1128,6 +1355,10 @@ function resultContractShape(contract: ResultContract): string {
|
|
|
1128
1355
|
return '{"mutatedPaths":["src/file.ts"],"validations":[{"name":"npm test","passed":true,"evidence":"exit 0"}],"commitMessage":"optional: the commit message for this change","summary":"optional: one line"}';
|
|
1129
1356
|
case "provenance-report":
|
|
1130
1357
|
return '{"confirmedFacts":["..."],"missingEvidence":["..."],"nextInspections":["..."]}';
|
|
1358
|
+
case "delegation-plan":
|
|
1359
|
+
return '{"tasks":[{"id":"build","agent":"coder","description":"Implement the change","depends_on":[],"writes":["src"],"mode":"parallel"}]}';
|
|
1360
|
+
case "oracle-report":
|
|
1361
|
+
return '{"verdict":"one line","challenge":"the strongest objection you can mount","changesMyMind":"the evidence that would reverse the verdict","citedDecisions":["decision key or task id you relied on"]}';
|
|
1131
1362
|
case "external-delegation":
|
|
1132
1363
|
return "any final text";
|
|
1133
1364
|
case "artifact-report":
|
|
@@ -1253,13 +1484,16 @@ export function parseResultContract(value: unknown, sourcePath: string): ResultC
|
|
|
1253
1484
|
const kinds = [
|
|
1254
1485
|
"scout-report",
|
|
1255
1486
|
"verifier-report",
|
|
1487
|
+
"council-report",
|
|
1256
1488
|
"debugger-report",
|
|
1257
1489
|
"research-report",
|
|
1258
1490
|
"mutation-report",
|
|
1259
1491
|
"provenance-report",
|
|
1492
|
+
"oracle-report",
|
|
1260
1493
|
"external-delegation",
|
|
1261
1494
|
"artifact-report",
|
|
1262
1495
|
"context-handbook",
|
|
1496
|
+
"delegation-plan",
|
|
1263
1497
|
] as const;
|
|
1264
1498
|
if (!(kinds as ReadonlyArray<string>).includes(record.kind) || !only("kind")) {
|
|
1265
1499
|
throw new Error(`agent recipe: ${sourcePath}: resultContract.kind is unsupported or has unknown keys`);
|
|
@@ -28,6 +28,10 @@ const HOT_RELOAD_FIELDS = new Set<string>([
|
|
|
28
28
|
"modelSelector",
|
|
29
29
|
"terminal.smoothStreaming",
|
|
30
30
|
"attribution.gitCommits",
|
|
31
|
+
// The watchdog registration reads its settings live on every trigger, so
|
|
32
|
+
// enabling it, retargeting it, or changing its cadence takes effect on the
|
|
33
|
+
// next turn boundary without a restart or a session-routing patch.
|
|
34
|
+
"watchdog",
|
|
31
35
|
]);
|
|
32
36
|
|
|
33
37
|
const NEXT_TURN_FIELDS = new Set<string>([
|
|
@@ -37,6 +37,12 @@ export function routeValidationProjection(
|
|
|
37
37
|
// Presentation-only parentage stamped by the calling tool, on the same
|
|
38
38
|
// terms: a model must not be able to author it.
|
|
39
39
|
parentToolCallId,
|
|
40
|
+
resolvedVerification,
|
|
41
|
+
resultContractOverride,
|
|
42
|
+
fleetGateReceipt,
|
|
43
|
+
taskWorktree,
|
|
44
|
+
runIdHint,
|
|
45
|
+
taskWorktreeDestination,
|
|
40
46
|
...raw
|
|
41
47
|
} = request;
|
|
42
48
|
const awaitingEnvelope =
|
|
@@ -54,6 +60,14 @@ export function routeValidationProjection(
|
|
|
54
60
|
...(assignmentDeadlineAt !== undefined ? { assignmentDeadlineAt } : {}),
|
|
55
61
|
...(ledger !== undefined ? { ledger } : {}),
|
|
56
62
|
...(parentToolCallId !== undefined ? { parentToolCallId } : {}),
|
|
63
|
+
...(resolvedVerification !== undefined
|
|
64
|
+
? { resolvedVerification: resolvedVerification.map((check) => ({ ...check, argv: [...check.argv] })) }
|
|
65
|
+
: {}),
|
|
66
|
+
...(resultContractOverride !== undefined ? { resultContractOverride } : {}),
|
|
67
|
+
...(fleetGateReceipt !== undefined ? { fleetGateReceipt: { ...fleetGateReceipt } } : {}),
|
|
68
|
+
...(taskWorktree !== undefined ? { taskWorktree } : {}),
|
|
69
|
+
...(runIdHint !== undefined ? { runIdHint } : {}),
|
|
70
|
+
...(taskWorktreeDestination !== undefined ? { taskWorktreeDestination } : {}),
|
|
57
71
|
...(awaitingEnvelope ? { failover: "approved" as const } : {}),
|
|
58
72
|
}),
|
|
59
73
|
};
|
|
@@ -54,6 +54,7 @@ export function isDeterministicOutcomeCode(code: RunOutcomeCode | null | undefin
|
|
|
54
54
|
code === "worker_tool_call_cap_exhausted" ||
|
|
55
55
|
code === "loop_guard_tools_disabled_exhausted" ||
|
|
56
56
|
code === "result_contract_exhausted" ||
|
|
57
|
-
code === "worker_final_output_missing"
|
|
57
|
+
code === "worker_final_output_missing" ||
|
|
58
|
+
code === "host_verification_rejected"
|
|
58
59
|
);
|
|
59
60
|
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { BIRTH_TOKEN_SOURCE_AVAILABLE, processAlive, processBirthToken } from "../../core/process-identity.js";
|
|
5
|
+
import { withStateFileLockSync } from "../../core/state-file-lock.js";
|
|
6
|
+
import { clioStateDir } from "../../core/xdg.js";
|
|
7
|
+
import { atomicWrite } from "../../engine/session.js";
|
|
8
|
+
|
|
9
|
+
export interface CheckoutWriterLeaseRecord {
|
|
10
|
+
version: 1;
|
|
11
|
+
checkout: string;
|
|
12
|
+
pid: number;
|
|
13
|
+
processBirthToken: string;
|
|
14
|
+
acquiredAt: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CheckoutWriterLease {
|
|
18
|
+
checkout: string;
|
|
19
|
+
path: string;
|
|
20
|
+
release(): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CheckoutWriterLeaseProbe {
|
|
24
|
+
birthToken(pid: number): string | null;
|
|
25
|
+
tokenProvesDeath?: boolean;
|
|
26
|
+
alive?(pid: number): boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const held = new Map<string, { count: number; path: string }>();
|
|
30
|
+
let exitHooksInstalled = false;
|
|
31
|
+
|
|
32
|
+
function canonicalCheckout(checkout: string): string {
|
|
33
|
+
return realpathSync(checkout);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function checkoutWriterLeasePath(checkout: string): string {
|
|
37
|
+
const canonical = canonicalCheckout(checkout);
|
|
38
|
+
const key = createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
39
|
+
return join(clioStateDir(), "checkout-writer-leases", `${key}.json`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function validRecord(value: unknown): value is CheckoutWriterLeaseRecord {
|
|
43
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
44
|
+
const record = value as Partial<CheckoutWriterLeaseRecord>;
|
|
45
|
+
return (
|
|
46
|
+
record.version === 1 &&
|
|
47
|
+
typeof record.checkout === "string" &&
|
|
48
|
+
Number.isSafeInteger(record.pid) &&
|
|
49
|
+
(record.pid ?? 0) > 0 &&
|
|
50
|
+
typeof record.processBirthToken === "string" &&
|
|
51
|
+
typeof record.acquiredAt === "string" &&
|
|
52
|
+
Number.isFinite(Date.parse(record.acquiredAt))
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function readRecord(path: string): CheckoutWriterLeaseRecord | null {
|
|
57
|
+
if (!existsSync(path)) return null;
|
|
58
|
+
try {
|
|
59
|
+
const value = JSON.parse(readFileSync(path, "utf8")) as unknown;
|
|
60
|
+
if (!validRecord(value)) throw new Error("invalid schema");
|
|
61
|
+
return value;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`checkout writer lease is unreadable at ${path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function ownerAlive(record: CheckoutWriterLeaseRecord, probe: CheckoutWriterLeaseProbe): boolean {
|
|
70
|
+
const current = probe.birthToken(record.pid);
|
|
71
|
+
if (current === null || current !== record.processBirthToken) return false;
|
|
72
|
+
if (probe.tokenProvesDeath === false) return probe.alive?.(record.pid) ?? false;
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const defaultProbe: CheckoutWriterLeaseProbe = {
|
|
77
|
+
birthToken: processBirthToken,
|
|
78
|
+
tokenProvesDeath: BIRTH_TOKEN_SOURCE_AVAILABLE,
|
|
79
|
+
alive: processAlive,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
function releaseAllOnExit(): void {
|
|
83
|
+
for (const [checkout, entry] of held) {
|
|
84
|
+
try {
|
|
85
|
+
withStateFileLockSync(entry.path, () => {
|
|
86
|
+
const record = readRecord(entry.path);
|
|
87
|
+
if (record?.checkout === checkout && record.pid === process.pid) unlinkSync(entry.path);
|
|
88
|
+
});
|
|
89
|
+
} catch {
|
|
90
|
+
// Process exit is best effort. A stale record is reclaimed by the next admission.
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
held.clear();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function installExitHooks(): void {
|
|
97
|
+
if (exitHooksInstalled) return;
|
|
98
|
+
exitHooksInstalled = true;
|
|
99
|
+
process.once("exit", releaseAllOnExit);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function acquireCheckoutWriterLease(input: {
|
|
103
|
+
checkout: string;
|
|
104
|
+
nowMs?: number;
|
|
105
|
+
pid?: number;
|
|
106
|
+
processBirthToken?: string;
|
|
107
|
+
probe?: CheckoutWriterLeaseProbe;
|
|
108
|
+
}): CheckoutWriterLease {
|
|
109
|
+
const checkout = canonicalCheckout(input.checkout);
|
|
110
|
+
const path = checkoutWriterLeasePath(checkout);
|
|
111
|
+
const local = held.get(checkout);
|
|
112
|
+
if (local !== undefined && (input.pid === undefined || input.pid === process.pid)) {
|
|
113
|
+
local.count += 1;
|
|
114
|
+
let released = false;
|
|
115
|
+
return {
|
|
116
|
+
checkout,
|
|
117
|
+
path,
|
|
118
|
+
release: () => {
|
|
119
|
+
if (released) return;
|
|
120
|
+
released = true;
|
|
121
|
+
releaseCheckoutWriterLease(checkout);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
126
|
+
const ownerPid = input.pid ?? process.pid;
|
|
127
|
+
const token = input.processBirthToken ?? processBirthToken(ownerPid);
|
|
128
|
+
if (token === null) throw new Error("dispatch: cannot establish checkout writer process birth token");
|
|
129
|
+
withStateFileLockSync(path, () => {
|
|
130
|
+
const existing = readRecord(path);
|
|
131
|
+
if (existing !== null && ownerAlive(existing, input.probe ?? defaultProbe)) {
|
|
132
|
+
throw new Error(`checkout_writer_lease_held: checkout writer lease is held by pid ${existing.pid}`);
|
|
133
|
+
}
|
|
134
|
+
const record: CheckoutWriterLeaseRecord = {
|
|
135
|
+
version: 1,
|
|
136
|
+
checkout,
|
|
137
|
+
pid: ownerPid,
|
|
138
|
+
processBirthToken: token,
|
|
139
|
+
acquiredAt: new Date(input.nowMs ?? Date.now()).toISOString(),
|
|
140
|
+
};
|
|
141
|
+
atomicWrite(path, JSON.stringify(record, null, 2));
|
|
142
|
+
});
|
|
143
|
+
if (ownerPid === process.pid) {
|
|
144
|
+
held.set(checkout, { count: 1, path });
|
|
145
|
+
installExitHooks();
|
|
146
|
+
}
|
|
147
|
+
let released = false;
|
|
148
|
+
return {
|
|
149
|
+
checkout,
|
|
150
|
+
path,
|
|
151
|
+
release: () => {
|
|
152
|
+
if (released) return;
|
|
153
|
+
released = true;
|
|
154
|
+
if (ownerPid === process.pid) releaseCheckoutWriterLease(checkout);
|
|
155
|
+
else releaseLeaseRecord(path, checkout, ownerPid, token);
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function releaseLeaseRecord(path: string, checkout: string, pid: number, token: string): void {
|
|
161
|
+
withStateFileLockSync(path, () => {
|
|
162
|
+
const record = readRecord(path);
|
|
163
|
+
if (record?.checkout === checkout && record.pid === pid && record.processBirthToken === token) unlinkSync(path);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function releaseCheckoutWriterLease(checkout: string): void {
|
|
168
|
+
const entry = held.get(checkout);
|
|
169
|
+
if (entry === undefined) return;
|
|
170
|
+
entry.count -= 1;
|
|
171
|
+
if (entry.count > 0) return;
|
|
172
|
+
held.delete(checkout);
|
|
173
|
+
const token = processBirthToken(process.pid);
|
|
174
|
+
if (token !== null) releaseLeaseRecord(entry.path, checkout, process.pid, token);
|
|
175
|
+
}
|
|
@@ -15,6 +15,30 @@ import type { RunEnvelope, RunLineage, RunNodeIdentity, RunPhaseDurations, RunRe
|
|
|
15
15
|
import type { DispatchFailoverCandidate, JobSpec } from "./validation.js";
|
|
16
16
|
|
|
17
17
|
export interface DispatchRequest extends JobSpec {
|
|
18
|
+
/** Coordinator-owned result contract override for a fleet plan step. */
|
|
19
|
+
resultContractOverride?: ResultContract;
|
|
20
|
+
/** Gate path whose post-run hash is sealed into this step's receipt. */
|
|
21
|
+
fleetGateReceipt?: { path: string };
|
|
22
|
+
/** Orchestrator-prepared task worktree state. Model arguments cannot author it. */
|
|
23
|
+
taskWorktree?: {
|
|
24
|
+
root: string;
|
|
25
|
+
runId: string;
|
|
26
|
+
path: string;
|
|
27
|
+
branch: string;
|
|
28
|
+
base: string;
|
|
29
|
+
ownerToken: string;
|
|
30
|
+
};
|
|
31
|
+
/** Run identity allocated before task worktree creation. Model arguments cannot author it. */
|
|
32
|
+
runIdHint?: string;
|
|
33
|
+
/** Parent checkout frozen by plan approval for task worktree application. */
|
|
34
|
+
taskWorktreeDestination?: string;
|
|
35
|
+
/** Admission-resolved commands. Model text never enters these argv vectors. */
|
|
36
|
+
resolvedVerification?: ReadonlyArray<{
|
|
37
|
+
check: string;
|
|
38
|
+
argv: ReadonlyArray<string>;
|
|
39
|
+
cwd: string;
|
|
40
|
+
timeoutMs: number;
|
|
41
|
+
}>;
|
|
18
42
|
/**
|
|
19
43
|
* Semantic role this request's first attempt runs under. Derived once by
|
|
20
44
|
* `execution-role.ts` at request construction, never authored by a model, and
|
|
@@ -212,6 +236,16 @@ export interface DispatchContract {
|
|
|
212
236
|
finalPromise: Promise<ReadonlyArray<RunReceipt>>;
|
|
213
237
|
}>;
|
|
214
238
|
|
|
239
|
+
/** Seal a coordinator-only council synthesis receipt without invoking a model. */
|
|
240
|
+
sealCouncilSynthesis?(input: {
|
|
241
|
+
group: string;
|
|
242
|
+
round: number;
|
|
243
|
+
kind: "none" | "vote";
|
|
244
|
+
text: string;
|
|
245
|
+
subjects: ReadonlyArray<{ runId: string; digest: string | null }>;
|
|
246
|
+
template: RunReceipt;
|
|
247
|
+
}): Promise<RunReceipt>;
|
|
248
|
+
|
|
215
249
|
/** List runs from the ledger. */
|
|
216
250
|
listRuns(status?: RunStatus): ReadonlyArray<RunEnvelope>;
|
|
217
251
|
|