@tea-agent/loop-agent 0.39.3 → 0.40.0-beta.2
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 +135 -92
- package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
- package/dist/adapters/context-transfer/pi-session.js +61 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +12 -0
- package/dist/cli/program.js +23 -1
- package/dist/commands/dag-artifact.js +284 -0
- package/dist/commands/dag-context.js +184 -0
- package/dist/commands/dag-rerun.js +206 -1
- package/dist/executors/dag-pi-executor.js +108 -19
- package/dist/executors/pi-executor.js +4 -2
- package/dist/executors/pi-sdk-executor.js +49 -1
- package/dist/executors/pi-writer-tool-policy.js +57 -1
- package/dist/executors/shell-executor.js +42 -2
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/{worker → infrastructure}/console/app-data.js +4 -0
- package/dist/infrastructure/console/artifact-revision-store.js +430 -0
- package/dist/infrastructure/console/context-export-store.js +160 -0
- package/dist/infrastructure/console/dir-lock.js +132 -0
- package/dist/infrastructure/harness/artifact-store.js +10 -1
- package/dist/infrastructure/harness/atomic-write.js +35 -2
- package/dist/shared/context-transfer/artifact-revision.js +172 -0
- package/dist/shared/context-transfer.js +418 -0
- package/dist/shared/operator/capabilities.js +14 -0
- package/dist/shared/operator/safe-run-summary.js +1 -0
- package/dist/shared/path-safety.js +93 -0
- package/dist/shared/preview.js +28 -4
- package/dist/task/config-types.js +12 -1
- package/dist/task/source-prepare/fragment-inventory.js +1 -14
- package/dist/task/task-demand-routing.js +3 -0
- package/dist/worker/console/chat/chat-event-store.js +77 -7
- package/dist/worker/console/chat/context-panel.js +7 -0
- package/dist/worker/console/chat/deferred-turn.js +12 -0
- package/dist/worker/console/chat/pi-runtime.js +331 -18
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +389 -56
- package/dist/worker/console/chat/session-stats.js +179 -0
- package/dist/worker/console/chat/session-store.js +94 -64
- package/dist/worker/console/chat/todos.js +115 -0
- package/dist/worker/console/chat/tool-preview.js +42 -0
- package/dist/worker/console/chat/turn-process.js +20 -55
- package/dist/worker/console/chat/user-questions.js +266 -0
- package/dist/worker/console/chat/workspace-landing.js +79 -0
- package/dist/worker/console/console-update-runtime.js +1 -1
- package/dist/worker/console/context-transfer-diagnostics.js +198 -0
- package/dist/worker/console/dag-confirmation.js +1 -1
- package/dist/worker/console/dag-execution-receipt.js +1 -1
- package/dist/worker/console/doctor.js +1 -1
- package/dist/worker/console/draft-store.js +1 -1
- package/dist/worker/console/human-gate-token.js +1 -1
- package/dist/worker/console/index.js +3 -3
- package/dist/worker/console/interview/assessment.js +1 -1
- package/dist/worker/console/interview/session.js +1 -1
- package/dist/worker/console/native-directory-picker.js +13 -0
- package/dist/worker/console/operation-runner.js +45 -4
- package/dist/worker/console/operation-sse.js +1 -1
- package/dist/worker/console/operation-wait.js +1 -1
- package/dist/worker/console/operator-actions.js +104 -3
- package/dist/worker/console/operator-surface-health.js +1 -1
- package/dist/worker/console/operator-user-error.js +128 -0
- package/dist/worker/console/routes.js +927 -8
- package/dist/worker/console/security.js +39 -4
- package/dist/worker/console/server.js +532 -187
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CTx1TrNa.js → abnfDiagram-N423BO3Z-CpWvwARc.js} +1 -1
- package/dist/worker/console/static/assets/{arc-B4i9cVxX.js → arc-DctXgO-g.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BAXE8_Rl.js → architectureDiagram-T3A2C74G-D4mIkoQl.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-C5Xh59at.js → blockDiagram-VBNYF7ZC-ClG0--KO.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B_asOzAu.js → c4Diagram-5PPSVZJV-CwdLk78T.js} +1 -1
- package/dist/worker/console/static/assets/channel-C9VQZ-ru.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-wRO_iRNc.js → chunk-2GRJ4B5K-DKczcu-O.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-ByOaSh_G.js → chunk-2Q5K7J3B-BV6F-vBI.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLzudan.js → chunk-5RXB4S5H-DtojVnvA.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-JCNGkXYT.js → chunk-5VM5RSS4-BjDQRGnO.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BhrPXXMZ.js → chunk-6Q2QTUOP-I0fV6ngU.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-TJ5JuEBT.js → chunk-GF5L2VYU-BM8C-mmC.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-VVthn_mD.js → chunk-JWPE2WC7-BEK-a15e.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D60-Mny4.js → chunk-KBJHAD2P-CQsg_fSR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-zXv7aVye.js → chunk-RYQCIY6F-B-BcaUm3.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-By9VQoi1.js → chunk-XXDRQBXY-D1Bu08fg.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BsCkw5MF.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BsCkw5MF.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-C4zQLe-M.js → cose-bilkent-JH36ORCC-q8nwtxBR.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-wh5N2ReN.js → cynefin-VYW2F7L2-CruhpURw.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Dlu5wIqs.js → cynefinDiagram-MW4NZA55-CJne6iYF.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CrDgthc6.js → dagre-VZM6K2ZE-CbeLvBU5.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-zN1clg27.js → diagram-7IWD3JNH-DmCxH1Pe.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BN-o7J7-.js → diagram-B4RE2ZJO-DJ5OYhtC.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C8SuPt0d.js → diagram-LBJQPF4R-B-UN-hln.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BoizuHbZ.js → diagram-Q27KOJAE-CcEro8Ey.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BzUzXixG.js → diagram-UB23O5K3-BBBzDden.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js → ebnfDiagram-BXEA7PRR-xKEyH-oo.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-GALUzRbs.js → erDiagram-JOGREHBK-C7xl63VC.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CZJN65Sb.js → flowDiagram-UKHOOZJN-nthQQ9nR.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-JLB9OESl.js → ganttDiagram-PKOTCBZU-CWN5mE33.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CcvCr4GD.js → gitGraphDiagram-DS77QQ5N-Wn86tQ6N.js} +1 -1
- package/dist/worker/console/static/assets/index-D-0NVyMk.css +1 -0
- package/dist/worker/console/static/assets/index-gwyOxQiS.js +407 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-HG5z7aVg.js → infoDiagram-6WML65LV-_WQymGtW.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BHEu9chR.js → ishikawaDiagram-WSZJBQD7-COBeivBm.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Cp_Up8-a.js → journeyDiagram-NVQOT4AX-CwGOJl3Q.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DvVeY-em.js → kanban-definition-27J2QSJJ-Ci0CJ5Vi.js} +1 -1
- package/dist/worker/console/static/assets/{linear-aGmcjgTn.js → linear-CPjMq6vN.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Biyo-tqA.js → mermaid.core-Z0wGoe0b.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DyxY2pIb.js → mindmap-definition-FAOFIHXS-dISked0d.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkdGuc50.js → pegDiagram-VL7TDLO6-BJxgof7Y.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-NNQI9xVR.js → pieDiagram-7S7Q4E2Y-CIblU8eW.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-PAJkuz_G.js → quadrantDiagram-CIZ2JOQS-vwYiqh8E.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-ZwrQod0O.js → railroadDiagram-AXF67PYL-_iOsVeFm.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D3ZfrzM2.js → requirementDiagram-LRYGKXZP-Dk0MLxO_.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DDGFmWk5.js → sankeyDiagram-W5VNT64P-Dx7HtLVW.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DO6mmXTA.js → sequenceDiagram-SI44F4Z6-DRuWjlpL.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DMbCQiJt.js → sizeCapture-X5ZJPWSS-Bhy9DQAA.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Di_YUEfC.js → stateDiagram-OKZ733FA-DAx9twnU.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dmdf8cGy.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Bn2PphNJ.js → swimlanes-SLNWSIFB-okMlvVrM.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-W1IcAFff.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DPh8pRsp.js → timeline-definition-Z64GVDOM-DXqCKe37.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-GMfl26pW.js → vennDiagram-T6HMQDX7-CeIUkwBh.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DTlmVrmF.js → wardleyDiagram-T6FBY63Y-BP76BKTN.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YGBjKAS5.js → xychartDiagram-ELKLHX3M-BpVI0OpF.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/app/console-types.js +29 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +36 -6
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +10 -11
- package/dist/worker/console/static-src/app/useRunProgress.js +2 -1
- package/dist/worker/console/static-src/app/useTaskWizard.js +2 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
- package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +3 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +159 -44
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
- package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
- package/dist/worker/console/workspace-context.js +260 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/observe/health.js +1 -1
- package/dist/worker/observe/node-transparency.js +572 -0
- package/dist/worker/observe/routes.js +51 -1
- package/dist/worker/observe/server.js +4 -0
- package/dist/worker/observe/static/api.js +94 -10
- package/dist/worker/observe/static/app.js +18 -70
- package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
- package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
- package/dist/worker/observe/static/index.html +1 -1
- package/dist/worker/observe/static/inspect-workspace.js +307 -0
- package/dist/worker/observe/static/operator-chrome.css +145 -20
- package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
- package/dist/worker/observe/static/operator-chrome.js +330 -100
- package/dist/worker/observe/static/router.d.ts +46 -0
- package/dist/worker/observe/static/router.js +25 -1
- package/dist/worker/observe/static/state.d.ts +50 -0
- package/dist/worker/observe/static/state.js +175 -1
- package/dist/worker/observe/static/styles.css +1039 -128
- package/dist/worker/observe/static/views/dag-inspector.js +1218 -60
- package/dist/worker/observe/static/views/dag.js +2 -1
- package/dist/worker/observe/static/views/dashboard.js +6 -4
- package/dist/worker/observe/static/views/night.js +2 -2
- package/dist/worker/observe/static/views/pool.js +1 -0
- package/dist/worker/observe/static/views/run.js +3 -1
- package/dist/worker/observe/static/views/session-timeline.js +75 -10
- package/dist/worker/observe/static/views/task.js +1 -1
- package/dist/worker/outcomes/adapters.js +19 -0
- package/dist/worker/outcomes/types.js +1 -0
- package/dist/worker/pool/attempt-lease.js +97 -66
- package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
- package/dist/worker/pool/reconcile.js +46 -1
- package/dist/worker/pool/run-store.js +2 -0
- package/dist/worker/pool/state-projection.js +2 -0
- package/dist/worker/task-spec/workflow-routing.js +8 -3
- package/dist/workflows/dag/artifact-bindings.js +149 -0
- package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +11 -10
- package/dist/workflows/dag/backend-test-writer-completeness.js +7 -12
- package/dist/workflows/dag/context-receipt.js +305 -0
- package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
- package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
- package/dist/workflows/dag/context-transfer/renderers.js +93 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +1 -89
- package/dist/workflows/dag/init-hybrid.js +259 -47
- package/dist/workflows/dag/interrupt-request.js +3 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/node-execution.js +112 -27
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +29 -4
- package/dist/workflows/dag/rerun-plan.js +272 -0
- package/dist/workflows/dag/rerun-run.js +390 -12
- package/dist/workflows/dag/skill-snapshot.js +1 -0
- package/dist/workflows/dag/types.js +203 -8
- package/docs/README.md +1 -0
- package/docs/architecture/runtime-boundaries.md +6 -6
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/operations/local-development-environment.md +21 -0
- package/docs/templates/agent-dag.schema.json +50 -0
- package/docs/templates/backend-test-dag.json +265 -237
- package/docs/templates/product-line/task.yaml +1 -1
- package/harness.json +3 -3
- package/package.json +5 -1
- package/skills/loop-agent/references/command-reference.md +6 -0
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +0 -1
- package/dist/worker/console/static/assets/index-CW88Fjbw.js +0 -406
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +0 -8
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
- /package/dist/{worker → infrastructure}/console/operation-store.js +0 -0
- /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { access, copyFile, mkdir, readdir, readFile } from "node:fs/promises";
|
|
1
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { access, copyFile, lstat, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
4
5
|
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
5
6
|
import { composeDagPromptOverridePrompt, isDagRerunPromptOverrideEnabled, } from "../../shared/dag-prompt-override.js";
|
|
6
7
|
import { resolveRunningControllerIdentity } from "../../shared/package-metadata.js";
|
|
7
8
|
import { readControllerIdentityArtifact, captureControllerIdentity } from "./controller-identity.js";
|
|
8
9
|
import { DAG_RUNS_DIR, getDagRunDir, isDagNonExecutionLifecycle, locateDagRun, readDagRunSpec, readDagRunState, } from "./lifecycle.js";
|
|
9
10
|
import { buildDagRunId, runDagContinuation, } from "./runner.js";
|
|
10
|
-
import { computePlanHash, evaluateDagRerunPlan, evaluateLiveDagRerunBindings, } from "./rerun-plan.js";
|
|
11
|
+
import { computePlanHash, evaluateArtifactOverrideRerunPlan, evaluateDagRerunPlan, evaluateLiveDagRerunBindings, } from "./rerun-plan.js";
|
|
11
12
|
import { prepareActiveRunDir, writeNodeRecord, writeRunSpec, writeRunState, } from "./run-store.js";
|
|
12
13
|
import { cloneParentSkillSnapshotForContinuation, readSkillSnapshot, } from "./skill-snapshot.js";
|
|
13
14
|
import { topoSortToRanks } from "./topo.js";
|
|
@@ -18,6 +19,35 @@ export const WORKER_ASSOCIATION_REL_PATH = ".runtime/worker-association.json";
|
|
|
18
19
|
function sha256Hex(content) {
|
|
19
20
|
return createHash("sha256").update(content).digest("hex");
|
|
20
21
|
}
|
|
22
|
+
function constantTimeHashEqual(left, right) {
|
|
23
|
+
if (!/^[a-f0-9]{64}$/.test(left) || !/^[a-f0-9]{64}$/.test(right))
|
|
24
|
+
return false;
|
|
25
|
+
return timingSafeEqual(Buffer.from(left, "hex"), Buffer.from(right, "hex"));
|
|
26
|
+
}
|
|
27
|
+
export async function snapshotDagRunManifest(runDir) {
|
|
28
|
+
const entries = [];
|
|
29
|
+
const walk = async (dir) => {
|
|
30
|
+
for (const entry of (await readdir(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
31
|
+
const absolute = path.join(dir, entry.name);
|
|
32
|
+
const relative = normalizeRunRelativePath(path.relative(runDir, absolute));
|
|
33
|
+
if (entry.isSymbolicLink())
|
|
34
|
+
throw new Error(`symlink rejected in DAG run manifest: ${relative}`);
|
|
35
|
+
if (entry.isDirectory()) {
|
|
36
|
+
await walk(absolute);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (!entry.isFile())
|
|
40
|
+
throw new Error(`non-regular DAG run entry: ${relative}`);
|
|
41
|
+
const stat = await lstat(absolute);
|
|
42
|
+
if (stat.nlink !== 1)
|
|
43
|
+
throw new Error(`hardlink rejected in DAG run manifest: ${relative}`);
|
|
44
|
+
const bytes = await readFile(absolute);
|
|
45
|
+
entries.push({ path: relative, sha256: sha256Hex(bytes), bytes: bytes.byteLength });
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
await walk(runDir);
|
|
49
|
+
return sha256Hex(JSON.stringify(entries));
|
|
50
|
+
}
|
|
21
51
|
async function fileExists(filePath) {
|
|
22
52
|
try {
|
|
23
53
|
await access(filePath);
|
|
@@ -30,18 +60,67 @@ async function fileExists(filePath) {
|
|
|
30
60
|
function operatorRequestPath(cwd, requestId) {
|
|
31
61
|
return path.join(cwd, DAG_RUNS_DIR, ".operator-requests", `${requestId}.json`);
|
|
32
62
|
}
|
|
63
|
+
/** Strict envelope for persisted operator-request replays (SF2). */
|
|
64
|
+
const operatorRequestEnvelopeSchema = z
|
|
65
|
+
.object({
|
|
66
|
+
schemaVersion: z.literal(1),
|
|
67
|
+
kind: z.literal("dag-rerun-from-node-request"),
|
|
68
|
+
requestId: z.string().min(1),
|
|
69
|
+
parentRunId: z.string().min(1),
|
|
70
|
+
newRunId: z.string().min(1),
|
|
71
|
+
createdAt: z.string().min(1),
|
|
72
|
+
result: z
|
|
73
|
+
.object({
|
|
74
|
+
ok: z.boolean(),
|
|
75
|
+
plan: z
|
|
76
|
+
.object({
|
|
77
|
+
parentRunId: z.string().min(1),
|
|
78
|
+
planHash: z.string().min(1),
|
|
79
|
+
})
|
|
80
|
+
.passthrough(),
|
|
81
|
+
newRunId: z.string().min(1).optional(),
|
|
82
|
+
})
|
|
83
|
+
.passthrough(),
|
|
84
|
+
})
|
|
85
|
+
.strict();
|
|
33
86
|
async function readExistingOperatorRequest(cwd, requestId) {
|
|
34
87
|
try {
|
|
35
88
|
const raw = JSON.parse(await readFile(operatorRequestPath(cwd, requestId), "utf-8"));
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
89
|
+
const parsed = operatorRequestEnvelopeSchema.safeParse(raw);
|
|
90
|
+
if (!parsed.success)
|
|
91
|
+
return undefined;
|
|
92
|
+
const result = parsed.data.result;
|
|
93
|
+
return {
|
|
94
|
+
...result,
|
|
95
|
+
plan: result.plan,
|
|
96
|
+
idempotentReplay: true,
|
|
97
|
+
};
|
|
39
98
|
}
|
|
40
99
|
catch {
|
|
41
100
|
// not found or unreadable
|
|
42
101
|
}
|
|
43
102
|
return undefined;
|
|
44
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Idempotent replay of a previously persisted operator request, if any.
|
|
106
|
+
* When expected context is supplied, a replay that belongs to a different
|
|
107
|
+
* parent run or plan is a request-id reuse conflict rather than a valid
|
|
108
|
+
* answer.
|
|
109
|
+
*/
|
|
110
|
+
export async function readOperatorRequestReplay(cwd, requestId, expected) {
|
|
111
|
+
const replay = await readExistingOperatorRequest(cwd, requestId);
|
|
112
|
+
if (!replay)
|
|
113
|
+
return undefined;
|
|
114
|
+
if (expected?.parentRunId !== undefined &&
|
|
115
|
+
replay.plan?.parentRunId !== expected.parentRunId) {
|
|
116
|
+
throw new Error("REQUEST_ID_REUSE_CONFLICT: operator request replay belongs to a different parent run");
|
|
117
|
+
}
|
|
118
|
+
if (expected?.planHash !== undefined &&
|
|
119
|
+
replay.plan?.planHash !== expected.planHash) {
|
|
120
|
+
throw new Error("REQUEST_ID_REUSE_CONFLICT: operator request replay belongs to a different plan");
|
|
121
|
+
}
|
|
122
|
+
return replay;
|
|
123
|
+
}
|
|
45
124
|
async function persistOperatorRequest(cwd, result, requestId) {
|
|
46
125
|
await writeJsonAtomic(operatorRequestPath(cwd, requestId), {
|
|
47
126
|
schemaVersion: 1,
|
|
@@ -246,6 +325,52 @@ export async function planDagRerun(input) {
|
|
|
246
325
|
bindingStatus,
|
|
247
326
|
});
|
|
248
327
|
}
|
|
328
|
+
export async function planDagArtifactOverride(input) {
|
|
329
|
+
const located = await locateDagRun(input.cwd, input.parentRunId);
|
|
330
|
+
if (!located)
|
|
331
|
+
throw new Error(`dag run not found: ${input.parentRunId}`);
|
|
332
|
+
if (located.lifecycle !== "completed") {
|
|
333
|
+
throw new Error(`artifact continuation requires completed parent: ${input.parentRunId}`);
|
|
334
|
+
}
|
|
335
|
+
const parentSpec = await readDagRunSpec(located.runDir);
|
|
336
|
+
const parentState = await readDagRunState(located.runDir);
|
|
337
|
+
const currentWorkspace = await captureWorkspaceCheckpoint(input.cwd).catch(() => undefined);
|
|
338
|
+
const parentTerminalWorkspace = await readWorkspaceCheckpoint(located.runDir, WORKSPACE_CHECKPOINT_TERMINAL_REL);
|
|
339
|
+
const fingerprints = await resolveControllerFingerprints(located.runDir);
|
|
340
|
+
const skillSnapshotOk = await verifyParentSkillSnapshot(located.runDir, parentState, input.parentRunId);
|
|
341
|
+
const workerHints = await detectWorkerManagedRun(located.runDir, parentState, parentSpec);
|
|
342
|
+
const liveBindings = await evaluateLiveDagRerunBindings({ cwd: input.cwd, parentSpec });
|
|
343
|
+
return evaluateArtifactOverrideRerunPlan({
|
|
344
|
+
parentRunId: input.parentRunId,
|
|
345
|
+
parentSpec,
|
|
346
|
+
parentState,
|
|
347
|
+
artifactId: input.artifactId,
|
|
348
|
+
artifactOverrides: input.artifactOverrides,
|
|
349
|
+
stagingRevisionSha256: input.stagingRevisionSha256,
|
|
350
|
+
parentLifecycle: "completed",
|
|
351
|
+
bindings: {
|
|
352
|
+
...liveBindings,
|
|
353
|
+
controllerIdentityMatched: fingerprints.currentControllerFingerprint === undefined ||
|
|
354
|
+
fingerprints.parentControllerFingerprint === undefined ||
|
|
355
|
+
fingerprints.currentControllerFingerprint === fingerprints.parentControllerFingerprint,
|
|
356
|
+
skillSnapshotVerified: skillSnapshotOk,
|
|
357
|
+
workspaceMatched: parentTerminalWorkspace?.fingerprint !== undefined &&
|
|
358
|
+
currentWorkspace?.fingerprint !== undefined &&
|
|
359
|
+
parentTerminalWorkspace.fingerprint === currentWorkspace.fingerprint,
|
|
360
|
+
},
|
|
361
|
+
workerAssociation: workerHints.workerManaged
|
|
362
|
+
? {
|
|
363
|
+
kind: "worker-managed",
|
|
364
|
+
...(workerHints.featureId ? { featureId: workerHints.featureId } : {}),
|
|
365
|
+
...(workerHints.taskId ? { taskId: workerHints.taskId } : {}),
|
|
366
|
+
...(workerHints.workerRunId ? { workerRunId: workerHints.workerRunId } : {}),
|
|
367
|
+
}
|
|
368
|
+
: { kind: "standalone" },
|
|
369
|
+
...(input.workerContinuationAuthority
|
|
370
|
+
? { workerContinuationAuthority: input.workerContinuationAuthority }
|
|
371
|
+
: {}),
|
|
372
|
+
});
|
|
373
|
+
}
|
|
249
374
|
function normalizeRunRelativePath(relPath) {
|
|
250
375
|
return relPath.replace(/\\/g, "/");
|
|
251
376
|
}
|
|
@@ -359,6 +484,18 @@ function buildImportedNodeRecord(parentRecord, parentRunId, sourceNodeRecordSha2
|
|
|
359
484
|
const { origin: _origin, ...rest } = parentRecord;
|
|
360
485
|
void _origin;
|
|
361
486
|
const imported = structuredClone(rest);
|
|
487
|
+
imported.declaredArtifacts = imported.declaredArtifacts?.map((artifact) => ({
|
|
488
|
+
...artifact,
|
|
489
|
+
...(artifact.sha256
|
|
490
|
+
? {
|
|
491
|
+
origin: {
|
|
492
|
+
kind: "imported",
|
|
493
|
+
parentRunId,
|
|
494
|
+
sourceSha256: artifact.sha256,
|
|
495
|
+
},
|
|
496
|
+
}
|
|
497
|
+
: {}),
|
|
498
|
+
}));
|
|
362
499
|
imported.nodeRecordPath = path.join(newRunDir, `${parentRecord.id}.json`);
|
|
363
500
|
for (const key of [
|
|
364
501
|
"stdoutArtifactPath",
|
|
@@ -424,6 +561,66 @@ function applyPromptOverridesToSpec(input) {
|
|
|
424
561
|
}
|
|
425
562
|
return spec;
|
|
426
563
|
}
|
|
564
|
+
async function applyArtifactOverrideToStagedRun(input) {
|
|
565
|
+
const override = input.plan.artifactOverrides?.overrides[0];
|
|
566
|
+
if (!override)
|
|
567
|
+
throw new Error("artifact override missing from V2 plan");
|
|
568
|
+
if (override.parentArtifact.runId !== input.parentRunId) {
|
|
569
|
+
throw new Error("artifact override parent run drift");
|
|
570
|
+
}
|
|
571
|
+
if (!constantTimeHashEqual(sha256Hex(input.candidateBytes), override.replacement.sha256)) {
|
|
572
|
+
throw new Error("ARTIFACT_REVISION_DRIFT: candidate hash mismatch");
|
|
573
|
+
}
|
|
574
|
+
if (input.candidateBytes.byteLength !== override.replacement.bytes) {
|
|
575
|
+
throw new Error("ARTIFACT_REVISION_DRIFT: candidate byte length mismatch");
|
|
576
|
+
}
|
|
577
|
+
const relativePath = assertArtifactPathWithinParentRun(input.parentRunDir, override.parentArtifact.path);
|
|
578
|
+
const parentBytes = await readFile(path.join(input.parentRunDir, ...relativePath.split("/")));
|
|
579
|
+
if (!constantTimeHashEqual(sha256Hex(parentBytes), override.parentArtifact.sha256)) {
|
|
580
|
+
throw new Error("ARTIFACT_REVISION_DRIFT: parent artifact changed");
|
|
581
|
+
}
|
|
582
|
+
const destinationPath = path.join(input.newRunDir, ...relativePath.split("/"));
|
|
583
|
+
const resolvedRun = path.resolve(input.newRunDir);
|
|
584
|
+
if (!path.resolve(destinationPath).startsWith(`${resolvedRun}${path.sep}`)) {
|
|
585
|
+
throw new Error("ARTIFACT_REVISION_PATH_INVALID");
|
|
586
|
+
}
|
|
587
|
+
await mkdir(path.dirname(destinationPath), { recursive: true });
|
|
588
|
+
await writeFile(destinationPath, input.candidateBytes, { mode: 0o600 });
|
|
589
|
+
const producer = input.nodes[override.parentArtifact.nodeId];
|
|
590
|
+
if (!producer || producer.origin?.kind !== "imported") {
|
|
591
|
+
throw new Error("artifact producer must be imported into continuation");
|
|
592
|
+
}
|
|
593
|
+
producer.structuredArtifactPath = destinationPath;
|
|
594
|
+
producer.structuredArtifactSha256 = override.replacement.sha256;
|
|
595
|
+
producer.declaredArtifacts = producer.declaredArtifacts?.map((artifact) => artifact.path === relativePath && artifact.sha256 === override.parentArtifact.sha256
|
|
596
|
+
? {
|
|
597
|
+
...artifact,
|
|
598
|
+
sha256: override.replacement.sha256,
|
|
599
|
+
bytes: override.replacement.bytes,
|
|
600
|
+
origin: {
|
|
601
|
+
kind: "overridden",
|
|
602
|
+
parentRunId: input.parentRunId,
|
|
603
|
+
revisionId: override.revisionId,
|
|
604
|
+
supersedes: override.supersedes,
|
|
605
|
+
},
|
|
606
|
+
}
|
|
607
|
+
: artifact);
|
|
608
|
+
await writeNodeRecord(input.newRunDir, override.parentArtifact.nodeId, producer);
|
|
609
|
+
const manifestNode = input.manifestNodes.find((entry) => entry.nodeId === override.parentArtifact.nodeId);
|
|
610
|
+
if (manifestNode) {
|
|
611
|
+
manifestNode.importedArtifacts = manifestNode.importedArtifacts.filter((artifact) => artifact.destinationRelativePath !== relativePath);
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
revisionId: override.revisionId,
|
|
615
|
+
producerNodeId: override.parentArtifact.nodeId,
|
|
616
|
+
destinationRelativePath: relativePath,
|
|
617
|
+
parentSha256: override.parentArtifact.sha256,
|
|
618
|
+
replacementSha256: override.replacement.sha256,
|
|
619
|
+
bytes: override.replacement.bytes,
|
|
620
|
+
mediaType: override.replacement.mediaType,
|
|
621
|
+
supersedes: override.supersedes,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
427
624
|
async function stageContinuationRun(input) {
|
|
428
625
|
const specForRun = input.promptOverrides !== undefined && input.promptOverrides.length > 0
|
|
429
626
|
? applyPromptOverridesToSpec({
|
|
@@ -433,7 +630,14 @@ async function stageContinuationRun(input) {
|
|
|
433
630
|
})
|
|
434
631
|
: input.parentSpec;
|
|
435
632
|
const { ranks } = topoSortToRanks(specForRun);
|
|
436
|
-
const
|
|
633
|
+
const workerContinuationAuthority = input.plan.schemaVersion === 2
|
|
634
|
+
? input.plan.workerContinuationAuthority
|
|
635
|
+
: undefined;
|
|
636
|
+
const runId = workerContinuationAuthority?.childWorkerRunId
|
|
637
|
+
?? await buildDagRunId(specForRun, { cwd: input.cwd });
|
|
638
|
+
if (workerContinuationAuthority && await locateDagRun(input.cwd, runId)) {
|
|
639
|
+
throw new Error(`WORKER_CONTINUATION_NEEDS_RECONCILE: child DAG already exists: ${runId}`);
|
|
640
|
+
}
|
|
437
641
|
const runDir = getDagRunDir(input.cwd, "active", runId);
|
|
438
642
|
await prepareActiveRunDir(runDir);
|
|
439
643
|
await writeRunSpec(runDir, specForRun);
|
|
@@ -483,6 +687,12 @@ async function stageContinuationRun(input) {
|
|
|
483
687
|
...(input.plan.promptOverrides
|
|
484
688
|
? { promptOverrides: input.plan.promptOverrides }
|
|
485
689
|
: {}),
|
|
690
|
+
...(input.plan.schemaVersion === 2 && input.plan.artifactOverrides
|
|
691
|
+
? { artifactOverrides: input.plan.artifactOverrides }
|
|
692
|
+
: {}),
|
|
693
|
+
...(workerContinuationAuthority
|
|
694
|
+
? { workerContinuationAuthority }
|
|
695
|
+
: {}),
|
|
486
696
|
createdAt,
|
|
487
697
|
};
|
|
488
698
|
const state = {
|
|
@@ -504,6 +714,16 @@ async function stageContinuationRun(input) {
|
|
|
504
714
|
...(input.parentState.budget
|
|
505
715
|
? { budget: structuredClone(input.parentState.budget) }
|
|
506
716
|
: {}),
|
|
717
|
+
...(workerContinuationAuthority
|
|
718
|
+
? {
|
|
719
|
+
workerAssociation: {
|
|
720
|
+
schemaVersion: 1,
|
|
721
|
+
workerRunId: workerContinuationAuthority.childWorkerRunId,
|
|
722
|
+
featureId: workerContinuationAuthority.featureId,
|
|
723
|
+
taskId: workerContinuationAuthority.taskId,
|
|
724
|
+
},
|
|
725
|
+
}
|
|
726
|
+
: {}),
|
|
507
727
|
};
|
|
508
728
|
if (!input.parentState.skillSnapshotRef) {
|
|
509
729
|
throw new Error("parent run is missing skill snapshot ref");
|
|
@@ -523,11 +743,23 @@ async function stageContinuationRun(input) {
|
|
|
523
743
|
catch (error) {
|
|
524
744
|
console.warn(`[dag rerun] warning: failed to write workspace start checkpoint: ${error instanceof Error ? error.message : String(error)}`);
|
|
525
745
|
}
|
|
746
|
+
const overrideManifest = input.plan.schemaVersion === 2 && input.plan.trigger === "artifact-override"
|
|
747
|
+
? [await applyArtifactOverrideToStagedRun({
|
|
748
|
+
parentRunDir: input.parentRunDir,
|
|
749
|
+
newRunDir: runDir,
|
|
750
|
+
parentRunId: input.parentRunId,
|
|
751
|
+
plan: input.plan,
|
|
752
|
+
candidateBytes: input.artifactCandidateBytes ?? Buffer.alloc(0),
|
|
753
|
+
nodes,
|
|
754
|
+
manifestNodes: importManifestNodes,
|
|
755
|
+
})]
|
|
756
|
+
: undefined;
|
|
526
757
|
const importManifest = {
|
|
527
758
|
schemaVersion: 1,
|
|
528
759
|
parentRunId: input.parentRunId,
|
|
529
760
|
createdAt,
|
|
530
761
|
nodes: importManifestNodes,
|
|
762
|
+
...(overrideManifest ? { overrides: overrideManifest } : {}),
|
|
531
763
|
};
|
|
532
764
|
await writeJsonAtomic(path.join(runDir, IMPORT_MANIFEST_REL_PATH), importManifest);
|
|
533
765
|
await writeJsonAtomic(path.join(runDir, RERUN_PLAN_REL_PATH), input.plan);
|
|
@@ -535,9 +767,16 @@ async function stageContinuationRun(input) {
|
|
|
535
767
|
return { runId, runDir, state, spec: specForRun };
|
|
536
768
|
}
|
|
537
769
|
export async function executeDagRerun(input) {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
770
|
+
// Replay-first with SF2 envelope validation at the core boundary (same
|
|
771
|
+
// contract as executeDagArtifactOverrideRerun): an existing operator
|
|
772
|
+
// request is only a valid idempotent answer when it matches the requested
|
|
773
|
+
// parent run and plan hash; otherwise REQUEST_ID_REUSE_CONFLICT.
|
|
774
|
+
const replay = await readOperatorRequestReplay(input.cwd, input.requestId, {
|
|
775
|
+
parentRunId: input.parentRunId,
|
|
776
|
+
planHash: input.planHash,
|
|
777
|
+
});
|
|
778
|
+
if (replay) {
|
|
779
|
+
return replay;
|
|
541
780
|
}
|
|
542
781
|
const plan = await planDagRerun({
|
|
543
782
|
cwd: input.cwd,
|
|
@@ -545,7 +784,7 @@ export async function executeDagRerun(input) {
|
|
|
545
784
|
selectedNodeId: input.selectedNodeId,
|
|
546
785
|
promptOverrides: input.promptOverrides,
|
|
547
786
|
});
|
|
548
|
-
if (plan.planHash
|
|
787
|
+
if (!constantTimeHashEqual(plan.planHash, input.planHash)) {
|
|
549
788
|
return {
|
|
550
789
|
ok: false,
|
|
551
790
|
plan,
|
|
@@ -602,10 +841,149 @@ export async function executeDagRerun(input) {
|
|
|
602
841
|
summary,
|
|
603
842
|
};
|
|
604
843
|
if (await fileExists(operatorRequestPath(input.cwd, input.requestId))) {
|
|
605
|
-
const replay = await
|
|
844
|
+
const replay = await readOperatorRequestReplay(input.cwd, input.requestId, {
|
|
845
|
+
parentRunId: input.parentRunId,
|
|
846
|
+
planHash: input.planHash,
|
|
847
|
+
});
|
|
606
848
|
if (replay)
|
|
607
849
|
return replay;
|
|
608
850
|
}
|
|
609
851
|
await persistOperatorRequest(input.cwd, result, input.requestId);
|
|
610
852
|
return result;
|
|
611
853
|
}
|
|
854
|
+
export async function executeDagArtifactOverrideRerun(input) {
|
|
855
|
+
// Replay-first, but with SF2 envelope validation at the core boundary: an
|
|
856
|
+
// existing operator request is only an idempotent answer when it belongs to
|
|
857
|
+
// the same parent run AND the same plan hash. A caller that supplies a
|
|
858
|
+
// different planHash/parent (direct call or a race between CLI validation
|
|
859
|
+
// and this core) must get REQUEST_ID_REUSE_CONFLICT, never an unvalidated
|
|
860
|
+
// replay.
|
|
861
|
+
const replay = await readOperatorRequestReplay(input.cwd, input.requestId, {
|
|
862
|
+
parentRunId: input.parentRunId,
|
|
863
|
+
planHash: input.planHash,
|
|
864
|
+
});
|
|
865
|
+
if (replay)
|
|
866
|
+
return replay;
|
|
867
|
+
const plan = await planDagArtifactOverride({
|
|
868
|
+
cwd: input.cwd,
|
|
869
|
+
parentRunId: input.parentRunId,
|
|
870
|
+
artifactId: input.artifactId,
|
|
871
|
+
artifactOverrides: input.artifactOverrides,
|
|
872
|
+
stagingRevisionSha256: input.stagingRevisionSha256,
|
|
873
|
+
...(input.workerContinuationAuthority
|
|
874
|
+
? { workerContinuationAuthority: input.workerContinuationAuthority }
|
|
875
|
+
: {}),
|
|
876
|
+
});
|
|
877
|
+
if (!constantTimeHashEqual(plan.planHash, input.planHash)) {
|
|
878
|
+
return {
|
|
879
|
+
ok: false,
|
|
880
|
+
plan,
|
|
881
|
+
errorCode: "RERUN_PLAN_DRIFT",
|
|
882
|
+
message: "plan hash mismatch; refresh impact preview before executing",
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
if (!plan.eligible) {
|
|
886
|
+
return {
|
|
887
|
+
ok: false,
|
|
888
|
+
plan,
|
|
889
|
+
errorCode: "INELIGIBLE",
|
|
890
|
+
message: plan.blockedReasons.join(", ") || "artifact continuation is not eligible",
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
const override = plan.artifactOverrides?.overrides[0];
|
|
894
|
+
if (!override || !constantTimeHashEqual(sha256Hex(input.candidateBytes), override.replacement.sha256)) {
|
|
895
|
+
return {
|
|
896
|
+
ok: false,
|
|
897
|
+
plan,
|
|
898
|
+
errorCode: "ARTIFACT_REVISION_DRIFT",
|
|
899
|
+
message: "staged revision bytes no longer match the plan",
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
const located = await locateDagRun(input.cwd, input.parentRunId);
|
|
903
|
+
if (!located || located.lifecycle !== "completed") {
|
|
904
|
+
return {
|
|
905
|
+
ok: false,
|
|
906
|
+
plan,
|
|
907
|
+
errorCode: "PARENT_LIFECYCLE",
|
|
908
|
+
message: `parent run must be completed: ${input.parentRunId}`,
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
const parentManifestBefore = await snapshotDagRunManifest(located.runDir);
|
|
912
|
+
const parentSpec = await readDagRunSpec(located.runDir);
|
|
913
|
+
const parentState = await readDagRunState(located.runDir);
|
|
914
|
+
let staged;
|
|
915
|
+
try {
|
|
916
|
+
staged = await stageContinuationRun({
|
|
917
|
+
cwd: input.cwd,
|
|
918
|
+
parentRunDir: located.runDir,
|
|
919
|
+
parentRunId: input.parentRunId,
|
|
920
|
+
parentSpec,
|
|
921
|
+
parentState,
|
|
922
|
+
plan,
|
|
923
|
+
reason: input.reason,
|
|
924
|
+
requestId: input.requestId,
|
|
925
|
+
artifactCandidateBytes: input.candidateBytes,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
catch (error) {
|
|
929
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
930
|
+
if (message.startsWith("WORKER_CONTINUATION_NEEDS_RECONCILE:")) {
|
|
931
|
+
const result = {
|
|
932
|
+
ok: false,
|
|
933
|
+
plan,
|
|
934
|
+
newRunId: input.workerContinuationAuthority?.childWorkerRunId,
|
|
935
|
+
errorCode: "WORKER_CONTINUATION_NEEDS_RECONCILE",
|
|
936
|
+
message,
|
|
937
|
+
};
|
|
938
|
+
await persistOperatorRequest(input.cwd, result, input.requestId);
|
|
939
|
+
return result;
|
|
940
|
+
}
|
|
941
|
+
throw error;
|
|
942
|
+
}
|
|
943
|
+
const parentManifestAfterStage = await snapshotDagRunManifest(located.runDir);
|
|
944
|
+
if (!constantTimeHashEqual(parentManifestBefore, parentManifestAfterStage)) {
|
|
945
|
+
return {
|
|
946
|
+
ok: false,
|
|
947
|
+
plan,
|
|
948
|
+
newRunId: staged.runId,
|
|
949
|
+
runDir: staged.runDir,
|
|
950
|
+
errorCode: "PARENT_IMMUTABILITY_VIOLATION",
|
|
951
|
+
message: "parent run changed while staging the child; child execution was blocked",
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
if (input.consumeRevision) {
|
|
955
|
+
await input.consumeRevision({
|
|
956
|
+
revisionId: override.revisionId,
|
|
957
|
+
expectedSha256: override.replacement.sha256,
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
const summary = await runDagContinuation({
|
|
961
|
+
cwd: input.cwd,
|
|
962
|
+
spec: staged.spec,
|
|
963
|
+
state: staged.state,
|
|
964
|
+
runDir: staged.runDir,
|
|
965
|
+
...(input.maxConcurrent !== undefined ? { maxConcurrent: input.maxConcurrent } : {}),
|
|
966
|
+
...(input.executeNode ? { executeNode: input.executeNode } : {}),
|
|
967
|
+
});
|
|
968
|
+
const parentManifestAfterRun = await snapshotDagRunManifest(located.runDir);
|
|
969
|
+
const immutable = constantTimeHashEqual(parentManifestBefore, parentManifestAfterRun);
|
|
970
|
+
const result = immutable
|
|
971
|
+
? {
|
|
972
|
+
ok: summary.status === "finished",
|
|
973
|
+
plan,
|
|
974
|
+
newRunId: summary.runId,
|
|
975
|
+
runDir: summary.runDir,
|
|
976
|
+
summary,
|
|
977
|
+
}
|
|
978
|
+
: {
|
|
979
|
+
ok: false,
|
|
980
|
+
plan,
|
|
981
|
+
newRunId: summary.runId,
|
|
982
|
+
runDir: summary.runDir,
|
|
983
|
+
summary,
|
|
984
|
+
errorCode: "PARENT_IMMUTABILITY_VIOLATION",
|
|
985
|
+
message: "parent run manifest changed during child execution",
|
|
986
|
+
};
|
|
987
|
+
await persistOperatorRequest(input.cwd, result, input.requestId);
|
|
988
|
+
return result;
|
|
989
|
+
}
|
|
@@ -540,6 +540,7 @@ export function buildNodePromptFromSnapshot(input) {
|
|
|
540
540
|
spec: input.spec,
|
|
541
541
|
task: input.task,
|
|
542
542
|
upstream: input.upstream,
|
|
543
|
+
runDir: input.runDir,
|
|
543
544
|
resolvedSkills: skillNames,
|
|
544
545
|
resolvedSkillInstructions,
|
|
545
546
|
maxUpstreamChars: policy.resolveMaxUpstreamChars(input.task),
|