harnery 0.43.0 → 0.44.0
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/README.md +3 -3
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +41 -48
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +38 -11
- package/dist/commands/diagnostics.d.ts.map +1 -1
- package/dist/commands/diagnostics.js +10 -0
- package/dist/commands/doctor.d.ts +12 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +22 -7
- package/dist/commands/governor.js +22 -10
- package/dist/commands/inbox.d.ts +1 -1
- package/dist/commands/inbox.d.ts.map +1 -1
- package/dist/commands/inbox.js +55 -2
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +28 -4
- package/dist/commands/servers.d.ts.map +1 -1
- package/dist/commands/servers.js +16 -1
- package/dist/commands/supervisor.d.ts.map +1 -1
- package/dist/commands/supervisor.js +7 -2
- package/dist/commands/toc.d.ts +14 -0
- package/dist/commands/toc.d.ts.map +1 -1
- package/dist/commands/toc.js +7 -4
- package/dist/commands/workflow.js +24 -30
- package/dist/core/agents/live-authority-v3.d.ts +1 -0
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +11 -0
- package/dist/core/agents/live-lifecycle-v3.d.ts +4 -2
- package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
- package/dist/core/agents/live-lifecycle-v3.js +28 -7
- package/dist/core/agents/mailbox.d.ts +9 -8
- package/dist/core/agents/mailbox.d.ts.map +1 -1
- package/dist/core/agents/mailbox.js +24 -17
- package/dist/core/agents/render/prompt-context.d.ts +2 -0
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +8 -12
- package/dist/core/agents/render/session-context.d.ts +2 -8
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +7 -30
- package/dist/core/agents/rules/stop-hook.js +3 -3
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +25 -14
- package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-writer.js +5 -0
- package/dist/core/agents/state/pidmap.d.ts.map +1 -1
- package/dist/core/agents/state/pidmap.js +5 -0
- package/dist/core/agents/state/proc-start.d.ts +19 -0
- package/dist/core/agents/state/proc-start.d.ts.map +1 -1
- package/dist/core/agents/state/proc-start.js +41 -0
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +180 -70
- package/dist/core/agents/state/unchanged-file.d.ts +44 -0
- package/dist/core/agents/state/unchanged-file.d.ts.map +1 -0
- package/dist/core/agents/state/unchanged-file.js +390 -0
- package/dist/core/context/index.d.ts +22 -10
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +381 -119
- package/dist/core/context/observation.d.ts +26 -0
- package/dist/core/context/observation.d.ts.map +1 -0
- package/dist/core/context/observation.js +11 -0
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +25 -14
- package/dist/core/diagnostics/path-identity.d.ts +3 -0
- package/dist/core/diagnostics/path-identity.d.ts.map +1 -0
- package/dist/core/diagnostics/path-identity.js +12 -0
- package/dist/core/diagnostics/sanitize.d.ts.map +1 -1
- package/dist/core/diagnostics/sanitize.js +4 -3
- package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
- package/dist/core/events/v3/authority-outbox.js +29 -0
- package/dist/core/events/v3/generation-custody.d.ts +5 -0
- package/dist/core/events/v3/generation-custody.d.ts.map +1 -0
- package/dist/core/events/v3/generation-custody.js +50 -0
- package/dist/core/events/v3/producers/coordination-recorder.d.ts +1 -0
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +7 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +77 -64
- package/dist/core/governor/planning.d.ts.map +1 -1
- package/dist/core/governor/planning.js +14 -1
- package/dist/core/governor/service-read.d.ts +10 -2
- package/dist/core/governor/service-read.d.ts.map +1 -1
- package/dist/core/governor/service-read.js +105 -22
- package/dist/core/governor/service.d.ts +20 -2
- package/dist/core/governor/service.d.ts.map +1 -1
- package/dist/core/governor/service.js +72 -47
- package/dist/core/hooks/cli.js +33 -5
- package/dist/core/inbox/contract.d.ts +2 -1
- package/dist/core/inbox/contract.d.ts.map +1 -1
- package/dist/core/inbox/index.d.ts +2 -0
- package/dist/core/inbox/index.d.ts.map +1 -1
- package/dist/core/inbox/index.js +2 -0
- package/dist/core/inbox/peer-messages.d.ts +37 -0
- package/dist/core/inbox/peer-messages.d.ts.map +1 -0
- package/dist/core/inbox/peer-messages.js +72 -0
- package/dist/core/inbox/service.d.ts +8 -1
- package/dist/core/inbox/service.d.ts.map +1 -1
- package/dist/core/inbox/service.js +67 -21
- package/dist/core/inbox/targets.d.ts +22 -0
- package/dist/core/inbox/targets.d.ts.map +1 -0
- package/dist/core/inbox/targets.js +108 -0
- package/dist/core/inbox/watcher.js +7 -3
- package/dist/core/journal/index.d.ts.map +1 -1
- package/dist/core/journal/index.js +17 -2
- package/dist/core/resources/contract.d.ts +3 -0
- package/dist/core/resources/contract.d.ts.map +1 -1
- package/dist/core/resources/service-status.d.ts +14 -1
- package/dist/core/resources/service-status.d.ts.map +1 -1
- package/dist/core/resources/service-status.js +23 -13
- package/dist/core/resources/service.d.ts +9 -1
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +47 -27
- package/dist/core/resources/status.d.ts +2 -5
- package/dist/core/resources/status.d.ts.map +1 -1
- package/dist/core/resources/status.js +36 -7
- package/dist/core/servers/index.d.ts +24 -13
- package/dist/core/servers/index.d.ts.map +1 -1
- package/dist/core/servers/index.js +132 -92
- package/dist/core/servers/tunnels.d.ts +3 -5
- package/dist/core/servers/tunnels.d.ts.map +1 -1
- package/dist/core/servers/tunnels.js +3 -9
- package/dist/core/supervisor/contract.d.ts +12 -0
- package/dist/core/supervisor/contract.d.ts.map +1 -1
- package/dist/core/supervisor/explanations.d.ts.map +1 -1
- package/dist/core/supervisor/explanations.js +2 -1
- package/dist/core/supervisor/findings.d.ts.map +1 -1
- package/dist/core/supervisor/findings.js +54 -9
- package/dist/core/supervisor/service.d.ts +9 -1
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +59 -29
- package/dist/core/supervisor/services.d.ts.map +1 -1
- package/dist/core/supervisor/services.js +22 -1
- package/dist/core/supervisor/status.d.ts +17 -2
- package/dist/core/supervisor/status.d.ts.map +1 -1
- package/dist/core/supervisor/status.js +70 -12
- package/dist/core/supervisor/timeline.d.ts.map +1 -1
- package/dist/core/supervisor/timeline.js +2 -1
- package/dist/core/work/state.js +7 -7
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +105 -32
- package/dist/core/workflow/failure-evidence.d.ts +16 -0
- package/dist/core/workflow/failure-evidence.d.ts.map +1 -0
- package/dist/core/workflow/failure-evidence.js +114 -0
- package/dist/core/workflow/index.d.ts +1 -1
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +1 -1
- package/dist/core/workflow/live-session-v3.d.ts +6 -0
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +126 -5
- package/dist/core/workflow/proof.d.ts +5 -5
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +115 -20
- package/dist/core/workflow/run-state.d.ts +3 -1
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +31 -27
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +19 -0
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +13 -0
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +19 -0
- package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
- package/dist/core/workflow/spawn-opencode.js +14 -0
- package/dist/core/workflow/types.d.ts +64 -17
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.d.ts +0 -1
- package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.js +0 -1
- package/dist/core/workflow/workspaces/leases.d.ts +3 -1
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +30 -16
- package/dist/core/workflow/workspaces/local-git.d.ts +6 -2
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +270 -6
- package/dist/core/workflow/workspaces/ownership.d.ts +11 -0
- package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -0
- package/dist/core/workflow/workspaces/ownership.js +194 -0
- package/dist/core/workflow/workspaces/reclaim.d.ts +35 -0
- package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/reclaim.js +14 -0
- package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/recovery.js +21 -13
- package/dist/core/workflow/workspaces/state.d.ts +1 -0
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +21 -1
- package/dist/lib/browser/windows-chrome.d.ts +100 -12
- package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
- package/dist/lib/browser/windows-chrome.js +262 -73
- package/dist/lib/docs-links.d.ts.map +1 -1
- package/dist/lib/docs-links.js +45 -2
- package/dist/lib/guarded-remove.d.ts +7 -0
- package/dist/lib/guarded-remove.d.ts.map +1 -1
- package/dist/lib/guarded-remove.js +106 -12
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +4 -0
- package/dist/lib/zip/index.d.ts +41 -0
- package/dist/lib/zip/index.d.ts.map +1 -0
- package/dist/lib/zip/index.js +398 -0
- package/package.json +7 -1
- package/src/commands/agents.ts +54 -63
- package/src/commands/browse.ts +50 -14
- package/src/commands/diagnostics.ts +10 -0
- package/src/commands/doctor.ts +26 -7
- package/src/commands/governor.ts +23 -10
- package/src/commands/inbox.ts +52 -2
- package/src/commands/rm.ts +43 -4
- package/src/commands/servers.ts +16 -4
- package/src/commands/supervisor.ts +7 -2
- package/src/commands/toc.ts +14 -6
- package/src/commands/workflow.ts +30 -30
- package/src/core/agents/live-authority-v3.ts +17 -0
- package/src/core/agents/live-lifecycle-v3.ts +52 -14
- package/src/core/agents/mailbox.ts +26 -17
- package/src/core/agents/render/prompt-context.ts +10 -12
- package/src/core/agents/render/session-context.ts +9 -32
- package/src/core/agents/rules/stop-hook.ts +3 -3
- package/src/core/agents/state/heartbeat-writer.ts +29 -12
- package/src/core/agents/state/live-coordination-writer.ts +29 -0
- package/src/core/agents/state/pidmap.ts +13 -0
- package/src/core/agents/state/proc-start.ts +57 -0
- package/src/core/agents/state/stale-sweep.ts +247 -82
- package/src/core/agents/state/unchanged-file.ts +456 -0
- package/src/core/context/index.ts +446 -136
- package/src/core/context/observation.ts +29 -0
- package/src/core/diagnostics/bundle.ts +23 -18
- package/src/core/diagnostics/path-identity.ts +15 -0
- package/src/core/diagnostics/sanitize.ts +4 -3
- package/src/core/events/v3/authority-outbox.ts +56 -0
- package/src/core/events/v3/generation-custody.ts +59 -0
- package/src/core/events/v3/producers/coordination-recorder.ts +15 -0
- package/src/core/events/v3/producers/recorder.ts +94 -68
- package/src/core/governor/planning.ts +20 -8
- package/src/core/governor/service-read.ts +128 -20
- package/src/core/governor/service.ts +119 -42
- package/src/core/hooks/cli.ts +33 -5
- package/src/core/inbox/contract.ts +2 -1
- package/src/core/inbox/index.ts +2 -0
- package/src/core/inbox/peer-messages.ts +94 -0
- package/src/core/inbox/service.ts +88 -22
- package/src/core/inbox/targets.ts +148 -0
- package/src/core/inbox/watcher.ts +8 -9
- package/src/core/journal/index.ts +19 -2
- package/src/core/resources/contract.ts +4 -0
- package/src/core/resources/service-status.ts +35 -12
- package/src/core/resources/service.ts +71 -24
- package/src/core/resources/status.ts +57 -8
- package/src/core/servers/index.ts +177 -91
- package/src/core/servers/tunnels.ts +3 -11
- package/src/core/supervisor/contract.ts +13 -0
- package/src/core/supervisor/explanations.ts +4 -1
- package/src/core/supervisor/findings.ts +68 -10
- package/src/core/supervisor/service.ts +88 -26
- package/src/core/supervisor/services.ts +26 -8
- package/src/core/supervisor/status.ts +89 -12
- package/src/core/supervisor/timeline.ts +2 -1
- package/src/core/work/state.ts +7 -7
- package/src/core/workflow/engine.ts +131 -49
- package/src/core/workflow/failure-evidence.ts +141 -0
- package/src/core/workflow/index.ts +0 -1
- package/src/core/workflow/live-session-v3.ts +146 -5
- package/src/core/workflow/proof.ts +134 -26
- package/src/core/workflow/run-state.ts +31 -29
- package/src/core/workflow/spawn-claude.ts +18 -0
- package/src/core/workflow/spawn-codex.ts +13 -0
- package/src/core/workflow/spawn-cursor.ts +18 -0
- package/src/core/workflow/spawn-opencode.ts +14 -0
- package/src/core/workflow/types.ts +51 -17
- package/src/core/workflow/workspaces/index.ts +0 -1
- package/src/core/workflow/workspaces/leases.ts +48 -17
- package/src/core/workflow/workspaces/local-git.ts +315 -5
- package/src/core/workflow/workspaces/ownership.ts +232 -0
- package/src/core/workflow/workspaces/reclaim.ts +58 -0
- package/src/core/workflow/workspaces/recovery.ts +25 -13
- package/src/core/workflow/workspaces/state.ts +20 -0
- package/src/lib/browser/windows-chrome.ts +346 -86
- package/src/lib/docs-links.ts +49 -2
- package/src/lib/guarded-remove.ts +132 -14
- package/src/lib/identities/assume.ts +4 -0
- package/src/lib/zip/index.ts +483 -0
|
@@ -45,7 +45,16 @@ import { freezeWorkflowAttemptContext } from "./attempt-context.ts";
|
|
|
45
45
|
import { type BillingProbe, probeBilling } from "./billing.ts";
|
|
46
46
|
import { stableDigest } from "./durable-record.ts";
|
|
47
47
|
import { evidencePreflightError } from "./evidence-preflight.ts";
|
|
48
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
canRetryFailedSpawn,
|
|
50
|
+
observeSpawnFailure,
|
|
51
|
+
resolveFailureEvidence,
|
|
52
|
+
} from "./failure-evidence.ts";
|
|
53
|
+
import {
|
|
54
|
+
endWorkflowChildSessionV3,
|
|
55
|
+
recordWorkflowChildTerminalFailureV3,
|
|
56
|
+
startWorkflowChildSessionV3,
|
|
57
|
+
} from "./live-session-v3.ts";
|
|
49
58
|
import {
|
|
50
59
|
buildWorkflowProof,
|
|
51
60
|
createEvidenceRecord,
|
|
@@ -404,7 +413,13 @@ async function executeWorkflow(
|
|
|
404
413
|
durationMs,
|
|
405
414
|
transcriptPath,
|
|
406
415
|
before: resumeState.manifest.repository_before,
|
|
407
|
-
after:
|
|
416
|
+
after: {
|
|
417
|
+
cwd: frozenBinding.active_root,
|
|
418
|
+
observation: "unavailable",
|
|
419
|
+
observation_detail:
|
|
420
|
+
"Workspace reattachment failed; no trusted after-run snapshot was observed.",
|
|
421
|
+
dirty_paths: [],
|
|
422
|
+
},
|
|
408
423
|
agents: [],
|
|
409
424
|
evidence: [],
|
|
410
425
|
diagnosticAdmission: initialDiagnosticAdmissionProof(diagnosticAdmissionConfig),
|
|
@@ -606,6 +621,7 @@ async function executeWorkflow(
|
|
|
606
621
|
let policySeq = 0;
|
|
607
622
|
const billingProbed = new Map<AdapterName, BillingProbe>();
|
|
608
623
|
const agentProofs = new Map<string, WorkflowAgentProof>();
|
|
624
|
+
let terminalRecordingError: Error | undefined;
|
|
609
625
|
const evidenceRecords: WorkflowEvidenceRecord[] = [];
|
|
610
626
|
const policyDecisions = new Map<string, PolicyDecision>(
|
|
611
627
|
history?.policyDecisions.map((decision) => [decision.id, decision]),
|
|
@@ -1022,6 +1038,49 @@ async function executeWorkflow(
|
|
|
1022
1038
|
});
|
|
1023
1039
|
agentProof.access = access;
|
|
1024
1040
|
|
|
1041
|
+
let childGenerationId: string | undefined;
|
|
1042
|
+
let recordAgentEnd: (() => void) | undefined;
|
|
1043
|
+
const finalizeChild = (): void => {
|
|
1044
|
+
try {
|
|
1045
|
+
if (childGenerationId)
|
|
1046
|
+
endWorkflowChildSessionV3({
|
|
1047
|
+
coordRoot: opts.coordRoot,
|
|
1048
|
+
instanceId: childInstanceId(runId, id),
|
|
1049
|
+
runId,
|
|
1050
|
+
agentId: id,
|
|
1051
|
+
adapter,
|
|
1052
|
+
cleanExit: agentProof.status === "succeeded",
|
|
1053
|
+
expectedGenerationId: childGenerationId,
|
|
1054
|
+
});
|
|
1055
|
+
// Result publication is the resume-cache boundary, after canonical custody.
|
|
1056
|
+
recordAgentEnd?.();
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
let reason = `V3 terminal recording failed: ${(error as Error).message}`;
|
|
1059
|
+
try {
|
|
1060
|
+
if (childGenerationId)
|
|
1061
|
+
recordWorkflowChildTerminalFailureV3({
|
|
1062
|
+
coordRoot: opts.coordRoot,
|
|
1063
|
+
instanceId: childInstanceId(runId, id),
|
|
1064
|
+
runId,
|
|
1065
|
+
agentId: id,
|
|
1066
|
+
adapter,
|
|
1067
|
+
expectedGenerationId: childGenerationId,
|
|
1068
|
+
reason,
|
|
1069
|
+
});
|
|
1070
|
+
} catch (visibilityError) {
|
|
1071
|
+
reason += `; blocked lifecycle recording failed: ${(visibilityError as Error).message}`;
|
|
1072
|
+
}
|
|
1073
|
+
const failure = new Error(agentProof.error ? `${agentProof.error}; ${reason}` : reason, {
|
|
1074
|
+
cause: error,
|
|
1075
|
+
});
|
|
1076
|
+
terminalRecordingError ??= failure;
|
|
1077
|
+
agentProof.status = "failed";
|
|
1078
|
+
agentProof.error = failure.message;
|
|
1079
|
+
transcript("agent.failed", { id, key, error: failure.message });
|
|
1080
|
+
log(`[${name}] ${failure.message}`);
|
|
1081
|
+
throw failure;
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1025
1084
|
await acquire();
|
|
1026
1085
|
try {
|
|
1027
1086
|
await ensureDiagnosticAdmissionObserved();
|
|
@@ -1043,7 +1102,7 @@ async function executeWorkflow(
|
|
|
1043
1102
|
// Headless children do not reliably fire adapter hooks. The engine owns
|
|
1044
1103
|
// their canonical V3 start so every dispatched child has an auditable
|
|
1045
1104
|
// generation before it begins work.
|
|
1046
|
-
startWorkflowChildSessionV3({
|
|
1105
|
+
const child = startWorkflowChildSessionV3({
|
|
1047
1106
|
coordRoot: opts.coordRoot,
|
|
1048
1107
|
instanceId: childInstanceId(runId, id),
|
|
1049
1108
|
runId,
|
|
@@ -1052,10 +1111,13 @@ async function executeWorkflow(
|
|
|
1052
1111
|
label,
|
|
1053
1112
|
model: agentOpts.model,
|
|
1054
1113
|
});
|
|
1114
|
+
if (!child.v3_generation_id) throw new Error("workflow_child_v3_generation_missing");
|
|
1115
|
+
childGenerationId = child.v3_generation_id;
|
|
1055
1116
|
|
|
1056
1117
|
let attemptPrompt = dispatchPrompt;
|
|
1057
1118
|
let last: SpawnResult | null = null;
|
|
1058
1119
|
let agentCostUsd = 0;
|
|
1120
|
+
let informativeAttempt: ReturnType<typeof observeSpawnFailure> | undefined;
|
|
1059
1121
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1060
1122
|
last = await spawner({
|
|
1061
1123
|
prompt: attemptPrompt,
|
|
@@ -1078,33 +1140,51 @@ async function executeWorkflow(
|
|
|
1078
1140
|
costUsd += last.costUsd ?? 0;
|
|
1079
1141
|
|
|
1080
1142
|
if (!last.ok) {
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1143
|
+
const observed =
|
|
1144
|
+
last.failureEvidence === undefined
|
|
1145
|
+
? observeSpawnFailure({}, last.text, "final_message")
|
|
1146
|
+
: resolveFailureEvidence(last.failureEvidence);
|
|
1147
|
+
agentProof.failure_evidence = informativeAttempt
|
|
1148
|
+
? {
|
|
1149
|
+
...observed,
|
|
1150
|
+
effect: { state: "unknown" },
|
|
1151
|
+
partial_output: informativeAttempt.partial_output,
|
|
1152
|
+
}
|
|
1153
|
+
: observed;
|
|
1154
|
+
transcript("agent.attempt_failed", {
|
|
1155
|
+
id,
|
|
1156
|
+
attempt,
|
|
1157
|
+
error: last.error,
|
|
1158
|
+
failure_evidence: observed,
|
|
1159
|
+
result_digest: digestResult(last.text, "text"),
|
|
1160
|
+
text: last.text,
|
|
1161
|
+
});
|
|
1162
|
+
if (!canRetryFailedSpawn({ ...last, failureEvidence: agentProof.failure_evidence }))
|
|
1163
|
+
break;
|
|
1164
|
+
continue;
|
|
1088
1165
|
}
|
|
1166
|
+
informativeAttempt = observeSpawnFailure({}, last.text, "final_message");
|
|
1089
1167
|
if (!agentOpts.schema) {
|
|
1090
1168
|
agentProof.status = "succeeded";
|
|
1091
1169
|
agentProof.cost_usd =
|
|
1092
1170
|
agentCostUsd > 0 || last.costUsd !== undefined ? agentCostUsd : undefined;
|
|
1093
1171
|
agentProof.session_id = last.sessionId;
|
|
1094
1172
|
agentProof.result = digestResult(last.text, "text");
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1173
|
+
const completed = last;
|
|
1174
|
+
recordAgentEnd = () =>
|
|
1175
|
+
transcriptAgentEnd(
|
|
1176
|
+
{
|
|
1177
|
+
id,
|
|
1178
|
+
key,
|
|
1179
|
+
attempts: attempt,
|
|
1180
|
+
cost_usd: completed.costUsd,
|
|
1181
|
+
total_cost_usd: agentCostUsd,
|
|
1182
|
+
duration_ms: completed.durationMs,
|
|
1183
|
+
session_id: completed.sessionId,
|
|
1184
|
+
},
|
|
1185
|
+
"text",
|
|
1186
|
+
completed.text,
|
|
1187
|
+
);
|
|
1108
1188
|
return last.text;
|
|
1109
1189
|
}
|
|
1110
1190
|
|
|
@@ -1119,23 +1199,32 @@ async function executeWorkflow(
|
|
|
1119
1199
|
agentCostUsd > 0 || last.costUsd !== undefined ? agentCostUsd : undefined;
|
|
1120
1200
|
agentProof.session_id = last.sessionId;
|
|
1121
1201
|
agentProof.result = digestResult(parsed.value, "json");
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1202
|
+
const completed = last;
|
|
1203
|
+
recordAgentEnd = () =>
|
|
1204
|
+
transcriptAgentEnd(
|
|
1205
|
+
{
|
|
1206
|
+
id,
|
|
1207
|
+
key,
|
|
1208
|
+
attempts: attempt,
|
|
1209
|
+
cost_usd: completed.costUsd,
|
|
1210
|
+
total_cost_usd: agentCostUsd,
|
|
1211
|
+
duration_ms: completed.durationMs,
|
|
1212
|
+
session_id: completed.sessionId,
|
|
1213
|
+
},
|
|
1214
|
+
"json",
|
|
1215
|
+
parsed.value,
|
|
1216
|
+
);
|
|
1135
1217
|
return parsed.value;
|
|
1136
1218
|
}
|
|
1137
1219
|
|
|
1138
|
-
|
|
1220
|
+
agentProof.failure_evidence = informativeAttempt;
|
|
1221
|
+
transcript("agent.schema_retry", {
|
|
1222
|
+
id,
|
|
1223
|
+
attempt,
|
|
1224
|
+
problems,
|
|
1225
|
+
rejected_text: last.text,
|
|
1226
|
+
result_digest: digestResult(last.text, "text"),
|
|
1227
|
+
});
|
|
1139
1228
|
// Keep the original cache identity while making the actual output
|
|
1140
1229
|
// contract explicit. A full bounded copy of the rejected value lets the
|
|
1141
1230
|
// child repair omissions instead of reconstructing an unseen object.
|
|
@@ -1168,20 +1257,12 @@ async function executeWorkflow(
|
|
|
1168
1257
|
throw error;
|
|
1169
1258
|
} finally {
|
|
1170
1259
|
reservedCostUsd = Math.max(0, reservedCostUsd - reservedForDispatch);
|
|
1171
|
-
// Record the canonical terminal before removing the disposable cache.
|
|
1172
1260
|
try {
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
agentId: id,
|
|
1178
|
-
adapter,
|
|
1179
|
-
cleanExit: agentProof.status === "succeeded",
|
|
1180
|
-
});
|
|
1181
|
-
} catch (error) {
|
|
1182
|
-
log(`[${name}] V3 terminal recording failed: ${(error as Error).message}`);
|
|
1261
|
+
finalizeChild();
|
|
1262
|
+
} finally {
|
|
1263
|
+
// A slot is disposable; unresolved custody remains visible and failed.
|
|
1264
|
+
release();
|
|
1183
1265
|
}
|
|
1184
|
-
release();
|
|
1185
1266
|
}
|
|
1186
1267
|
};
|
|
1187
1268
|
|
|
@@ -1286,6 +1367,7 @@ async function executeWorkflow(
|
|
|
1286
1367
|
if (initializationError) throw initializationError;
|
|
1287
1368
|
if (!workflow) throw new Error("workflow module initialization did not produce an executable");
|
|
1288
1369
|
const result = await workflow(ctx);
|
|
1370
|
+
if (terminalRecordingError) throw terminalRecordingError;
|
|
1289
1371
|
const endedAt = new Date().toISOString();
|
|
1290
1372
|
const durationMs = Date.now() - t0;
|
|
1291
1373
|
const workspaceAttestation = workspaceBinding
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import type { AdapterRawResult } from "../adapters/types.ts";
|
|
3
|
+
import type { SpawnFailureEvidence, SpawnOutputSource, SpawnResult } from "./types.ts";
|
|
4
|
+
|
|
5
|
+
export function unknownFailureEvidence(): SpawnFailureEvidence {
|
|
6
|
+
return {
|
|
7
|
+
effect: { state: "unknown" },
|
|
8
|
+
refusal: { state: "unknown" },
|
|
9
|
+
partial_output: { state: "unknown" },
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Built-in subprocess adapters observe output and process-start failures.
|
|
14
|
+
* Their current vendor envelopes do not prove effects or native refusal.
|
|
15
|
+
* In particular, blank output and 429/5xx statuses leave both unknown. */
|
|
16
|
+
export function observeSpawnFailure(
|
|
17
|
+
raw: Pick<AdapterRawResult, "spawnErrno" | "timedOut"> &
|
|
18
|
+
Partial<Pick<AdapterRawResult, "stdout" | "resultFileText">>,
|
|
19
|
+
text: string,
|
|
20
|
+
source: SpawnOutputSource,
|
|
21
|
+
): SpawnFailureEvidence {
|
|
22
|
+
const bytes = Buffer.byteLength(text);
|
|
23
|
+
return {
|
|
24
|
+
effect:
|
|
25
|
+
raw.spawnErrno === "ENOENT" &&
|
|
26
|
+
!raw.timedOut &&
|
|
27
|
+
bytes === 0 &&
|
|
28
|
+
!raw.stdout?.trim() &&
|
|
29
|
+
!raw.resultFileText?.trim()
|
|
30
|
+
? { state: "none", source: "spawn_errno", reference: "ENOENT" }
|
|
31
|
+
: { state: "unknown" },
|
|
32
|
+
refusal: { state: "unknown" },
|
|
33
|
+
partial_output:
|
|
34
|
+
bytes > 0
|
|
35
|
+
? {
|
|
36
|
+
state: "present",
|
|
37
|
+
source,
|
|
38
|
+
bytes,
|
|
39
|
+
sha256: createHash("sha256").update(text).digest("hex"),
|
|
40
|
+
}
|
|
41
|
+
: { state: "absent", source },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
46
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function keys(value: Record<string, unknown>, expected: readonly string[]): boolean {
|
|
50
|
+
return (
|
|
51
|
+
Object.keys(value).length === expected.length &&
|
|
52
|
+
expected.every((key) => Object.hasOwn(value, key))
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function reference(value: unknown): value is string {
|
|
57
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= 512;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function validEffect(value: unknown): boolean {
|
|
61
|
+
if (!record(value)) return false;
|
|
62
|
+
if (value.state === "unknown") return keys(value, ["state"]);
|
|
63
|
+
if (!keys(value, ["state", "source", "reference"])) return false;
|
|
64
|
+
if (value.state === "none" && value.source === "spawn_errno") {
|
|
65
|
+
return value.reference === "ENOENT";
|
|
66
|
+
}
|
|
67
|
+
return (
|
|
68
|
+
(value.state === "none" || value.state === "observed") &&
|
|
69
|
+
value.source === "adapter_evidence" &&
|
|
70
|
+
reference(value.reference)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function validRefusal(value: unknown): boolean {
|
|
75
|
+
if (!record(value)) return false;
|
|
76
|
+
if (value.state === "unknown") return keys(value, ["state"]);
|
|
77
|
+
return (
|
|
78
|
+
keys(value, ["state", "source", "reference"]) &&
|
|
79
|
+
value.state === "refused" &&
|
|
80
|
+
value.source === "vendor_envelope" &&
|
|
81
|
+
reference(value.reference)
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function validOutput(value: unknown): boolean {
|
|
86
|
+
if (!record(value)) return false;
|
|
87
|
+
if (value.state === "unknown") return keys(value, ["state"]);
|
|
88
|
+
if (
|
|
89
|
+
value.source !== "final_message" &&
|
|
90
|
+
value.source !== "vendor_envelope" &&
|
|
91
|
+
value.source !== "stdout"
|
|
92
|
+
) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (value.state === "absent") return keys(value, ["state", "source"]);
|
|
96
|
+
return (
|
|
97
|
+
value.state === "present" &&
|
|
98
|
+
keys(value, ["state", "source", "bytes", "sha256"]) &&
|
|
99
|
+
Number.isSafeInteger(value.bytes) &&
|
|
100
|
+
(value.bytes as number) > 0 &&
|
|
101
|
+
typeof value.sha256 === "string" &&
|
|
102
|
+
/^[a-f0-9]{64}$/.test(value.sha256)
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function isSpawnFailureEvidence(value: unknown): value is SpawnFailureEvidence {
|
|
107
|
+
return (
|
|
108
|
+
record(value) &&
|
|
109
|
+
keys(value, ["effect", "refusal", "partial_output"]) &&
|
|
110
|
+
validEffect(value.effect) &&
|
|
111
|
+
validRefusal(value.refusal) &&
|
|
112
|
+
validOutput(value.partial_output)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Missing observations are unknown; malformed positive claims are rejected.
|
|
117
|
+
* This is also used when reading persisted workflow proof. */
|
|
118
|
+
export function resolveFailureEvidence(value: unknown): SpawnFailureEvidence {
|
|
119
|
+
if (value === undefined) return unknownFailureEvidence();
|
|
120
|
+
if (!isSpawnFailureEvidence(value)) throw new Error("invalid spawn failure evidence");
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function failureEvidenceAllowsUncharged(value: unknown): boolean {
|
|
125
|
+
const evidence = resolveFailureEvidence(value);
|
|
126
|
+
return (
|
|
127
|
+
evidence.effect.state === "none" &&
|
|
128
|
+
evidence.refusal.state !== "refused" &&
|
|
129
|
+
evidence.partial_output.state === "absent"
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** A provider retry requires positive no-effect evidence. A missing binary is
|
|
134
|
+
* known not to have started, but retrying that unchanged environment cannot help. */
|
|
135
|
+
export function canRetryFailedSpawn(result: SpawnResult): boolean {
|
|
136
|
+
return (
|
|
137
|
+
!result.ok &&
|
|
138
|
+
result.class !== "environment" &&
|
|
139
|
+
failureEvidenceAllowsUncharged(result.failureEvidence)
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, linkSync, readdirSync, readFileSync, renameSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { basename, dirname } from "node:path";
|
|
1
4
|
import { type Adapter, adapterFromPlatform } from "../adapter.ts";
|
|
2
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
recordLiveLifecycleChangeV3,
|
|
7
|
+
recordLiveTaskChangeV3,
|
|
8
|
+
} from "../agents/live-authority-v3.ts";
|
|
3
9
|
import { endSessionExplicitV3 } from "../agents/session-finalizer-v3.ts";
|
|
10
|
+
import { heartbeatPath } from "../agents/state/heartbeat-reader.ts";
|
|
4
11
|
import {
|
|
5
|
-
clearCoordinationCache,
|
|
6
12
|
type Heartbeat,
|
|
13
|
+
readHeartbeat,
|
|
7
14
|
setIdentityCache,
|
|
8
15
|
} from "../agents/state/heartbeat-writer.ts";
|
|
9
16
|
import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.ts";
|
|
@@ -12,9 +19,17 @@ import { assignName } from "../agents/state/names.ts";
|
|
|
12
19
|
import { coordFreshnessSeconds } from "../config.ts";
|
|
13
20
|
import { ensureEventLedgerV3 } from "../events/v3/bootstrap.ts";
|
|
14
21
|
import {
|
|
22
|
+
readCoordinationViewV3,
|
|
23
|
+
requireAuthoritySafeCoordinationViewV3,
|
|
24
|
+
} from "../events/v3/coordination-view.ts";
|
|
25
|
+
import { withGenerationCustodyV3 } from "../events/v3/generation-custody.ts";
|
|
26
|
+
import {
|
|
27
|
+
liveInstanceIdV3,
|
|
15
28
|
recordLiveHookSignalV3,
|
|
16
29
|
resolveLiveEventLedgerRouteV3,
|
|
17
30
|
} from "../events/v3/live-routing.ts";
|
|
31
|
+
import { readHookProducerStateV3 } from "../events/v3/producers/recorder.ts";
|
|
32
|
+
import { fsyncParentDirectory } from "./durable-record.ts";
|
|
18
33
|
import { stableScopeId } from "./scope-id.ts";
|
|
19
34
|
|
|
20
35
|
export interface WorkflowChildSessionV3Input {
|
|
@@ -77,16 +92,45 @@ export function startWorkflowChildSessionV3(input: WorkflowChildSessionV3Input):
|
|
|
77
92
|
}
|
|
78
93
|
const projected = readLiveCoordinationRow(input.coordRoot, input.instanceId);
|
|
79
94
|
if (!projected) throw new Error("workflow_child_v3_projection_missing");
|
|
95
|
+
const own = readHookProducerStateV3(input.coordRoot, adapter, sessionId);
|
|
96
|
+
if (!own || own.terminal || own.generation_id !== projected.v3_generation_id) {
|
|
97
|
+
throw new Error("workflow_child_v3_start_generation_changed");
|
|
98
|
+
}
|
|
80
99
|
return projected;
|
|
81
100
|
}
|
|
82
101
|
|
|
83
102
|
/** Record the authoritative terminal before removing the disposable coordination cache. */
|
|
84
103
|
export function endWorkflowChildSessionV3(
|
|
85
|
-
input: WorkflowChildSessionV3Input & { cleanExit: boolean },
|
|
104
|
+
input: WorkflowChildSessionV3Input & { cleanExit: boolean; expectedGenerationId: string },
|
|
105
|
+
): void {
|
|
106
|
+
withGenerationCustodyV3(input.coordRoot, [liveInstanceIdV3(input.instanceId)], () =>
|
|
107
|
+
endExpectedChild(input),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function endExpectedChild(
|
|
112
|
+
input: WorkflowChildSessionV3Input & { cleanExit: boolean; expectedGenerationId: string },
|
|
86
113
|
): void {
|
|
87
114
|
requireV3Route(input.coordRoot);
|
|
115
|
+
const view = requireAuthoritySafeCoordinationViewV3(readCoordinationViewV3(input.coordRoot));
|
|
116
|
+
const instanceId = liveInstanceIdV3(input.instanceId);
|
|
117
|
+
const current = view.instances[instanceId];
|
|
118
|
+
const terminal = view.terminal_generations[input.expectedGenerationId];
|
|
119
|
+
if (terminal?.instance_id === instanceId) recoverHeldCaches(input);
|
|
120
|
+
if (current && current.generation_id !== input.expectedGenerationId) {
|
|
121
|
+
throw new Error("workflow_child_v3_generation_changed");
|
|
122
|
+
}
|
|
123
|
+
if (!current) {
|
|
124
|
+
if (!terminal || terminal.instance_id !== instanceId)
|
|
125
|
+
throw new Error("workflow_child_v3_generation_missing");
|
|
126
|
+
if (terminal.terminal?.outcome !== (input.cleanExit ? "succeeded" : "failed")) {
|
|
127
|
+
throw new Error("workflow_child_v3_terminal_outcome_changed");
|
|
128
|
+
}
|
|
129
|
+
clearExpectedCache(input);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
88
132
|
const row = readLiveCoordinationRow(input.coordRoot, input.instanceId);
|
|
89
|
-
if (!row?.v3_instance_id ||
|
|
133
|
+
if (!row?.v3_instance_id || row.v3_generation_id !== input.expectedGenerationId) {
|
|
90
134
|
throw new Error("workflow_child_v3_generation_missing");
|
|
91
135
|
}
|
|
92
136
|
const result = endSessionExplicitV3({
|
|
@@ -101,7 +145,104 @@ export function endWorkflowChildSessionV3(
|
|
|
101
145
|
`workflow_child_v3_end_failed:${result.state}${"reason" in result ? `:${result.reason}` : ""}`,
|
|
102
146
|
);
|
|
103
147
|
}
|
|
104
|
-
|
|
148
|
+
clearExpectedCache(input);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Persist unresolved terminal custody only for the generation this dispatch owns. */
|
|
152
|
+
export function recordWorkflowChildTerminalFailureV3(
|
|
153
|
+
input: WorkflowChildSessionV3Input & { expectedGenerationId: string; reason: string },
|
|
154
|
+
): void {
|
|
155
|
+
withGenerationCustodyV3(input.coordRoot, [liveInstanceIdV3(input.instanceId)], () => {
|
|
156
|
+
const row = readLiveCoordinationRow(input.coordRoot, input.instanceId);
|
|
157
|
+
if (!row || row.v3_generation_id !== input.expectedGenerationId) return;
|
|
158
|
+
recordLiveLifecycleChangeV3({
|
|
159
|
+
coordRoot: input.coordRoot,
|
|
160
|
+
owner: input.instanceId,
|
|
161
|
+
nativeSessionId: input.sessionId ?? input.instanceId,
|
|
162
|
+
adapter: workflowAdapter(input.adapter),
|
|
163
|
+
expectedGenerationId: input.expectedGenerationId as `gen_${string}`,
|
|
164
|
+
state: "blocked",
|
|
165
|
+
reason: input.reason,
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function heldPrefix(input: WorkflowChildSessionV3Input & { expectedGenerationId: string }): string {
|
|
171
|
+
if (!/^gen_[a-zA-Z0-9._-]{1,128}$/.test(input.expectedGenerationId))
|
|
172
|
+
throw new Error("workflow_child_v3_generation_invalid");
|
|
173
|
+
return `${basename(heartbeatPath(input.coordRoot, input.instanceId))}.terminal-${input.expectedGenerationId}-`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function recoverHeldCaches(
|
|
177
|
+
input: WorkflowChildSessionV3Input & { expectedGenerationId: string },
|
|
178
|
+
): void {
|
|
179
|
+
const path = heartbeatPath(input.coordRoot, input.instanceId);
|
|
180
|
+
if (!existsSync(dirname(path))) return;
|
|
181
|
+
const prefix = heldPrefix(input);
|
|
182
|
+
for (const name of readdirSync(dirname(path)).filter((value) => value.startsWith(prefix))) {
|
|
183
|
+
finishHeldCache(`${dirname(path)}/${name}`, path, input.expectedGenerationId);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function clearExpectedCache(
|
|
188
|
+
input: WorkflowChildSessionV3Input & { expectedGenerationId: string },
|
|
189
|
+
): void {
|
|
190
|
+
recoverHeldCaches(input);
|
|
191
|
+
if (
|
|
192
|
+
readHeartbeat(input.coordRoot, input.instanceId)?.v3_generation_id !==
|
|
193
|
+
input.expectedGenerationId
|
|
194
|
+
)
|
|
195
|
+
return;
|
|
196
|
+
const path = heartbeatPath(input.coordRoot, input.instanceId);
|
|
197
|
+
const held = `${dirname(path)}/${heldPrefix(input)}${randomUUID()}`;
|
|
198
|
+
try {
|
|
199
|
+
// A unique held name pins the actual bytes atomically. Checking before
|
|
200
|
+
// unlink alone could consume a replacement published at this boundary.
|
|
201
|
+
renameSync(path, held);
|
|
202
|
+
fsyncParentDirectory(path);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return;
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
finishHeldCache(held, path, input.expectedGenerationId);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function finishHeldCache(held: string, path: string, expectedGenerationId: string): void {
|
|
211
|
+
let removed = false;
|
|
212
|
+
let cleanupFailed = false;
|
|
213
|
+
let cleanupError: unknown;
|
|
214
|
+
try {
|
|
215
|
+
const cache = JSON.parse(readFileSync(held, "utf8")) as Heartbeat;
|
|
216
|
+
if (cache.v3_generation_id === expectedGenerationId) {
|
|
217
|
+
unlinkSync(held);
|
|
218
|
+
fsyncParentDirectory(held);
|
|
219
|
+
removed = true;
|
|
220
|
+
}
|
|
221
|
+
} catch (error) {
|
|
222
|
+
cleanupFailed = true;
|
|
223
|
+
cleanupError = error;
|
|
224
|
+
}
|
|
225
|
+
if (!removed) {
|
|
226
|
+
try {
|
|
227
|
+
// Never overwrite a later publication. Keep the held evidence when
|
|
228
|
+
// another cache already owns the live name.
|
|
229
|
+
linkSync(held, path);
|
|
230
|
+
fsyncParentDirectory(path);
|
|
231
|
+
unlinkSync(held);
|
|
232
|
+
fsyncParentDirectory(held);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") {
|
|
235
|
+
if (cleanupFailed) {
|
|
236
|
+
throw new AggregateError(
|
|
237
|
+
[cleanupError, error],
|
|
238
|
+
`workflow_child_v3_cache_cleanup_and_restore_failed: ${String(cleanupError)}; restoration: ${String(error)}`,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (cleanupFailed) throw cleanupError;
|
|
105
246
|
}
|
|
106
247
|
|
|
107
248
|
function requireV3Route(coordRoot: string) {
|