@tangle-network/agent-runtime 0.300.0 → 0.306.1
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/dist/{activation-DqWmWTyB.js → activation-C5wesfzd.js} +3 -3
- package/dist/{activation-DqWmWTyB.js.map → activation-C5wesfzd.js.map} +1 -1
- package/dist/{activation-Bhp8dYV5.d.ts → activation-wZ-8vOjV.d.ts} +88 -13
- package/dist/agent.d.ts +2 -2
- package/dist/agent.js +2 -2
- package/dist/{bundle-C9v5TH_H.js → bundle-C8EO05u3.js} +2 -2
- package/dist/{bundle-C9v5TH_H.js.map → bundle-C8EO05u3.js.map} +1 -1
- package/dist/candidate-execution/index.d.ts +3 -26
- package/dist/candidate-execution/index.js +6 -6
- package/dist/{coordination-driver-DzTvehd6.js → coordination-driver-yj9HPXef.js} +322 -223
- package/dist/coordination-driver-yj9HPXef.js.map +1 -0
- package/dist/{delegate-vJ2g76r6.js → delegate-KtrL9HWq.js} +5 -5
- package/dist/delegate-KtrL9HWq.js.map +1 -0
- package/dist/durable.d.ts +3 -15
- package/dist/durable.js +158 -124
- package/dist/durable.js.map +1 -1
- package/dist/{graph-BDQh3qry.js → graph-DcdyHbgf.js} +5 -6
- package/dist/graph-DcdyHbgf.js.map +1 -0
- package/dist/{improvement-cycle-D6cPLi6k.js → improvement-cycle-DsN-TVXR.js} +1129 -10
- package/dist/improvement-cycle-DsN-TVXR.js.map +1 -0
- package/dist/{index-sC6sl88d.d.ts → index-DflDZq_I.d.ts} +188 -748
- package/dist/index.d.ts +378 -12
- package/dist/index.js +223 -320
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +5 -5
- package/dist/intelligence.js +7 -8
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +4 -4
- package/dist/kernel.js +9 -9
- package/dist/{loop-runner-bin-DZglq4Cf.d.ts → loop-runner-bin-DZfRUVWy.d.ts} +3 -3
- package/dist/{loop-runner-bin-DQIHnCL0.js → loop-runner-bin-rVrSX5l1.js} +3 -3
- package/dist/{loop-runner-bin-DQIHnCL0.js.map → loop-runner-bin-rVrSX5l1.js.map} +1 -1
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +6 -35
- package/dist/mcp/index.js +311 -364
- package/dist/mcp/index.js.map +1 -1
- package/dist/model-policy-DydyWTQg.js.map +1 -1
- package/dist/{openai-tools-DvR82zEo.d.ts → openai-tools-YO30D2s3.d.ts} +2 -2
- package/dist/{prepare-CGVNMb1o.js → prepare--nWcazPr.js} +7 -13
- package/dist/prepare--nWcazPr.js.map +1 -0
- package/dist/{profile-BetOB4_Q.js → profile-h04LDjV6.js} +2 -2
- package/dist/{profile-BetOB4_Q.js.map → profile-h04LDjV6.js.map} +1 -1
- package/dist/profiles.d.ts +2 -94
- package/dist/profiles.js +2 -451
- package/dist/profiles.js.map +1 -1
- package/dist/{protected-model-port-CAa0t1MT.d.ts → protected-model-port-C9QtEf3y.d.ts} +2 -2
- package/dist/{protected-model-port-CuVHkhti.js → protected-model-port-wwalbyXM.js} +4 -4
- package/dist/{protected-model-port-CuVHkhti.js.map → protected-model-port-wwalbyXM.js.map} +1 -1
- package/dist/{provision-supervisor-IXBlLHE0.js → provision-supervisor-WxFEFQeJ.js} +14 -24
- package/dist/provision-supervisor-WxFEFQeJ.js.map +1 -0
- package/dist/{runtime-DpEt5v42.js → runtime-B9eBIyws.js} +86 -496
- package/dist/runtime-B9eBIyws.js.map +1 -0
- package/dist/{search-method-DvU-fc-f.js → search-executor-2tWr38Qw.js} +239 -1132
- package/dist/search-executor-2tWr38Qw.js.map +1 -0
- package/dist/{server-CIE8s4AN.js → server-CpKcrOPj.js} +5 -12
- package/dist/server-CpKcrOPj.js.map +1 -0
- package/dist/{stream-agent-turn-I3bb0eqF.d.ts → stream-agent-turn-B1giFgOg.d.ts} +4 -2
- package/dist/{substrate-UjsxukjZ.d.ts → substrate-L_sC7jCD.d.ts} +2 -2
- package/dist/{supervise-C5mktzrh.js → supervise-DllOHM4M.js} +206 -80
- package/dist/supervise-DllOHM4M.js.map +1 -0
- package/dist/{supervisor-CQSu5XGl.js → supervisor-gCfG6K1-.js} +1130 -1667
- package/dist/supervisor-gCfG6K1-.js.map +1 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +14 -14
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.js +1 -1
- package/dist/{types-C73caP0U.d.ts → types-DeuEkBhI.d.ts} +167 -261
- package/dist/{workspace-archive-pz8YJ1fr.d.ts → workspace-streams-CpSmt3zx.d.ts} +67 -3
- package/dist/{workspace-streams-D5gHJdTb.js → workspace-streams-ILw0eWZ0.js} +694 -2
- package/dist/workspace-streams-ILw0eWZ0.js.map +1 -0
- package/package.json +8 -8
- package/skills/generate-eval/SKILL.md +1 -1
- package/skills/profile-authoring/SKILL.md +5 -5
- package/skills/supervise/SKILL.md +2 -2
- package/dist/coordination-driver-DzTvehd6.js.map +0 -1
- package/dist/delegate-vJ2g76r6.js.map +0 -1
- package/dist/graph-BDQh3qry.js.map +0 -1
- package/dist/improvement-cycle-D6cPLi6k.js.map +0 -1
- package/dist/prepare-CGVNMb1o.js.map +0 -1
- package/dist/provision-supervisor-IXBlLHE0.js.map +0 -1
- package/dist/runtime-DpEt5v42.js.map +0 -1
- package/dist/search-method-DvU-fc-f.js.map +0 -1
- package/dist/server-CIE8s4AN.js.map +0 -1
- package/dist/supervise-C5mktzrh.js.map +0 -1
- package/dist/supervisor-CQSu5XGl.js.map +0 -1
- package/dist/workspace-streams-D5gHJdTb.js.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { c as RetainedInteractiveAdmissionError, d as RetainedRunDispatchBindingError, f as RuntimeRunStateError, i as ConfigError, l as RetainedInteractiveBindingError, m as ValidationError$1, r as BackendTransportError, t as AgentEvalError$1, u as RetainedRunAdmissionError } from "./errors-DodWX-cb.js";
|
|
2
|
+
import { $ as randomUuid, B as addTokenUsage, G as errorMessage, H as cloneSpend, K as fnv1a32Hex, N as deepFreezeCandidate, O as verifyWorkspaceSnapshotArtifacts, Q as randomSuffix, U as cloneTokenUsage, V as chargedTokens, W as deleteBoxSafe, X as mapWithConcurrency, Y as isAsyncIterable$1, Z as promptCacheTokenClasses, _t as armDeadlineTimer, a as verifyAgentCandidateWorkspaceArtifacts, at as unmeteredSpend, bt as singleFlightTeardown, ct as zeroTokenUsage, dt as resourceTelemetry, et as roundBoxMinutes, ft as withBudgetResources, gt as ROOT_DEADLINE_REASON, i as readAgentCandidateWorkspaceArtifactFiles, it as throwIfAborted, k as verifiedArtifactChunks, lt as addResourceSpend, mt as CHILD_DEADLINE_REASON, nt as stringifySafe, o as verifyAgentCandidateWorkspaceTree, ot as withTimeout, pt as ACCEPTED_RESULT_GRACE_MS, q as hasCompleteCacheBreakdown, rt as throwAbort, st as zeroSpend, tt as sleep, u as verifyAgentCandidateWorkspaceArchive, ut as assertResources, vt as boundedChildDeadlineAt, xt as teardownExecutor, yt as isDeadlineAbortReason, z as addSpend } from "./workspace-streams-ILw0eWZ0.js";
|
|
2
3
|
import { runTaskQueue } from "./task-queue.js";
|
|
3
4
|
import { _ as agentHarness, a as concreteProfileModel, c as profileModelExecutionSettings, d as assertRouterAutoDeclaration, f as assertRouterAutoResponse, g as observedModelMatchesDeclared, h as observedModelHasSnapshot, l as profileProviderModel, m as mergeObservedModelIdentity, o as enforceTokenLimits, s as profileBridgeWireModel, t as assertExecutableAgentProfile, u as resolveRouterRetryPolicy } from "./model-policy-DydyWTQg.js";
|
|
4
|
-
import { D as verifiedArtifactChunks, E as verifyWorkspaceSnapshotArtifacts, c as verifyAgentCandidateWorkspaceArchive, i as verifyAgentCandidateWorkspaceArtifacts, j as deepFreezeCandidate, r as readAgentCandidateWorkspaceArtifactFiles } from "./workspace-streams-D5gHJdTb.js";
|
|
5
5
|
import { n as assertNoSymlinkDescendant, r as publishExclusiveDurableFile } from "./durable-file-DWQA4ooo.js";
|
|
6
6
|
import { i as notifyRuntimeHookEvent } from "./runtime-hooks-tXpAarhW.js";
|
|
7
|
-
import { AGENT_PROFILE_MATERIALIZATION_AXES, AgentEnvironmentCapabilitiesSchema, AgentExactRunControlRefSchema, AgentInteractiveSessionControlClaimAcknowledgementSchema, AgentInteractiveSessionControlClaimSchema, AgentInteractiveSessionPromptAcknowledgementSchema, AgentInteractiveSessionRefSchema, AgentInteractiveSessionStatusSchema, AgentInteractiveSessionStopAcknowledgementSchema, AgentNativeContextContinuationResultSchema, AgentRunCancellationAcknowledgementSchema, AgentRunCancellationRequestSchema, AgentTurnInputSchema, AgentTurnResultSchema, CanonicalStreamEventSchema, ContextTransferRequestSchema, InteractionAcknowledgementSchema, InteractionResponseCommandSchema, NativeContextBoundaryProofSchema, NativeContextContinuationRequestSchema, REASONING_EFFORTS, RuntimeEventEnvelopeSchema, TerminalReplayWindowSchema, TerminalSessionRefSchema, agentCandidateArtifactRefSchema, agentCandidateWorkspaceManifestMaterialSchema, agentCandidateWorkspaceSnapshotEvidenceSchema, agentInteractiveSessionControlClaimAcknowledgementMatchesRequest, agentInteractiveSessionControlClaimMatchesRef, agentInteractiveSessionPromptAcknowledgementMatchesCommand, agentInteractiveSessionRefMatchesStart, agentInteractiveSessionRunRef, agentInteractiveSessionStatusMatchesRef, agentInteractiveSessionStopAcknowledgementMatchesCommand, agentNativeContextContinuationResultMatchesRequest, agentProfileSchema, agentRunCancellationAcknowledgementMatchesRequest, agentRunCancellationRequestDigest, canonicalAgentProfileDigest, canonicalCandidateDigest, canonicalWorkspaceCwd, exactAgentInteractiveSessionStart, harnessTypeSchema, nativeContextContinuationTurnDigest, nativeReasoningControl, profileMaterializationAxes, profileMaterializationAxes as profileMaterializationAxes$1, renderInputPartsAsText, sha256DigestSchema, workspaceCheckpointRequestDigest, workspaceCheckpointResultMatchesRequest, workspaceCleanupAcknowledgementMatches, workspaceCleanupRequestDigest } from "@tangle-network/agent-interface";
|
|
7
|
+
import { AGENT_PROFILE_MATERIALIZATION_AXES, AgentEnvironmentCapabilitiesSchema, AgentExactRunControlRefSchema, AgentInteractiveSessionControlClaimAcknowledgementSchema, AgentInteractiveSessionControlClaimSchema, AgentInteractiveSessionPromptAcknowledgementSchema, AgentInteractiveSessionRefSchema, AgentInteractiveSessionStatusSchema, AgentInteractiveSessionStopAcknowledgementSchema, AgentNativeContextContinuationResultSchema, AgentRunCancellationAcknowledgementSchema, AgentRunCancellationRequestSchema, AgentTurnInputSchema, AgentTurnResultSchema, CanonicalStreamEventSchema, ContextTransferRequestSchema, InteractionAcknowledgementSchema, InteractionResponseCommandSchema, NativeContextBoundaryProofSchema, NativeContextContinuationRequestSchema, REASONING_EFFORTS, RuntimeEventEnvelopeSchema, TerminalReplayWindowSchema, TerminalSessionRefSchema, agentCandidateArtifactRefSchema, agentCandidateWorkspaceManifestMaterialSchema, agentCandidateWorkspaceSnapshotEvidenceSchema, agentInteractiveSessionControlClaimAcknowledgementMatchesRequest, agentInteractiveSessionControlClaimMatchesRef, agentInteractiveSessionPromptAcknowledgementMatchesCommand, agentInteractiveSessionRefMatchesStart, agentInteractiveSessionRunRef, agentInteractiveSessionStatusMatchesRef, agentInteractiveSessionStopAcknowledgementMatchesCommand, agentNativeContextContinuationResultMatchesRequest, agentProfileSchema, agentRunCancellationAcknowledgementMatchesRequest, agentRunCancellationRequestDigest, canonicalAgentProfileDigest, canonicalCandidateDigest, canonicalWorkspaceCwd, exactAgentInteractiveSessionStart, harnessTypeSchema, nativeContextContinuationTurnDigest, nativeReasoningControl, profileMaterializationAxes, profileMaterializationAxes as profileMaterializationAxes$1, renderInputPartsAsText, sha256DigestSchema, workspaceCheckpointRequestDigest, workspaceCheckpointResultMatchesRequest, workspaceCleanupAcknowledgementMatches, workspaceCleanupRequestDigest, workspaceForkRequestDigest, workspaceForkResultMatchesRequest } from "@tangle-network/agent-interface";
|
|
8
8
|
import { estimateCost, isModelPriced, isToolSpan, toolSpansToTraceAnalysisStore } from "@tangle-network/agent-eval";
|
|
9
9
|
import { createAgentRunOutcomeTracker } from "@tangle-network/sandbox/runtime";
|
|
10
10
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
@@ -102,123 +102,6 @@ function promptPartFromInputPart(part) {
|
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
//#endregion
|
|
105
|
-
//#region src/runtime/supervise/deadline.ts
|
|
106
|
-
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
107
|
-
const DEFAULT_SUCCESSFUL_SHUTDOWN_MS = 5e3;
|
|
108
|
-
/** The abort reason a child's own deadline carries. */
|
|
109
|
-
const CHILD_DEADLINE_REASON = "child deadline exceeded";
|
|
110
|
-
/** The abort reason the root's budget deadline cascades to every node. */
|
|
111
|
-
const ROOT_DEADLINE_REASON = "root budget deadline exceeded";
|
|
112
|
-
/** Whether an abort reason is a deadline: the child's own or the root budget's. */
|
|
113
|
-
function isDeadlineAbortReason(reason) {
|
|
114
|
-
return reason === "child deadline exceeded" || reason === "root budget deadline exceeded";
|
|
115
|
-
}
|
|
116
|
-
const TEARDOWN_ACKNOWLEDGEMENT_MS = 250;
|
|
117
|
-
/** Arm a finite wall-clock deadline in safe chunks. Resource deadlines unref by default;
|
|
118
|
-
* a standalone wait opts into keeping the process alive until it settles or is cancelled. */
|
|
119
|
-
function armDeadlineTimer(delayMs, onDeadline, keepAlive = false) {
|
|
120
|
-
const deadlineAtMs = Date.now() + Math.max(0, delayMs);
|
|
121
|
-
if (!Number.isFinite(delayMs) || !Number.isSafeInteger(Math.ceil(deadlineAtMs))) throw new ValidationError$1("timer delay must produce a finite safe deadline");
|
|
122
|
-
let cleared = false;
|
|
123
|
-
let timer;
|
|
124
|
-
const arm = () => {
|
|
125
|
-
if (cleared) return;
|
|
126
|
-
const remainingMs = Math.max(0, deadlineAtMs - Date.now());
|
|
127
|
-
timer = setTimeout(() => {
|
|
128
|
-
if (cleared) return;
|
|
129
|
-
if (Date.now() >= deadlineAtMs) onDeadline();
|
|
130
|
-
else arm();
|
|
131
|
-
}, Math.min(remainingMs, MAX_TIMER_DELAY_MS));
|
|
132
|
-
if (!keepAlive && typeof timer.unref === "function") timer.unref();
|
|
133
|
-
};
|
|
134
|
-
arm();
|
|
135
|
-
return () => {
|
|
136
|
-
cleared = true;
|
|
137
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Resolve a spawned child's duration into one absolute cutoff. An omitted child deadline inherits
|
|
142
|
-
* the parent cutoff; an explicit child duration may shorten, but never extend, that cutoff.
|
|
143
|
-
*/
|
|
144
|
-
function boundedChildDeadlineAt(parentDeadlineAtMs, childDeadlineMs, nowMs) {
|
|
145
|
-
const parent = parentDeadlineAtMs > 0 ? parentDeadlineAtMs : void 0;
|
|
146
|
-
const child = childDeadlineMs === void 0 ? void 0 : nowMs + childDeadlineMs;
|
|
147
|
-
if (parent === void 0) return child;
|
|
148
|
-
if (child === void 0) return parent;
|
|
149
|
-
return Math.min(parent, child);
|
|
150
|
-
}
|
|
151
|
-
/** Invoke teardown once and bound how long the runtime waits for its acknowledgement. A hard
|
|
152
|
-
* execution deadline always wins; without one, numeric grace gets a short acknowledgement
|
|
153
|
-
* allowance and a brutal kill gets only that allowance. Explicit `infinity` remains unbounded
|
|
154
|
-
* only when no execution deadline exists. The losing promise stays observed. `ask` issues the
|
|
155
|
-
* request; a scope passes its per-child single-flight asker (see `singleFlightTeardown`). */
|
|
156
|
-
async function teardownExecutor(executor, grace, deadlineAtMs, now, ask = (requested) => Promise.resolve(executor.teardown(requested))) {
|
|
157
|
-
const work = Promise.resolve(ask(grace));
|
|
158
|
-
const requestedWaitMs = grace === "infinity" ? void 0 : grace === "brutalKill" ? executor.teardownTimeoutMs ?? TEARDOWN_ACKNOWLEDGEMENT_MS : grace + TEARDOWN_ACKNOWLEDGEMENT_MS;
|
|
159
|
-
const deadlineWaitMs = deadlineAtMs === void 0 ? void 0 : Math.max(0, deadlineAtMs - now()) + TEARDOWN_ACKNOWLEDGEMENT_MS;
|
|
160
|
-
const waitMs = requestedWaitMs === void 0 ? deadlineWaitMs : deadlineWaitMs === void 0 ? requestedWaitMs : Math.min(requestedWaitMs, deadlineWaitMs);
|
|
161
|
-
let receipt;
|
|
162
|
-
if (waitMs === void 0) receipt = await work;
|
|
163
|
-
else if (waitMs <= 0) {
|
|
164
|
-
work.catch(() => void 0);
|
|
165
|
-
throw new ValidationError$1("executor teardown did not acknowledge before its deadline");
|
|
166
|
-
} else {
|
|
167
|
-
let clearTimer;
|
|
168
|
-
const timedOut = new Promise((_resolve, reject) => {
|
|
169
|
-
clearTimer = armDeadlineTimer(waitMs, () => reject(new ValidationError$1(`executor teardown did not acknowledge within ${waitMs}ms`)), true);
|
|
170
|
-
});
|
|
171
|
-
try {
|
|
172
|
-
receipt = await Promise.race([work, timedOut]);
|
|
173
|
-
} finally {
|
|
174
|
-
clearTimer?.();
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (!receipt.destroyed) throw new ValidationError$1(`executor teardown reported destroyed=false${receipt.detail ? `: ${receipt.detail}` : ""}`);
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* The optional teardown surfaces a wrapping executor forwards, so wrapping never changes how a
|
|
181
|
-
* child is released: the acknowledgement window a remote teardown needs, the release of a
|
|
182
|
-
* retained execution at root settlement, and the environments an unconfirmed teardown names for
|
|
183
|
-
* a sweeper. A wrapper that dropped `releaseRetained` left every retained child it wrapped
|
|
184
|
-
* running after settlement. Each surface is present only when the inner executor implements it.
|
|
185
|
-
*/
|
|
186
|
-
function teardownSurfaces(inner) {
|
|
187
|
-
return {
|
|
188
|
-
...inner.teardownTimeoutMs === void 0 ? {} : { teardownTimeoutMs: inner.teardownTimeoutMs },
|
|
189
|
-
...inner.releaseRetained === void 0 ? {} : { releaseRetained: (signal) => inner.releaseRetained(signal) },
|
|
190
|
-
...inner.heldEnvironments === void 0 ? {} : { heldEnvironments: () => inner.heldEnvironments() }
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* An asker that sends an executor at most one teardown at a time. While a request is still
|
|
195
|
-
* running, every later ask returns that same request instead of sending another beside it.
|
|
196
|
-
*
|
|
197
|
-
* A settlement's own teardown has a short acknowledgement window, and a request that misses it
|
|
198
|
-
* keeps running. Without this, the first settlement retry sent a second teardown next to the
|
|
199
|
-
* first. An executor that does not deduplicate internally, such as one that re-issues its
|
|
200
|
-
* cancels, then ran two teardowns at once. `onRequest` runs once for each request actually sent.
|
|
201
|
-
*/
|
|
202
|
-
function singleFlightTeardown(executor, onRequest) {
|
|
203
|
-
let inFlight;
|
|
204
|
-
return (grace) => {
|
|
205
|
-
if (inFlight !== void 0) return inFlight;
|
|
206
|
-
onRequest();
|
|
207
|
-
let request;
|
|
208
|
-
try {
|
|
209
|
-
request = Promise.resolve(executor.teardown(grace));
|
|
210
|
-
} catch (error) {
|
|
211
|
-
request = Promise.reject(error);
|
|
212
|
-
}
|
|
213
|
-
inFlight = request;
|
|
214
|
-
const settle = () => {
|
|
215
|
-
if (inFlight === request) inFlight = void 0;
|
|
216
|
-
};
|
|
217
|
-
request.then(settle, settle);
|
|
218
|
-
return request;
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
//#endregion
|
|
222
105
|
//#region src/runtime/retained-run-binding.ts
|
|
223
106
|
function exactSession(environment, controlRef) {
|
|
224
107
|
if (environment.id !== controlRef.environmentId || environment.provider !== controlRef.provider) throw new Error("provider reconstructed a different retained environment");
|
|
@@ -315,7 +198,7 @@ async function awaitAbortable$1(work, signal) {
|
|
|
315
198
|
if (signal === void 0) return await work;
|
|
316
199
|
if (signal.aborted) {
|
|
317
200
|
Promise.resolve(work).catch(() => void 0);
|
|
318
|
-
throw abortError$
|
|
201
|
+
throw abortError$1(signal.reason);
|
|
319
202
|
}
|
|
320
203
|
return await new Promise((resolve, reject) => {
|
|
321
204
|
let settled = false;
|
|
@@ -325,7 +208,7 @@ async function awaitAbortable$1(work, signal) {
|
|
|
325
208
|
if (settled) return;
|
|
326
209
|
settled = true;
|
|
327
210
|
cleanup();
|
|
328
|
-
reject(abortError$
|
|
211
|
+
reject(abortError$1(signal.reason));
|
|
329
212
|
});
|
|
330
213
|
};
|
|
331
214
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -382,7 +265,7 @@ function isRecord$3(value) {
|
|
|
382
265
|
function assertStableText(value, label) {
|
|
383
266
|
if (value.length === 0 || value.trim() !== value) throw new Error(`${label} must be non-empty and have no outer whitespace`);
|
|
384
267
|
}
|
|
385
|
-
function abortError$
|
|
268
|
+
function abortError$1(reason) {
|
|
386
269
|
const error = new Error(reason instanceof Error ? reason.message : reason === void 0 ? "aborted" : String(reason));
|
|
387
270
|
error.name = "AbortError";
|
|
388
271
|
return error;
|
|
@@ -419,7 +302,7 @@ function isTerminalSessionStatus(status) {
|
|
|
419
302
|
return status !== null && sessionStatusIsTerminal[status];
|
|
420
303
|
}
|
|
421
304
|
function delay(ms, signal) {
|
|
422
|
-
if (signal?.aborted) return Promise.reject(abortError$
|
|
305
|
+
if (signal?.aborted) return Promise.reject(abortError$1(signal.reason));
|
|
423
306
|
return new Promise((resolveDelay, rejectDelay) => {
|
|
424
307
|
const clearTimer = armDeadlineTimer(ms, finish, true);
|
|
425
308
|
function finish() {
|
|
@@ -429,7 +312,7 @@ function delay(ms, signal) {
|
|
|
429
312
|
function onAbort() {
|
|
430
313
|
clearTimer();
|
|
431
314
|
signal?.removeEventListener("abort", onAbort);
|
|
432
|
-
rejectDelay(abortError$
|
|
315
|
+
rejectDelay(abortError$1(signal?.reason));
|
|
433
316
|
}
|
|
434
317
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
435
318
|
});
|
|
@@ -654,477 +537,6 @@ function isRecord$2(value) {
|
|
|
654
537
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
655
538
|
}
|
|
656
539
|
//#endregion
|
|
657
|
-
//#region src/runtime/supervise/resources.ts
|
|
658
|
-
/** Validate caller-owned names and units without assigning domain meaning to them. */
|
|
659
|
-
function assertResources(resources, kind, label) {
|
|
660
|
-
if (resources === void 0) return;
|
|
661
|
-
if (resources === null || typeof resources !== "object" || Array.isArray(resources)) throw new Error(`${label} must be a resource map`);
|
|
662
|
-
for (const [name, resource] of Object.entries(resources)) {
|
|
663
|
-
if (!name.trim() || !resource || typeof resource !== "object" || typeof resource.unit !== "string" || !resource.unit.trim()) throw new Error(`${label}: resource names and units must be non-empty strings`);
|
|
664
|
-
const value = kind === "limit" ? "limit" in resource ? resource.limit : void 0 : "amount" in resource ? resource.amount : void 0;
|
|
665
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new Error(`${label}.${name}.${kind} must be a non-negative safe integer`);
|
|
666
|
-
if (kind === "amount" && (!("known" in resource) || typeof resource.known !== "boolean")) throw new Error(`${label}.${name}.known must be a boolean`);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
/** Missing contributors are not usage claims; enforced omissions are marked before aggregation.
|
|
670
|
-
* Overflow retains a safe lower bound and marks it unknown, preserving readable component receipts. */
|
|
671
|
-
function addResourceSpend(...maps) {
|
|
672
|
-
const totals = /* @__PURE__ */ new Map();
|
|
673
|
-
for (const resources of maps) {
|
|
674
|
-
assertResources(resources, "amount", "resource spend");
|
|
675
|
-
for (const [name, resource] of Object.entries(resources ?? {})) {
|
|
676
|
-
const prior = totals.get(name);
|
|
677
|
-
if (prior && prior.unit !== resource.unit) throw new Error(`resource ${name}: unit mismatch`);
|
|
678
|
-
const amount = (prior?.amount ?? 0) + resource.amount;
|
|
679
|
-
totals.set(name, {
|
|
680
|
-
unit: resource.unit,
|
|
681
|
-
amount: Math.min(amount, Number.MAX_SAFE_INTEGER),
|
|
682
|
-
known: (prior?.known ?? true) && resource.known && Number.isSafeInteger(amount)
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
return totals.size ? { resources: Object.fromEntries(totals) } : {};
|
|
687
|
-
}
|
|
688
|
-
/** A terminal omission under an enforced ceiling is unknown, never measured zero. */
|
|
689
|
-
function withBudgetResources(spend, budget, notStarted = false) {
|
|
690
|
-
const resources = new Map(Object.entries(addResourceSpend(spend.resources).resources ?? {}));
|
|
691
|
-
for (const [name, limit] of Object.entries(budget.resources ?? {})) {
|
|
692
|
-
const observed = resources.get(name);
|
|
693
|
-
if (observed && observed.unit !== limit.unit) throw new Error(`resource ${name}: unit mismatch`);
|
|
694
|
-
if (!observed) resources.set(name, {
|
|
695
|
-
unit: limit.unit,
|
|
696
|
-
amount: 0,
|
|
697
|
-
known: notStarted
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
return resources.size ? {
|
|
701
|
-
...spend,
|
|
702
|
-
resources: Object.fromEntries(resources)
|
|
703
|
-
} : spend;
|
|
704
|
-
}
|
|
705
|
-
/** Stream increments and a terminal total describe the same work; never sum them twice. */
|
|
706
|
-
function resourceTelemetry(streamed, terminal) {
|
|
707
|
-
const resources = new Map(Object.entries(addResourceSpend(terminal.resources).resources ?? {}));
|
|
708
|
-
for (const [name, value] of Object.entries(streamed.resources ?? {})) {
|
|
709
|
-
const other = resources.get(name);
|
|
710
|
-
if (other && other.unit !== value.unit) throw new Error(`resource ${name}: unit mismatch`);
|
|
711
|
-
resources.set(name, {
|
|
712
|
-
...value,
|
|
713
|
-
amount: Math.max(value.amount, other?.amount ?? 0),
|
|
714
|
-
known: value.known && (other?.known ?? true) && (other === void 0 || other.amount === value.amount)
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
return addResourceSpend(Object.fromEntries(resources));
|
|
718
|
-
}
|
|
719
|
-
//#endregion
|
|
720
|
-
//#region src/runtime/util.ts
|
|
721
|
-
/**
|
|
722
|
-
* Best-effort sandbox delete. Skips instances without a `delete` (test fakes);
|
|
723
|
-
* swallows errors (the platform reaps on expiry). Returns `false` when delete
|
|
724
|
-
* threw, `true` otherwise, so callers can surface a leak if they choose.
|
|
725
|
-
*/
|
|
726
|
-
async function deleteBoxSafe(box) {
|
|
727
|
-
if (!box || typeof box.delete !== "function") return true;
|
|
728
|
-
try {
|
|
729
|
-
await box.delete();
|
|
730
|
-
return true;
|
|
731
|
-
} catch {
|
|
732
|
-
return false;
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
/** Short base36 id for trace correlation. Not cryptographic, not collision-free. */
|
|
736
|
-
function randomSuffix(len = 8) {
|
|
737
|
-
return Math.random().toString(36).slice(2, 2 + len);
|
|
738
|
-
}
|
|
739
|
-
/** Collision-resistant id for sandbox naming (find-by-name recovery must be unique). */
|
|
740
|
-
function randomUuid() {
|
|
741
|
-
return crypto.randomUUID();
|
|
742
|
-
}
|
|
743
|
-
/** Construct an AbortError. Downstream code pattern-matches on `err.name`. */
|
|
744
|
-
function abortError$1() {
|
|
745
|
-
const err = /* @__PURE__ */ new Error("aborted");
|
|
746
|
-
err.name = "AbortError";
|
|
747
|
-
return err;
|
|
748
|
-
}
|
|
749
|
-
/** Throw an AbortError. */
|
|
750
|
-
function throwAbort() {
|
|
751
|
-
throw abortError$1();
|
|
752
|
-
}
|
|
753
|
-
/** Throw if the signal is already aborted; otherwise no-op. */
|
|
754
|
-
function throwIfAborted(signal) {
|
|
755
|
-
if (signal?.aborted) throw abortError$1();
|
|
756
|
-
}
|
|
757
|
-
/** True for any error whose `name` is `AbortError` (the cross-kernel abort contract). */
|
|
758
|
-
function isAbortError$2(err) {
|
|
759
|
-
return err instanceof Error && err.name === "AbortError";
|
|
760
|
-
}
|
|
761
|
-
/**
|
|
762
|
-
* Sleep that resolves early on abort and always clears its timer so it never
|
|
763
|
-
* keeps the event loop alive. Resolves (does not reject) on abort — callers
|
|
764
|
-
* re-check the signal explicitly after the sleep.
|
|
765
|
-
*/
|
|
766
|
-
function sleep(ms, signal, keepAlive = true) {
|
|
767
|
-
if (signal?.aborted) return Promise.resolve();
|
|
768
|
-
return new Promise((resolve) => {
|
|
769
|
-
const done = () => {
|
|
770
|
-
clearTimer();
|
|
771
|
-
signal?.removeEventListener("abort", done);
|
|
772
|
-
resolve();
|
|
773
|
-
};
|
|
774
|
-
const clearTimer = armDeadlineTimer(ms, done, keepAlive);
|
|
775
|
-
signal?.addEventListener("abort", done, { once: true });
|
|
776
|
-
});
|
|
777
|
-
}
|
|
778
|
-
/**
|
|
779
|
-
* Race a promise against a timeout. Resolves with the value if it settles in
|
|
780
|
-
* time, otherwise resolves with `undefined`. Always clears the timer.
|
|
781
|
-
*/
|
|
782
|
-
function withTimeout(promise, ms) {
|
|
783
|
-
return new Promise((resolve) => {
|
|
784
|
-
const clearTimer = armDeadlineTimer(ms, () => resolve(void 0), true);
|
|
785
|
-
promise.then((value) => {
|
|
786
|
-
clearTimer();
|
|
787
|
-
resolve(value);
|
|
788
|
-
}, () => {
|
|
789
|
-
clearTimer();
|
|
790
|
-
resolve(void 0);
|
|
791
|
-
});
|
|
792
|
-
});
|
|
793
|
-
}
|
|
794
|
-
/**
|
|
795
|
-
* `JSON.stringify` with a `String()` fallback on throw (cyclic / non-JSON).
|
|
796
|
-
* Strings pass through unstringified so a preview of a string output is the
|
|
797
|
-
* string itself, not a quoted re-encoding.
|
|
798
|
-
*/
|
|
799
|
-
function stringifySafe(value, opts = {}) {
|
|
800
|
-
let s;
|
|
801
|
-
try {
|
|
802
|
-
if (typeof value === "string") s = value;
|
|
803
|
-
else s = (opts.pretty ? JSON.stringify(value, null, 2) : JSON.stringify(value)) ?? String(value);
|
|
804
|
-
} catch {
|
|
805
|
-
s = String(value);
|
|
806
|
-
}
|
|
807
|
-
if (opts.max !== void 0 && s.length > opts.max) return `${s.slice(0, opts.max)}…`;
|
|
808
|
-
return s;
|
|
809
|
-
}
|
|
810
|
-
/** A fresh zero token-usage accumulator. */
|
|
811
|
-
function zeroTokenUsage() {
|
|
812
|
-
return {
|
|
813
|
-
input: 0,
|
|
814
|
-
output: 0
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
/**
|
|
818
|
-
* Sum the catalog-priced part of a dollar total across spends, as a field to spread.
|
|
819
|
-
*
|
|
820
|
-
* Returns nothing when no input carried one. A fold of pure provider receipts must not gain a
|
|
821
|
-
* `usdEstimated: 0`, which would read as "this runtime checked and priced none" on a path that
|
|
822
|
-
* never prices at all.
|
|
823
|
-
*/
|
|
824
|
-
function usdEstimatedOf(...spends) {
|
|
825
|
-
let total = 0;
|
|
826
|
-
let priced = false;
|
|
827
|
-
for (const spend of spends) {
|
|
828
|
-
if (spend.usdEstimated === void 0) continue;
|
|
829
|
-
priced = true;
|
|
830
|
-
total += spend.usdEstimated;
|
|
831
|
-
}
|
|
832
|
-
return priced ? { usdEstimated: total } : {};
|
|
833
|
-
}
|
|
834
|
-
/** A conserved spend with every channel at zero and no completeness claim on it. */
|
|
835
|
-
function zeroSpend() {
|
|
836
|
-
return {
|
|
837
|
-
iterations: 0,
|
|
838
|
-
tokens: zeroTokenUsage(),
|
|
839
|
-
usd: 0,
|
|
840
|
-
ms: 0
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
|
-
/**
|
|
844
|
-
* The spend of work that HAPPENED and reported no usage receipt.
|
|
845
|
-
*
|
|
846
|
-
* Not the same value as {@link zeroSpend} even though both carry `{0,0}` tokens and `$0`. A bare
|
|
847
|
-
* zero asserts a MEASUREMENT — "this ran and cost nothing" — and every consumer downstream reads it
|
|
848
|
-
* that way: the pool keeps reporting `readout().tokensKnown === true`, the journal totals stay
|
|
849
|
-
* clean, the OTEL span records a priced zero, and a caller's token-denominated ceiling can never
|
|
850
|
-
* fire no matter how much the work really burned. A ceiling that cannot fire is worse than no
|
|
851
|
-
* ceiling, because it reads as protection.
|
|
852
|
-
*
|
|
853
|
-
* `tokensKnown` is the marker the substrate already threads end to end for exactly this case
|
|
854
|
-
* (`budget.ts`, `otel-spans.ts`, `spawn-journal.ts`, `supervisor.ts`): the work is recorded, the
|
|
855
|
-
* zero is labelled a floor rather than a total, and every rollup that touches it reports its
|
|
856
|
-
* balance as a ceiling rather than a measurement. Use this — never a bare zero — whenever a runtime
|
|
857
|
-
* cannot see what its worker spent.
|
|
858
|
-
*
|
|
859
|
-
* The same rule applies to dollars. A dollar-capped run must refuse an executor whose billed spend
|
|
860
|
-
* is unknowable; `budgetExempt` does not authorize Runtime to relabel unknown spend as a measured
|
|
861
|
-
* zero. Callers that require dollar accounting must use a backend with a trusted billing receipt.
|
|
862
|
-
*/
|
|
863
|
-
function unmeteredSpend(ms) {
|
|
864
|
-
return {
|
|
865
|
-
iterations: 0,
|
|
866
|
-
tokens: zeroTokenUsage(),
|
|
867
|
-
tokensKnown: false,
|
|
868
|
-
usd: 0,
|
|
869
|
-
usdKnown: false,
|
|
870
|
-
ms
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
/** Copy a conserved spend without dropping a completeness marker or the catalog-priced part. */
|
|
874
|
-
function cloneSpend(spend) {
|
|
875
|
-
return {
|
|
876
|
-
...addResourceSpend(spend.resources),
|
|
877
|
-
iterations: spend.iterations,
|
|
878
|
-
tokens: cloneTokenUsage(spend.tokens),
|
|
879
|
-
...spend.tokensKnown === false ? { tokensKnown: false } : {},
|
|
880
|
-
usd: spend.usd,
|
|
881
|
-
...spend.usdKnown === false ? { usdKnown: false } : {},
|
|
882
|
-
...spend.usdEstimated !== void 0 ? { usdEstimated: spend.usdEstimated } : {},
|
|
883
|
-
ms: spend.ms,
|
|
884
|
-
...boxMinutesOf(spend),
|
|
885
|
-
...spend.tokensProvenance === void 0 ? {} : { tokensProvenance: spend.tokensProvenance }
|
|
886
|
-
};
|
|
887
|
-
}
|
|
888
|
-
/**
|
|
889
|
-
* Sum two conserved spends per channel. Tokens and dollars never fold into each other, an
|
|
890
|
-
* incomplete input keeps the sum incomplete on that channel, and the catalog-priced part of the
|
|
891
|
-
* dollar total is summed separately so `usd - usdEstimated` still names what a provider billed.
|
|
892
|
-
*/
|
|
893
|
-
function addSpend(a, b) {
|
|
894
|
-
const tokens = cloneTokenUsage(a.tokens);
|
|
895
|
-
addTokenUsage(tokens, b.tokens);
|
|
896
|
-
return {
|
|
897
|
-
...addResourceSpend(a.resources, b.resources),
|
|
898
|
-
iterations: a.iterations + b.iterations,
|
|
899
|
-
tokens,
|
|
900
|
-
...a.tokensKnown === false || b.tokensKnown === false ? { tokensKnown: false } : {},
|
|
901
|
-
usd: a.usd + b.usd,
|
|
902
|
-
...a.usdKnown === false || b.usdKnown === false ? { usdKnown: false } : {},
|
|
903
|
-
...usdEstimatedOf(a, b),
|
|
904
|
-
ms: a.ms + b.ms,
|
|
905
|
-
...boxMinutesOf(a, b),
|
|
906
|
-
...tokensProvenanceOf(a, b)
|
|
907
|
-
};
|
|
908
|
-
}
|
|
909
|
-
/**
|
|
910
|
-
* Fold the token-provenance channel across spends, as a field to spread.
|
|
911
|
-
*
|
|
912
|
-
* Returns nothing when no contributor stated one, because ABSENT already means "the executor's own
|
|
913
|
-
* live stream", which is what every path reported before a harness store was readable. A
|
|
914
|
-
* contributor that stated one and a contributor that did not therefore disagree, and the sum is
|
|
915
|
-
* `'mixed'` — the honest answer for a tree whose spend came from two kinds of receipt.
|
|
916
|
-
*/
|
|
917
|
-
function tokensProvenanceOf(...spends) {
|
|
918
|
-
let folded;
|
|
919
|
-
let sawStated = false;
|
|
920
|
-
for (const spend of spends) {
|
|
921
|
-
const stated = spend.tokensProvenance ?? "stream-receipt";
|
|
922
|
-
if (spend.tokensProvenance !== void 0) sawStated = true;
|
|
923
|
-
folded = folded === void 0 || folded === stated ? stated : "mixed";
|
|
924
|
-
}
|
|
925
|
-
return !sawStated || folded === void 0 || folded === "stream-receipt" ? {} : { tokensProvenance: folded };
|
|
926
|
-
}
|
|
927
|
-
/**
|
|
928
|
-
* Fold the platform box-time channel across spends, as a field to spread.
|
|
929
|
-
*
|
|
930
|
-
* Returns nothing when no input carried the channel, so a fold over router or cli-bridge work —
|
|
931
|
-
* which runs no box at all — never gains a `boxMinutesProvenance: 'uncaptured'` that would claim
|
|
932
|
-
* a box ran and went unmetered.
|
|
933
|
-
*
|
|
934
|
-
* Three rules, one per field:
|
|
935
|
-
* - `boxMinutes` sums the numbers that are present, and stays ABSENT when none is. A missing
|
|
936
|
-
* measurement never enters the sum as a zero.
|
|
937
|
-
* - `boxMinutesKnown` is sticky-false, like `tokensKnown`: one contributor that could not state
|
|
938
|
-
* its box time makes the whole sum a floor.
|
|
939
|
-
* - `boxMinutesProvenance` degrades. `'observed'` survives only when every contributor that
|
|
940
|
-
* carried a number was observed; one derived number makes the sum `'estimated'`; no number at
|
|
941
|
-
* all is `'uncaptured'`.
|
|
942
|
-
*/
|
|
943
|
-
function boxMinutesOf(...spends) {
|
|
944
|
-
const present = spends.filter((spend) => spend.boxMinutesProvenance !== void 0);
|
|
945
|
-
if (present.length === 0) return {};
|
|
946
|
-
let minutes = 0;
|
|
947
|
-
let sawNumber = false;
|
|
948
|
-
let known = true;
|
|
949
|
-
let observedOnly = true;
|
|
950
|
-
for (const spend of present) {
|
|
951
|
-
if (typeof spend.boxMinutes === "number" && Number.isFinite(spend.boxMinutes)) {
|
|
952
|
-
minutes += spend.boxMinutes;
|
|
953
|
-
sawNumber = true;
|
|
954
|
-
if (spend.boxMinutesProvenance !== "observed") observedOnly = false;
|
|
955
|
-
}
|
|
956
|
-
if (spend.boxMinutesKnown !== true) known = false;
|
|
957
|
-
}
|
|
958
|
-
return {
|
|
959
|
-
...sawNumber ? { boxMinutes: roundBoxMinutes(minutes) } : {},
|
|
960
|
-
boxMinutesKnown: known,
|
|
961
|
-
boxMinutesProvenance: sawNumber ? observedOnly ? "observed" : "estimated" : "uncaptured"
|
|
962
|
-
};
|
|
963
|
-
}
|
|
964
|
-
/**
|
|
965
|
-
* Box minutes at four decimals — a tenth of a second.
|
|
966
|
-
*
|
|
967
|
-
* Fine enough that a box alive for two seconds reports `0.0333` instead of a zero that would read
|
|
968
|
-
* as a measured free box, and coarse enough that summing floats does not print
|
|
969
|
-
* `1.5000000000000002`.
|
|
970
|
-
*/
|
|
971
|
-
function roundBoxMinutes(minutes) {
|
|
972
|
-
return Number(minutes.toFixed(4));
|
|
973
|
-
}
|
|
974
|
-
/** Copy a token subtotal without dropping optional provider cache telemetry. */
|
|
975
|
-
function cloneTokenUsage(usage) {
|
|
976
|
-
const cacheBreakdownUnknown = usage.cacheBreakdownKnown === false || usage.input > 0 && !hasClassifiedCacheBreakdown(usage);
|
|
977
|
-
return {
|
|
978
|
-
input: usage.input,
|
|
979
|
-
output: usage.output,
|
|
980
|
-
...usage.tokensKnown === false ? { tokensKnown: false } : {},
|
|
981
|
-
...usage.freshInput !== void 0 ? { freshInput: usage.freshInput } : {},
|
|
982
|
-
...usage.cacheRead !== void 0 ? { cacheRead: usage.cacheRead } : {},
|
|
983
|
-
...usage.cacheWrite !== void 0 ? { cacheWrite: usage.cacheWrite } : {},
|
|
984
|
-
...cacheBreakdownUnknown ? { cacheBreakdownKnown: false } : {}
|
|
985
|
-
};
|
|
986
|
-
}
|
|
987
|
-
/** True only when every positive prompt token has a consistent cache classification. */
|
|
988
|
-
function hasCompleteCacheBreakdown(usage) {
|
|
989
|
-
if (usage.cacheBreakdownKnown === false) return false;
|
|
990
|
-
return hasClassifiedCacheBreakdown(usage);
|
|
991
|
-
}
|
|
992
|
-
function hasClassifiedCacheBreakdown(usage) {
|
|
993
|
-
if (usage.input === 0) return true;
|
|
994
|
-
if (usage.freshInput === void 0 || usage.cacheRead === void 0 || usage.cacheWrite === void 0) return false;
|
|
995
|
-
return usage.freshInput + usage.cacheRead + usage.cacheWrite === usage.input;
|
|
996
|
-
}
|
|
997
|
-
/**
|
|
998
|
-
* The token charge for one observation: every token counted ONCE, at the moment it first enters
|
|
999
|
-
* the context — `input − cacheRead + output`.
|
|
1000
|
-
*
|
|
1001
|
-
* `input` is a rolled-up prompt total that re-counts a cached prefix on every turn that reads it,
|
|
1002
|
-
* so charging `input` makes a 100K prefix read 40 times cost 4.1M for content authored once. A
|
|
1003
|
-
* cache read is content that was already charged when it was written, so it is the one class the
|
|
1004
|
-
* charge subtracts. Under a complete split the result is exactly `freshInput + cacheWrite`. No
|
|
1005
|
-
* price weight is involved: this counts work, and money is a separate channel.
|
|
1006
|
-
*
|
|
1007
|
-
* The subtraction form is what makes the charge ADDITIVE. `input` and `cacheRead` both accumulate
|
|
1008
|
-
* through `addTokenUsage`, so the charge on an aggregate equals the sum of the charges on the
|
|
1009
|
-
* records that built it — including an aggregate that mixes classified and unclassified turns,
|
|
1010
|
-
* where the unclassified remainder is charged in full and only the reported cache reads are
|
|
1011
|
-
* credited. A form that read `freshInput + cacheWrite` directly loses that: one unclassified turn
|
|
1012
|
-
* would drop the whole aggregate to `input + output` and over-charge every classified turn with it.
|
|
1013
|
-
*
|
|
1014
|
-
* The one arithmetic guarantee: a set of classes that does not FIT inside the prompt total it
|
|
1015
|
-
* partitions credits nothing, so the charge is never below `output` and a class that overflows its
|
|
1016
|
-
* own total buys no tokens. It is not a guarantee against a provider that reports a cache read it
|
|
1017
|
-
* never served — that provider can under-report `input` just as easily, and the token channel is an
|
|
1018
|
-
* accounting unit, not a trust boundary. Prompt tokens the provider left unclassified are charged
|
|
1019
|
-
* in full, so a spend with no readable split charges an upper bound on newly-presented work rather
|
|
1020
|
-
* than a measurement, and every caller that enforces a ceiling must report the difference
|
|
1021
|
-
* (`BudgetPool` does it through `readout().cacheBreakdownKnown`).
|
|
1022
|
-
*/
|
|
1023
|
-
function chargedTokens(usage) {
|
|
1024
|
-
return usage.input - creditedCacheRead(usage) + usage.output;
|
|
1025
|
-
}
|
|
1026
|
-
/** `cacheRead` if the reported classes fit inside `input`, else nothing. */
|
|
1027
|
-
function creditedCacheRead(usage) {
|
|
1028
|
-
const { cacheRead } = usage;
|
|
1029
|
-
if (cacheRead === void 0) return 0;
|
|
1030
|
-
return classifiedTotal(usage, cacheRead) > usage.input ? 0 : cacheRead;
|
|
1031
|
-
}
|
|
1032
|
-
function classifiedTotal(usage, cacheRead) {
|
|
1033
|
-
return (usage.freshInput ?? 0) + cacheRead + (usage.cacheWrite ?? 0);
|
|
1034
|
-
}
|
|
1035
|
-
/**
|
|
1036
|
-
* Translate one provider prompt-cache report into the prompt token classes `addTokenUsage` folds
|
|
1037
|
-
* and `chargedTokens` credits.
|
|
1038
|
-
*
|
|
1039
|
-
* The input is the open `promptCache` record an `llm_call` carries, keyed by the `PromptCacheUsage`
|
|
1040
|
-
* vocabulary (`readTokens`, `writeTokens`). Providers report that vocabulary unevenly, and the
|
|
1041
|
-
* three outcomes are different facts that must not collapse into one another:
|
|
1042
|
-
*
|
|
1043
|
-
* - **Nothing reported** — no classes and no marker. The caller's own unknown handling decides,
|
|
1044
|
-
* because a provider that says nothing about caching has not said the split is unknowable.
|
|
1045
|
-
* - **Read and write both reported, and fitting inside `input`** — a complete partition, so
|
|
1046
|
-
* `freshInput` is the remainder and the split is known.
|
|
1047
|
-
* - **One counter reported, or classes that overflow `input`** — the counters that ARE measured
|
|
1048
|
-
* pass through and `cacheBreakdownKnown` is false. A read with no write counter (OpenAI reports
|
|
1049
|
-
* no write) still credits the read it measured, while declaring the rest unclassified so the
|
|
1050
|
-
* charge stays a declared upper bound. An overflowing claim buys nothing: it cannot be a
|
|
1051
|
-
* partition of a total it exceeds.
|
|
1052
|
-
*
|
|
1053
|
-
* A counter the provider did not report stays absent. A zero would claim the provider measured no
|
|
1054
|
-
* cache, which is a different fact from a provider that did not report.
|
|
1055
|
-
*/
|
|
1056
|
-
function promptCacheTokenClasses(input, promptCache) {
|
|
1057
|
-
if (promptCache === void 0) return {};
|
|
1058
|
-
const read = tokenCount(promptCache.readTokens);
|
|
1059
|
-
const write = tokenCount(promptCache.writeTokens);
|
|
1060
|
-
if (read === void 0 && write === void 0) return {};
|
|
1061
|
-
if (input === void 0 || (read ?? 0) + (write ?? 0) > input) return { cacheBreakdownKnown: false };
|
|
1062
|
-
if (read !== void 0 && write !== void 0) return {
|
|
1063
|
-
freshInput: input - read - write,
|
|
1064
|
-
cacheRead: read,
|
|
1065
|
-
cacheWrite: write
|
|
1066
|
-
};
|
|
1067
|
-
return {
|
|
1068
|
-
...read !== void 0 ? { cacheRead: read } : {},
|
|
1069
|
-
...write !== void 0 ? { cacheWrite: write } : {},
|
|
1070
|
-
cacheBreakdownKnown: false
|
|
1071
|
-
};
|
|
1072
|
-
}
|
|
1073
|
-
function tokenCount(value) {
|
|
1074
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
1075
|
-
}
|
|
1076
|
-
/**
|
|
1077
|
-
* Add the observed subtotal into `acc`; token and cache incompleteness are sticky.
|
|
1078
|
-
*
|
|
1079
|
-
* A delta whose classes do not FIT inside its own `input` contributes its prompt total and NO
|
|
1080
|
-
* classes. Folding them in would hide the overflow: the accumulator's larger `input` can absorb a
|
|
1081
|
-
* class total that overflowed the one delta that reported it, and `chargedTokens` would then credit
|
|
1082
|
-
* at the aggregate a cache read it refuses at the record. That is the one way the charge could come
|
|
1083
|
-
* out below the sum of the charges on the records that built it.
|
|
1084
|
-
*/
|
|
1085
|
-
function addTokenUsage(acc, delta) {
|
|
1086
|
-
acc.input += delta.input ?? 0;
|
|
1087
|
-
acc.output += delta.output ?? 0;
|
|
1088
|
-
if (delta.tokensKnown === false) acc.tokensKnown = false;
|
|
1089
|
-
const input = delta.input ?? 0;
|
|
1090
|
-
const fits = classifiedTotal(delta, delta.cacheRead ?? 0) <= input;
|
|
1091
|
-
if (fits) {
|
|
1092
|
-
if (delta.freshInput !== void 0) acc.freshInput = (acc.freshInput ?? 0) + delta.freshInput;
|
|
1093
|
-
if (delta.cacheRead !== void 0) acc.cacheRead = (acc.cacheRead ?? 0) + delta.cacheRead;
|
|
1094
|
-
if (delta.cacheWrite !== void 0) acc.cacheWrite = (acc.cacheWrite ?? 0) + delta.cacheWrite;
|
|
1095
|
-
}
|
|
1096
|
-
const classified = fits && delta.freshInput !== void 0 && delta.cacheRead !== void 0 && delta.cacheWrite !== void 0 && delta.freshInput + delta.cacheRead + delta.cacheWrite === input;
|
|
1097
|
-
if (delta.cacheBreakdownKnown === false || input > 0 && !classified) acc.cacheBreakdownKnown = false;
|
|
1098
|
-
}
|
|
1099
|
-
/**
|
|
1100
|
-
* Map `items` through `fn` with at most `limit` calls in flight at once,
|
|
1101
|
-
* preserving input order in the result. On the first `fn` rejection no NEW
|
|
1102
|
-
* items are picked up; already-in-flight calls are awaited, then the first
|
|
1103
|
-
* error is rethrown. `limit` is clamped to ≥ 1.
|
|
1104
|
-
*
|
|
1105
|
-
* Used where a burst of provisioning (e.g. forking N child boxes) must respect
|
|
1106
|
-
* the loop's concurrency bound instead of firing all N at once.
|
|
1107
|
-
*/
|
|
1108
|
-
async function mapWithConcurrency(items, limit, fn) {
|
|
1109
|
-
const results = new Array(items.length);
|
|
1110
|
-
let next = 0;
|
|
1111
|
-
for await (const settled of runTaskQueue({
|
|
1112
|
-
concurrency: Math.max(1, Math.min(Math.floor(limit), items.length)),
|
|
1113
|
-
take: () => {
|
|
1114
|
-
const index = next++;
|
|
1115
|
-
return index < items.length ? {
|
|
1116
|
-
id: String(index),
|
|
1117
|
-
value: index
|
|
1118
|
-
} : void 0;
|
|
1119
|
-
},
|
|
1120
|
-
execute: ({ value: index }) => fn(items[index], index)
|
|
1121
|
-
})) {
|
|
1122
|
-
if (settled.status === "rejected") throw settled.reason;
|
|
1123
|
-
results[settled.task.value] = settled.value;
|
|
1124
|
-
}
|
|
1125
|
-
return results;
|
|
1126
|
-
}
|
|
1127
|
-
//#endregion
|
|
1128
540
|
//#region src/runtime/sandbox-events.ts
|
|
1129
541
|
const CANONICAL_STREAM_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
1130
542
|
"child-task",
|
|
@@ -1809,6 +1221,7 @@ function mapSandboxToolEvent(event, state) {
|
|
|
1809
1221
|
if (String(part.type ?? "") !== "tool") return [];
|
|
1810
1222
|
return projectToolPart(part, state, typeof event.id === "string" ? event.id : void 0);
|
|
1811
1223
|
}
|
|
1224
|
+
if (CANONICAL_STREAM_EVENT_TYPES.has(type)) return [];
|
|
1812
1225
|
if (type.includes("tool")) {
|
|
1813
1226
|
const callId = pickString(data, [
|
|
1814
1227
|
"toolCallId",
|
|
@@ -2494,140 +1907,6 @@ function providerAttemptEvidence(model) {
|
|
|
2494
1907
|
});
|
|
2495
1908
|
}
|
|
2496
1909
|
//#endregion
|
|
2497
|
-
//#region src/durable/jsonl-file.ts
|
|
2498
|
-
/** Read a fixed file prefix one record at a time, never allocating the whole journal as a string.
|
|
2499
|
-
* A reader cannot chase concurrent appends forever. Missing files are empty only when requested;
|
|
2500
|
-
* corruption, short reads, and other I/O errors remain failures. Closing the iterator closes the fd. */
|
|
2501
|
-
async function* readCommittedJsonLines(path, options = {}) {
|
|
2502
|
-
const fs = await import("node:fs/promises");
|
|
2503
|
-
let handle;
|
|
2504
|
-
try {
|
|
2505
|
-
handle = await fs.open(path, "r");
|
|
2506
|
-
} catch (error) {
|
|
2507
|
-
if (options.allowMissing && isNoEntError(error)) return;
|
|
2508
|
-
throw error;
|
|
2509
|
-
}
|
|
2510
|
-
try {
|
|
2511
|
-
const { size } = await handle.stat();
|
|
2512
|
-
if (size === 0) return;
|
|
2513
|
-
const { StringDecoder } = await import("node:string_decoder");
|
|
2514
|
-
const decoder = new StringDecoder("utf8");
|
|
2515
|
-
const stream = handle.createReadStream({
|
|
2516
|
-
autoClose: false,
|
|
2517
|
-
end: size - 1
|
|
2518
|
-
});
|
|
2519
|
-
const fragments = [];
|
|
2520
|
-
let lineNumber = 1;
|
|
2521
|
-
try {
|
|
2522
|
-
for await (const chunk of stream) {
|
|
2523
|
-
options.onBytes?.(chunk);
|
|
2524
|
-
const text = decoder.write(chunk);
|
|
2525
|
-
let start = 0;
|
|
2526
|
-
let end = text.indexOf("\n", start);
|
|
2527
|
-
while (end !== -1) {
|
|
2528
|
-
fragments.push(text.slice(start, end));
|
|
2529
|
-
const record = parseRecord(fragments.join(""), path, lineNumber++, true);
|
|
2530
|
-
fragments.length = 0;
|
|
2531
|
-
if (record !== void 0) yield record.value;
|
|
2532
|
-
start = end + 1;
|
|
2533
|
-
end = text.indexOf("\n", start);
|
|
2534
|
-
}
|
|
2535
|
-
if (start < text.length) fragments.push(text.slice(start));
|
|
2536
|
-
}
|
|
2537
|
-
if (stream.bytesRead !== size) throw new Error(`${path}: journal changed while reading its committed prefix`);
|
|
2538
|
-
fragments.push(decoder.end());
|
|
2539
|
-
const record = parseRecord(fragments.join(""), path, lineNumber, false);
|
|
2540
|
-
if (record !== void 0) yield record.value;
|
|
2541
|
-
} finally {
|
|
2542
|
-
stream.destroy();
|
|
2543
|
-
}
|
|
2544
|
-
} finally {
|
|
2545
|
-
await handle.close();
|
|
2546
|
-
}
|
|
2547
|
-
}
|
|
2548
|
-
/** An invalid unterminated tail may be a torn write; other parse failures are not evidence of one. */
|
|
2549
|
-
function parseRecord(line, source, lineNumber, terminated) {
|
|
2550
|
-
if (line.length === 0) return void 0;
|
|
2551
|
-
try {
|
|
2552
|
-
return { value: JSON.parse(line) };
|
|
2553
|
-
} catch (cause) {
|
|
2554
|
-
if (!(cause instanceof SyntaxError)) throw cause;
|
|
2555
|
-
if (!terminated) return void 0;
|
|
2556
|
-
throw new Error(`${source}: malformed JSONL record at line ${lineNumber}`, { cause });
|
|
2557
|
-
}
|
|
2558
|
-
}
|
|
2559
|
-
/** FileHandle.write may legally make a short write. Loop until every byte is appended. */
|
|
2560
|
-
async function writeAllBytes(handle, value) {
|
|
2561
|
-
const bytes = typeof value === "string" ? Buffer.from(value) : Buffer.from(value);
|
|
2562
|
-
let offset = 0;
|
|
2563
|
-
while (offset < bytes.byteLength) {
|
|
2564
|
-
const { bytesWritten } = await handle.write(bytes, offset, bytes.byteLength - offset, null);
|
|
2565
|
-
if (bytesWritten <= 0) throw new Error(`append-only file write made no progress at byte ${offset}`);
|
|
2566
|
-
offset += bytesWritten;
|
|
2567
|
-
}
|
|
2568
|
-
}
|
|
2569
|
-
/** Prepare the tail under the journal owner's existing single-writer discipline.
|
|
2570
|
-
* Healthy appends read one byte, regardless of history size. Only recovery scans backward,
|
|
2571
|
-
* retaining a valid unterminated value or truncating an uncommitted torn tail. */
|
|
2572
|
-
async function prepareJsonlAppend(path) {
|
|
2573
|
-
const fs = await import("node:fs/promises");
|
|
2574
|
-
let handle;
|
|
2575
|
-
try {
|
|
2576
|
-
handle = await fs.open(path, "r+");
|
|
2577
|
-
} catch (error) {
|
|
2578
|
-
if (isNoEntError(error)) return false;
|
|
2579
|
-
throw error;
|
|
2580
|
-
}
|
|
2581
|
-
try {
|
|
2582
|
-
const size = (await handle.stat()).size;
|
|
2583
|
-
if (size === 0) return false;
|
|
2584
|
-
const lastByte = Buffer.allocUnsafe(1);
|
|
2585
|
-
await readAt(handle, lastByte, size - 1);
|
|
2586
|
-
if (lastByte[0] === 10) return false;
|
|
2587
|
-
const chunks = [];
|
|
2588
|
-
let end = size;
|
|
2589
|
-
let tailStart = 0;
|
|
2590
|
-
while (end > 0) {
|
|
2591
|
-
const start = Math.max(0, end - 65536);
|
|
2592
|
-
const chunk = Buffer.allocUnsafe(end - start);
|
|
2593
|
-
await readAt(handle, chunk, start);
|
|
2594
|
-
const newline = chunk.lastIndexOf(10);
|
|
2595
|
-
chunks.push(chunk.subarray(newline + 1));
|
|
2596
|
-
if (newline >= 0) {
|
|
2597
|
-
tailStart = start + newline + 1;
|
|
2598
|
-
break;
|
|
2599
|
-
}
|
|
2600
|
-
end = start;
|
|
2601
|
-
}
|
|
2602
|
-
const tail = Buffer.concat(chunks.reverse()).toString("utf8");
|
|
2603
|
-
try {
|
|
2604
|
-
JSON.parse(tail);
|
|
2605
|
-
return true;
|
|
2606
|
-
} catch (error) {
|
|
2607
|
-
if (!(error instanceof SyntaxError)) throw error;
|
|
2608
|
-
await handle.truncate(tailStart);
|
|
2609
|
-
await handle.sync();
|
|
2610
|
-
return false;
|
|
2611
|
-
}
|
|
2612
|
-
} finally {
|
|
2613
|
-
await handle.close();
|
|
2614
|
-
}
|
|
2615
|
-
}
|
|
2616
|
-
/** A short read is legal; an unexpected EOF means the owner's snapshot changed. */
|
|
2617
|
-
async function readAt(handle, buffer, position) {
|
|
2618
|
-
let offset = 0;
|
|
2619
|
-
while (offset < buffer.length) {
|
|
2620
|
-
const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, position + offset);
|
|
2621
|
-
if (bytesRead === 0) throw new Error("journal changed while preparing its append tail");
|
|
2622
|
-
offset += bytesRead;
|
|
2623
|
-
}
|
|
2624
|
-
}
|
|
2625
|
-
/** True for a filesystem error meaning "the file is not there yet" — the one condition an
|
|
2626
|
-
* append-only reader treats as an empty log rather than a fault. */
|
|
2627
|
-
function isNoEntError(error) {
|
|
2628
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2629
|
-
}
|
|
2630
|
-
//#endregion
|
|
2631
1910
|
//#region src/agent/profile-materialization.ts
|
|
2632
1911
|
const KNOWN_AXIS_SET = new Set(AGENT_PROFILE_MATERIALIZATION_AXES);
|
|
2633
1912
|
/**
|
|
@@ -7313,6 +6592,49 @@ function normalizeWorkerSlotBound(value) {
|
|
|
7313
6592
|
if (!Number.isSafeInteger(value)) throw new ValidationError$1(`workerSlots must be a positive safe integer, or 0 or less for no bound, got ${String(value)}`);
|
|
7314
6593
|
return value;
|
|
7315
6594
|
}
|
|
6595
|
+
const checkpointSlotKeys = /* @__PURE__ */ new WeakMap();
|
|
6596
|
+
function checkpointSlots(port) {
|
|
6597
|
+
let key = checkpointSlotKeys.get(port);
|
|
6598
|
+
if (key === void 0) {
|
|
6599
|
+
key = Object.freeze({ maxConcurrentCaptures: port.maxConcurrentCaptures });
|
|
6600
|
+
checkpointSlotKeys.set(port, key);
|
|
6601
|
+
}
|
|
6602
|
+
return key;
|
|
6603
|
+
}
|
|
6604
|
+
/**
|
|
6605
|
+
* Capture a checkpoint through the port on its own queue and bound, then verify that the store
|
|
6606
|
+
* holds every file of the returned tree and, when the checkpoint took a marker, that the tree
|
|
6607
|
+
* holds the marker's exact bytes, which proves it read this checkpoint and no other state.
|
|
6608
|
+
*/
|
|
6609
|
+
async function captureProviderCheckpointWorkspace(port, context, marker) {
|
|
6610
|
+
assertProviderWorkspaceRetentionPort(port, "provider workspace retention");
|
|
6611
|
+
const capture = port.captureCheckpoint;
|
|
6612
|
+
if (capture === void 0) throw new ValidationError$1("provider workspace retention: the port has no captureCheckpoint");
|
|
6613
|
+
const controller = new AbortController();
|
|
6614
|
+
const clearDeadline = armDeadlineTimer(port.timeoutMs, () => controller.abort(/* @__PURE__ */ new Error(`checkpoint capture timed out after ${port.timeoutMs}ms`)), true);
|
|
6615
|
+
try {
|
|
6616
|
+
return await runAbortable(() => withCaptureSlot(checkpointSlots(port), controller.signal, async () => {
|
|
6617
|
+
const tree = detachedSnapshot(await capture.call(port, {
|
|
6618
|
+
...context,
|
|
6619
|
+
signal: controller.signal
|
|
6620
|
+
}), "checkpoint capture tree");
|
|
6621
|
+
if (tree?.kind !== "agent-candidate-workspace-tree") throw new Error("checkpoint capture returned no workspace tree");
|
|
6622
|
+
const material = await verifyAgentCandidateWorkspaceTree(tree, port.artifacts, {
|
|
6623
|
+
...port.limits === void 0 ? {} : { limits: port.limits },
|
|
6624
|
+
signal: controller.signal
|
|
6625
|
+
});
|
|
6626
|
+
if (material.files.length !== tree.files) throw new Error("checkpoint capture tree misstates its file count");
|
|
6627
|
+
if (marker !== void 0) {
|
|
6628
|
+
const bytes = Buffer.from(marker.content, "utf8");
|
|
6629
|
+
const held = material.files.find((file) => file.path === marker.path);
|
|
6630
|
+
if (held === void 0 || held.byteLength !== bytes.byteLength || held.sha256 !== `sha256:${createHash("sha256").update(bytes).digest("hex")}`) throw new Error(`checkpoint capture does not hold the checkpoint's marker ${marker.path}`);
|
|
6631
|
+
}
|
|
6632
|
+
return tree;
|
|
6633
|
+
}), controller.signal, `checkpoint capture timed out after ${port.timeoutMs}ms`);
|
|
6634
|
+
} finally {
|
|
6635
|
+
clearDeadline();
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
7316
6638
|
/** Validate the executable retention port at the create boundary. */
|
|
7317
6639
|
function assertProviderWorkspaceRetentionPort(port, context) {
|
|
7318
6640
|
if (!Number.isSafeInteger(port.timeoutMs) || port.timeoutMs <= 0) throw new ValidationError$1(`${context}: workspaceRetention.timeoutMs must be a positive safe integer`);
|
|
@@ -7320,6 +6642,7 @@ function assertProviderWorkspaceRetentionPort(port, context) {
|
|
|
7320
6642
|
if (port.artifacts === null || typeof port.artifacts?.read !== "function") throw new ValidationError$1(`${context}: workspaceRetention.artifacts.read is required`);
|
|
7321
6643
|
if (typeof port.capture !== "function") throw new ValidationError$1(`${context}: workspaceRetention.capture is required`);
|
|
7322
6644
|
if (port.captureNative !== void 0 && typeof port.captureNative !== "function") throw new ValidationError$1(`${context}: workspaceRetention.captureNative must be a function`);
|
|
6645
|
+
if (port.captureCheckpoint !== void 0 && typeof port.captureCheckpoint !== "function") throw new ValidationError$1(`${context}: workspaceRetention.captureCheckpoint must be a function`);
|
|
7323
6646
|
for (const key of ["nativeIntervalMs", "nativeTimeoutMs"]) {
|
|
7324
6647
|
const value = port[key];
|
|
7325
6648
|
if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) throw new ValidationError$1(`${context}: workspaceRetention.${key} must be a positive safe integer`);
|
|
@@ -7335,6 +6658,7 @@ function nativeSlots(port) {
|
|
|
7335
6658
|
}
|
|
7336
6659
|
return key;
|
|
7337
6660
|
}
|
|
6661
|
+
const capturesInFlight = /* @__PURE__ */ new WeakMap();
|
|
7338
6662
|
async function withCaptureSlot(port, signal, capture) {
|
|
7339
6663
|
if (port.maxConcurrentCaptures === void 0) return capture();
|
|
7340
6664
|
let group = captureGroups.get(port);
|
|
@@ -7351,13 +6675,56 @@ async function withCaptureSlot(port, signal, capture) {
|
|
|
7351
6675
|
permit.release();
|
|
7352
6676
|
}
|
|
7353
6677
|
}
|
|
7354
|
-
/**
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
6678
|
+
/** Count one capture of `port` in flight until `work` settles, and say how many were ahead. */
|
|
6679
|
+
function trackCapture(port, work) {
|
|
6680
|
+
const ahead = capturesInFlight.get(port) ?? 0;
|
|
6681
|
+
capturesInFlight.set(port, ahead + 1);
|
|
6682
|
+
return work(ahead).finally(() => {
|
|
6683
|
+
capturesInFlight.set(port, Math.max(0, (capturesInFlight.get(port) ?? 1) - 1));
|
|
6684
|
+
});
|
|
6685
|
+
}
|
|
6686
|
+
/**
|
|
6687
|
+
* Capture, require durable refs, and verify every returned manifest/archive byte.
|
|
6688
|
+
* `native` calls {@link ProviderWorkspaceRetentionPort.captureNative} under its own queue and
|
|
6689
|
+
* bound, and judges coverage by the sessions alone. `onTiming` receives the queue wait and the
|
|
6690
|
+
* capture time of a workspace capture once it settles, whether it succeeded or failed.
|
|
6691
|
+
*/
|
|
6692
|
+
async function captureProviderWorkspaceSnapshot(port, context, scope = "environment", onTiming) {
|
|
6693
|
+
assertProviderWorkspaceRetentionPort(port, "provider workspace retention");
|
|
6694
|
+
if (scope === "native" || onTiming === void 0) return await captureWithinSlot(port, context, scope);
|
|
6695
|
+
return await trackCapture(port, async (ahead) => {
|
|
6696
|
+
const requestedAt = Date.now();
|
|
6697
|
+
let grantedAt;
|
|
6698
|
+
const report = (timing) => {
|
|
6699
|
+
const settledAt = Date.now();
|
|
6700
|
+
try {
|
|
6701
|
+
onTiming({
|
|
6702
|
+
ahead,
|
|
6703
|
+
queuedMs: (grantedAt ?? settledAt) - requestedAt,
|
|
6704
|
+
...grantedAt === void 0 ? {} : { captureMs: settledAt - grantedAt },
|
|
6705
|
+
...timing
|
|
6706
|
+
});
|
|
6707
|
+
} catch {}
|
|
6708
|
+
};
|
|
6709
|
+
try {
|
|
6710
|
+
const captured = await captureWithinSlot(port, context, scope, () => {
|
|
6711
|
+
grantedAt = Date.now();
|
|
6712
|
+
});
|
|
6713
|
+
report({
|
|
6714
|
+
outcome: "captured",
|
|
6715
|
+
archiveBytes: captured.snapshot.archive.byteLength
|
|
6716
|
+
});
|
|
6717
|
+
return captured;
|
|
6718
|
+
} catch (error) {
|
|
6719
|
+
report({
|
|
6720
|
+
outcome: "failed",
|
|
6721
|
+
error: (error instanceof Error ? error.message : String(error)).slice(0, 1e3)
|
|
6722
|
+
});
|
|
6723
|
+
throw error;
|
|
6724
|
+
}
|
|
6725
|
+
});
|
|
6726
|
+
}
|
|
6727
|
+
async function captureWithinSlot(port, context, scope, onGranted) {
|
|
7361
6728
|
const native = scope === "native";
|
|
7362
6729
|
if (native && port.captureNative === void 0) throw new ValidationError$1("provider workspace retention: the port has no captureNative");
|
|
7363
6730
|
const timeoutMs = native ? port.nativeTimeoutMs ?? 12e4 : port.timeoutMs;
|
|
@@ -7367,6 +6734,7 @@ async function captureProviderWorkspaceSnapshot(port, context, scope = "environm
|
|
|
7367
6734
|
const clearDeadline = armDeadlineTimer(timeoutMs, () => controller.abort(/* @__PURE__ */ new Error(`provider workspace retention timed out after ${timeoutMs}ms`)), true);
|
|
7368
6735
|
try {
|
|
7369
6736
|
return await runAbortable(() => withCaptureSlot(native ? nativeSlots(port) : port, controller.signal, async () => {
|
|
6737
|
+
onGranted?.();
|
|
7370
6738
|
const input = {
|
|
7371
6739
|
...context,
|
|
7372
6740
|
...controlRef === void 0 ? {} : { controlRef: Object.freeze({ ...controlRef }) },
|
|
@@ -7539,7 +6907,7 @@ async function environmentGone(provider, environmentId, signal) {
|
|
|
7539
6907
|
}
|
|
7540
6908
|
/** Create one environment while retaining ownership if cancellation wins the race. */
|
|
7541
6909
|
async function createInteractiveEnvironment(create, signal) {
|
|
7542
|
-
if (signal?.aborted) throw abortError$
|
|
6910
|
+
if (signal?.aborted) throw abortError$1(signal.reason);
|
|
7543
6911
|
const creation = Promise.resolve().then(create);
|
|
7544
6912
|
if (signal === void 0) return creation;
|
|
7545
6913
|
return new Promise((resolve, reject) => {
|
|
@@ -7550,7 +6918,7 @@ async function createInteractiveEnvironment(create, signal) {
|
|
|
7550
6918
|
owner = "runtime";
|
|
7551
6919
|
removeAbortListener();
|
|
7552
6920
|
disposeInteractiveEnvironment(creation, signal.reason);
|
|
7553
|
-
reject(abortError$
|
|
6921
|
+
reject(abortError$1(signal.reason));
|
|
7554
6922
|
};
|
|
7555
6923
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
7556
6924
|
creation.then((environment) => {
|
|
@@ -7570,7 +6938,7 @@ async function createInteractiveEnvironment(create, signal) {
|
|
|
7570
6938
|
async function startInteractiveProcess(environment, start, signal) {
|
|
7571
6939
|
if (signal?.aborted) {
|
|
7572
6940
|
disposeInteractiveEnvironment(Promise.resolve(environment), signal.reason);
|
|
7573
|
-
throw abortError$
|
|
6941
|
+
throw abortError$1(signal.reason);
|
|
7574
6942
|
}
|
|
7575
6943
|
const pending = Promise.resolve().then(start);
|
|
7576
6944
|
if (signal === void 0) return pending;
|
|
@@ -7582,7 +6950,7 @@ async function startInteractiveProcess(environment, start, signal) {
|
|
|
7582
6950
|
owner = "runtime";
|
|
7583
6951
|
removeAbortListener();
|
|
7584
6952
|
disposeInteractiveEnvironment(Promise.resolve(environment), signal.reason);
|
|
7585
|
-
reject(abortError$
|
|
6953
|
+
reject(abortError$1(signal.reason));
|
|
7586
6954
|
};
|
|
7587
6955
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
7588
6956
|
pending.then((result) => {
|
|
@@ -7717,7 +7085,7 @@ async function* retainedRunEvents(session, controlRef, options, now) {
|
|
|
7717
7085
|
try {
|
|
7718
7086
|
Promise.resolve(iterator.return?.()).catch(() => void 0);
|
|
7719
7087
|
} catch {}
|
|
7720
|
-
throw abortError$
|
|
7088
|
+
throw abortError$1(options.signal.reason);
|
|
7721
7089
|
}
|
|
7722
7090
|
if (error instanceof RetainedRunProviderContractError) throw error;
|
|
7723
7091
|
const fromProvider = error instanceof Error && !runtimeChecks.has(error);
|
|
@@ -7768,13 +7136,13 @@ function createRetainedRunHandle(environment, session, initialControlRef, capabi
|
|
|
7768
7136
|
}
|
|
7769
7137
|
};
|
|
7770
7138
|
const snapshot = async (reason, signal) => {
|
|
7771
|
-
if (signal?.aborted) throw abortError$
|
|
7139
|
+
if (signal?.aborted) throw abortError$1(signal.reason);
|
|
7772
7140
|
synchronizeActiveControlRef();
|
|
7773
7141
|
let status;
|
|
7774
7142
|
try {
|
|
7775
7143
|
status = await awaitAbortable$1(Promise.resolve().then(() => session.status({ signal })), signal);
|
|
7776
7144
|
} catch {
|
|
7777
|
-
if (signal?.aborted) throw abortError$
|
|
7145
|
+
if (signal?.aborted) throw abortError$1(signal.reason);
|
|
7778
7146
|
status = "unknown";
|
|
7779
7147
|
}
|
|
7780
7148
|
synchronizeActiveControlRef();
|
|
@@ -8001,7 +7369,7 @@ function createRetainedRunHandle(environment, session, initialControlRef, capabi
|
|
|
8001
7369
|
synchronizeActiveControlRef();
|
|
8002
7370
|
assertStableText(options.operationId, "retained cancellation operation id");
|
|
8003
7371
|
if (options.reason !== void 0) assertStableText(options.reason, "retained cancellation reason");
|
|
8004
|
-
if (options.signal?.aborted) throw abortError$
|
|
7372
|
+
if (options.signal?.aborted) throw abortError$1(options.signal.reason);
|
|
8005
7373
|
if (!hasDurableCancel(session)) throw new Error(`provider "${activeControlRef.provider}" does not expose durable cancellation operations`);
|
|
8006
7374
|
const request = cancellationRequest(activeControlRef, options.operationId, options.reason);
|
|
8007
7375
|
let effect = "unknown";
|
|
@@ -8366,6 +7734,7 @@ async function recoverRetainedRun(options) {
|
|
|
8366
7734
|
if (options.admission.provider !== options.provider.name) throw new Error("retained run intent belongs to another provider");
|
|
8367
7735
|
return {
|
|
8368
7736
|
outcome: "recovered",
|
|
7737
|
+
via: "resumed",
|
|
8369
7738
|
handle: await startRetainedRun({
|
|
8370
7739
|
provider: options.provider,
|
|
8371
7740
|
...options.replay,
|
|
@@ -8406,6 +7775,7 @@ async function recoverRetainedRun(options) {
|
|
|
8406
7775
|
const exact = exactSession(environment, controlRef);
|
|
8407
7776
|
return {
|
|
8408
7777
|
outcome: "recovered",
|
|
7778
|
+
via: "reattached",
|
|
8409
7779
|
handle: createRetainedRunHandle(environment, exact.session, exact.controlRef, capabilities, options.now)
|
|
8410
7780
|
};
|
|
8411
7781
|
}
|
|
@@ -8652,6 +8022,119 @@ function describeRetainedNativeStops(stops) {
|
|
|
8652
8022
|
}).join("; ");
|
|
8653
8023
|
}
|
|
8654
8024
|
//#endregion
|
|
8025
|
+
//#region src/runtime/supervise/executor-seams.ts
|
|
8026
|
+
function assertExactConfigKeys(value, allowed, context) {
|
|
8027
|
+
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
8028
|
+
if (unknown.length > 0) throw new ValidationError$1(`${context}: unknown fields ${unknown.sort().join(", ")}; execution behavior belongs in AgentProfile`);
|
|
8029
|
+
}
|
|
8030
|
+
/** Narrow a named seam off the `ExecutorContext`, failing loud when absent — no
|
|
8031
|
+
* silent default for a required external-boundary seam. */
|
|
8032
|
+
function readSeam(ctx, key, who) {
|
|
8033
|
+
const seam = ctx.seams[key];
|
|
8034
|
+
if (seam === void 0 || seam === null) throw new ValidationError$1(`${who} executor: missing required seam "${key}" on ExecutorContext`);
|
|
8035
|
+
return seam;
|
|
8036
|
+
}
|
|
8037
|
+
function readOptionalAbortSignal(ctx, key, who) {
|
|
8038
|
+
const value = ctx.seams[key];
|
|
8039
|
+
if (value === void 0) return void 0;
|
|
8040
|
+
if (value === null || typeof value !== "object" || typeof value.aborted !== "boolean" || typeof value.addEventListener !== "function") throw new ValidationError$1(`${who} executor: seam "${key}" must be an AbortSignal`);
|
|
8041
|
+
return value;
|
|
8042
|
+
}
|
|
8043
|
+
/** Narrow the Runtime-owned MCP attachment seam. Absent means the run mounts nothing; a present
|
|
8044
|
+
* value must be a named map of MCP servers, never an empty one that would silently mount nothing. */
|
|
8045
|
+
function readOptionalMcpAttachments(ctx, key, who) {
|
|
8046
|
+
const value = ctx.seams[key];
|
|
8047
|
+
if (value === void 0) return void 0;
|
|
8048
|
+
const entries = value !== null && typeof value === "object" && !Array.isArray(value) ? Object.entries(value) : void 0;
|
|
8049
|
+
if (entries === void 0 || entries.length === 0 || entries.some(([, server]) => server === null || typeof server !== "object")) throw new ValidationError$1(`${who} executor: seam ${JSON.stringify(key)} must be a non-empty map of MCP servers`);
|
|
8050
|
+
return value;
|
|
8051
|
+
}
|
|
8052
|
+
//#endregion
|
|
8053
|
+
//#region src/runtime/supervise/bridge-config.ts
|
|
8054
|
+
const bridgeSeamKey = "bridge";
|
|
8055
|
+
/** Internal control seam used by Runtime-owned external supervisors. A completion request stops
|
|
8056
|
+
* the next bridge turn; it must not abort the paid request that is already streaming. */
|
|
8057
|
+
const bridgeStopSignalKey = "__bridge_stop_signal";
|
|
8058
|
+
/** Internal attachment seam used by Runtime-owned external supervisors. The named MCP servers ride
|
|
8059
|
+
* beside the AgentProfile as `runtime_attachments`, so the bridge mounts them for the run and keeps
|
|
8060
|
+
* them out of its session profile binding. A rebound coordination port therefore cannot move the
|
|
8061
|
+
* authored profile digest that binds a durable session. */
|
|
8062
|
+
const bridgeRuntimeAttachmentsKey = "__bridge_runtime_attachments";
|
|
8063
|
+
const maxBridgeTimeoutMs = 2147483647;
|
|
8064
|
+
function isLoopbackBridgeHost(hostname) {
|
|
8065
|
+
const host = hostname.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
|
|
8066
|
+
if (host === "localhost" || host === "::1" || host === "0:0:0:0:0:0:0:1") return true;
|
|
8067
|
+
if (host === "::ffff:127.0.0.1") return true;
|
|
8068
|
+
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/u.test(host);
|
|
8069
|
+
}
|
|
8070
|
+
function isLoopbackBridgeUrl(value) {
|
|
8071
|
+
try {
|
|
8072
|
+
const target = new URL(value);
|
|
8073
|
+
return (target.protocol === "http:" || target.protocol === "https:") && isLoopbackBridgeHost(target.hostname);
|
|
8074
|
+
} catch {
|
|
8075
|
+
return false;
|
|
8076
|
+
}
|
|
8077
|
+
}
|
|
8078
|
+
/** Validate and capture the non-serialisable credential service without reading its value. */
|
|
8079
|
+
function captureBridgeModelCredential(value, context) {
|
|
8080
|
+
if (value === null || typeof value !== "object") throw new ValidationError$1(`${context}: modelCredential must be an object`);
|
|
8081
|
+
assertExactConfigKeys(value, /* @__PURE__ */ new Set([
|
|
8082
|
+
"baseUrlKey",
|
|
8083
|
+
"key",
|
|
8084
|
+
"provider"
|
|
8085
|
+
]), `${context} modelCredential`);
|
|
8086
|
+
if (typeof value.key !== "string" || value.key.trim().length === 0) throw new ValidationError$1(`${context}: modelCredential.key must be a non-empty string`);
|
|
8087
|
+
if (typeof value.baseUrlKey !== "string" || value.baseUrlKey.trim().length === 0) throw new ValidationError$1(`${context}: modelCredential.baseUrlKey must be a non-empty string`);
|
|
8088
|
+
if (value.baseUrlKey === value.key) throw new ValidationError$1(`${context}: modelCredential.baseUrlKey must differ from key`);
|
|
8089
|
+
if (!value.provider || typeof value.provider.get !== "function") throw new ValidationError$1(`${context}: modelCredential.provider must implement get(name)`);
|
|
8090
|
+
const captured = {
|
|
8091
|
+
key: value.key,
|
|
8092
|
+
baseUrlKey: value.baseUrlKey
|
|
8093
|
+
};
|
|
8094
|
+
Object.defineProperty(captured, "provider", {
|
|
8095
|
+
configurable: false,
|
|
8096
|
+
enumerable: false,
|
|
8097
|
+
value: value.provider,
|
|
8098
|
+
writable: false
|
|
8099
|
+
});
|
|
8100
|
+
return Object.freeze(captured);
|
|
8101
|
+
}
|
|
8102
|
+
function validateBridgeModelCredential(value, bridgeUrl, context) {
|
|
8103
|
+
if (value === void 0) return void 0;
|
|
8104
|
+
if (!isLoopbackBridgeUrl(bridgeUrl)) throw new ValidationError$1(`${context}: modelCredential is allowed only for a loopback bridge URL`);
|
|
8105
|
+
return captureBridgeModelCredential(value, context);
|
|
8106
|
+
}
|
|
8107
|
+
function resolveProtectedModelBaseUrl(value, context) {
|
|
8108
|
+
let target;
|
|
8109
|
+
try {
|
|
8110
|
+
target = new URL(value);
|
|
8111
|
+
} catch {
|
|
8112
|
+
throw new ValidationError$1(`${context}: modelCredential.baseUrl must be an absolute HTTPS URL`);
|
|
8113
|
+
}
|
|
8114
|
+
if (target.protocol !== "https:" || target.username || target.password || target.search || target.hash) throw new ValidationError$1(`${context}: modelCredential.baseUrl must be an HTTPS URL without credentials, query, or fragment`);
|
|
8115
|
+
return target.toString().replace(/\/$/u, "");
|
|
8116
|
+
}
|
|
8117
|
+
/** Resolve only at the outbound boundary. Provider errors are redacted because they may contain
|
|
8118
|
+
* the protected value; diagnostics identify key names, never values or provider errors. */
|
|
8119
|
+
async function resolveBridgeModelCredential(credential, context) {
|
|
8120
|
+
if (credential === void 0) return void 0;
|
|
8121
|
+
const resolveValue = async (key) => {
|
|
8122
|
+
let value;
|
|
8123
|
+
try {
|
|
8124
|
+
value = await credential.provider.get(key);
|
|
8125
|
+
} catch {
|
|
8126
|
+
throw new ValidationError$1(`${context}: modelCredential provider failed for '${key}'`);
|
|
8127
|
+
}
|
|
8128
|
+
if (typeof value !== "string" || value.length === 0 || /[\r\n\0]/u.test(value)) throw new ValidationError$1(`${context}: modelCredential provider has no usable value for '${key}'`);
|
|
8129
|
+
return value;
|
|
8130
|
+
};
|
|
8131
|
+
const [token, baseUrl] = await Promise.all([resolveValue(credential.key), resolveValue(credential.baseUrlKey)]);
|
|
8132
|
+
return {
|
|
8133
|
+
token,
|
|
8134
|
+
baseUrl: resolveProtectedModelBaseUrl(baseUrl, context)
|
|
8135
|
+
};
|
|
8136
|
+
}
|
|
8137
|
+
//#endregion
|
|
8655
8138
|
//#region src/runtime/supervise/cost-estimate.ts
|
|
8656
8139
|
/**
|
|
8657
8140
|
* Pricing for work that arrived without a provider receipt.
|
|
@@ -9405,12 +8888,7 @@ function contentRef$1(prefix, value) {
|
|
|
9405
8888
|
} catch {
|
|
9406
8889
|
str = String(value);
|
|
9407
8890
|
}
|
|
9408
|
-
|
|
9409
|
-
for (let i = 0; i < str.length; i += 1) {
|
|
9410
|
-
h ^= str.charCodeAt(i);
|
|
9411
|
-
h = Math.imul(h, 16777619);
|
|
9412
|
-
}
|
|
9413
|
-
return `${prefix}:${(h >>> 0).toString(16).padStart(8, "0")}`;
|
|
8891
|
+
return `${prefix}:${fnv1a32Hex(str)}`;
|
|
9414
8892
|
}
|
|
9415
8893
|
//#endregion
|
|
9416
8894
|
//#region src/runtime/supervise/trace-evidence.ts
|
|
@@ -9530,6 +9008,140 @@ function runtimeOwnedNestedDriverTreeRoot(owner, parentTreeRoot, driverNodeId) {
|
|
|
9530
9008
|
return nestedDriverTreeOwners.has(owner) ? nestedDriverTreeRoot(parentTreeRoot, driverNodeId) : void 0;
|
|
9531
9009
|
}
|
|
9532
9010
|
//#endregion
|
|
9011
|
+
//#region src/durable/jsonl-file.ts
|
|
9012
|
+
/** Read a fixed file prefix one record at a time, never allocating the whole journal as a string.
|
|
9013
|
+
* A reader cannot chase concurrent appends forever. Missing files are empty only when requested;
|
|
9014
|
+
* corruption, short reads, and other I/O errors remain failures. Closing the iterator closes the fd. */
|
|
9015
|
+
async function* readCommittedJsonLines(path, options = {}) {
|
|
9016
|
+
const fs = await import("node:fs/promises");
|
|
9017
|
+
let handle;
|
|
9018
|
+
try {
|
|
9019
|
+
handle = await fs.open(path, "r");
|
|
9020
|
+
} catch (error) {
|
|
9021
|
+
if (options.allowMissing && isNoEntError(error)) return;
|
|
9022
|
+
throw error;
|
|
9023
|
+
}
|
|
9024
|
+
try {
|
|
9025
|
+
const { size } = await handle.stat();
|
|
9026
|
+
if (size === 0) return;
|
|
9027
|
+
const { StringDecoder } = await import("node:string_decoder");
|
|
9028
|
+
const decoder = new StringDecoder("utf8");
|
|
9029
|
+
const stream = handle.createReadStream({
|
|
9030
|
+
autoClose: false,
|
|
9031
|
+
end: size - 1
|
|
9032
|
+
});
|
|
9033
|
+
const fragments = [];
|
|
9034
|
+
let lineNumber = 1;
|
|
9035
|
+
try {
|
|
9036
|
+
for await (const chunk of stream) {
|
|
9037
|
+
options.onBytes?.(chunk);
|
|
9038
|
+
const text = decoder.write(chunk);
|
|
9039
|
+
let start = 0;
|
|
9040
|
+
let end = text.indexOf("\n", start);
|
|
9041
|
+
while (end !== -1) {
|
|
9042
|
+
fragments.push(text.slice(start, end));
|
|
9043
|
+
const record = parseRecord(fragments.join(""), path, lineNumber++, true);
|
|
9044
|
+
fragments.length = 0;
|
|
9045
|
+
if (record !== void 0) yield record.value;
|
|
9046
|
+
start = end + 1;
|
|
9047
|
+
end = text.indexOf("\n", start);
|
|
9048
|
+
}
|
|
9049
|
+
if (start < text.length) fragments.push(text.slice(start));
|
|
9050
|
+
}
|
|
9051
|
+
if (stream.bytesRead !== size) throw new Error(`${path}: journal changed while reading its committed prefix`);
|
|
9052
|
+
fragments.push(decoder.end());
|
|
9053
|
+
const record = parseRecord(fragments.join(""), path, lineNumber, false);
|
|
9054
|
+
if (record !== void 0) yield record.value;
|
|
9055
|
+
} finally {
|
|
9056
|
+
stream.destroy();
|
|
9057
|
+
}
|
|
9058
|
+
} finally {
|
|
9059
|
+
await handle.close();
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
/** An invalid unterminated tail may be a torn write; other parse failures are not evidence of one. */
|
|
9063
|
+
function parseRecord(line, source, lineNumber, terminated) {
|
|
9064
|
+
if (line.length === 0) return void 0;
|
|
9065
|
+
try {
|
|
9066
|
+
return { value: JSON.parse(line) };
|
|
9067
|
+
} catch (cause) {
|
|
9068
|
+
if (!(cause instanceof SyntaxError)) throw cause;
|
|
9069
|
+
if (!terminated) return void 0;
|
|
9070
|
+
throw new Error(`${source}: malformed JSONL record at line ${lineNumber}`, { cause });
|
|
9071
|
+
}
|
|
9072
|
+
}
|
|
9073
|
+
/** FileHandle.write may legally make a short write. Loop until every byte is appended. */
|
|
9074
|
+
async function writeAllBytes(handle, value) {
|
|
9075
|
+
const bytes = typeof value === "string" ? Buffer.from(value) : Buffer.from(value);
|
|
9076
|
+
let offset = 0;
|
|
9077
|
+
while (offset < bytes.byteLength) {
|
|
9078
|
+
const { bytesWritten } = await handle.write(bytes, offset, bytes.byteLength - offset, null);
|
|
9079
|
+
if (bytesWritten <= 0) throw new Error(`append-only file write made no progress at byte ${offset}`);
|
|
9080
|
+
offset += bytesWritten;
|
|
9081
|
+
}
|
|
9082
|
+
}
|
|
9083
|
+
/** Prepare the tail under the journal owner's existing single-writer discipline.
|
|
9084
|
+
* Healthy appends read one byte, regardless of history size. Only recovery scans backward,
|
|
9085
|
+
* retaining a valid unterminated value or truncating an uncommitted torn tail. */
|
|
9086
|
+
async function prepareJsonlAppend(path) {
|
|
9087
|
+
const fs = await import("node:fs/promises");
|
|
9088
|
+
let handle;
|
|
9089
|
+
try {
|
|
9090
|
+
handle = await fs.open(path, "r+");
|
|
9091
|
+
} catch (error) {
|
|
9092
|
+
if (isNoEntError(error)) return false;
|
|
9093
|
+
throw error;
|
|
9094
|
+
}
|
|
9095
|
+
try {
|
|
9096
|
+
const size = (await handle.stat()).size;
|
|
9097
|
+
if (size === 0) return false;
|
|
9098
|
+
const lastByte = Buffer.allocUnsafe(1);
|
|
9099
|
+
await readAt(handle, lastByte, size - 1);
|
|
9100
|
+
if (lastByte[0] === 10) return false;
|
|
9101
|
+
const chunks = [];
|
|
9102
|
+
let end = size;
|
|
9103
|
+
let tailStart = 0;
|
|
9104
|
+
while (end > 0) {
|
|
9105
|
+
const start = Math.max(0, end - 65536);
|
|
9106
|
+
const chunk = Buffer.allocUnsafe(end - start);
|
|
9107
|
+
await readAt(handle, chunk, start);
|
|
9108
|
+
const newline = chunk.lastIndexOf(10);
|
|
9109
|
+
chunks.push(chunk.subarray(newline + 1));
|
|
9110
|
+
if (newline >= 0) {
|
|
9111
|
+
tailStart = start + newline + 1;
|
|
9112
|
+
break;
|
|
9113
|
+
}
|
|
9114
|
+
end = start;
|
|
9115
|
+
}
|
|
9116
|
+
const tail = Buffer.concat(chunks.reverse()).toString("utf8");
|
|
9117
|
+
try {
|
|
9118
|
+
JSON.parse(tail);
|
|
9119
|
+
return true;
|
|
9120
|
+
} catch (error) {
|
|
9121
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
9122
|
+
await handle.truncate(tailStart);
|
|
9123
|
+
await handle.sync();
|
|
9124
|
+
return false;
|
|
9125
|
+
}
|
|
9126
|
+
} finally {
|
|
9127
|
+
await handle.close();
|
|
9128
|
+
}
|
|
9129
|
+
}
|
|
9130
|
+
/** A short read is legal; an unexpected EOF means the owner's snapshot changed. */
|
|
9131
|
+
async function readAt(handle, buffer, position) {
|
|
9132
|
+
let offset = 0;
|
|
9133
|
+
while (offset < buffer.length) {
|
|
9134
|
+
const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, position + offset);
|
|
9135
|
+
if (bytesRead === 0) throw new Error("journal changed while preparing its append tail");
|
|
9136
|
+
offset += bytesRead;
|
|
9137
|
+
}
|
|
9138
|
+
}
|
|
9139
|
+
/** True for a filesystem error meaning "the file is not there yet" — the one condition an
|
|
9140
|
+
* append-only reader treats as an empty log rather than a fault. */
|
|
9141
|
+
function isNoEntError(error) {
|
|
9142
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
9143
|
+
}
|
|
9144
|
+
//#endregion
|
|
9533
9145
|
//#region src/durable/spawn-journal.ts
|
|
9534
9146
|
/**
|
|
9535
9147
|
* Mint the content-addressed `outRef` for a result artifact: `sha256:<hex>` over a
|
|
@@ -10175,17 +9787,15 @@ var SpawnEventIndex = class {
|
|
|
10175
9787
|
}
|
|
10176
9788
|
};
|
|
10177
9789
|
/**
|
|
10178
|
-
* The records that CLOSE a node's cursor slot: a settlement
|
|
10179
|
-
* woken. The complement question to {@link outsideCursorNamespace}, and the one a resume needs —
|
|
9790
|
+
* The records that CLOSE a node's cursor slot: a settlement or a cancellation. The complement question to {@link outsideCursorNamespace}, and the one a resume needs —
|
|
10180
9791
|
* a kind missing here leaves a finished node looking live, so its budget is never reclaimed and
|
|
10181
9792
|
* its reservation is replayed as still in doubt.
|
|
10182
9793
|
*/
|
|
10183
9794
|
function closesCursorSlot(ev) {
|
|
10184
|
-
return ev.kind === "settled" || ev.kind === "cancelled"
|
|
9795
|
+
return ev.kind === "settled" || ev.kind === "cancelled";
|
|
10185
9796
|
}
|
|
10186
9797
|
const outsideKinds = /* @__PURE__ */ new Set([
|
|
10187
9798
|
"spawned",
|
|
10188
|
-
"waiting",
|
|
10189
9799
|
"metered",
|
|
10190
9800
|
"materialized",
|
|
10191
9801
|
"execution-bound",
|
|
@@ -10202,8 +9812,12 @@ const outsideKinds = /* @__PURE__ */ new Set([
|
|
|
10202
9812
|
"environment-teardown",
|
|
10203
9813
|
"workspace-checkpoint-requested",
|
|
10204
9814
|
"workspace-checkpoint",
|
|
9815
|
+
"workspace-checkpoint-fork-requested",
|
|
9816
|
+
"workspace-checkpoint-fork-teardown",
|
|
9817
|
+
"workspace-checkpoint-capture",
|
|
10205
9818
|
"workspace-checkpoint-cleanup",
|
|
10206
9819
|
"workspace-restored",
|
|
9820
|
+
"workspace-capture",
|
|
10207
9821
|
"trace-unpropagated",
|
|
10208
9822
|
"paused",
|
|
10209
9823
|
"driver-attempt",
|
|
@@ -10242,7 +9856,7 @@ async function replaySpawnTree(journal, blobs, root) {
|
|
|
10242
9856
|
const materializations = /* @__PURE__ */ new Map();
|
|
10243
9857
|
const executionBindings = /* @__PURE__ */ new Map();
|
|
10244
9858
|
for (const ev of ordered) {
|
|
10245
|
-
if (ev.kind === "spawned"
|
|
9859
|
+
if (ev.kind === "spawned") labels.set(ev.id, ev.label);
|
|
10246
9860
|
if (ev.kind === "spawned" && ev.assignmentId !== void 0) assignmentIds.set(ev.id, ev.assignmentId);
|
|
10247
9861
|
if (ev.kind === "spawned" && ev.identity !== void 0) identities.set(ev.id, copyFrozenIdentity(ev.identity));
|
|
10248
9862
|
if (ev.kind === "materialized") materializations.set(ev.id, ev.receipt);
|
|
@@ -10265,7 +9879,6 @@ async function replaySpawnTree(journal, blobs, root) {
|
|
|
10265
9879
|
const settled = [];
|
|
10266
9880
|
for (const ev of ordered) {
|
|
10267
9881
|
if (ev.kind === "spawned") continue;
|
|
10268
|
-
if (ev.kind === "waiting") continue;
|
|
10269
9882
|
if (ev.kind === "metered") continue;
|
|
10270
9883
|
if (ev.kind === "progress") continue;
|
|
10271
9884
|
if (ev.kind === "reconciled") continue;
|
|
@@ -10278,47 +9891,18 @@ async function replaySpawnTree(journal, blobs, root) {
|
|
|
10278
9891
|
if (ev.kind === "environment-teardown") continue;
|
|
10279
9892
|
if (ev.kind === "workspace-checkpoint-requested") continue;
|
|
10280
9893
|
if (ev.kind === "workspace-checkpoint") continue;
|
|
9894
|
+
if (ev.kind === "workspace-checkpoint-fork-requested") continue;
|
|
9895
|
+
if (ev.kind === "workspace-checkpoint-fork-teardown") continue;
|
|
9896
|
+
if (ev.kind === "workspace-checkpoint-capture") continue;
|
|
10281
9897
|
if (ev.kind === "workspace-checkpoint-cleanup") continue;
|
|
10282
9898
|
if (ev.kind === "workspace-restored") continue;
|
|
9899
|
+
if (ev.kind === "workspace-capture") continue;
|
|
10283
9900
|
if (ev.kind === "trace-unpropagated") continue;
|
|
10284
9901
|
if (ev.kind === "paused") continue;
|
|
10285
9902
|
if (ev.kind === "driver-attempt") continue;
|
|
10286
9903
|
if (ev.kind === "node-inputs-resolved") continue;
|
|
10287
9904
|
if (ev.kind === "edge-verdict") continue;
|
|
10288
9905
|
if (ev.kind === "join-state") continue;
|
|
10289
|
-
if (ev.kind === "woken") {
|
|
10290
|
-
if (ev.by === "cancelled" || ev.outRef === void 0) {
|
|
10291
|
-
settled.push({
|
|
10292
|
-
kind: "down",
|
|
10293
|
-
handle: handleFor(ev.id, "cancelled"),
|
|
10294
|
-
reason: "wait cancelled",
|
|
10295
|
-
infra: false,
|
|
10296
|
-
trace: {
|
|
10297
|
-
status: "unavailable",
|
|
10298
|
-
reason: "not-an-executor"
|
|
10299
|
-
},
|
|
10300
|
-
...settlementTime(ev.at),
|
|
10301
|
-
seq: ev.seq
|
|
10302
|
-
});
|
|
10303
|
-
continue;
|
|
10304
|
-
}
|
|
10305
|
-
const outcome = await blobs.get(ev.outRef);
|
|
10306
|
-
if (outcome === void 0) throw new Error(`replaySpawnTree: blob store has no wait outcome for outRef '${ev.outRef}' (node '${ev.id}', seq ${ev.seq})`);
|
|
10307
|
-
settled.push({
|
|
10308
|
-
kind: "done",
|
|
10309
|
-
handle: handleFor(ev.id, "done"),
|
|
10310
|
-
out: outcome,
|
|
10311
|
-
outRef: ev.outRef,
|
|
10312
|
-
spent: zeroSpend(),
|
|
10313
|
-
trace: {
|
|
10314
|
-
status: "unavailable",
|
|
10315
|
-
reason: "not-an-executor"
|
|
10316
|
-
},
|
|
10317
|
-
...settlementTime(ev.at),
|
|
10318
|
-
seq: ev.seq
|
|
10319
|
-
});
|
|
10320
|
-
continue;
|
|
10321
|
-
}
|
|
10322
9906
|
if (ev.kind === "cancelled") {
|
|
10323
9907
|
settled.push({
|
|
10324
9908
|
kind: "down",
|
|
@@ -10418,24 +10002,9 @@ function copyFrozenIdentity(identity) {
|
|
|
10418
10002
|
function materializeTreeView(events) {
|
|
10419
10003
|
const nodes = /* @__PURE__ */ new Map();
|
|
10420
10004
|
let root;
|
|
10421
|
-
const spawns = events.filter((ev) => ev.kind === "spawned"
|
|
10005
|
+
const spawns = events.filter((ev) => ev.kind === "spawned").sort((a, b) => a.seq - b.seq);
|
|
10422
10006
|
const settlements = events.filter((ev) => insideCursorNamespace(ev)).sort((a, b) => a.seq - b.seq);
|
|
10423
10007
|
for (const ev of spawns) {
|
|
10424
|
-
if (ev.kind === "waiting") {
|
|
10425
|
-
nodes.set(ev.id, {
|
|
10426
|
-
id: ev.id,
|
|
10427
|
-
parent: ev.parent,
|
|
10428
|
-
label: ev.label,
|
|
10429
|
-
status: "waiting",
|
|
10430
|
-
runtime: "wait",
|
|
10431
|
-
budget: {
|
|
10432
|
-
maxIterations: 0,
|
|
10433
|
-
maxTokens: 0
|
|
10434
|
-
},
|
|
10435
|
-
spent: zeroSpend()
|
|
10436
|
-
});
|
|
10437
|
-
continue;
|
|
10438
|
-
}
|
|
10439
10008
|
if (ev.parent === void 0 && root === void 0) root = ev.id;
|
|
10440
10009
|
nodes.set(ev.id, {
|
|
10441
10010
|
id: ev.id,
|
|
@@ -10463,16 +10032,6 @@ function materializeTreeView(events) {
|
|
|
10463
10032
|
if (ev.retainedPendingCause !== void 0) node.retainedPendingCause = ev.retainedPendingCause;
|
|
10464
10033
|
const settledAt = Date.parse(ev.at);
|
|
10465
10034
|
if (Number.isFinite(settledAt)) node.settledAt = settledAt;
|
|
10466
|
-
} else if (ev.kind === "woken") {
|
|
10467
|
-
const node = requireNode(nodes, ev.id);
|
|
10468
|
-
node.status = ev.by === "cancelled" ? "cancelled" : "done";
|
|
10469
|
-
node.outRef = ev.outRef;
|
|
10470
|
-
node.trace = {
|
|
10471
|
-
status: "unavailable",
|
|
10472
|
-
reason: "not-an-executor"
|
|
10473
|
-
};
|
|
10474
|
-
const settledAt = Date.parse(ev.at);
|
|
10475
|
-
if (Number.isFinite(settledAt)) node.settledAt = settledAt;
|
|
10476
10035
|
} else {
|
|
10477
10036
|
const node = requireNode(nodes, ev.id);
|
|
10478
10037
|
node.status = "cancelled";
|
|
@@ -10514,31 +10073,9 @@ function materializeTreeView(events) {
|
|
|
10514
10073
|
return {
|
|
10515
10074
|
root: root ?? snapshots[0]?.id ?? "",
|
|
10516
10075
|
nodes: snapshots,
|
|
10517
|
-
inFlight: snapshots.filter((n) => n.status === "running" || n.status === "acquiring").length
|
|
10518
|
-
waiting: snapshots.filter((n) => n.status === "waiting").length
|
|
10076
|
+
inFlight: snapshots.filter((n) => n.status === "running" || n.status === "acquiring").length
|
|
10519
10077
|
};
|
|
10520
10078
|
}
|
|
10521
|
-
/**
|
|
10522
|
-
* The waits a journaled tree shows as ARMED but never woken — what a resumed run re-arms with the
|
|
10523
|
-
* ORIGINAL absolute deadline. Reading it from the journal (rather than from any live state) is
|
|
10524
|
-
* what makes "SIGKILL a waiting tree, a new process keeps waiting to the same instant" true.
|
|
10525
|
-
*/
|
|
10526
|
-
function pendingWaits(events) {
|
|
10527
|
-
const woken = /* @__PURE__ */ new Set();
|
|
10528
|
-
for (const ev of events) if (ev.kind === "woken") woken.add(ev.id);
|
|
10529
|
-
const pending = [];
|
|
10530
|
-
for (const ev of events) {
|
|
10531
|
-
if (ev.kind !== "waiting" || woken.has(ev.id)) continue;
|
|
10532
|
-
pending.push({
|
|
10533
|
-
id: ev.id,
|
|
10534
|
-
label: ev.label,
|
|
10535
|
-
spec: ev.spec,
|
|
10536
|
-
armedAt: ev.armedAt,
|
|
10537
|
-
ordinal: ev.seq
|
|
10538
|
-
});
|
|
10539
|
-
}
|
|
10540
|
-
return pending.sort((a, b) => a.ordinal - b.ordinal);
|
|
10541
|
-
}
|
|
10542
10079
|
/** Copy a terminal record's overspend at the journal boundary; absent stays absent. */
|
|
10543
10080
|
function budgetViolationOf(event) {
|
|
10544
10081
|
if (event.budgetViolation === void 0) return {};
|
|
@@ -10622,6 +10159,18 @@ function executorEvidenceWriter(args) {
|
|
|
10622
10159
|
});
|
|
10623
10160
|
};
|
|
10624
10161
|
}
|
|
10162
|
+
/** @internal The journal writer for `onWorkspaceCapture`; `nextSequence` is the node's own. */
|
|
10163
|
+
function workspaceCaptureWriter(args) {
|
|
10164
|
+
return async (capture) => {
|
|
10165
|
+
await args.journal.appendEvent(args.rootId, {
|
|
10166
|
+
kind: "workspace-capture",
|
|
10167
|
+
id: args.nodeId,
|
|
10168
|
+
...detachedSnapshot(capture, "workspace capture timing"),
|
|
10169
|
+
seq: args.nextSequence(),
|
|
10170
|
+
at: new Date(args.now()).toISOString()
|
|
10171
|
+
});
|
|
10172
|
+
};
|
|
10173
|
+
}
|
|
10625
10174
|
function retainedExecutorContext(ctx) {
|
|
10626
10175
|
return ctx.seams[retainedExecutorSeamKey];
|
|
10627
10176
|
}
|
|
@@ -11054,6 +10603,9 @@ function jitteredMs(ms, random) {
|
|
|
11054
10603
|
const WORKSPACE_CHECKPOINT_MARKER_PATH = ".agent-runtime-checkpoint";
|
|
11055
10604
|
/** Bound on one checkpoint: environment lookup, marker write, and the provider operation. */
|
|
11056
10605
|
const WORKSPACE_CHECKPOINT_TIMEOUT_MS = 12e4;
|
|
10606
|
+
/** Bound on creating and reconstructing the fork a checkpoint's capture reads: a Sandbox box
|
|
10607
|
+
* created from a snapshot (measured 2026-09-24: 19 s). The capture itself is bounded by its port. */
|
|
10608
|
+
const WORKSPACE_CHECKPOINT_FORK_TIMEOUT_MS = 10 * 6e4;
|
|
11057
10609
|
/** Write the marker. False when the environment cannot take it, so no restore can be verified. */
|
|
11058
10610
|
async function writeWorkspaceMarker(environment, marker, signal) {
|
|
11059
10611
|
if (typeof environment.write !== "function") return false;
|
|
@@ -11286,6 +10838,18 @@ function createProviderExecutor(provider, profile, ctx, options, placement) {
|
|
|
11286
10838
|
let pending = false;
|
|
11287
10839
|
let harnessTranscript = harnessTranscriptUnavailable("execution-never-started");
|
|
11288
10840
|
const retention = retainedExecutorContext(ctx);
|
|
10841
|
+
const managerStop = readOptionalAbortSignal(ctx, bridgeStopSignalKey, "provider");
|
|
10842
|
+
const cancelRetainedOnManagerStop = (handle) => {
|
|
10843
|
+
if (managerStop === void 0) return;
|
|
10844
|
+
const cancel = () => {
|
|
10845
|
+
handle.cancel({
|
|
10846
|
+
operationId: `manager-stop:${randomUUID()}`,
|
|
10847
|
+
reason: String(managerStop.reason ?? "the manager stopped")
|
|
10848
|
+
}).catch(() => void 0);
|
|
10849
|
+
};
|
|
10850
|
+
if (managerStop.aborted) cancel();
|
|
10851
|
+
else managerStop.addEventListener("abort", cancel, { once: true });
|
|
10852
|
+
};
|
|
11289
10853
|
let destroyed = false;
|
|
11290
10854
|
let workspaceEnvironmentId;
|
|
11291
10855
|
let workspaceSnapshot;
|
|
@@ -11378,7 +10942,19 @@ function createProviderExecutor(provider, profile, ctx, options, placement) {
|
|
|
11378
10942
|
profile: createProfile,
|
|
11379
10943
|
...outcome === void 0 ? {} : { outcome }
|
|
11380
10944
|
};
|
|
11381
|
-
|
|
10945
|
+
const reportTiming = retention?.onWorkspaceCapture;
|
|
10946
|
+
let timing;
|
|
10947
|
+
const capturedExecutionId = workspaceExecutionId;
|
|
10948
|
+
workspaceCapturePromise = (nativeInFlight ?? Promise.resolve()).then(() => captureProviderWorkspaceSnapshot(workspaceRetention, captureContext, "environment", reportTiming === void 0 ? void 0 : (measured) => {
|
|
10949
|
+
timing = measured;
|
|
10950
|
+
})).finally(async () => {
|
|
10951
|
+
if (timing === void 0 || reportTiming === void 0) return;
|
|
10952
|
+
await reportTiming({
|
|
10953
|
+
executionId: capturedExecutionId,
|
|
10954
|
+
environmentId: next.id,
|
|
10955
|
+
...timing
|
|
10956
|
+
}).catch(() => void 0);
|
|
10957
|
+
}).then(({ snapshot, provenance, coverageComplete, incompleteReason }) => {
|
|
11382
10958
|
workspaceSnapshot = snapshot;
|
|
11383
10959
|
workspaceProvenance = provenance;
|
|
11384
10960
|
workspaceCoverageComplete = coverageComplete ?? false;
|
|
@@ -11695,10 +11271,12 @@ function createProviderExecutor(provider, profile, ctx, options, placement) {
|
|
|
11695
11271
|
...carried === void 0 ? {} : { carried },
|
|
11696
11272
|
executionStartedAt,
|
|
11697
11273
|
mayContinue: options.unavailablePause !== false && invocation.retention?.continueInvocation !== void 0,
|
|
11274
|
+
managerStopped: () => managerStop?.aborted === true,
|
|
11698
11275
|
onRetained: (handle) => {
|
|
11699
11276
|
retained = handle;
|
|
11700
11277
|
workspaceControlRef = freezeControlRef(handle.controlRef);
|
|
11701
11278
|
providerSessionId = workspaceControlRef.sessionId;
|
|
11279
|
+
cancelRetainedOnManagerStop(handle);
|
|
11702
11280
|
},
|
|
11703
11281
|
onPending: (value) => {
|
|
11704
11282
|
pending = value;
|
|
@@ -11978,6 +11556,19 @@ async function* streamProviderExecutor(args) {
|
|
|
11978
11556
|
let failure;
|
|
11979
11557
|
let failed = false;
|
|
11980
11558
|
let unavailable;
|
|
11559
|
+
let finalCopyTaken = false;
|
|
11560
|
+
const ownerTakesCapture = () => args.options.workspaceRetention?.captureNative !== void 0 && args.createProfile.harness !== void 0 && source.retained && args.retention?.preserveEnvironment === true && args.retention.onEndOfTurnWork !== void 0;
|
|
11561
|
+
const handCaptureToOwner = (reported, captureOutcome, publish) => {
|
|
11562
|
+
args.retention?.onEndOfTurnWork?.(args.captureWorkspace(environment, captureOutcome).then(async (snapshot) => {
|
|
11563
|
+
if (publish) args.onPublishedSnapshot(snapshot);
|
|
11564
|
+
if (snapshot === void 0) return;
|
|
11565
|
+
await args.retention?.onEvidence?.({
|
|
11566
|
+
...reported,
|
|
11567
|
+
workspaceSnapshot: snapshot,
|
|
11568
|
+
workspaceCapture: providerWorkspaceCaptureReceipt(args, environment, snapshot, turn.sessionId ?? null)
|
|
11569
|
+
});
|
|
11570
|
+
}, () => void 0));
|
|
11571
|
+
};
|
|
11981
11572
|
let harnessStop;
|
|
11982
11573
|
const stopEndedHarness = () => {
|
|
11983
11574
|
if (settled !== void 0 || !linked.aborted) return Promise.resolve();
|
|
@@ -12058,14 +11649,17 @@ async function* streamProviderExecutor(args) {
|
|
|
12058
11649
|
box: environmentAsSandboxInstance(environment, { requireTerminalEvent: args.options.requireTerminalEvent ?? true }),
|
|
12059
11650
|
signal: linked
|
|
12060
11651
|
});
|
|
11652
|
+
const deferCapture = ownerTakesCapture() && args.managerStopped();
|
|
12061
11653
|
let retainedWorkspace;
|
|
12062
11654
|
let workspaceCaptureFailure;
|
|
12063
|
-
|
|
12064
|
-
|
|
12065
|
-
|
|
12066
|
-
|
|
11655
|
+
if (!deferCapture) {
|
|
11656
|
+
try {
|
|
11657
|
+
retainedWorkspace = await args.captureWorkspace(environment, outcome);
|
|
11658
|
+
} catch (error) {
|
|
11659
|
+
workspaceCaptureFailure = `${errorText(error instanceof Error ? error.message : error)}; source environment preserved`;
|
|
11660
|
+
}
|
|
11661
|
+
args.onPublishedSnapshot(retainedWorkspace);
|
|
12067
11662
|
}
|
|
12068
|
-
args.onPublishedSnapshot(retainedWorkspace);
|
|
12069
11663
|
const settledResult = {
|
|
12070
11664
|
...result,
|
|
12071
11665
|
...workspaceCaptureFailure === void 0 ? {} : { workspaceCaptureFailure },
|
|
@@ -12099,6 +11693,11 @@ async function* streamProviderExecutor(args) {
|
|
|
12099
11693
|
else if (retainedWorkspace !== void 0) await args.retention?.onEvidence?.(settledResult);
|
|
12100
11694
|
args.onPending(false);
|
|
12101
11695
|
args.onArtifact(settled);
|
|
11696
|
+
if (deferCapture) {
|
|
11697
|
+
await args.finishNativeMirror(environment, "settled");
|
|
11698
|
+
finalCopyTaken = true;
|
|
11699
|
+
handCaptureToOwner(settledResult, outcome, true);
|
|
11700
|
+
}
|
|
12102
11701
|
} catch (error) {
|
|
12103
11702
|
failure = source.retained ? new RetainedExecutionPendingError(error) : error;
|
|
12104
11703
|
failed = true;
|
|
@@ -12112,7 +11711,14 @@ async function* streamProviderExecutor(args) {
|
|
|
12112
11711
|
await stopEndedHarness();
|
|
12113
11712
|
if (linked.aborted) await args.finishNativeMirror(environment, "failed");
|
|
12114
11713
|
args.onTranscriptSettled();
|
|
12115
|
-
|
|
11714
|
+
if (ownerTakesCapture()) {
|
|
11715
|
+
if (!linked.aborted) await args.finishNativeMirror(environment, "failed");
|
|
11716
|
+
finalCopyTaken = true;
|
|
11717
|
+
handCaptureToOwner({
|
|
11718
|
+
...resultFromEvents(archive.events(), text),
|
|
11719
|
+
outcome: failureOutcome
|
|
11720
|
+
}, failureOutcome, false);
|
|
11721
|
+
} else try {
|
|
12116
11722
|
const retainedWorkspace = await args.captureWorkspace(environment, failureOutcome);
|
|
12117
11723
|
if (retainedWorkspace !== void 0 && args.retention?.onEvidence !== void 0) await args.retention.onEvidence({
|
|
12118
11724
|
...resultFromEvents(archive.events(), text),
|
|
@@ -12127,7 +11733,7 @@ async function* streamProviderExecutor(args) {
|
|
|
12127
11733
|
if (args.options.workspaceRetention === void 0) args.onHarnessTranscript(await captureHarnessTranscript(environment, args.profile.harness, linked));
|
|
12128
11734
|
} finally {
|
|
12129
11735
|
await stopEndedHarness();
|
|
12130
|
-
await args.finishNativeMirror(environment, failed ? "failed" : settled !== void 0 ? "settled" : "interrupted");
|
|
11736
|
+
if (!finalCopyTaken) await args.finishNativeMirror(environment, failed ? "failed" : settled !== void 0 ? "settled" : "interrupted");
|
|
12131
11737
|
if ((!source.retained || settled !== void 0 && !failed) && !(source.retained && args.retention?.preserveEnvironment) && !(unavailable !== void 0 && !failed) && (args.options.destroyOnSettle ?? true)) {
|
|
12132
11738
|
const cleanup = await args.destroyEnvironment();
|
|
12133
11739
|
if (!cleanup.destroyed) {
|
|
@@ -13277,6 +12883,14 @@ async function runBrainLoop(opts) {
|
|
|
13277
12883
|
role: "assistant",
|
|
13278
12884
|
content: r.content
|
|
13279
12885
|
});
|
|
12886
|
+
const next = maxTurns === 0 || turn < maxTurns ? await opts.hooks?.afterFinal?.(turn) : void 0;
|
|
12887
|
+
if (next !== void 0) {
|
|
12888
|
+
messages.push({
|
|
12889
|
+
role: "user",
|
|
12890
|
+
content: next
|
|
12891
|
+
});
|
|
12892
|
+
continue;
|
|
12893
|
+
}
|
|
13280
12894
|
return {
|
|
13281
12895
|
final: lastText,
|
|
13282
12896
|
turns: turn,
|
|
@@ -14364,34 +13978,6 @@ function buildBackendOptions(profile, overrides) {
|
|
|
14364
13978
|
};
|
|
14365
13979
|
}
|
|
14366
13980
|
//#endregion
|
|
14367
|
-
//#region src/runtime/sandbox-capabilities.ts
|
|
14368
|
-
const probeCache = /* @__PURE__ */ new WeakMap();
|
|
14369
|
-
/**
|
|
14370
|
-
* Probe (and memoize per client) what the loop may rely on. A client without a
|
|
14371
|
-
* `criuStatus` method, or whose probe rejects, yields `canFork = false` — a
|
|
14372
|
-
* failed probe must never claim a capability the platform may not have. The
|
|
14373
|
-
* promise is cached so concurrent fanout branches share one round-trip.
|
|
14374
|
-
*
|
|
14375
|
-
* @experimental
|
|
14376
|
-
*/
|
|
14377
|
-
function probeSandboxCapabilities(client) {
|
|
14378
|
-
const key = client;
|
|
14379
|
-
const cached = probeCache.get(key);
|
|
14380
|
-
if (cached) return cached;
|
|
14381
|
-
const probe = resolveCapabilities(client);
|
|
14382
|
-
probeCache.set(key, probe);
|
|
14383
|
-
return probe;
|
|
14384
|
-
}
|
|
14385
|
-
async function resolveCapabilities(client) {
|
|
14386
|
-
const criuStatus = client.criuStatus;
|
|
14387
|
-
if (typeof criuStatus !== "function") return { canFork: false };
|
|
14388
|
-
try {
|
|
14389
|
-
return { canFork: (await criuStatus.call(client))?.available === true };
|
|
14390
|
-
} catch {
|
|
14391
|
-
return { canFork: false };
|
|
14392
|
-
}
|
|
14393
|
-
}
|
|
14394
|
-
//#endregion
|
|
14395
13981
|
//#region src/runtime/sandbox-lineage.ts
|
|
14396
13982
|
const TEARDOWN_TIMEOUT_MS$1 = 15e3;
|
|
14397
13983
|
const DEFAULT_FORK_CONCURRENCY = 4;
|
|
@@ -14457,13 +14043,11 @@ function promptEvents(streaming, box, prompt, sessionId, signal, promptOptions)
|
|
|
14457
14043
|
});
|
|
14458
14044
|
}
|
|
14459
14045
|
/**
|
|
14460
|
-
* Build a lineage bound to one client
|
|
14461
|
-
* capabilities are passed in (not re-probed) so the kernel probes once per run
|
|
14462
|
-
* and the lineage stays a pure function of "what this platform can do".
|
|
14046
|
+
* Build a lineage bound to one client. Branching is read from each box.
|
|
14463
14047
|
*
|
|
14464
14048
|
* @experimental
|
|
14465
14049
|
*/
|
|
14466
|
-
function createSandboxLineage(client,
|
|
14050
|
+
function createSandboxLineage(client, options = {}) {
|
|
14467
14051
|
if (!client || typeof client.create !== "function") throw new ValidationError$1("createSandboxLineage: client.create is required");
|
|
14468
14052
|
const streaming = options.streaming ?? "sse";
|
|
14469
14053
|
const recordMount = options.recordMount ?? (() => {});
|
|
@@ -14553,30 +14137,10 @@ function createSandboxLineage(client, capabilities, options = {}) {
|
|
|
14553
14137
|
};
|
|
14554
14138
|
});
|
|
14555
14139
|
}
|
|
14556
|
-
const checkpointId = capabilities.canFork ? await checkpointForFork(parent.box, signal) : void 0;
|
|
14557
14140
|
return mapWithConcurrency(prompts, forkConcurrency, async (prompt, i) => {
|
|
14558
14141
|
throwIfAborted(signal);
|
|
14559
14142
|
const spec = specs[i % specs.length];
|
|
14560
14143
|
if (!spec) throw new ValidationError$1("SandboxLineage.fork: no AgentRunSpec for branch");
|
|
14561
|
-
if (checkpointId !== void 0) {
|
|
14562
|
-
const box = await forkFromCheckpoint(parent.box, checkpointId, signal);
|
|
14563
|
-
owned.push(box);
|
|
14564
|
-
const inheritedProfile = profiles.get(parent.box) ?? spec.profile;
|
|
14565
|
-
profiles.set(box, inheritedProfile);
|
|
14566
|
-
options.onAcquire?.(box, inheritedProfile);
|
|
14567
|
-
await spec.prepareBox?.(box, {
|
|
14568
|
-
signal,
|
|
14569
|
-
recordMount
|
|
14570
|
-
});
|
|
14571
|
-
const sessionId = mintSessionId();
|
|
14572
|
-
return {
|
|
14573
|
-
handle: {
|
|
14574
|
-
box,
|
|
14575
|
-
sessionId
|
|
14576
|
-
},
|
|
14577
|
-
events: promptEvents(streaming, box, prompt, sessionId, signal, optionsFor(i))
|
|
14578
|
-
};
|
|
14579
|
-
}
|
|
14580
14144
|
const box = await acquireFresh(spec, signal);
|
|
14581
14145
|
const sessionId = mintSessionId();
|
|
14582
14146
|
return {
|
|
@@ -14616,8 +14180,8 @@ function mintSessionId() {
|
|
|
14616
14180
|
}
|
|
14617
14181
|
/**
|
|
14618
14182
|
* Branch a running parent through the current Sandbox SDK, in bounded waves.
|
|
14619
|
-
* `undefined` means the box exposes
|
|
14620
|
-
*
|
|
14183
|
+
* `undefined` means the box exposes no branching API, so the caller starts
|
|
14184
|
+
* fresh boxes.
|
|
14621
14185
|
*/
|
|
14622
14186
|
async function branchParent(box, count, concurrency, signal) {
|
|
14623
14187
|
const branch = box.branch;
|
|
@@ -14638,34 +14202,6 @@ async function branchParent(box, count, concurrency, signal) {
|
|
|
14638
14202
|
}
|
|
14639
14203
|
}
|
|
14640
14204
|
/**
|
|
14641
|
-
* Checkpoint the parent leaving it running, returning the checkpoint id to fork
|
|
14642
|
-
* from, or `undefined` when the box exposes no legacy `checkpoint` method or
|
|
14643
|
-
* the call produced no id. `undefined` makes the caller use fresh boxes — a
|
|
14644
|
-
* fork that cannot preserve context must not pretend to share it.
|
|
14645
|
-
*/
|
|
14646
|
-
async function checkpointForFork(box, signal) {
|
|
14647
|
-
const checkpoint = box.checkpoint;
|
|
14648
|
-
if (typeof checkpoint !== "function") return void 0;
|
|
14649
|
-
if (signal.aborted) throwAbort();
|
|
14650
|
-
const id = (await checkpoint.call(box, { leaveRunning: true }))?.checkpointId;
|
|
14651
|
-
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
14652
|
-
}
|
|
14653
|
-
/**
|
|
14654
|
-
* Fork a child box from `checkpointId`. The box exposes `fork` whenever the
|
|
14655
|
-
* platform advertised `canFork`; a missing `fork` here is a contract violation
|
|
14656
|
-
* (probe said yes, box says no) and fails loud rather than silently degrading.
|
|
14657
|
-
*
|
|
14658
|
-
* `signal` gates entry only: the legacy SDK's `fork(checkpointId, options)`
|
|
14659
|
-
* takes no abort signal, so an in-flight fork cannot be interrupted. The
|
|
14660
|
-
* caller checks abort per branch, so cancellation is responsive at boundaries.
|
|
14661
|
-
*/
|
|
14662
|
-
async function forkFromCheckpoint(box, checkpointId, signal) {
|
|
14663
|
-
const fork = box.fork;
|
|
14664
|
-
if (typeof fork !== "function") throw new ValidationError$1("SandboxLineage.fork: capabilities report canFork but the box has no fork() method");
|
|
14665
|
-
if (signal.aborted) throwAbort();
|
|
14666
|
-
return fork.call(box, checkpointId);
|
|
14667
|
-
}
|
|
14668
|
-
/**
|
|
14669
14205
|
* Fail loud when a handle's session is no longer known to the sandbox. The real
|
|
14670
14206
|
* SDK box exposes `session(id).status()` which resolves `null` for an unknown /
|
|
14671
14207
|
* reaped id; a `null` here means a `continue` would run WITHOUT the prior
|
|
@@ -14909,9 +14445,8 @@ async function setUpLineage(options, maxConcurrency, recordMount, promptOptions,
|
|
|
14909
14445
|
const lineageOpts = options.lineage;
|
|
14910
14446
|
if (!lineageOpts || !lineageOpts.sessionContinuity && !lineageOpts.forkFanout) return void 0;
|
|
14911
14447
|
if (options.onWorkerBox) throw new ValidationError$1("runAgentRounds: `lineage` and `onWorkerBox` both own worker boxes — pass only one");
|
|
14912
|
-
const capabilities = await probeSandboxCapabilities(options.ctx.sandboxClient);
|
|
14913
14448
|
return {
|
|
14914
|
-
lineage: createSandboxLineage(options.ctx.sandboxClient,
|
|
14449
|
+
lineage: createSandboxLineage(options.ctx.sandboxClient, {
|
|
14915
14450
|
maxConcurrency,
|
|
14916
14451
|
streaming: lineageOpts.streaming,
|
|
14917
14452
|
recordMount,
|
|
@@ -15526,209 +15061,91 @@ const TERMINAL_DECISIONS = [
|
|
|
15526
15061
|
];
|
|
15527
15062
|
/** True when the kernel stops the loop for this decision value. @stable */
|
|
15528
15063
|
function isTerminalDecision(decision) {
|
|
15529
|
-
return TERMINAL_DECISIONS.includes(decision);
|
|
15530
|
-
}
|
|
15531
|
-
function emitRunLoopHook(options, event) {
|
|
15532
|
-
notifyRuntimeHookEvent(options.ctx.hooks, {
|
|
15533
|
-
id: `${event.runId}:${event.target}:${event.phase}${event.stepIndex === void 0 ? "" : `:${event.stepIndex}`}`,
|
|
15534
|
-
runId: event.runId,
|
|
15535
|
-
target: event.target,
|
|
15536
|
-
phase: event.phase,
|
|
15537
|
-
timestamp: event.timestamp,
|
|
15538
|
-
stepIndex: event.stepIndex,
|
|
15539
|
-
payload: event.payload,
|
|
15540
|
-
metadata: { producer: "run-loop" }
|
|
15541
|
-
}, { signal: options.ctx.signal });
|
|
15542
|
-
}
|
|
15543
|
-
async function emitTrace(emitter, event) {
|
|
15544
|
-
if (!emitter) return;
|
|
15545
|
-
await emitter.emit(event);
|
|
15546
|
-
}
|
|
15547
|
-
/**
|
|
15548
|
-
* Stable hash for the trace payload. Not cryptographic — only used so
|
|
15549
|
-
* downstream eval pipelines can group iterations whose task / output is the
|
|
15550
|
-
* same. Bare structural hash; non-JSON values stringify via their `toString`.
|
|
15551
|
-
*/
|
|
15552
|
-
function hashJson(value) {
|
|
15553
|
-
let str;
|
|
15554
|
-
try {
|
|
15555
|
-
str = JSON.stringify(value) ?? String(value);
|
|
15556
|
-
} catch {
|
|
15557
|
-
str = String(value);
|
|
15558
|
-
}
|
|
15559
|
-
let h = 2166136261;
|
|
15560
|
-
for (let i = 0; i < str.length; i += 1) {
|
|
15561
|
-
h ^= str.charCodeAt(i);
|
|
15562
|
-
h = Math.imul(h, 16777619);
|
|
15563
|
-
}
|
|
15564
|
-
return (h >>> 0).toString(16).padStart(8, "0");
|
|
15565
|
-
}
|
|
15566
|
-
//#endregion
|
|
15567
|
-
//#region src/runtime/sandbox-executor-output.ts
|
|
15568
|
-
/** Project the complete Sandbox event stream into the standard executor result shape. */
|
|
15569
|
-
function sandboxLeafOutputFromEvents(events) {
|
|
15570
|
-
const content = collectAgentFinalMessageText(events) ?? collectAgentResponseText(events);
|
|
15571
|
-
const toolCalls = sandboxToolCalls(events);
|
|
15572
|
-
const servedBackend = lastServedBackend(events);
|
|
15573
|
-
return {
|
|
15574
|
-
events,
|
|
15575
|
-
content,
|
|
15576
|
-
output: sandboxOutputMarker(content, events.some(carriesText)),
|
|
15577
|
-
...servedBackend === void 0 ? {} : { servedBackend },
|
|
15578
|
-
...toolCalls.length === 0 ? {} : { toolCalls }
|
|
15579
|
-
};
|
|
15580
|
-
}
|
|
15581
|
-
/**
|
|
15582
|
-
* Classify observed text into the explicit output marker. `sawTextEvent` says whether the stream
|
|
15583
|
-
* carried a text-bearing event at all, which is what separates a box that answered nothing from a
|
|
15584
|
-
* box whose answer was never observed.
|
|
15585
|
-
*/
|
|
15586
|
-
function sandboxOutputMarker(content, sawTextEvent = content !== void 0) {
|
|
15587
|
-
if (content !== void 0 && content.length > 0) return {
|
|
15588
|
-
kind: "text",
|
|
15589
|
-
bytes: Buffer.byteLength(content, "utf8")
|
|
15590
|
-
};
|
|
15591
|
-
return sawTextEvent ? { kind: "empty" } : { kind: "absent" };
|
|
15592
|
-
}
|
|
15593
|
-
/** True when the event carries a text field a turn's answer could come from, empty or not. */
|
|
15594
|
-
function carriesText(event) {
|
|
15595
|
-
if (!event || typeof event !== "object") return false;
|
|
15596
|
-
const data = event.data && typeof event.data === "object" ? event.data : {};
|
|
15597
|
-
if (typeof data.finalText === "string" || typeof data.text === "string") return true;
|
|
15598
|
-
if (typeof data.delta === "string") return true;
|
|
15599
|
-
const part = data.part && typeof data.part === "object" ? data.part : void 0;
|
|
15600
|
-
return part !== void 0 && part.type === "text" && typeof part.text === "string";
|
|
15601
|
-
}
|
|
15602
|
-
/** The last served identity the platform reported across a turn's events, or `undefined`. */
|
|
15603
|
-
function lastServedBackend(events) {
|
|
15604
|
-
let served;
|
|
15605
|
-
for (const event of events) {
|
|
15606
|
-
const next = sandboxEventServedBackend(event);
|
|
15607
|
-
if (next !== void 0) served = next;
|
|
15608
|
-
}
|
|
15609
|
-
return served;
|
|
15610
|
-
}
|
|
15611
|
-
function sandboxToolCalls(events) {
|
|
15612
|
-
const state = createSandboxToolPartState();
|
|
15613
|
-
return events.flatMap((event) => mapSandboxToolEvent(event, state).flatMap((projected) => projected.type === "tool_call" ? [{
|
|
15614
|
-
...projected.toolCallId === void 0 ? {} : { id: projected.toolCallId },
|
|
15615
|
-
name: projected.toolName,
|
|
15616
|
-
arguments: projected.args ?? {}
|
|
15617
|
-
}] : []));
|
|
15618
|
-
}
|
|
15619
|
-
//#endregion
|
|
15620
|
-
//#region src/runtime/supervise/executor-seams.ts
|
|
15621
|
-
function assertExactConfigKeys(value, allowed, context) {
|
|
15622
|
-
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
15623
|
-
if (unknown.length > 0) throw new ValidationError$1(`${context}: unknown fields ${unknown.sort().join(", ")}; execution behavior belongs in AgentProfile`);
|
|
15624
|
-
}
|
|
15625
|
-
/** Narrow a named seam off the `ExecutorContext`, failing loud when absent — no
|
|
15626
|
-
* silent default for a required external-boundary seam. */
|
|
15627
|
-
function readSeam(ctx, key, who) {
|
|
15628
|
-
const seam = ctx.seams[key];
|
|
15629
|
-
if (seam === void 0 || seam === null) throw new ValidationError$1(`${who} executor: missing required seam "${key}" on ExecutorContext`);
|
|
15630
|
-
return seam;
|
|
15631
|
-
}
|
|
15632
|
-
function readOptionalAbortSignal(ctx, key, who) {
|
|
15633
|
-
const value = ctx.seams[key];
|
|
15634
|
-
if (value === void 0) return void 0;
|
|
15635
|
-
if (value === null || typeof value !== "object" || typeof value.aborted !== "boolean" || typeof value.addEventListener !== "function") throw new ValidationError$1(`${who} executor: seam "${key}" must be an AbortSignal`);
|
|
15636
|
-
return value;
|
|
15637
|
-
}
|
|
15638
|
-
/** Narrow the Runtime-owned MCP attachment seam. Absent means the run mounts nothing; a present
|
|
15639
|
-
* value must be a named map of MCP servers, never an empty one that would silently mount nothing. */
|
|
15640
|
-
function readOptionalMcpAttachments(ctx, key, who) {
|
|
15641
|
-
const value = ctx.seams[key];
|
|
15642
|
-
if (value === void 0) return void 0;
|
|
15643
|
-
const entries = value !== null && typeof value === "object" && !Array.isArray(value) ? Object.entries(value) : void 0;
|
|
15644
|
-
if (entries === void 0 || entries.length === 0 || entries.some(([, server]) => server === null || typeof server !== "object")) throw new ValidationError$1(`${who} executor: seam ${JSON.stringify(key)} must be a non-empty map of MCP servers`);
|
|
15645
|
-
return value;
|
|
15646
|
-
}
|
|
15647
|
-
//#endregion
|
|
15648
|
-
//#region src/runtime/supervise/bridge-config.ts
|
|
15649
|
-
const bridgeSeamKey = "bridge";
|
|
15650
|
-
/** Internal control seam used by Runtime-owned external supervisors. A completion request stops
|
|
15651
|
-
* the next bridge turn; it must not abort the paid request that is already streaming. */
|
|
15652
|
-
const bridgeStopSignalKey = "__bridge_stop_signal";
|
|
15653
|
-
/** Internal attachment seam used by Runtime-owned external supervisors. The named MCP servers ride
|
|
15654
|
-
* beside the AgentProfile as `runtime_attachments`, so the bridge mounts them for the run and keeps
|
|
15655
|
-
* them out of its session profile binding. A rebound coordination port therefore cannot move the
|
|
15656
|
-
* authored profile digest that binds a durable session. */
|
|
15657
|
-
const bridgeRuntimeAttachmentsKey = "__bridge_runtime_attachments";
|
|
15658
|
-
const maxBridgeTimeoutMs = 2147483647;
|
|
15659
|
-
function isLoopbackBridgeHost(hostname) {
|
|
15660
|
-
const host = hostname.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
|
|
15661
|
-
if (host === "localhost" || host === "::1" || host === "0:0:0:0:0:0:0:1") return true;
|
|
15662
|
-
if (host === "::ffff:127.0.0.1") return true;
|
|
15663
|
-
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/u.test(host);
|
|
15664
|
-
}
|
|
15665
|
-
function isLoopbackBridgeUrl(value) {
|
|
15666
|
-
try {
|
|
15667
|
-
const target = new URL(value);
|
|
15668
|
-
return (target.protocol === "http:" || target.protocol === "https:") && isLoopbackBridgeHost(target.hostname);
|
|
15669
|
-
} catch {
|
|
15670
|
-
return false;
|
|
15671
|
-
}
|
|
15672
|
-
}
|
|
15673
|
-
/** Validate and capture the non-serialisable credential service without reading its value. */
|
|
15674
|
-
function captureBridgeModelCredential(value, context) {
|
|
15675
|
-
if (value === null || typeof value !== "object") throw new ValidationError$1(`${context}: modelCredential must be an object`);
|
|
15676
|
-
assertExactConfigKeys(value, /* @__PURE__ */ new Set([
|
|
15677
|
-
"baseUrlKey",
|
|
15678
|
-
"key",
|
|
15679
|
-
"provider"
|
|
15680
|
-
]), `${context} modelCredential`);
|
|
15681
|
-
if (typeof value.key !== "string" || value.key.trim().length === 0) throw new ValidationError$1(`${context}: modelCredential.key must be a non-empty string`);
|
|
15682
|
-
if (typeof value.baseUrlKey !== "string" || value.baseUrlKey.trim().length === 0) throw new ValidationError$1(`${context}: modelCredential.baseUrlKey must be a non-empty string`);
|
|
15683
|
-
if (value.baseUrlKey === value.key) throw new ValidationError$1(`${context}: modelCredential.baseUrlKey must differ from key`);
|
|
15684
|
-
if (!value.provider || typeof value.provider.get !== "function") throw new ValidationError$1(`${context}: modelCredential.provider must implement get(name)`);
|
|
15685
|
-
const captured = {
|
|
15686
|
-
key: value.key,
|
|
15687
|
-
baseUrlKey: value.baseUrlKey
|
|
15688
|
-
};
|
|
15689
|
-
Object.defineProperty(captured, "provider", {
|
|
15690
|
-
configurable: false,
|
|
15691
|
-
enumerable: false,
|
|
15692
|
-
value: value.provider,
|
|
15693
|
-
writable: false
|
|
15694
|
-
});
|
|
15695
|
-
return Object.freeze(captured);
|
|
15064
|
+
return TERMINAL_DECISIONS.includes(decision);
|
|
15696
15065
|
}
|
|
15697
|
-
function
|
|
15698
|
-
|
|
15699
|
-
|
|
15700
|
-
|
|
15066
|
+
function emitRunLoopHook(options, event) {
|
|
15067
|
+
notifyRuntimeHookEvent(options.ctx.hooks, {
|
|
15068
|
+
id: `${event.runId}:${event.target}:${event.phase}${event.stepIndex === void 0 ? "" : `:${event.stepIndex}`}`,
|
|
15069
|
+
runId: event.runId,
|
|
15070
|
+
target: event.target,
|
|
15071
|
+
phase: event.phase,
|
|
15072
|
+
timestamp: event.timestamp,
|
|
15073
|
+
stepIndex: event.stepIndex,
|
|
15074
|
+
payload: event.payload,
|
|
15075
|
+
metadata: { producer: "run-loop" }
|
|
15076
|
+
}, { signal: options.ctx.signal });
|
|
15701
15077
|
}
|
|
15702
|
-
function
|
|
15703
|
-
|
|
15078
|
+
async function emitTrace(emitter, event) {
|
|
15079
|
+
if (!emitter) return;
|
|
15080
|
+
await emitter.emit(event);
|
|
15081
|
+
}
|
|
15082
|
+
/**
|
|
15083
|
+
* Stable hash for the trace payload. Not cryptographic — only used so
|
|
15084
|
+
* downstream eval pipelines can group iterations whose task / output is the
|
|
15085
|
+
* same. Bare structural hash; non-JSON values stringify via their `toString`.
|
|
15086
|
+
*/
|
|
15087
|
+
function hashJson(value) {
|
|
15088
|
+
let str;
|
|
15704
15089
|
try {
|
|
15705
|
-
|
|
15090
|
+
str = JSON.stringify(value) ?? String(value);
|
|
15706
15091
|
} catch {
|
|
15707
|
-
|
|
15092
|
+
str = String(value);
|
|
15708
15093
|
}
|
|
15709
|
-
|
|
15710
|
-
return target.toString().replace(/\/$/u, "");
|
|
15094
|
+
return fnv1a32Hex(str);
|
|
15711
15095
|
}
|
|
15712
|
-
|
|
15713
|
-
|
|
15714
|
-
|
|
15715
|
-
|
|
15716
|
-
const
|
|
15717
|
-
|
|
15718
|
-
|
|
15719
|
-
value = await credential.provider.get(key);
|
|
15720
|
-
} catch {
|
|
15721
|
-
throw new ValidationError$1(`${context}: modelCredential provider failed for '${key}'`);
|
|
15722
|
-
}
|
|
15723
|
-
if (typeof value !== "string" || value.length === 0 || /[\r\n\0]/u.test(value)) throw new ValidationError$1(`${context}: modelCredential provider has no usable value for '${key}'`);
|
|
15724
|
-
return value;
|
|
15725
|
-
};
|
|
15726
|
-
const [token, baseUrl] = await Promise.all([resolveValue(credential.key), resolveValue(credential.baseUrlKey)]);
|
|
15096
|
+
//#endregion
|
|
15097
|
+
//#region src/runtime/sandbox-executor-output.ts
|
|
15098
|
+
/** Project the complete Sandbox event stream into the standard executor result shape. */
|
|
15099
|
+
function sandboxLeafOutputFromEvents(events) {
|
|
15100
|
+
const content = collectAgentFinalMessageText(events) ?? collectAgentResponseText(events);
|
|
15101
|
+
const toolCalls = sandboxToolCalls(events);
|
|
15102
|
+
const servedBackend = lastServedBackend(events);
|
|
15727
15103
|
return {
|
|
15728
|
-
|
|
15729
|
-
|
|
15104
|
+
events,
|
|
15105
|
+
content,
|
|
15106
|
+
output: sandboxOutputMarker(content, events.some(carriesText)),
|
|
15107
|
+
...servedBackend === void 0 ? {} : { servedBackend },
|
|
15108
|
+
...toolCalls.length === 0 ? {} : { toolCalls }
|
|
15730
15109
|
};
|
|
15731
15110
|
}
|
|
15111
|
+
/**
|
|
15112
|
+
* Classify observed text into the explicit output marker. `sawTextEvent` says whether the stream
|
|
15113
|
+
* carried a text-bearing event at all, which is what separates a box that answered nothing from a
|
|
15114
|
+
* box whose answer was never observed.
|
|
15115
|
+
*/
|
|
15116
|
+
function sandboxOutputMarker(content, sawTextEvent = content !== void 0) {
|
|
15117
|
+
if (content !== void 0 && content.length > 0) return {
|
|
15118
|
+
kind: "text",
|
|
15119
|
+
bytes: Buffer.byteLength(content, "utf8")
|
|
15120
|
+
};
|
|
15121
|
+
return sawTextEvent ? { kind: "empty" } : { kind: "absent" };
|
|
15122
|
+
}
|
|
15123
|
+
/** True when the event carries a text field a turn's answer could come from, empty or not. */
|
|
15124
|
+
function carriesText(event) {
|
|
15125
|
+
if (!event || typeof event !== "object") return false;
|
|
15126
|
+
const data = event.data && typeof event.data === "object" ? event.data : {};
|
|
15127
|
+
if (typeof data.finalText === "string" || typeof data.text === "string") return true;
|
|
15128
|
+
if (typeof data.delta === "string") return true;
|
|
15129
|
+
const part = data.part && typeof data.part === "object" ? data.part : void 0;
|
|
15130
|
+
return part !== void 0 && part.type === "text" && typeof part.text === "string";
|
|
15131
|
+
}
|
|
15132
|
+
/** The last served identity the platform reported across a turn's events, or `undefined`. */
|
|
15133
|
+
function lastServedBackend(events) {
|
|
15134
|
+
let served;
|
|
15135
|
+
for (const event of events) {
|
|
15136
|
+
const next = sandboxEventServedBackend(event);
|
|
15137
|
+
if (next !== void 0) served = next;
|
|
15138
|
+
}
|
|
15139
|
+
return served;
|
|
15140
|
+
}
|
|
15141
|
+
function sandboxToolCalls(events) {
|
|
15142
|
+
const state = createSandboxToolPartState();
|
|
15143
|
+
return events.flatMap((event) => mapSandboxToolEvent(event, state).flatMap((projected) => projected.type === "tool_call" ? [{
|
|
15144
|
+
...projected.toolCallId === void 0 ? {} : { id: projected.toolCallId },
|
|
15145
|
+
name: projected.toolName,
|
|
15146
|
+
arguments: projected.args ?? {}
|
|
15147
|
+
}] : []));
|
|
15148
|
+
}
|
|
15732
15149
|
//#endregion
|
|
15733
15150
|
//#region src/runtime/codex-rollout-store.ts
|
|
15734
15151
|
const zeroCounters = {
|
|
@@ -17179,9 +16596,6 @@ async function cancelBridgeRunToTerminal(seam, run, grace, stopSignal) {
|
|
|
17179
16596
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
17180
16597
|
}
|
|
17181
16598
|
}
|
|
17182
|
-
function errorMessage(error) {
|
|
17183
|
-
return error instanceof Error ? error.message : String(error);
|
|
17184
|
-
}
|
|
17185
16599
|
/** Read only the Router-owned one-sided field from an error body. */
|
|
17186
16600
|
function providerDispatchFromErrorBody(body) {
|
|
17187
16601
|
let parsed;
|
|
@@ -17197,13 +16611,7 @@ function providerDispatchFromErrorBody(body) {
|
|
|
17197
16611
|
}
|
|
17198
16612
|
//#endregion
|
|
17199
16613
|
//#region src/runtime/supervise/node-status.ts
|
|
17200
|
-
/**
|
|
17201
|
-
* Which {@link NodeStatus} values mean the node is finished.
|
|
17202
|
-
*
|
|
17203
|
-
* `'waiting'` is deliberately NOT terminal: a wait-state node holds no executor, no box and no
|
|
17204
|
-
* conserved budget, so it is neither in flight nor settled. Callers that must also exclude it say
|
|
17205
|
-
* so at their own site, because "finished" and "not a live worker" are different questions.
|
|
17206
|
-
*/
|
|
16614
|
+
/** Which {@link NodeStatus} values mean the node is finished. */
|
|
17207
16615
|
const terminalNodeStatuses = /* @__PURE__ */ new Set([
|
|
17208
16616
|
"done",
|
|
17209
16617
|
"failed",
|
|
@@ -17613,11 +17021,20 @@ function renderPeerMail(message, nonce) {
|
|
|
17613
17021
|
function createInbox() {
|
|
17614
17022
|
const pending = [];
|
|
17615
17023
|
let live = null;
|
|
17024
|
+
let waiters = [];
|
|
17616
17025
|
return {
|
|
17026
|
+
nextDelivery() {
|
|
17027
|
+
return new Promise((resolve) => {
|
|
17028
|
+
waiters.push(resolve);
|
|
17029
|
+
});
|
|
17030
|
+
},
|
|
17617
17031
|
deliver(msg) {
|
|
17618
17032
|
const m = parseDown(msg);
|
|
17619
17033
|
if (!m) return false;
|
|
17620
17034
|
pending.push(m);
|
|
17035
|
+
const woken = waiters;
|
|
17036
|
+
waiters = [];
|
|
17037
|
+
for (const wake of woken) wake();
|
|
17621
17038
|
if (m.interrupt && live && !live.signal.aborted) live.abort();
|
|
17622
17039
|
return true;
|
|
17623
17040
|
},
|
|
@@ -19171,8 +18588,7 @@ function createSteerableSandboxSession(args) {
|
|
|
19171
18588
|
let preservationFailure;
|
|
19172
18589
|
let preservationError;
|
|
19173
18590
|
let executionFailure;
|
|
19174
|
-
const
|
|
19175
|
-
const lineage = createSandboxLineage(args.sandboxClient, capabilities, {
|
|
18591
|
+
const lineage = createSandboxLineage(args.sandboxClient, {
|
|
19176
18592
|
maxConcurrency: 1,
|
|
19177
18593
|
failOnDestroyError: args.failOnDestroyError
|
|
19178
18594
|
});
|
|
@@ -20923,9 +20339,6 @@ function bridgeOutputText(out) {
|
|
|
20923
20339
|
return String(out);
|
|
20924
20340
|
}
|
|
20925
20341
|
}
|
|
20926
|
-
function isAsyncIterable$1(value) {
|
|
20927
|
-
return value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function";
|
|
20928
|
-
}
|
|
20929
20342
|
/**
|
|
20930
20343
|
* The leaf `createWorktreeCliExecutor` as a backend-as-data factory: a supervisor-authored
|
|
20931
20344
|
* `AgentProfile` driving claude / codex / opencode on its own worktree. `budgetExempt` like
|
|
@@ -21737,7 +21150,13 @@ async function startRetainedInteractiveRun(options) {
|
|
|
21737
21150
|
});
|
|
21738
21151
|
return createRetainedInteractiveRunHandle(environment, ref, capabilities, request);
|
|
21739
21152
|
}
|
|
21740
|
-
/**
|
|
21153
|
+
/**
|
|
21154
|
+
* Retry one exact start after its provider response may have been lost.
|
|
21155
|
+
*
|
|
21156
|
+
* Resolves to the handle of the admitted process (resumed through the exact,
|
|
21157
|
+
* idempotent start request), or `null` when the provider no longer holds the
|
|
21158
|
+
* environment (not found; nothing remains to stop). @stable
|
|
21159
|
+
*/
|
|
21741
21160
|
async function recoverRetainedInteractiveRun(options) {
|
|
21742
21161
|
options.signal?.throwIfAborted();
|
|
21743
21162
|
if (typeof options.onAdmission !== "function") throw new Error("recoverRetainedInteractiveRun requires an awaited onAdmission durability hook");
|
|
@@ -22169,27 +21588,6 @@ function workerInteractiveAdmissionFile(eventDir, workerId, phase) {
|
|
|
22169
21588
|
assertNoSymlinkDescendant(eventDir, dir, "interactive admission");
|
|
22170
21589
|
return join(dir, `${canonicalCandidateDigest({ workerId: id }).slice(7).slice(0, 32)}.${phase}.admission.json`);
|
|
22171
21590
|
}
|
|
22172
|
-
/** Read all durable admissions for one worker, oldest phase first. */
|
|
22173
|
-
function readWorkerInteractiveAdmissions(eventDir, workerId) {
|
|
22174
|
-
const phases = [
|
|
22175
|
-
"interactive_intent",
|
|
22176
|
-
"interactive_environment",
|
|
22177
|
-
"interactive_started"
|
|
22178
|
-
];
|
|
22179
|
-
const admissions = [];
|
|
22180
|
-
let missingPhase = false;
|
|
22181
|
-
for (const phase of phases) {
|
|
22182
|
-
const file = workerInteractiveAdmissionFile(eventDir, workerId, phase);
|
|
22183
|
-
if (!existsSync(file)) {
|
|
22184
|
-
missingPhase = true;
|
|
22185
|
-
continue;
|
|
22186
|
-
}
|
|
22187
|
-
if (missingPhase) throw new Error(`interactive admission phase '${phase}' exists after a missing earlier phase`);
|
|
22188
|
-
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
22189
|
-
admissions.push(parseAdmission(parsed, workerId, phase));
|
|
22190
|
-
}
|
|
22191
|
-
return admissions;
|
|
22192
|
-
}
|
|
22193
21591
|
/**
|
|
22194
21592
|
* Persist one retained-interactive admission without retaining sensitive start material.
|
|
22195
21593
|
*
|
|
@@ -22848,8 +22246,6 @@ async function prepareScopeResume(opts, events, signal, now, parentId = opts.run
|
|
|
22848
22246
|
view: materializeTreeView(prior),
|
|
22849
22247
|
maxSpawnOrdinal: maxSeqOf(prior, (event) => event.kind === "spawned"),
|
|
22850
22248
|
maxCursorSeq: reservedCursorFloor(prior),
|
|
22851
|
-
maxWaitOrdinal: maxSeqOf(prior, (event) => event.kind === "waiting"),
|
|
22852
|
-
waits: pendingWaits(prior),
|
|
22853
22249
|
keys: keyedAssignments(prior, settled),
|
|
22854
22250
|
priorSpend: sumMeasuredSpendFromEvents(prior)
|
|
22855
22251
|
}
|
|
@@ -23245,6 +22641,18 @@ function registerScopeRetainedOwner(scope, args) {
|
|
|
23245
22641
|
nextSequence: () => state.nextSequence(),
|
|
23246
22642
|
now: args.now
|
|
23247
22643
|
}),
|
|
22644
|
+
onWorkspaceCapture: workspaceCaptureWriter({
|
|
22645
|
+
journal: args.journal,
|
|
22646
|
+
rootId: args.rootId,
|
|
22647
|
+
nodeId: args.nodeId,
|
|
22648
|
+
nextSequence: () => state.nextSequence(),
|
|
22649
|
+
now: args.now
|
|
22650
|
+
}),
|
|
22651
|
+
onEndOfTurnWork: (work) => {
|
|
22652
|
+
const prior = state.turnEnd;
|
|
22653
|
+
const settled = work.catch(() => void 0);
|
|
22654
|
+
state.turnEnd = prior === void 0 ? settled : Promise.all([prior, settled]).then(() => {});
|
|
22655
|
+
},
|
|
23248
22656
|
onResult: async (result) => {
|
|
23249
22657
|
scope.signal.throwIfAborted();
|
|
23250
22658
|
assertValidSpend(result.spent, "retained owner result");
|
|
@@ -23274,10 +22682,16 @@ function registerScopeRetainedOwner(scope, args) {
|
|
|
23274
22682
|
function scopeRetainedOwnerContext(scope) {
|
|
23275
22683
|
return owners.get(scope)?.context;
|
|
23276
22684
|
}
|
|
23277
|
-
/**
|
|
23278
|
-
|
|
22685
|
+
/**
|
|
22686
|
+
* Bind cleanup before replay can return an already accepted owner result. `checkpointCapture` is
|
|
22687
|
+
* where each checkpoint's files are stored before the checkpoint is deleted; without it, no
|
|
22688
|
+
* checkpoint of this owner is deleted.
|
|
22689
|
+
*/
|
|
22690
|
+
function bindScopeRetainedOwnerProvider(scope, provider, checkpointCapture) {
|
|
23279
22691
|
const state = owners.get(scope);
|
|
23280
|
-
if (state
|
|
22692
|
+
if (state === void 0) return;
|
|
22693
|
+
state.provider = provider;
|
|
22694
|
+
if (checkpointCapture !== void 0) state.checkpointCapture = checkpointCapture;
|
|
23281
22695
|
}
|
|
23282
22696
|
/** Bind the owner cleanup policy selected by its provider executor. */
|
|
23283
22697
|
function bindScopeRetainedOwnerWorkspaceRetention(scope, enabled) {
|
|
@@ -23452,7 +22866,8 @@ async function checkpointOwnerWorkspace(scope, state) {
|
|
|
23452
22866
|
seq: state.nextSequence(),
|
|
23453
22867
|
at
|
|
23454
22868
|
});
|
|
23455
|
-
|
|
22869
|
+
const events = await args.journal.loadTree(args.rootId) ?? [];
|
|
22870
|
+
await cleanCheckpoints(state, branching, pendingCheckpoints(events, state).filter((event) => event.environmentId === environment.id && state.captureRefused?.has(event.checkpoint.checkpointId) !== true).slice(0, -2), events, scope.signal);
|
|
23456
22871
|
}
|
|
23457
22872
|
/** Reconstruct authority for this source only; a replacement cannot own its snapshots. */
|
|
23458
22873
|
async function checkpointHandle(state, sourceEnvironmentId, signal) {
|
|
@@ -23475,7 +22890,14 @@ async function checkpointHandle(state, sourceEnvironmentId, signal) {
|
|
|
23475
22890
|
return;
|
|
23476
22891
|
}
|
|
23477
22892
|
}
|
|
23478
|
-
/**
|
|
22893
|
+
/**
|
|
22894
|
+
* An aborted observation cannot prove that the provider never created a snapshot, so a request
|
|
22895
|
+
* whose lookup does not find it keeps its source alive for a later lookup, with one exception: a
|
|
22896
|
+
* provider that answers `not_found` for a request older than {@link WORKSPACE_CHECKPOINT_ABSENT_AFTER_MS}
|
|
22897
|
+
* never created it. No operation that request started can still be in flight, and keeping the
|
|
22898
|
+
* source then holds a sandbox for nothing: on Discovery Lab runs terraform-economics-20261005e and
|
|
22899
|
+
* f, each root was kept after its run settled over requests made hours earlier.
|
|
22900
|
+
*/
|
|
23479
22901
|
async function reconcileCheckpointRequests(state, events) {
|
|
23480
22902
|
const operationKey = (operation) => JSON.stringify([operation.idempotencyKey, operation.requestDigest]);
|
|
23481
22903
|
const resolved = new Set(events.flatMap((event) => event.kind === "workspace-checkpoint" && event.id === state.args.nodeId && event.provider === state.provider?.name ? [operationKey(event.checkpoint)] : []));
|
|
@@ -23490,7 +22912,29 @@ async function reconcileCheckpointRequests(state, events) {
|
|
|
23490
22912
|
idempotencyKey: event.request.idempotencyKey,
|
|
23491
22913
|
requestDigest: event.request.requestDigest
|
|
23492
22914
|
}, { signal }), signal, "retained owner checkpoint lookup timed out");
|
|
23493
|
-
if (result.status
|
|
22915
|
+
if (result.status === "not_found") {
|
|
22916
|
+
const requestedAt = Date.parse(event.at);
|
|
22917
|
+
if (Number.isFinite(requestedAt) && state.args.now() - requestedAt >= 12e5) continue;
|
|
22918
|
+
unconfirmed.push({
|
|
22919
|
+
event,
|
|
22920
|
+
reason: "lookup not_found while the request may be in flight"
|
|
22921
|
+
});
|
|
22922
|
+
continue;
|
|
22923
|
+
}
|
|
22924
|
+
if (result.status !== "found") {
|
|
22925
|
+
unconfirmed.push({
|
|
22926
|
+
event,
|
|
22927
|
+
reason: `lookup ${result.status}${"message" in result && result.message ? `: ${result.message}` : ""}`
|
|
22928
|
+
});
|
|
22929
|
+
continue;
|
|
22930
|
+
}
|
|
22931
|
+
if (!workspaceCheckpointResultMatchesRequest(event.request, result)) {
|
|
22932
|
+
unconfirmed.push({
|
|
22933
|
+
event,
|
|
22934
|
+
reason: "lookup found a checkpoint for another request"
|
|
22935
|
+
});
|
|
22936
|
+
continue;
|
|
22937
|
+
}
|
|
23494
22938
|
const recovered = {
|
|
23495
22939
|
kind: "workspace-checkpoint",
|
|
23496
22940
|
id: event.id,
|
|
@@ -23503,8 +22947,11 @@ async function reconcileCheckpointRequests(state, events) {
|
|
|
23503
22947
|
};
|
|
23504
22948
|
await state.args.journal.appendEvent(state.args.rootId, recovered);
|
|
23505
22949
|
events.push(recovered);
|
|
23506
|
-
} catch {
|
|
23507
|
-
unconfirmed.push(
|
|
22950
|
+
} catch (error) {
|
|
22951
|
+
unconfirmed.push({
|
|
22952
|
+
event,
|
|
22953
|
+
reason: `lookup failed: ${errorText(error instanceof Error ? error.message : error)}`
|
|
22954
|
+
});
|
|
23508
22955
|
}
|
|
23509
22956
|
}
|
|
23510
22957
|
return unconfirmed;
|
|
@@ -23513,10 +22960,35 @@ function pendingCheckpoints(events, state) {
|
|
|
23513
22960
|
const confirmed = new Set(events.flatMap((event) => event.kind === "workspace-checkpoint-cleanup" && event.id === state.args.nodeId && event.provider === state.provider?.name && event.confirmed ? [JSON.stringify([event.environmentId, event.checkpointId])] : []));
|
|
23514
22961
|
return events.filter((event) => event.kind === "workspace-checkpoint" && event.id === state.args.nodeId && event.provider === state.provider?.name && !confirmed.has(JSON.stringify([event.environmentId, event.checkpoint.checkpointId])));
|
|
23515
22962
|
}
|
|
23516
|
-
/**
|
|
23517
|
-
|
|
22963
|
+
/**
|
|
22964
|
+
* Delete checkpoints, each only after its files are captured into the run's content-addressed
|
|
22965
|
+
* store; a checkpoint no capture holds is kept, and the cleanup record says why. Only the
|
|
22966
|
+
* source-scoped handle can fork a checkpoint and attest its deletion, including after its source
|
|
22967
|
+
* is lost. `parent` cancels the captures (the owner's scope during rotation); each provider
|
|
22968
|
+
* operation also has its own bound.
|
|
22969
|
+
*/
|
|
22970
|
+
async function cleanCheckpoints(state, branching, checkpoints, events, parent) {
|
|
23518
22971
|
const unconfirmed = [];
|
|
22972
|
+
const refused = [];
|
|
23519
22973
|
for (const event of checkpoints) {
|
|
22974
|
+
const captured = await captureBeforeCleanup(state, branching, event, events, parent);
|
|
22975
|
+
if ("reason" in captured) {
|
|
22976
|
+
(state.captureRefused ??= /* @__PURE__ */ new Set()).add(event.checkpoint.checkpointId);
|
|
22977
|
+
refused.push(captured);
|
|
22978
|
+
await state.args.journal.appendEvent(state.args.rootId, {
|
|
22979
|
+
kind: "workspace-checkpoint-cleanup",
|
|
22980
|
+
id: state.args.nodeId,
|
|
22981
|
+
provider: event.provider,
|
|
22982
|
+
environmentId: event.environmentId,
|
|
22983
|
+
checkpointId: event.checkpoint.checkpointId,
|
|
22984
|
+
confirmed: false,
|
|
22985
|
+
refused: captured.reason.slice(0, 1e3),
|
|
22986
|
+
seq: state.nextSequence(),
|
|
22987
|
+
at: new Date(state.args.now()).toISOString()
|
|
22988
|
+
});
|
|
22989
|
+
continue;
|
|
22990
|
+
}
|
|
22991
|
+
state.captureRefused?.delete(event.checkpoint.checkpointId);
|
|
23520
22992
|
const material = {
|
|
23521
22993
|
kind: "checkpoint",
|
|
23522
22994
|
targetId: event.checkpoint.checkpointId,
|
|
@@ -23530,6 +23002,7 @@ async function deleteCheckpoints(state, branching, checkpoints, signal) {
|
|
|
23530
23002
|
let confirmed = false;
|
|
23531
23003
|
try {
|
|
23532
23004
|
if (branching === void 0) throw new Error("source-scoped checkpoint handle unavailable");
|
|
23005
|
+
const signal = AbortSignal.timeout(3e4);
|
|
23533
23006
|
const acknowledgement = await runAbortable(() => branching.deleteCheckpoint(request, { signal }), signal, "retained owner checkpoint cleanup timed out");
|
|
23534
23007
|
confirmed = workspaceCleanupAcknowledgementMatches(request, acknowledgement) && (acknowledgement.status === "deleted" || acknowledgement.status === "already_absent");
|
|
23535
23008
|
} catch {}
|
|
@@ -23541,11 +23014,178 @@ async function deleteCheckpoints(state, branching, checkpoints, signal) {
|
|
|
23541
23014
|
environmentId: event.environmentId,
|
|
23542
23015
|
checkpointId: event.checkpoint.checkpointId,
|
|
23543
23016
|
confirmed,
|
|
23017
|
+
capturedDigest: captured.digest,
|
|
23544
23018
|
seq: state.nextSequence(),
|
|
23545
23019
|
at: new Date(state.args.now()).toISOString()
|
|
23546
23020
|
});
|
|
23547
23021
|
}
|
|
23548
|
-
return
|
|
23022
|
+
return {
|
|
23023
|
+
unconfirmed,
|
|
23024
|
+
refused
|
|
23025
|
+
};
|
|
23026
|
+
}
|
|
23027
|
+
/**
|
|
23028
|
+
* The digest of the tree that holds a checkpoint's files, capturing it first when no record says
|
|
23029
|
+
* one exists, or why it cannot be had. The files are read from a fork of the checkpoint: a box
|
|
23030
|
+
* created from the snapshot itself, so the capture is the checkpoint's state and not the live
|
|
23031
|
+
* workspace's, which has moved on. The fork request is journaled before it is made and the fork
|
|
23032
|
+
* destroyed once the capture is journaled, so neither a failure nor a crash leaves it running
|
|
23033
|
+
* unrecorded.
|
|
23034
|
+
*/
|
|
23035
|
+
async function captureBeforeCleanup(state, branching, event, events, parent) {
|
|
23036
|
+
const { args } = state;
|
|
23037
|
+
const checkpointId = event.checkpoint.checkpointId;
|
|
23038
|
+
const recorded = events.find((prior) => prior.kind === "workspace-checkpoint-capture" && prior.id === args.nodeId && prior.provider === event.provider && prior.environmentId === event.environmentId && prior.checkpointId === checkpointId);
|
|
23039
|
+
if (recorded !== void 0) return { digest: recorded.tree.digest };
|
|
23040
|
+
const refuse = (reason, needsSource) => ({
|
|
23041
|
+
event,
|
|
23042
|
+
reason,
|
|
23043
|
+
needsSource
|
|
23044
|
+
});
|
|
23045
|
+
const capture = state.checkpointCapture;
|
|
23046
|
+
if (capture?.port.captureCheckpoint === void 0) return refuse("no checkpoint capture: the owner backend has no captureCheckpoint", false);
|
|
23047
|
+
const provider = state.provider;
|
|
23048
|
+
if (provider?.get === void 0) return refuse("the provider cannot reconstruct a fork of the checkpoint", false);
|
|
23049
|
+
if (branching === void 0) return refuse("source-scoped checkpoint handle unavailable", true);
|
|
23050
|
+
const material = {
|
|
23051
|
+
checkpoint: event.checkpoint,
|
|
23052
|
+
placement: { kind: "sandbox" }
|
|
23053
|
+
};
|
|
23054
|
+
const request = {
|
|
23055
|
+
...material,
|
|
23056
|
+
idempotencyKey: `${event.checkpoint.idempotencyKey}:capture:${state.nextSequence()}`,
|
|
23057
|
+
requestDigest: workspaceForkRequestDigest(material)
|
|
23058
|
+
};
|
|
23059
|
+
const forkSignal = AbortSignal.any([...parent === void 0 ? [] : [parent], AbortSignal.timeout(WORKSPACE_CHECKPOINT_FORK_TIMEOUT_MS)]);
|
|
23060
|
+
let forkEnvironmentId;
|
|
23061
|
+
try {
|
|
23062
|
+
await args.journal.appendEvent(args.rootId, {
|
|
23063
|
+
kind: "workspace-checkpoint-fork-requested",
|
|
23064
|
+
id: args.nodeId,
|
|
23065
|
+
provider: event.provider,
|
|
23066
|
+
environmentId: event.environmentId,
|
|
23067
|
+
checkpointId,
|
|
23068
|
+
idempotencyKey: request.idempotencyKey,
|
|
23069
|
+
requestDigest: request.requestDigest,
|
|
23070
|
+
seq: state.nextSequence(),
|
|
23071
|
+
at: new Date(args.now()).toISOString()
|
|
23072
|
+
});
|
|
23073
|
+
const result = await runAbortable(() => branching.fork(request, { signal: forkSignal }), forkSignal, "checkpoint fork timed out");
|
|
23074
|
+
if (result.status === "created" || result.status === "replayed") forkEnvironmentId = result.environment.environmentId;
|
|
23075
|
+
if (result.status !== "created" && result.status !== "replayed" || !workspaceForkResultMatchesRequest(request, result)) return refuse(`fork ${result.status}${"message" in result ? `: ${result.message}` : ""}`, true);
|
|
23076
|
+
const forkId = result.environment.environmentId;
|
|
23077
|
+
const get = provider.get.bind(provider);
|
|
23078
|
+
const environment = await runAbortable(() => get(forkId), forkSignal, "checkpoint fork lookup timed out");
|
|
23079
|
+
if (environment === null || environment.id !== forkId || environment.provider !== provider.name) return refuse(`fork ${forkId} could not be reconstructed`, true);
|
|
23080
|
+
parent?.throwIfAborted();
|
|
23081
|
+
const tree = await captureProviderCheckpointWorkspace(capture.port, {
|
|
23082
|
+
environment,
|
|
23083
|
+
checkpoint: event.checkpoint,
|
|
23084
|
+
executionId: `${args.nodeId}:checkpoint:${checkpointId}`,
|
|
23085
|
+
profile: capture.profile
|
|
23086
|
+
}, event.marker);
|
|
23087
|
+
await args.journal.appendEvent(args.rootId, {
|
|
23088
|
+
kind: "workspace-checkpoint-capture",
|
|
23089
|
+
id: args.nodeId,
|
|
23090
|
+
provider: event.provider,
|
|
23091
|
+
environmentId: event.environmentId,
|
|
23092
|
+
checkpointId,
|
|
23093
|
+
fork: {
|
|
23094
|
+
idempotencyKey: request.idempotencyKey,
|
|
23095
|
+
environmentId: forkId
|
|
23096
|
+
},
|
|
23097
|
+
tree: {
|
|
23098
|
+
digest: tree.digest,
|
|
23099
|
+
manifest: tree.manifest,
|
|
23100
|
+
files: tree.files,
|
|
23101
|
+
bytes: tree.bytes,
|
|
23102
|
+
storedBytes: tree.storedBytes
|
|
23103
|
+
},
|
|
23104
|
+
seq: state.nextSequence(),
|
|
23105
|
+
at: new Date(args.now()).toISOString()
|
|
23106
|
+
});
|
|
23107
|
+
return { digest: tree.digest };
|
|
23108
|
+
} catch (error) {
|
|
23109
|
+
return refuse(`capture failed: ${errorText(error instanceof Error ? error.message : error)}`, true);
|
|
23110
|
+
} finally {
|
|
23111
|
+
if (forkEnvironmentId !== void 0) await destroyCaptureFork(state, branching, {
|
|
23112
|
+
provider: event.provider,
|
|
23113
|
+
environmentId: event.environmentId,
|
|
23114
|
+
checkpointId,
|
|
23115
|
+
fork: {
|
|
23116
|
+
idempotencyKey: request.idempotencyKey,
|
|
23117
|
+
environmentId: forkEnvironmentId
|
|
23118
|
+
}
|
|
23119
|
+
});
|
|
23120
|
+
}
|
|
23121
|
+
}
|
|
23122
|
+
/** Destroy a capture's fork and journal whether the provider confirmed it. */
|
|
23123
|
+
async function destroyCaptureFork(state, branching, target) {
|
|
23124
|
+
const material = {
|
|
23125
|
+
kind: "fork",
|
|
23126
|
+
targetId: target.fork.environmentId,
|
|
23127
|
+
provider: target.provider
|
|
23128
|
+
};
|
|
23129
|
+
const request = {
|
|
23130
|
+
...material,
|
|
23131
|
+
operationId: `${target.fork.idempotencyKey}:destroy`,
|
|
23132
|
+
requestDigest: workspaceCleanupRequestDigest(material)
|
|
23133
|
+
};
|
|
23134
|
+
let destroyed = false;
|
|
23135
|
+
try {
|
|
23136
|
+
const signal = AbortSignal.timeout(3e4);
|
|
23137
|
+
const acknowledgement = await runAbortable(() => branching.destroyFork(request, { signal }), signal, "checkpoint capture fork cleanup timed out");
|
|
23138
|
+
destroyed = workspaceCleanupAcknowledgementMatches(request, acknowledgement) && (acknowledgement.status === "deleted" || acknowledgement.status === "already_absent");
|
|
23139
|
+
} catch {}
|
|
23140
|
+
await state.args.journal.appendEvent(state.args.rootId, {
|
|
23141
|
+
kind: "workspace-checkpoint-fork-teardown",
|
|
23142
|
+
id: state.args.nodeId,
|
|
23143
|
+
...target,
|
|
23144
|
+
destroyed,
|
|
23145
|
+
seq: state.nextSequence(),
|
|
23146
|
+
at: new Date(state.args.now()).toISOString()
|
|
23147
|
+
});
|
|
23148
|
+
return destroyed;
|
|
23149
|
+
}
|
|
23150
|
+
/**
|
|
23151
|
+
* Destroy every capture fork the journal does not show destroyed: one a crash left between its
|
|
23152
|
+
* request and its teardown, or whose teardown the provider did not confirm. Returns the forks
|
|
23153
|
+
* still not confirmed destroyed, by environment or, when none was ever named, by request key.
|
|
23154
|
+
*/
|
|
23155
|
+
async function settleCaptureForks(state) {
|
|
23156
|
+
const { args } = state;
|
|
23157
|
+
const providerName = state.provider?.name;
|
|
23158
|
+
const events = (await args.journal.loadTree(args.rootId) ?? []).filter((event) => event.id === args.nodeId);
|
|
23159
|
+
const named = (key) => {
|
|
23160
|
+
for (const event of events) if ((event.kind === "workspace-checkpoint-capture" || event.kind === "workspace-checkpoint-fork-teardown") && event.fork.idempotencyKey === key) return event.fork.environmentId;
|
|
23161
|
+
};
|
|
23162
|
+
const destroyed = new Set(events.flatMap((event) => event.kind === "workspace-checkpoint-fork-teardown" && event.destroyed ? [event.fork.idempotencyKey] : []));
|
|
23163
|
+
const unresolved = [];
|
|
23164
|
+
for (const request of events) {
|
|
23165
|
+
if (request.kind !== "workspace-checkpoint-fork-requested" || request.provider !== providerName) continue;
|
|
23166
|
+
if (destroyed.has(request.idempotencyKey)) continue;
|
|
23167
|
+
const signal = AbortSignal.timeout(3e4);
|
|
23168
|
+
const branching = await checkpointHandle(state, request.environmentId, signal);
|
|
23169
|
+
let forkEnvironmentId = named(request.idempotencyKey);
|
|
23170
|
+
if (forkEnvironmentId === void 0 && branching !== void 0) try {
|
|
23171
|
+
const found = await runAbortable(() => branching.lookupFork({
|
|
23172
|
+
idempotencyKey: request.idempotencyKey,
|
|
23173
|
+
requestDigest: request.requestDigest
|
|
23174
|
+
}, { signal }), signal, "checkpoint capture fork lookup timed out");
|
|
23175
|
+
if (found.status === "not_found") continue;
|
|
23176
|
+
if (found.status === "found") forkEnvironmentId = found.environment.environmentId;
|
|
23177
|
+
} catch {}
|
|
23178
|
+
if (forkEnvironmentId === void 0 || branching === void 0 || !await destroyCaptureFork(state, branching, {
|
|
23179
|
+
provider: request.provider,
|
|
23180
|
+
environmentId: request.environmentId,
|
|
23181
|
+
checkpointId: request.checkpointId,
|
|
23182
|
+
fork: {
|
|
23183
|
+
idempotencyKey: request.idempotencyKey,
|
|
23184
|
+
environmentId: forkEnvironmentId
|
|
23185
|
+
}
|
|
23186
|
+
})) unresolved.push(forkEnvironmentId ?? request.idempotencyKey);
|
|
23187
|
+
}
|
|
23188
|
+
return unresolved;
|
|
23549
23189
|
}
|
|
23550
23190
|
/**
|
|
23551
23191
|
* The checkpoint a new environment of this owner starts from: the latest one journaled before
|
|
@@ -23565,33 +23205,111 @@ async function restoreCapable(provider) {
|
|
|
23565
23205
|
}
|
|
23566
23206
|
}
|
|
23567
23207
|
/**
|
|
23568
|
-
* What the owner's next new environment will hold of the lost one, read before the drive so the
|
|
23569
|
-
* re-entered director is told: the checkpoint's time, or undefined when there is nothing to
|
|
23570
|
-
* restore.
|
|
23208
|
+
* What the owner's next new environment will hold of the lost one, read before the drive so the
|
|
23209
|
+
* re-entered director is told: the checkpoint's time, or undefined when there is nothing to
|
|
23210
|
+
* restore.
|
|
23211
|
+
*/
|
|
23212
|
+
async function scopeRetainedOwnerRestorePoint(scope) {
|
|
23213
|
+
const state = owners.get(scope);
|
|
23214
|
+
if (state === void 0 || !await restoreCapable(state.provider)) return void 0;
|
|
23215
|
+
const latest = restoreBefore((await state.args.journal.loadTree(state.args.rootId) ?? []).filter((event) => event.id === state.args.nodeId), -1);
|
|
23216
|
+
return latest === void 0 ? void 0 : {
|
|
23217
|
+
checkpointId: latest.checkpoint.checkpointId,
|
|
23218
|
+
takenAt: latest.at
|
|
23219
|
+
};
|
|
23220
|
+
}
|
|
23221
|
+
/** @internal Whether another owner release could still destroy what the last one left. */
|
|
23222
|
+
function retainedOwnerReleaseRetriable(scope) {
|
|
23223
|
+
return owners.get(scope)?.releaseRetriable ?? false;
|
|
23224
|
+
}
|
|
23225
|
+
/**
|
|
23226
|
+
* Mark one drive of the owner in progress until the returned function is called. An abort settles
|
|
23227
|
+
* the node while the drive is still ending its turn: stopping the harness, copying its session and
|
|
23228
|
+
* handing its workspace capture over. A release that ran then would find no receipt for the turn
|
|
23229
|
+
* and keep the source alive, as on terraform-economics-20261005f, where the root's last attempt was
|
|
23230
|
+
* recorded two minutes after its environment's release.
|
|
23231
|
+
*/
|
|
23232
|
+
function beginScopeRetainedOwnerDrive(scope) {
|
|
23233
|
+
const state = owners.get(scope);
|
|
23234
|
+
if (state === void 0) return () => {};
|
|
23235
|
+
let end;
|
|
23236
|
+
const drive = new Promise((resolve) => {
|
|
23237
|
+
end = resolve;
|
|
23238
|
+
});
|
|
23239
|
+
state.drive = drive;
|
|
23240
|
+
return () => {
|
|
23241
|
+
end();
|
|
23242
|
+
if (state.drive === drive) delete state.drive;
|
|
23243
|
+
};
|
|
23244
|
+
}
|
|
23245
|
+
/** Wait, bounded like a turn's own ending, for the owner's drive in progress to end. */
|
|
23246
|
+
async function ownerDriveEnded(state) {
|
|
23247
|
+
const drive = state.drive;
|
|
23248
|
+
if (drive === void 0) return;
|
|
23249
|
+
let timer;
|
|
23250
|
+
try {
|
|
23251
|
+
await Promise.race([drive, new Promise((resolve) => {
|
|
23252
|
+
timer = setTimeout(resolve, HARNESS_TRANSCRIPT_SETTLE_TIMEOUT_MS);
|
|
23253
|
+
})]);
|
|
23254
|
+
} finally {
|
|
23255
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
23256
|
+
}
|
|
23257
|
+
}
|
|
23258
|
+
/**
|
|
23259
|
+
* Wait for the end-of-turn work the owner's last turn handed over: its workspace capture and the
|
|
23260
|
+
* evidence record of its receipt. A turn hands it over so that its node settles without it; the
|
|
23261
|
+
* owner's next turn and its environment's release wait for it here.
|
|
23262
|
+
*/
|
|
23263
|
+
async function scopeRetainedOwnerTurnEnded(scope) {
|
|
23264
|
+
const state = owners.get(scope);
|
|
23265
|
+
if (state === void 0) return;
|
|
23266
|
+
for (;;) {
|
|
23267
|
+
const pending = state.turnEnd;
|
|
23268
|
+
if (pending === void 0) return;
|
|
23269
|
+
await pending;
|
|
23270
|
+
if (state.turnEnd === pending) {
|
|
23271
|
+
delete state.turnEnd;
|
|
23272
|
+
return;
|
|
23273
|
+
}
|
|
23274
|
+
}
|
|
23275
|
+
}
|
|
23276
|
+
/** @internal Wait for everything this owner still has in flight: the drive ending its turn, the
|
|
23277
|
+
* work that turn handed over, and a release. */
|
|
23278
|
+
async function scopeRetainedOwnerSettled(scope) {
|
|
23279
|
+
const state = owners.get(scope);
|
|
23280
|
+
if (state === void 0) return;
|
|
23281
|
+
await ownerDriveEnded(state);
|
|
23282
|
+
await scopeRetainedOwnerTurnEnded(scope);
|
|
23283
|
+
await state.releasing?.catch(() => void 0);
|
|
23284
|
+
}
|
|
23285
|
+
/**
|
|
23286
|
+
* Release the owner's environment once no turn of it will run again: at the run's final
|
|
23287
|
+
* settlement for the root, and when a nested manager's own scope has finished. A second call while
|
|
23288
|
+
* one is in flight joins it. The release first waits for the end-of-turn capture of the owner's
|
|
23289
|
+
* last turn, so the capture always precedes the destroy.
|
|
23571
23290
|
*/
|
|
23572
|
-
|
|
23291
|
+
function releaseScopeRetainedOwnerEnvironment(scope) {
|
|
23573
23292
|
const state = owners.get(scope);
|
|
23574
|
-
|
|
23575
|
-
|
|
23576
|
-
|
|
23577
|
-
|
|
23578
|
-
|
|
23579
|
-
|
|
23580
|
-
|
|
23581
|
-
|
|
23582
|
-
|
|
23583
|
-
|
|
23293
|
+
const provider = state?.provider;
|
|
23294
|
+
if (state === void 0 || provider === void 0) return Promise.resolve([]);
|
|
23295
|
+
if (state.releasing !== void 0) return state.releasing;
|
|
23296
|
+
const tracked = (async () => {
|
|
23297
|
+
await ownerDriveEnded(state);
|
|
23298
|
+
await scopeRetainedOwnerTurnEnded(scope);
|
|
23299
|
+
return await releaseOwnerEnvironment(state, provider);
|
|
23300
|
+
})().finally(() => {
|
|
23301
|
+
if (state.releasing === tracked) delete state.releasing;
|
|
23302
|
+
});
|
|
23303
|
+
state.releasing = tracked;
|
|
23304
|
+
return tracked;
|
|
23584
23305
|
}
|
|
23585
|
-
|
|
23586
|
-
|
|
23587
|
-
const state = owners.get(scope);
|
|
23588
|
-
if (!state?.provider) return [];
|
|
23589
|
-
const { provider, args } = state;
|
|
23306
|
+
async function releaseOwnerEnvironment(state, provider) {
|
|
23307
|
+
const { args } = state;
|
|
23590
23308
|
state.checkpointsUnavailable ??= "released";
|
|
23591
23309
|
await state.checkpointing?.catch(() => void 0);
|
|
23592
23310
|
const events = [...await args.journal.loadTree(args.rootId) ?? []];
|
|
23593
23311
|
const requestFailures = await reconcileCheckpointRequests(state, events);
|
|
23594
|
-
const pendingSources = new Set(requestFailures.map((event) => event.environmentId));
|
|
23312
|
+
const pendingSources = new Set(requestFailures.map(({ event }) => event.environmentId));
|
|
23595
23313
|
const released = new Set(events.flatMap((event) => event.kind === "environment-teardown" && event.id === args.nodeId && event.provider === provider.name && event.destroyed ? [event.environmentId] : []));
|
|
23596
23314
|
const environments = new Set(events.flatMap((event) => event.id === args.nodeId && event.kind === "execution-admitted" && event.admission.phase === "environment" ? [event.admission.environmentId] : []));
|
|
23597
23315
|
const retentionFailures = state.workspaceRetention || events.some((event) => event.kind === "execution-input" && event.id === args.nodeId && event.workspaceRetention === true) ? await ownerWorkspaceRetentionFailures(events, args.nodeId, args.blobs) : /* @__PURE__ */ new Set();
|
|
@@ -23600,19 +23318,23 @@ async function releaseScopeRetainedOwnerEnvironment(scope) {
|
|
|
23600
23318
|
let lastDetail;
|
|
23601
23319
|
let retriable = false;
|
|
23602
23320
|
const checkpointFailures = [];
|
|
23321
|
+
const checkpointsKept = [];
|
|
23322
|
+
const uncapturedSources = /* @__PURE__ */ new Set();
|
|
23603
23323
|
const pending = pendingCheckpoints(events, state).filter((event) => !retentionFailures.has(event.environmentId));
|
|
23604
23324
|
for (const sourceEnvironmentId of new Set(pending.map((event) => event.environmentId))) {
|
|
23605
|
-
const
|
|
23606
|
-
|
|
23607
|
-
|
|
23325
|
+
const cleaned = await cleanCheckpoints(state, await checkpointHandle(state, sourceEnvironmentId, AbortSignal.timeout(3e4)), pending.filter((event) => event.environmentId === sourceEnvironmentId), events);
|
|
23326
|
+
checkpointFailures.push(...cleaned.unconfirmed);
|
|
23327
|
+
checkpointsKept.push(...cleaned.refused);
|
|
23328
|
+
for (const refusal of cleaned.refused) if (refusal.needsSource) uncapturedSources.add(sourceEnvironmentId);
|
|
23608
23329
|
}
|
|
23330
|
+
const forksUnresolved = await settleCaptureForks(state);
|
|
23609
23331
|
for (const event of checkpointFailures) pendingSources.add(event.environmentId);
|
|
23610
23332
|
for (const environmentId of environments) {
|
|
23611
23333
|
if (released.has(environmentId)) continue;
|
|
23612
23334
|
let destroyed = false;
|
|
23613
23335
|
let detail;
|
|
23614
|
-
if (retentionFailures.has(environmentId) || pendingSources.has(environmentId)) {
|
|
23615
|
-
detail = pendingSources.has(environmentId) ? "checkpoint cleanup unresolved: source preserved for exact lookup and cleanup" : "provider workspace retention: source preserved because the owner execution has no verified workspace receipt";
|
|
23336
|
+
if (retentionFailures.has(environmentId) || pendingSources.has(environmentId) || uncapturedSources.has(environmentId)) {
|
|
23337
|
+
detail = pendingSources.has(environmentId) ? "checkpoint cleanup unresolved: source preserved for exact lookup and cleanup" : uncapturedSources.has(environmentId) ? "checkpoint kept uncaptured: source preserved so its checkpoint can still be forked and captured" : "provider workspace retention: source preserved because the owner execution has no verified workspace receipt";
|
|
23616
23338
|
preserved.push(environmentId);
|
|
23617
23339
|
lastDetail = detail;
|
|
23618
23340
|
} else {
|
|
@@ -23668,19 +23390,36 @@ async function releaseScopeRetainedOwnerEnvironment(scope) {
|
|
|
23668
23390
|
});
|
|
23669
23391
|
}
|
|
23670
23392
|
state.releaseRetriable = retriable;
|
|
23671
|
-
const checkpointTeardowns = [
|
|
23672
|
-
|
|
23673
|
-
|
|
23674
|
-
|
|
23675
|
-
|
|
23676
|
-
|
|
23677
|
-
|
|
23678
|
-
|
|
23679
|
-
|
|
23680
|
-
|
|
23681
|
-
|
|
23682
|
-
|
|
23683
|
-
|
|
23393
|
+
const checkpointTeardowns = [
|
|
23394
|
+
...checkpointFailures.map((event) => ({
|
|
23395
|
+
id: args.nodeId,
|
|
23396
|
+
label: "scope owner workspace checkpoint",
|
|
23397
|
+
runtime: provider.name,
|
|
23398
|
+
status: "done",
|
|
23399
|
+
detail: `checkpoint cleanup unconfirmed: ${event.checkpoint.checkpointId} from ${event.environmentId}`
|
|
23400
|
+
})),
|
|
23401
|
+
...requestFailures.map(({ event, reason }) => ({
|
|
23402
|
+
id: args.nodeId,
|
|
23403
|
+
label: "scope owner checkpoint request",
|
|
23404
|
+
runtime: provider.name,
|
|
23405
|
+
status: "done",
|
|
23406
|
+
detail: `checkpoint request unresolved: ${event.request.idempotencyKey} from ${event.environmentId} (${reason})`
|
|
23407
|
+
})),
|
|
23408
|
+
...checkpointsKept.map(({ event, reason }) => ({
|
|
23409
|
+
id: args.nodeId,
|
|
23410
|
+
label: "scope owner workspace checkpoint",
|
|
23411
|
+
runtime: provider.name,
|
|
23412
|
+
status: "done",
|
|
23413
|
+
detail: `checkpoint kept uncaptured: ${event.checkpoint.checkpointId} from ${event.environmentId} (${reason})`
|
|
23414
|
+
})),
|
|
23415
|
+
...forksUnresolved.length === 0 ? [] : [{
|
|
23416
|
+
id: args.nodeId,
|
|
23417
|
+
label: "scope owner checkpoint capture fork",
|
|
23418
|
+
runtime: provider.name,
|
|
23419
|
+
status: "done",
|
|
23420
|
+
detail: `checkpoint capture fork destruction unconfirmed: ${forksUnresolved.join(", ")}`
|
|
23421
|
+
}]
|
|
23422
|
+
];
|
|
23684
23423
|
return unconfirmed.length > 0 || preserved.length > 0 ? [{
|
|
23685
23424
|
id: args.nodeId,
|
|
23686
23425
|
label: "scope owner",
|
|
@@ -23718,7 +23457,15 @@ function unsettledOwnerRuns(events, nodeId, environmentId) {
|
|
|
23718
23457
|
close();
|
|
23719
23458
|
return [...runs.values()];
|
|
23720
23459
|
}
|
|
23721
|
-
/**
|
|
23460
|
+
/**
|
|
23461
|
+
* Group owner admissions by input and require a verified workspace receipt before deletion.
|
|
23462
|
+
*
|
|
23463
|
+
* The receipt is the attempt's committed result, or an evidence record of the same attempt: a
|
|
23464
|
+
* turn its manager stopped commits its result first and journals its capture as evidence after
|
|
23465
|
+
* it, and a turn that failed journals the capture it took of the stopped session. This runs only
|
|
23466
|
+
* at a release no process will resume, so a complete, verified capture of the exact execution is
|
|
23467
|
+
* the whole of what keeping the source would preserve.
|
|
23468
|
+
*/
|
|
23722
23469
|
async function ownerWorkspaceRetentionFailures(events, nodeId, blobs) {
|
|
23723
23470
|
const attempts = [];
|
|
23724
23471
|
let current;
|
|
@@ -23726,30 +23473,26 @@ async function ownerWorkspaceRetentionFailures(events, nodeId, blobs) {
|
|
|
23726
23473
|
if (current !== void 0) attempts.push(current);
|
|
23727
23474
|
current = void 0;
|
|
23728
23475
|
};
|
|
23476
|
+
const fresh = (executionId) => ({
|
|
23477
|
+
...executionId === void 0 ? {} : { executionId },
|
|
23478
|
+
environmentIds: /* @__PURE__ */ new Set(),
|
|
23479
|
+
controlByEnvironment: /* @__PURE__ */ new Map(),
|
|
23480
|
+
evidence: []
|
|
23481
|
+
});
|
|
23729
23482
|
for (const event of events) {
|
|
23730
23483
|
if (!("id" in event) || event.id !== nodeId) continue;
|
|
23731
23484
|
if (event.kind === "execution-input") {
|
|
23732
23485
|
flush();
|
|
23733
|
-
current = {
|
|
23734
|
-
executionId: `${nodeId}:input:${event.seq}`,
|
|
23735
|
-
environmentIds: /* @__PURE__ */ new Set(),
|
|
23736
|
-
controlByEnvironment: /* @__PURE__ */ new Map()
|
|
23737
|
-
};
|
|
23486
|
+
current = fresh(`${nodeId}:input:${event.seq}`);
|
|
23738
23487
|
} else if (event.kind === "execution-admitted" && event.admission.phase === "environment") {
|
|
23739
23488
|
if (current?.result !== void 0) flush();
|
|
23740
|
-
current ??=
|
|
23741
|
-
environmentIds: /* @__PURE__ */ new Set(),
|
|
23742
|
-
controlByEnvironment: /* @__PURE__ */ new Map()
|
|
23743
|
-
};
|
|
23489
|
+
current ??= fresh();
|
|
23744
23490
|
current.environmentIds.add(event.admission.environmentId);
|
|
23745
23491
|
} else if (event.kind === "execution-admitted" && event.admission.phase === "dispatched") current?.controlByEnvironment.set(event.admission.controlRef.environmentId, event.admission.controlRef);
|
|
23746
23492
|
else if (event.kind === "execution-result") {
|
|
23747
|
-
current ??=
|
|
23748
|
-
environmentIds: /* @__PURE__ */ new Set(),
|
|
23749
|
-
controlByEnvironment: /* @__PURE__ */ new Map()
|
|
23750
|
-
};
|
|
23493
|
+
current ??= fresh();
|
|
23751
23494
|
current.result = event;
|
|
23752
|
-
}
|
|
23495
|
+
} else if (event.kind === "execution-evidence") current?.evidence.push(event);
|
|
23753
23496
|
}
|
|
23754
23497
|
flush();
|
|
23755
23498
|
const failures = /* @__PURE__ */ new Set();
|
|
@@ -23759,25 +23502,30 @@ async function ownerWorkspaceRetentionFailures(events, nodeId, blobs) {
|
|
|
23759
23502
|
for (const environmentId of attempt.environmentIds) failures.add(environmentId);
|
|
23760
23503
|
continue;
|
|
23761
23504
|
}
|
|
23762
|
-
const result = attempt.result;
|
|
23763
|
-
if (result === void 0) {
|
|
23764
|
-
for (const environmentId of attempt.environmentIds) failures.add(environmentId);
|
|
23765
|
-
continue;
|
|
23766
|
-
}
|
|
23767
|
-
let output;
|
|
23768
|
-
try {
|
|
23769
|
-
output = await blobs.get(result.outRef);
|
|
23770
|
-
if (output === void 0 || contentAddress(output) !== result.outRef) throw new Error();
|
|
23771
|
-
} catch {
|
|
23772
|
-
output = void 0;
|
|
23773
|
-
}
|
|
23774
|
-
const snapshot = output !== null && typeof output === "object" ? output.workspaceSnapshot : void 0;
|
|
23775
23505
|
const environmentId = [...attempt.environmentIds][0];
|
|
23776
23506
|
const controlRef = attempt.controlByEnvironment.get(environmentId);
|
|
23777
|
-
|
|
23507
|
+
const receipts = [...attempt.result === void 0 ? [] : [attempt.result.outRef], ...attempt.evidence.map((event) => event.outRef)];
|
|
23508
|
+
let verified = false;
|
|
23509
|
+
for (const outRef of receipts) {
|
|
23510
|
+
const output = await storedOutput(blobs, outRef);
|
|
23511
|
+
if (hasDurableWorkspaceCapture(output, output !== null && typeof output === "object" ? output.workspaceSnapshot : void 0, environmentId, attempt.executionId, controlRef)) {
|
|
23512
|
+
verified = true;
|
|
23513
|
+
break;
|
|
23514
|
+
}
|
|
23515
|
+
}
|
|
23516
|
+
if (!verified) failures.add(environmentId);
|
|
23778
23517
|
}
|
|
23779
23518
|
return failures;
|
|
23780
23519
|
}
|
|
23520
|
+
/** A stored output whose bytes match its content reference, or undefined. */
|
|
23521
|
+
async function storedOutput(blobs, outRef) {
|
|
23522
|
+
try {
|
|
23523
|
+
const output = await blobs.get(outRef);
|
|
23524
|
+
return output === void 0 || contentAddress(output) !== outRef ? void 0 : output;
|
|
23525
|
+
} catch {
|
|
23526
|
+
return;
|
|
23527
|
+
}
|
|
23528
|
+
}
|
|
23781
23529
|
/** A stored result authorizes cleanup only for the exact source and complete capture. */
|
|
23782
23530
|
function hasDurableWorkspaceCapture(output, snapshot, environmentId, executionId, controlRef) {
|
|
23783
23531
|
const parsed = agentCandidateWorkspaceSnapshotEvidenceSchema.safeParse(snapshot);
|
|
@@ -23976,180 +23724,6 @@ function consumeScopeRetainedOwnerResult(scope) {
|
|
|
23976
23724
|
state.acceptedConsumed = true;
|
|
23977
23725
|
}
|
|
23978
23726
|
//#endregion
|
|
23979
|
-
//#region src/runtime/supervise/wait.ts
|
|
23980
|
-
/**
|
|
23981
|
-
*
|
|
23982
|
-
* WAIT-STATES — a supervision-tree node that waits on wall-clock time or an external condition
|
|
23983
|
-
* without holding a worker, an executor, a sandbox, or a single LLM turn.
|
|
23984
|
-
*
|
|
23985
|
-
* A long-horizon run spends most of its wall-clock NOT computing: waiting for CI to finish,
|
|
23986
|
-
* for a nightly job to land, for a human to reply, for a rate-limit window to reopen. Before
|
|
23987
|
-
* this, the only way to express that was to keep something alive and re-ask — which spends
|
|
23988
|
-
* driver tokens per re-ask and pins a process to the wait.
|
|
23989
|
-
*
|
|
23990
|
-
* Two node kinds, both first-class:
|
|
23991
|
-
* - `timer` — wake at an ABSOLUTE wall-clock instant (`untilMs`).
|
|
23992
|
-
* - `poll` — re-run a named predicate every `intervalMs` until it returns true, or until an
|
|
23993
|
-
* absolute `timeoutAtMs` passes (CI status, file existence, an HTTP probe, an
|
|
23994
|
-
* inbox message).
|
|
23995
|
-
*
|
|
23996
|
-
* ── How this differs from `await_event`'s 45s poll fence (`DEFAULT_AWAIT_EVENT_TIMEOUT_MS`) ──
|
|
23997
|
-
*
|
|
23998
|
-
* They look similar and are not the same mechanism. `await_event` is an IN-RUN RENDEZVOUS: the
|
|
23999
|
-
* driver blocks on the coordination bus for the next event from a live worker, and the 45s fence
|
|
24000
|
-
* exists only so a remote MCP request does not exceed the client's timeout — the caller re-polls.
|
|
24001
|
-
* Every re-poll is another driver inference turn (real tokens) against a process that must stay
|
|
24002
|
-
* up, and nothing about that wait is recorded: kill the process and the wait is simply gone.
|
|
24003
|
-
*
|
|
24004
|
-
* A wait-state is a NODE, not a call. It has a node id in the tree, a journal record carrying its
|
|
24005
|
-
* ABSOLUTE deadline, and it settles through the same `Scope.next()` cursor as any worker. Nobody
|
|
24006
|
-
* is blocked on it — the driver can stop reasoning entirely, and the process can die. Cost while
|
|
24007
|
-
* waiting: zero LLM calls, zero executor, zero sandbox, zero conserved budget (a wait reserves
|
|
24008
|
-
* nothing from the pool). The only in-process residue is one timer entry.
|
|
24009
|
-
*
|
|
24010
|
-
* ── Why probes are NAMED, not passed as closures ────────────────────────────────────────────
|
|
24011
|
-
*
|
|
24012
|
-
* A wait must survive a process restart with its original deadline intact, and a closure cannot
|
|
24013
|
-
* be journaled. So a `poll` names its predicate (`probe: 'ci-green'`) and the run resolves it
|
|
24014
|
-
* through a `WaitProbeRegistry`. A brand-new process re-resolves the SAME name against its own
|
|
24015
|
-
* registry and re-arms the wait — which is what makes "kill the box, the wait keeps waiting"
|
|
24016
|
-
* true rather than aspirational.
|
|
24017
|
-
*
|
|
24018
|
-
* Absolute instants for the same reason: `untilMs` / `timeoutAtMs` are epoch ms, not durations,
|
|
24019
|
-
* so a resumed wait counts down from the original arm, not from the restart. `timerAt`/`pollFor`
|
|
24020
|
-
* build them from a duration when that is what the caller has.
|
|
24021
|
-
*
|
|
24022
|
-
* @experimental
|
|
24023
|
-
*/
|
|
24024
|
-
/** Build a `timer` spec from a DURATION. The instant is resolved once, at arm time — a resumed
|
|
24025
|
-
* wait re-uses the journaled instant, never a fresh `now + ms`. */
|
|
24026
|
-
function timerAt(ms, now) {
|
|
24027
|
-
return {
|
|
24028
|
-
kind: "timer",
|
|
24029
|
-
untilMs: now + ms
|
|
24030
|
-
};
|
|
24031
|
-
}
|
|
24032
|
-
/** Build a bounded `poll` spec from a duration. */
|
|
24033
|
-
function pollFor(probe, opts, now) {
|
|
24034
|
-
return {
|
|
24035
|
-
kind: "poll",
|
|
24036
|
-
probe,
|
|
24037
|
-
intervalMs: opts.intervalMs,
|
|
24038
|
-
...opts.timeoutMs !== void 0 ? { timeoutAtMs: now + opts.timeoutMs } : {},
|
|
24039
|
-
...opts.args ? { args: opts.args } : {}
|
|
24040
|
-
};
|
|
24041
|
-
}
|
|
24042
|
-
/** Registry over a plain name→predicate record. */
|
|
24043
|
-
function createWaitProbes(entries) {
|
|
24044
|
-
const map = new Map(Object.entries(entries));
|
|
24045
|
-
return { resolve: (name) => map.get(name) };
|
|
24046
|
-
}
|
|
24047
|
-
/** Narrow a settlement's `out` to a wait outcome — a wait settles on the SAME cursor as workers,
|
|
24048
|
-
* so a driver that mixes them tags them apart with this. */
|
|
24049
|
-
function isWaitOutcome(value) {
|
|
24050
|
-
return typeof value === "object" && value !== null && value.waitOutcome === true;
|
|
24051
|
-
}
|
|
24052
|
-
/** The absolute instant a spec is bounded by, or `undefined` for an unbounded wait. */
|
|
24053
|
-
function waitUntil(spec) {
|
|
24054
|
-
if (spec.kind === "timer") return spec.untilMs;
|
|
24055
|
-
if (spec.kind === "token") return spec.expiresAtMs;
|
|
24056
|
-
return spec.timeoutAtMs;
|
|
24057
|
-
}
|
|
24058
|
-
/** Structural validation, independent of the run. Returns null when the spec is usable. */
|
|
24059
|
-
function validateWaitSpec(spec) {
|
|
24060
|
-
if (spec.kind === "timer") return Number.isFinite(spec.untilMs) ? null : "timer.untilMs must be a finite epoch-ms instant";
|
|
24061
|
-
if (spec.kind === "token") return "token waits are graph-engine-owned; deliver them through engine resume/expire";
|
|
24062
|
-
if (typeof spec.probe !== "string" || spec.probe.length === 0) return "poll.probe must be a non-empty probe name";
|
|
24063
|
-
if (!Number.isFinite(spec.intervalMs) || spec.intervalMs <= 0) return "poll.intervalMs must be > 0";
|
|
24064
|
-
if (spec.timeoutAtMs !== void 0 && !Number.isFinite(spec.timeoutAtMs)) return "poll.timeoutAtMs must be a finite epoch-ms instant";
|
|
24065
|
-
return null;
|
|
24066
|
-
}
|
|
24067
|
-
/**
|
|
24068
|
-
* A wait must not silently outlive the run's HARD wall-clock ceiling. When the conserved pool
|
|
24069
|
-
* carries an absolute `deadlineMs`, a wait bounded past it — or an unbounded poll — is REFUSED at
|
|
24070
|
-
* arm time (`deadline-exceeded`) rather than sleeping through a ceiling the caller already set.
|
|
24071
|
-
* The alternative (extending the pool's deadline to cover the wait) would let a wait override a
|
|
24072
|
-
* hard budget guard, which is exactly what mechanic D forbids; a run that intends week-long waits
|
|
24073
|
-
* either sets a deadline that covers them or sets none.
|
|
24074
|
-
*/
|
|
24075
|
-
function assertWaitWithinDeadline(spec, deadlineMs) {
|
|
24076
|
-
if (deadlineMs <= 0) return true;
|
|
24077
|
-
const until = waitUntil(spec);
|
|
24078
|
-
if (until === void 0) return false;
|
|
24079
|
-
return until <= deadlineMs;
|
|
24080
|
-
}
|
|
24081
|
-
/**
|
|
24082
|
-
* Drive one wait to its resolution. Spends nothing: a `timer` is one sleep, a `poll` is a sleep
|
|
24083
|
-
* between predicate checks. No LLM call, no executor, no box, no budget reservation.
|
|
24084
|
-
*/
|
|
24085
|
-
async function runWait(args) {
|
|
24086
|
-
const { spec, label, armedAt, resumed, signal, now } = args;
|
|
24087
|
-
const sleep = args.sleep ?? defaultSleep$1;
|
|
24088
|
-
if (signal.aborted) return {
|
|
24089
|
-
kind: "cancelled",
|
|
24090
|
-
reason: "aborted before arming"
|
|
24091
|
-
};
|
|
24092
|
-
if (spec.kind === "timer") {
|
|
24093
|
-
const remaining = spec.untilMs - now();
|
|
24094
|
-
if (remaining > 0) await sleep(remaining, signal);
|
|
24095
|
-
if (signal.aborted) return {
|
|
24096
|
-
kind: "cancelled",
|
|
24097
|
-
reason: "wait aborted"
|
|
24098
|
-
};
|
|
24099
|
-
return {
|
|
24100
|
-
kind: "woke",
|
|
24101
|
-
outcome: outcome("timer", "fired", label, spec.untilMs, armedAt, now(), 0, 0, resumed)
|
|
24102
|
-
};
|
|
24103
|
-
}
|
|
24104
|
-
if (spec.kind === "token") throw new ValidationError$1(`wait '${label}': token waits are graph-engine-owned; deliver them through engine resume/expire`);
|
|
24105
|
-
const probe = args.probes?.resolve(spec.probe);
|
|
24106
|
-
if (!probe) throw new ValidationError$1(`wait '${label}': poll probe '${spec.probe}' is not registered in this run's WaitProbeRegistry`);
|
|
24107
|
-
let polls = 0;
|
|
24108
|
-
let probeErrors = 0;
|
|
24109
|
-
for (;;) {
|
|
24110
|
-
if (signal.aborted) return {
|
|
24111
|
-
kind: "cancelled",
|
|
24112
|
-
reason: "wait aborted"
|
|
24113
|
-
};
|
|
24114
|
-
polls += 1;
|
|
24115
|
-
let fired = false;
|
|
24116
|
-
try {
|
|
24117
|
-
fired = await probe(spec.args, signal);
|
|
24118
|
-
} catch {
|
|
24119
|
-
probeErrors += 1;
|
|
24120
|
-
}
|
|
24121
|
-
if (fired) return {
|
|
24122
|
-
kind: "woke",
|
|
24123
|
-
outcome: outcome("poll", "fired", label, spec.timeoutAtMs, armedAt, now(), polls, probeErrors, resumed)
|
|
24124
|
-
};
|
|
24125
|
-
if (spec.timeoutAtMs !== void 0 && now() >= spec.timeoutAtMs) return {
|
|
24126
|
-
kind: "woke",
|
|
24127
|
-
outcome: outcome("poll", "timeout", label, spec.timeoutAtMs, armedAt, now(), polls, probeErrors, resumed)
|
|
24128
|
-
};
|
|
24129
|
-
await sleep(spec.timeoutAtMs === void 0 ? spec.intervalMs : Math.min(spec.intervalMs, Math.max(0, spec.timeoutAtMs - now())), signal);
|
|
24130
|
-
}
|
|
24131
|
-
}
|
|
24132
|
-
function outcome(kind, settled, label, untilMs, armedAt, wokenAt, polls, probeErrors, resumed) {
|
|
24133
|
-
return {
|
|
24134
|
-
waitOutcome: true,
|
|
24135
|
-
kind,
|
|
24136
|
-
settled,
|
|
24137
|
-
label,
|
|
24138
|
-
...untilMs !== void 0 ? { untilMs } : {},
|
|
24139
|
-
armedAt,
|
|
24140
|
-
wokenAt,
|
|
24141
|
-
polls,
|
|
24142
|
-
probeErrors,
|
|
24143
|
-
resumed
|
|
24144
|
-
};
|
|
24145
|
-
}
|
|
24146
|
-
/** `setTimeout` that resolves early (and clears) when the scope aborts, so a cancelled wait
|
|
24147
|
-
* releases the event loop immediately instead of holding the process to its deadline. */
|
|
24148
|
-
function defaultSleep$1(ms, signal) {
|
|
24149
|
-
if (ms <= 0 || signal?.aborted) return Promise.resolve();
|
|
24150
|
-
return sleep(ms, signal);
|
|
24151
|
-
}
|
|
24152
|
-
//#endregion
|
|
24153
23727
|
//#region src/runtime/supervise/scope.ts
|
|
24154
23728
|
/**
|
|
24155
23729
|
*
|
|
@@ -24191,6 +23765,10 @@ const teardownRetriable = /* @__PURE__ */ new WeakMap();
|
|
|
24191
23765
|
const ownerUnconfirmedByScope = /* @__PURE__ */ new WeakMap();
|
|
24192
23766
|
/** The scope a recursive executor owns, so the settlement retry reaches its descendants. */
|
|
24193
23767
|
const nestedTeardownScopes = /* @__PURE__ */ new WeakMap();
|
|
23768
|
+
/** Each scope's reader of the manager scopes its children own, read live. */
|
|
23769
|
+
const nestedManagerScopeReaders = /* @__PURE__ */ new WeakMap();
|
|
23770
|
+
/** A manager scope's release of its own environment, started once and joined by later callers. */
|
|
23771
|
+
const managerEnvironmentReleases = /* @__PURE__ */ new WeakMap();
|
|
24194
23772
|
/** Scopes whose settlement released retained environments: none of their retained children is
|
|
24195
23773
|
* kept for a resume any longer. */
|
|
24196
23774
|
const releasingScopes = /* @__PURE__ */ new WeakSet();
|
|
@@ -24268,9 +23846,47 @@ async function releaseRetainedEnvironments(scope) {
|
|
|
24268
23846
|
releasingScopes.add(scope);
|
|
24269
23847
|
const release = retainedReleasers.get(scope);
|
|
24270
23848
|
if (release) await release();
|
|
23849
|
+
await releaseNestedManagerEnvironments(scope);
|
|
24271
23850
|
ownerUnconfirmedByScope.set(scope, await releaseScopeRetainedOwnerEnvironment(scope));
|
|
24272
23851
|
return unconfirmedTeardowns(scope);
|
|
24273
23852
|
}
|
|
23853
|
+
/** The manager scopes this scope's children own, read live. */
|
|
23854
|
+
function nestedManagerScopes(scope) {
|
|
23855
|
+
return nestedManagerScopeReaders.get(scope)?.() ?? [];
|
|
23856
|
+
}
|
|
23857
|
+
async function releaseNestedManagerEnvironments(scope) {
|
|
23858
|
+
await Promise.all(nestedManagerScopes(scope).map(async (nested) => {
|
|
23859
|
+
await releaseNestedManagerEnvironments(nested);
|
|
23860
|
+
if (!ownerUnconfirmedByScope.has(nested)) await releaseManagerEnvironment(nested);
|
|
23861
|
+
}));
|
|
23862
|
+
}
|
|
23863
|
+
/**
|
|
23864
|
+
* @internal Release the environment a manager kept between its turns, once its own scope has
|
|
23865
|
+
* finished and no turn of it will run again. The release first waits for the manager's last
|
|
23866
|
+
* end-of-turn capture, so it runs in the background of the manager's settlement; a later call,
|
|
23867
|
+
* including the run's settlement sweep, joins it. Its receipt is journaled in the manager's own
|
|
23868
|
+
* tree. It is the manager's own resource, not one of its descendants, so it never changes the
|
|
23869
|
+
* teardown answer its executor gives the parent.
|
|
23870
|
+
*/
|
|
23871
|
+
function releaseManagerEnvironment(scope) {
|
|
23872
|
+
let release = managerEnvironmentReleases.get(scope);
|
|
23873
|
+
if (release === void 0) {
|
|
23874
|
+
release = releaseScopeRetainedOwnerEnvironment(scope).then(() => void 0);
|
|
23875
|
+
managerEnvironmentReleases.set(scope, release);
|
|
23876
|
+
}
|
|
23877
|
+
return release;
|
|
23878
|
+
}
|
|
23879
|
+
/**
|
|
23880
|
+
* @internal Wait for the work every manager in this scope's tree still has in flight after its
|
|
23881
|
+
* node settled: the end-of-turn capture its last turn handed over, and the release of its
|
|
23882
|
+
* environment. The run's settlement awaits it under either retention policy, so nothing writes to
|
|
23883
|
+
* the run's journal after the run settles and no capture is cut short by the process exiting.
|
|
23884
|
+
*/
|
|
23885
|
+
async function settleManagerWork(scope) {
|
|
23886
|
+
await Promise.all(nestedManagerScopes(scope).map((nested) => settleManagerWork(nested)));
|
|
23887
|
+
await scopeRetainedOwnerSettled(scope);
|
|
23888
|
+
await managerEnvironmentReleases.get(scope)?.catch(() => void 0);
|
|
23889
|
+
}
|
|
24274
23890
|
/**
|
|
24275
23891
|
* @internal Close the cursor slot of every retained child this scope and its nested scopes still
|
|
24276
23892
|
* hold open, once the settlement is final and the release and its retries are done.
|
|
@@ -24441,13 +24057,11 @@ function createScope(args) {
|
|
|
24441
24057
|
} : void 0;
|
|
24442
24058
|
let spawnOrdinal = args.resumeFrom ? args.resumeFrom.maxSpawnOrdinal + 1 : 0;
|
|
24443
24059
|
let cursorSeq = args.resumeFrom ? args.resumeFrom.maxCursorSeq + 1 : 0;
|
|
24444
|
-
let waitOrdinal = args.resumeFrom ? args.resumeFrom.maxWaitOrdinal + 1 : 0;
|
|
24445
24060
|
let meterSeq = 0;
|
|
24446
24061
|
let ownerTurns = 0;
|
|
24447
24062
|
let ownerLastTurnAt = 0;
|
|
24448
24063
|
let progressSeq = 0;
|
|
24449
24064
|
const now = args.now ?? Date.now;
|
|
24450
|
-
const unclaimedWaits = new Map((args.resumeFrom?.waits ?? []).map((w) => [w.label, w]));
|
|
24451
24065
|
const keyed = /* @__PURE__ */ new Map();
|
|
24452
24066
|
for (const [key, prior] of args.resumeFrom?.keys ?? []) if (prior.state === "completed" && prior.settled?.kind === "done") {
|
|
24453
24067
|
if (prior.identity === void 0) continue;
|
|
@@ -24694,6 +24308,14 @@ function createScope(args) {
|
|
|
24694
24308
|
}, "accepted retained child result");
|
|
24695
24309
|
live.recoveryPending = false;
|
|
24696
24310
|
});
|
|
24311
|
+
let captureSeq = 0;
|
|
24312
|
+
const onWorkspaceCapture = workspaceCaptureWriter({
|
|
24313
|
+
journal: args.journal,
|
|
24314
|
+
rootId: args.root,
|
|
24315
|
+
nodeId: id,
|
|
24316
|
+
nextSequence: () => captureSeq++,
|
|
24317
|
+
now
|
|
24318
|
+
});
|
|
24697
24319
|
const onPause = (pause) => retainedWrite(async () => {
|
|
24698
24320
|
await args.journal.appendEvent(args.root, {
|
|
24699
24321
|
kind: "paused",
|
|
@@ -24722,6 +24344,7 @@ function createScope(args) {
|
|
|
24722
24344
|
now
|
|
24723
24345
|
}),
|
|
24724
24346
|
onPause,
|
|
24347
|
+
onWorkspaceCapture,
|
|
24725
24348
|
continueInvocation: async (nextTask) => {
|
|
24726
24349
|
controller.signal.throwIfAborted();
|
|
24727
24350
|
if (live.acceptedResult === void 0) throw new ValidationError$1("scope: a retained child continues only after its invocation committed a result");
|
|
@@ -24838,7 +24461,7 @@ function createScope(args) {
|
|
|
24838
24461
|
abortChild: (reason) => controller.abort(reason)
|
|
24839
24462
|
};
|
|
24840
24463
|
const recordCancellation = () => {
|
|
24841
|
-
if (!live.executorDone && live.acceptedResult === void 0 && controller.signal.reason instanceof RunCancellationReason) live.cancellationReason = controller.signal.reason;
|
|
24464
|
+
if (!live.executorDone && live.acceptedResult === void 0 && executor.resultAccepted?.() !== true && controller.signal.reason instanceof RunCancellationReason) live.cancellationReason = controller.signal.reason;
|
|
24842
24465
|
};
|
|
24843
24466
|
if (controller.signal.aborted) recordCancellation();
|
|
24844
24467
|
else controller.signal.addEventListener("abort", recordCancellation, { once: true });
|
|
@@ -25109,165 +24732,6 @@ function createScope(args) {
|
|
|
25109
24732
|
child.lastActivityAt = now();
|
|
25110
24733
|
return true;
|
|
25111
24734
|
}
|
|
25112
|
-
/**
|
|
25113
|
-
* Arm a wait-state node. Deliberately NOT a `spawn` with a sleeping executor: it resolves no
|
|
25114
|
-
* `AgentSpec`, constructs no `Executor`, and — the point of the whole mechanic — reserves NOTHING
|
|
25115
|
-
* from the conserved pool, so a week-long wait costs zero tokens and zero dollars and cannot
|
|
25116
|
-
* starve a worker of budget.
|
|
25117
|
-
*
|
|
25118
|
-
* Fail-closed admission, mirroring `spawn`'s typed outcome. The deadline check is the one that
|
|
25119
|
-
* matters: a wait may not silently sleep past the pool's hard wall-clock ceiling, and it may not
|
|
25120
|
-
* extend that ceiling either (that would make a wait override a budget guard).
|
|
25121
|
-
*/
|
|
25122
|
-
function wait(spec, opts) {
|
|
25123
|
-
if (args.signal.aborted) return {
|
|
25124
|
-
ok: false,
|
|
25125
|
-
reason: "deadline-exceeded"
|
|
25126
|
-
};
|
|
25127
|
-
if (validateWaitSpec(spec) !== null) return {
|
|
25128
|
-
ok: false,
|
|
25129
|
-
reason: "invalid-spec"
|
|
25130
|
-
};
|
|
25131
|
-
if (spec.kind === "poll" && args.probes?.resolve(spec.probe) === void 0) return {
|
|
25132
|
-
ok: false,
|
|
25133
|
-
reason: "unknown-probe"
|
|
25134
|
-
};
|
|
25135
|
-
const adopted = unclaimedWaits.get(opts.label);
|
|
25136
|
-
if (adopted) unclaimedWaits.delete(opts.label);
|
|
25137
|
-
const effectiveSpec = adopted?.spec ?? spec;
|
|
25138
|
-
const armedAt = adopted?.armedAt ?? now();
|
|
25139
|
-
if (!assertWaitWithinDeadline(effectiveSpec, args.pool.readout().deadlineMs)) return {
|
|
25140
|
-
ok: false,
|
|
25141
|
-
reason: "deadline-exceeded"
|
|
25142
|
-
};
|
|
25143
|
-
const id = adopted ? adopted.id : `${args.parentId}:w${waitOrdinal}`;
|
|
25144
|
-
const ordinal = adopted ? adopted.ordinal : waitOrdinal;
|
|
25145
|
-
if (!adopted) waitOrdinal += 1;
|
|
25146
|
-
const waitAbort = new AbortController();
|
|
25147
|
-
const cascadeAbort = () => waitAbort.abort();
|
|
25148
|
-
if (args.signal.aborted) waitAbort.abort();
|
|
25149
|
-
else args.signal.addEventListener("abort", cascadeAbort, { once: true });
|
|
25150
|
-
const handle = {
|
|
25151
|
-
id,
|
|
25152
|
-
label: opts.label,
|
|
25153
|
-
get status() {
|
|
25154
|
-
return children.get(id)?.status ?? "cancelled";
|
|
25155
|
-
},
|
|
25156
|
-
abort(reason) {
|
|
25157
|
-
waitAbort.abort(reason);
|
|
25158
|
-
}
|
|
25159
|
-
};
|
|
25160
|
-
const live = {
|
|
25161
|
-
id,
|
|
25162
|
-
status: "waiting",
|
|
25163
|
-
runtime: "wait",
|
|
25164
|
-
abortChild: (reason) => waitAbort.abort(reason),
|
|
25165
|
-
budget: {
|
|
25166
|
-
maxIterations: 0,
|
|
25167
|
-
maxTokens: 0
|
|
25168
|
-
},
|
|
25169
|
-
label: opts.label,
|
|
25170
|
-
spent: zeroSpend(),
|
|
25171
|
-
settled: void 0,
|
|
25172
|
-
delivered: false,
|
|
25173
|
-
executorDone: false,
|
|
25174
|
-
cleanupConfirmed: true,
|
|
25175
|
-
teardownAttempts: 0,
|
|
25176
|
-
executionBindings: [],
|
|
25177
|
-
startedAt: armedAt,
|
|
25178
|
-
lastActivityAt: now(),
|
|
25179
|
-
wait: {
|
|
25180
|
-
spec: effectiveSpec,
|
|
25181
|
-
armedAt,
|
|
25182
|
-
label: opts.label,
|
|
25183
|
-
armCommitted: adopted !== void 0
|
|
25184
|
-
}
|
|
25185
|
-
};
|
|
25186
|
-
children.set(id, live);
|
|
25187
|
-
live.settled = (adopted ? Promise.resolve() : args.journal.appendEvent(args.root, {
|
|
25188
|
-
kind: "waiting",
|
|
25189
|
-
id,
|
|
25190
|
-
parent: args.parentId,
|
|
25191
|
-
label: opts.label,
|
|
25192
|
-
spec: effectiveSpec,
|
|
25193
|
-
armedAt,
|
|
25194
|
-
seq: ordinal,
|
|
25195
|
-
at: new Date(now()).toISOString()
|
|
25196
|
-
})).then(() => {
|
|
25197
|
-
if (live.wait) live.wait.armCommitted = true;
|
|
25198
|
-
notifyRuntimeHookEvent(args.hooks, {
|
|
25199
|
-
id: `${id}:waiting`,
|
|
25200
|
-
runId: args.root,
|
|
25201
|
-
target: "agent.spawn",
|
|
25202
|
-
phase: "after",
|
|
25203
|
-
timestamp: now(),
|
|
25204
|
-
stepIndex: ordinal,
|
|
25205
|
-
parentId: args.parentId,
|
|
25206
|
-
payload: {
|
|
25207
|
-
childId: id,
|
|
25208
|
-
label: opts.label,
|
|
25209
|
-
runtime: "wait",
|
|
25210
|
-
wait: effectiveSpec,
|
|
25211
|
-
armedAt,
|
|
25212
|
-
resumed: adopted !== void 0
|
|
25213
|
-
}
|
|
25214
|
-
}, { signal: args.signal });
|
|
25215
|
-
return runWait({
|
|
25216
|
-
spec: effectiveSpec,
|
|
25217
|
-
label: opts.label,
|
|
25218
|
-
armedAt,
|
|
25219
|
-
resumed: adopted !== void 0,
|
|
25220
|
-
signal: waitAbort.signal,
|
|
25221
|
-
...args.probes ? { probes: args.probes } : {},
|
|
25222
|
-
now,
|
|
25223
|
-
...args.waitSleep ? { sleep: args.waitSleep } : {}
|
|
25224
|
-
});
|
|
25225
|
-
}).then(async (resolution) => {
|
|
25226
|
-
live.executorDone = true;
|
|
25227
|
-
live.lastActivityAt = now();
|
|
25228
|
-
if (resolution.kind === "cancelled") return {
|
|
25229
|
-
kind: "down",
|
|
25230
|
-
reason: resolution.reason,
|
|
25231
|
-
infra: false,
|
|
25232
|
-
trace: {
|
|
25233
|
-
status: "unavailable",
|
|
25234
|
-
reason: "not-an-executor"
|
|
25235
|
-
}
|
|
25236
|
-
};
|
|
25237
|
-
const outRef = contentAddress(resolution.outcome);
|
|
25238
|
-
await args.blobs.put(outRef, resolution.outcome);
|
|
25239
|
-
return {
|
|
25240
|
-
kind: "done",
|
|
25241
|
-
out: resolution.outcome,
|
|
25242
|
-
outRef,
|
|
25243
|
-
spent: zeroSpend(),
|
|
25244
|
-
trace: {
|
|
25245
|
-
status: "unavailable",
|
|
25246
|
-
reason: "not-an-executor"
|
|
25247
|
-
}
|
|
25248
|
-
};
|
|
25249
|
-
}).catch((err) => {
|
|
25250
|
-
live.executorDone = true;
|
|
25251
|
-
return {
|
|
25252
|
-
kind: "down",
|
|
25253
|
-
reason: errMessage(err),
|
|
25254
|
-
infra: true,
|
|
25255
|
-
trace: {
|
|
25256
|
-
status: "unavailable",
|
|
25257
|
-
reason: "not-an-executor"
|
|
25258
|
-
}
|
|
25259
|
-
};
|
|
25260
|
-
}).then((s) => {
|
|
25261
|
-
live.resolved = s;
|
|
25262
|
-
return s;
|
|
25263
|
-
}).finally(() => {
|
|
25264
|
-
args.signal.removeEventListener("abort", cascadeAbort);
|
|
25265
|
-
});
|
|
25266
|
-
return {
|
|
25267
|
-
ok: true,
|
|
25268
|
-
handle
|
|
25269
|
-
};
|
|
25270
|
-
}
|
|
25271
24735
|
function progress(nodeId, opts = {}) {
|
|
25272
24736
|
const child = children.get(nodeId);
|
|
25273
24737
|
if (!child) return void 0;
|
|
@@ -25317,7 +24781,7 @@ function createScope(args) {
|
|
|
25317
24781
|
function interactive(nodeId) {
|
|
25318
24782
|
const child = children.get(nodeId);
|
|
25319
24783
|
if (!child) return noInteractiveSession("unknown-node");
|
|
25320
|
-
if (child.
|
|
24784
|
+
if (child.executorDone || isTerminalNodeStatus(child.status)) return noInteractiveSession("not-live");
|
|
25321
24785
|
const session = readInteractiveSession(child);
|
|
25322
24786
|
if (args.interactiveBindingDir !== void 0 && session.status === "available") writeWorkerInteractiveBinding(args.interactiveBindingDir, child.id, child.label, args.root, session, now);
|
|
25323
24787
|
return session;
|
|
@@ -25361,7 +24825,6 @@ function createScope(args) {
|
|
|
25361
24825
|
const resume = args.resumeFrom ? {
|
|
25362
24826
|
settled: args.resumeFrom.settled,
|
|
25363
24827
|
view: args.resumeFrom.view,
|
|
25364
|
-
waits: args.resumeFrom.waits,
|
|
25365
24828
|
keys: args.resumeFrom.keys,
|
|
25366
24829
|
priorSpend: args.resumeFrom.priorSpend
|
|
25367
24830
|
} : void 0;
|
|
@@ -25370,7 +24833,6 @@ function createScope(args) {
|
|
|
25370
24833
|
next,
|
|
25371
24834
|
nextResolved,
|
|
25372
24835
|
send,
|
|
25373
|
-
wait,
|
|
25374
24836
|
progress,
|
|
25375
24837
|
traceSource,
|
|
25376
24838
|
interactive,
|
|
@@ -25498,6 +24960,10 @@ function createScope(args) {
|
|
|
25498
24960
|
return child.ownedTreeRoot === void 0 || nested === void 0 || hasRetriableTeardowns(nested, true);
|
|
25499
24961
|
});
|
|
25500
24962
|
teardownRetriable.set(scope, (release) => retriableTeardowns().length > 0 || release && (releasable().length > 0 || retainedOwnerReleaseRetriable(scope)));
|
|
24963
|
+
nestedManagerScopeReaders.set(scope, () => [...children.values()].flatMap((child) => {
|
|
24964
|
+
const nested = child.readNestedScope?.();
|
|
24965
|
+
return nested === void 0 ? [] : [nested];
|
|
24966
|
+
}));
|
|
25501
24967
|
teardownRetriers.set(scope, async () => {
|
|
25502
24968
|
await Promise.all(retriableTeardowns().map((child) => {
|
|
25503
24969
|
child.teardownRetry ??= (async () => {
|
|
@@ -25684,6 +25150,15 @@ async function publishOwnerMaterialization(state, runtime, declaration, bindingI
|
|
|
25684
25150
|
state.publishedThisProcess = true;
|
|
25685
25151
|
return;
|
|
25686
25152
|
}
|
|
25153
|
+
if (state.receipt !== void 0 && state.receipt.status !== "known") {
|
|
25154
|
+
if (!state.bindingPublished) {
|
|
25155
|
+
const boundToCommitted = knownExecutionBindingReceipt(state.receipt, bindingInput);
|
|
25156
|
+
await appendOwnerBinding(state, boundToCommitted);
|
|
25157
|
+
state.onReceipt?.(state.receipt, boundToCommitted);
|
|
25158
|
+
}
|
|
25159
|
+
state.publishedThisProcess = true;
|
|
25160
|
+
return;
|
|
25161
|
+
}
|
|
25687
25162
|
if (state.receipt !== void 0) {
|
|
25688
25163
|
if (canonicalCandidateDigest(state.receipt) !== canonicalCandidateDigest(receipt)) {
|
|
25689
25164
|
const changed = changedRunIdentityFields(state.receipt, receipt);
|
|
@@ -25951,7 +25426,6 @@ async function appendAcknowledged(journal, root, event) {
|
|
|
25951
25426
|
* `PreSeqSettled` into the frozen `Settled` the driver branches on. */
|
|
25952
25427
|
async function finalizeSettlement(child, settlement, seq, args, now) {
|
|
25953
25428
|
const handle = frozenHandle(child);
|
|
25954
|
-
if (child.wait) return finalizeWait(child, settlement, seq, args, now, handle);
|
|
25955
25429
|
const nestedScope = child.readNestedScope?.();
|
|
25956
25430
|
const subtree = nestedScope === void 0 ? void 0 : subtreeSummaryOf(nestedScope);
|
|
25957
25431
|
if (subtree !== void 0) child.subtree = subtree;
|
|
@@ -26072,68 +25546,6 @@ async function finalizeSettlement(child, settlement, seq, args, now) {
|
|
|
26072
25546
|
seq
|
|
26073
25547
|
};
|
|
26074
25548
|
}
|
|
26075
|
-
/** Journal a wait-state's settlement as a `woken` event and project it onto the same `Settled`
|
|
26076
|
-
* the driver branches on. `by` names WHY it woke — `fired` / `timeout` / `cancelled` — which is
|
|
26077
|
-
* the fact a resumed reader needs and cannot recover from a payload it may never fetch. */
|
|
26078
|
-
async function finalizeWait(child, settlement, seq, args, now, handle) {
|
|
26079
|
-
const settledAt = now();
|
|
26080
|
-
child.settledAt = settledAt;
|
|
26081
|
-
const at = new Date(settledAt).toISOString();
|
|
26082
|
-
if (settlement.kind === "down") {
|
|
26083
|
-
child.status = "cancelled";
|
|
26084
|
-
if (child.wait?.armCommitted) await args.journal.appendEvent(args.root, {
|
|
26085
|
-
kind: "woken",
|
|
26086
|
-
id: child.id,
|
|
26087
|
-
by: "cancelled",
|
|
26088
|
-
seq,
|
|
26089
|
-
at
|
|
26090
|
-
});
|
|
26091
|
-
return {
|
|
26092
|
-
kind: "down",
|
|
26093
|
-
handle,
|
|
26094
|
-
reason: settlement.reason,
|
|
26095
|
-
...settlement.infra === void 0 ? {} : { infra: settlement.infra },
|
|
26096
|
-
trace: settlement.trace,
|
|
26097
|
-
settledAt,
|
|
26098
|
-
seq
|
|
26099
|
-
};
|
|
26100
|
-
}
|
|
26101
|
-
child.status = "done";
|
|
26102
|
-
child.outRef = settlement.outRef;
|
|
26103
|
-
const out = settlement.out;
|
|
26104
|
-
await args.journal.appendEvent(args.root, {
|
|
26105
|
-
kind: "woken",
|
|
26106
|
-
id: child.id,
|
|
26107
|
-
by: out.settled,
|
|
26108
|
-
outRef: settlement.outRef,
|
|
26109
|
-
seq,
|
|
26110
|
-
at
|
|
26111
|
-
});
|
|
26112
|
-
notifyRuntimeHookEvent(args.hooks, {
|
|
26113
|
-
id: `${child.id}:woken`,
|
|
26114
|
-
runId: args.root,
|
|
26115
|
-
target: "agent.child",
|
|
26116
|
-
phase: "after",
|
|
26117
|
-
timestamp: settledAt,
|
|
26118
|
-
stepIndex: seq,
|
|
26119
|
-
parentId: args.parentId,
|
|
26120
|
-
payload: {
|
|
26121
|
-
childId: child.id,
|
|
26122
|
-
status: "done",
|
|
26123
|
-
wait: out
|
|
26124
|
-
}
|
|
26125
|
-
}, { signal: args.signal });
|
|
26126
|
-
return {
|
|
26127
|
-
kind: "done",
|
|
26128
|
-
handle,
|
|
26129
|
-
out: settlement.out,
|
|
26130
|
-
outRef: settlement.outRef,
|
|
26131
|
-
spent: settlement.spent,
|
|
26132
|
-
trace: settlement.trace,
|
|
26133
|
-
settledAt,
|
|
26134
|
-
seq
|
|
26135
|
-
};
|
|
26136
|
-
}
|
|
26137
25549
|
/**
|
|
26138
25550
|
* Drive one child's `Executor` to a terminal `PreSeqSettled`, folding usage into the
|
|
26139
25551
|
* conserved `Spend`, reconciling the reservation, and persisting the result blob. Both
|
|
@@ -26261,7 +25673,7 @@ async function runChild(live, executor, childAbort, task, opts, pool, ticket, bl
|
|
|
26261
25673
|
const reconcileError = reconcileOnce(accounting?.reservation ?? terminalSpend);
|
|
26262
25674
|
if (reconcileError !== void 0) throw reconcileError;
|
|
26263
25675
|
} else {
|
|
26264
|
-
const terminal = await retainOutput(await
|
|
25676
|
+
const terminal = await retainOutput(await awaitUnlessAccepted(Promise.resolve(ran), childAbort.signal, executor));
|
|
26265
25677
|
await executionEvidence.complete();
|
|
26266
25678
|
const accounting = executor.accounting?.();
|
|
26267
25679
|
live.spent = accounting?.reported ?? terminal.spent;
|
|
@@ -26275,7 +25687,7 @@ async function runChild(live, executor, childAbort, task, opts, pool, ticket, bl
|
|
|
26275
25687
|
const ownMetered = executor.metered?.();
|
|
26276
25688
|
const trace = await captureTraceOnce();
|
|
26277
25689
|
const childHarnessTranscript = await persistTranscriptOnce();
|
|
26278
|
-
if (childAbort.signal.aborted && live.acceptedResult === void 0) {
|
|
25690
|
+
if (childAbort.signal.aborted && live.acceptedResult === void 0 && executor.resultAccepted?.() !== true) {
|
|
26279
25691
|
await teardownOnce(opts.shutdown ?? "brutalKill");
|
|
26280
25692
|
return downRecord("aborted before settle", true, trace, ownMetered, runtimeOwnedExecutorProviderEvidence(executor), retainedOutputRef, childHarnessTranscript);
|
|
26281
25693
|
}
|
|
@@ -26410,8 +25822,6 @@ function settledToIteration(settled) {
|
|
|
26410
25822
|
tokenUsage: cloneTokenUsage(settled.spent.tokens)
|
|
26411
25823
|
};
|
|
26412
25824
|
}
|
|
26413
|
-
/** Summarize one scope's children and everything below them, listing the best direct children
|
|
26414
|
-
* and counting the rest. Wait-states are not agents and are left out. */
|
|
26415
25825
|
/** The live team a child manager leads, read through the nested scope it owns. */
|
|
26416
25826
|
function liveTeamOf(child) {
|
|
26417
25827
|
let nested;
|
|
@@ -26446,7 +25856,6 @@ function readLiveTeam(children, ownerTurns, ownerLastTurnAt) {
|
|
|
26446
25856
|
let down = 0;
|
|
26447
25857
|
let lastActivityAt = ownerLastTurnAt;
|
|
26448
25858
|
for (const child of children) {
|
|
26449
|
-
if (child.wait) continue;
|
|
26450
25859
|
agents += 1;
|
|
26451
25860
|
lastActivityAt = Math.max(lastActivityAt, child.lastActivityAt, child.settledAt ?? 0, executorActivityAt(child));
|
|
26452
25861
|
if (child.status === "done") done += 1;
|
|
@@ -26484,7 +25893,6 @@ function summarizeTeam(children) {
|
|
|
26484
25893
|
let members = 0;
|
|
26485
25894
|
const results = [];
|
|
26486
25895
|
for (const child of children) {
|
|
26487
|
-
if (child.wait) continue;
|
|
26488
25896
|
members += 1;
|
|
26489
25897
|
const below = child.subtree;
|
|
26490
25898
|
agents += 1 + (below?.agents ?? 0);
|
|
@@ -26544,8 +25952,7 @@ function makeTreeView(root, children) {
|
|
|
26544
25952
|
return {
|
|
26545
25953
|
root,
|
|
26546
25954
|
nodes,
|
|
26547
|
-
inFlight: nodes.filter((n) => n.status === "running" || n.status === "acquiring" || n.status === "queued").length
|
|
26548
|
-
waiting: nodes.filter((n) => n.status === "waiting").length
|
|
25955
|
+
inFlight: nodes.filter((n) => n.status === "running" || n.status === "acquiring" || n.status === "queued").length
|
|
26549
25956
|
};
|
|
26550
25957
|
}
|
|
26551
25958
|
function frozenHandle(child) {
|
|
@@ -26667,6 +26074,19 @@ function preserveUnknownTelemetry(streamed, terminal) {
|
|
|
26667
26074
|
...(streamed.tokensProvenance ?? terminal.tokensProvenance) === void 0 ? {} : { tokensProvenance: streamed.tokensProvenance ?? terminal.tokensProvenance }
|
|
26668
26075
|
};
|
|
26669
26076
|
}
|
|
26077
|
+
/**
|
|
26078
|
+
* Await a one-shot executor's terminal result unless an abort wins first, except for an execution
|
|
26079
|
+
* whose result was accepted before the abort arrived: it is ending the turn that submitted that
|
|
26080
|
+
* result, so it is awaited rather than discarded, and the executor bounds that wait itself.
|
|
26081
|
+
*/
|
|
26082
|
+
async function awaitUnlessAccepted(work, signal, executor) {
|
|
26083
|
+
try {
|
|
26084
|
+
return await awaitAbortable(work, signal);
|
|
26085
|
+
} catch (error) {
|
|
26086
|
+
if (signal.aborted && executor.resultAccepted?.() === true) return await work;
|
|
26087
|
+
throw error;
|
|
26088
|
+
}
|
|
26089
|
+
}
|
|
26670
26090
|
async function awaitAbortable(work, signal) {
|
|
26671
26091
|
if (signal.aborted) {
|
|
26672
26092
|
work.catch(() => void 0);
|
|
@@ -26879,6 +26299,7 @@ const driverExecutorFactory = (rawSpec, ctx) => {
|
|
|
26879
26299
|
...spec.progress ? { progress: spec.progress } : {},
|
|
26880
26300
|
...spec.harnessTranscript ? { harnessTranscript: spec.harnessTranscript } : {},
|
|
26881
26301
|
...spec.harnessTranscriptSettled ? { harnessTranscriptSettled: spec.harnessTranscriptSettled } : {},
|
|
26302
|
+
resultAccepted: () => driver.resultAccepted?.() === true,
|
|
26882
26303
|
...deliver ? { deliver(message) {
|
|
26883
26304
|
return deliver(message) !== false;
|
|
26884
26305
|
} } : {},
|
|
@@ -26901,8 +26322,15 @@ const driverExecutorFactory = (rawSpec, ctx) => {
|
|
|
26901
26322
|
if (recovering && (!spec.recoverExecutor || !seam.restore)) throw new RetainedExecutionPendingError(new ValidationError$1("driverExecutor: nested recovery has no original executor reconstruction"), "nested-recovery");
|
|
26902
26323
|
signal.throwIfAborted();
|
|
26903
26324
|
const controller = new AbortController();
|
|
26904
|
-
|
|
26905
|
-
|
|
26325
|
+
let clearAcceptedGrace;
|
|
26326
|
+
const onParentAbort = () => {
|
|
26327
|
+
if (driver.resultAccepted?.() === true) {
|
|
26328
|
+
clearAcceptedGrace ??= armDeadlineTimer(ACCEPTED_RESULT_GRACE_MS, () => controller.abort(signal.reason));
|
|
26329
|
+
return;
|
|
26330
|
+
}
|
|
26331
|
+
controller.abort(signal.reason);
|
|
26332
|
+
};
|
|
26333
|
+
if (signal.aborted) onParentAbort();
|
|
26906
26334
|
else signal.addEventListener("abort", onParentAbort, { once: true });
|
|
26907
26335
|
let nestedScope;
|
|
26908
26336
|
try {
|
|
@@ -26925,6 +26353,7 @@ const driverExecutorFactory = (rawSpec, ctx) => {
|
|
|
26925
26353
|
const out = await runAbortable(() => driver.act(task, nestedScope), controller.signal, "driver aborted");
|
|
26926
26354
|
await finalizeScopeOwnerMaterialization(nestedScope);
|
|
26927
26355
|
await closeActive("driver completed");
|
|
26356
|
+
releaseManagerEnvironment(nestedScope).catch(() => void 0);
|
|
26928
26357
|
const events = await loadTreeEvents(journal, nestedRoot);
|
|
26929
26358
|
const childWork = sumSpend(events.filter(isSettled));
|
|
26930
26359
|
const totalMetered = sumMetered(events);
|
|
@@ -26955,6 +26384,7 @@ const driverExecutorFactory = (rawSpec, ctx) => {
|
|
|
26955
26384
|
throw err;
|
|
26956
26385
|
} finally {
|
|
26957
26386
|
signal.removeEventListener("abort", onParentAbort);
|
|
26387
|
+
clearAcceptedGrace?.();
|
|
26958
26388
|
active = void 0;
|
|
26959
26389
|
}
|
|
26960
26390
|
},
|
|
@@ -27020,7 +26450,7 @@ async function closeNestedScope(scope, controller, reason) {
|
|
|
27020
26450
|
if (!controller.signal.aborted) controller.abort(reason);
|
|
27021
26451
|
for (;;) if (await scope.next() === null) break;
|
|
27022
26452
|
const view = scope.view;
|
|
27023
|
-
if (view.inFlight > 0
|
|
26453
|
+
if (view.inFlight > 0) throw new ValidationError$1(`driverExecutor: nested cleanup left ${view.inFlight} running nodes`);
|
|
27024
26454
|
}
|
|
27025
26455
|
/** The nested tree's full event list — the one evidence the spend, verdict, AND driver-inference
|
|
27026
26456
|
* roll-ups read off the same journal the supervisor sums. */
|
|
@@ -27953,7 +27383,7 @@ var DriverAttemptsExhaustedError = class extends RuntimeRunStateError {
|
|
|
27953
27383
|
function summarizeDriverAttempts(records) {
|
|
27954
27384
|
let barren = 0;
|
|
27955
27385
|
for (const record of records) if (record.madeProgress) barren = 0;
|
|
27956
|
-
else if (record.error === void 0 && record.reentry !== void 0 && record.contract === "unmet") barren += 1;
|
|
27386
|
+
else if (record.error === void 0 && record.reentry !== void 0 && record.reentry !== "wake" && record.contract === "unmet") barren += 1;
|
|
27957
27387
|
const last = records.at(-1);
|
|
27958
27388
|
const refused = records.filter((record) => record.classification === "unavailable");
|
|
27959
27389
|
return {
|
|
@@ -27963,6 +27393,8 @@ function summarizeDriverAttempts(records) {
|
|
|
27963
27393
|
unavailablePauses: refused.filter((record) => record.retryInMs !== void 0).length,
|
|
27964
27394
|
unavailableMs: refused.reduce((sum, record) => sum + (record.madeProgress ? 0 : record.durationMs) + (record.retryInMs ?? 0), 0),
|
|
27965
27395
|
barrenReentries: barren,
|
|
27396
|
+
wakes: records.filter((record) => record.reentry === "wake").length,
|
|
27397
|
+
waitedMs: records.reduce((sum, record) => sum + (record.waitedMs ?? 0), 0),
|
|
27966
27398
|
ended: last?.stop ?? "unrecorded",
|
|
27967
27399
|
...last?.repromptRefusedBy === void 0 ? {} : { repromptRefusedBy: last.repromptRefusedBy }
|
|
27968
27400
|
};
|
|
@@ -28005,6 +27437,7 @@ async function runDriverWithRetry(run) {
|
|
|
28005
27437
|
let consecutivePauses = 0;
|
|
28006
27438
|
let pauses = 0;
|
|
28007
27439
|
let continuations = 0;
|
|
27440
|
+
let wakes = 0;
|
|
28008
27441
|
let reentry;
|
|
28009
27442
|
const emit = async (record) => {
|
|
28010
27443
|
const entered = reentry === void 0 ? {} : { reentry: reentry.reason };
|
|
@@ -28164,7 +27597,28 @@ async function runDriverWithRetry(run) {
|
|
|
28164
27597
|
const contract = contractOf(after);
|
|
28165
27598
|
const contractField = contract === "none" ? {} : { contract };
|
|
28166
27599
|
if (progressed) barrenReentries = 0;
|
|
28167
|
-
else if (reentry !== void 0 && contract === "unmet") barrenReentries += 1;
|
|
27600
|
+
else if (reentry !== void 0 && reentry.reason !== "wake" && contract === "unmet") barrenReentries += 1;
|
|
27601
|
+
const wait = run.wait;
|
|
27602
|
+
if (wait !== void 0 && contract !== "met" && !(continuation?.closed() ?? false) && !run.signal.aborted && budgetStop(run.budget(), now()) === void 0 && wait.open()) {
|
|
27603
|
+
const woken = await wait.wake(attempt + 1);
|
|
27604
|
+
if (woken !== void 0 && !run.signal.aborted) {
|
|
27605
|
+
wakes += 1;
|
|
27606
|
+
await emit({
|
|
27607
|
+
attempt,
|
|
27608
|
+
durationMs,
|
|
27609
|
+
madeProgress: progressed,
|
|
27610
|
+
...contractField,
|
|
27611
|
+
waitedMs: woken.idleMs,
|
|
27612
|
+
retryInMs: 0
|
|
27613
|
+
});
|
|
27614
|
+
reentry = {
|
|
27615
|
+
reason: "wake",
|
|
27616
|
+
input: woken.input,
|
|
27617
|
+
wake: wakes
|
|
27618
|
+
};
|
|
27619
|
+
continue;
|
|
27620
|
+
}
|
|
27621
|
+
}
|
|
28168
27622
|
if (contract === "unmet" && continuation !== void 0) {
|
|
28169
27623
|
const decision = await decideContinuation(continuation, attempt, after);
|
|
28170
27624
|
if ("steer" in decision) {
|
|
@@ -28544,7 +27998,7 @@ function describeRejection(error) {
|
|
|
28544
27998
|
function createSupervisor() {
|
|
28545
27999
|
let attached;
|
|
28546
28000
|
async function run(root, task, opts) {
|
|
28547
|
-
const { budget, rootIdentity, rootMaterialization, runId, journal: journalStore, blobs: blobStore, executors: executorRegistry, recoverExecutor,
|
|
28001
|
+
const { budget, rootIdentity, rootMaterialization, runId, journal: journalStore, blobs: blobStore, executors: executorRegistry, recoverExecutor, maxDepth, workerSlots, reservationPolicy, maxRestarts, withinMs, childSettleGraceMs, teardownConfirmMs, resume, retainedAtSettlement: requestedRetainedAtSettlement, ownerWorkspaceRetention, now: suppliedNow, signal, hooks, workerTrace, workerTraceUnpropagated, interactiveBindingDir } = opts;
|
|
28548
28002
|
const input = detachedSnapshot({
|
|
28549
28003
|
task,
|
|
28550
28004
|
options: {
|
|
@@ -28579,7 +28033,6 @@ function createSupervisor() {
|
|
|
28579
28033
|
blobs: persistence.blobs,
|
|
28580
28034
|
executors: executorRegistry,
|
|
28581
28035
|
...recoverExecutor ? { recoverExecutor } : {},
|
|
28582
|
-
...probes === void 0 ? {} : { probes },
|
|
28583
28036
|
...suppliedNow === void 0 ? {} : { now: suppliedNow },
|
|
28584
28037
|
...signal === void 0 ? {} : { signal },
|
|
28585
28038
|
...hooks === void 0 ? {} : { hooks },
|
|
@@ -28590,6 +28043,7 @@ function createSupervisor() {
|
|
|
28590
28043
|
task = input.task;
|
|
28591
28044
|
const rootAct = root.act.bind(root);
|
|
28592
28045
|
const rootDeliver = root.deliver?.bind(root);
|
|
28046
|
+
const rootResultAccepted = root.resultAccepted?.bind(root);
|
|
28593
28047
|
const now = opts.now ?? Date.now;
|
|
28594
28048
|
assertRootIdentity(opts);
|
|
28595
28049
|
const rootLease = attached?.acquire();
|
|
@@ -28696,7 +28150,6 @@ function createSupervisor() {
|
|
|
28696
28150
|
requiredKnown: true,
|
|
28697
28151
|
...priorRootMaterialization === void 0 ? {} : { prior: priorRootMaterialization }
|
|
28698
28152
|
} } : {},
|
|
28699
|
-
...opts.probes ? { probes: opts.probes } : {},
|
|
28700
28153
|
...opts.workerTrace ? { workerTrace: opts.workerTrace } : {},
|
|
28701
28154
|
...opts.workerTraceUnpropagated ? { workerTraceUnpropagated: opts.workerTraceUnpropagated } : {},
|
|
28702
28155
|
...opts.interactiveBindingDir ? { interactiveBindingDir: opts.interactiveBindingDir } : {},
|
|
@@ -28711,10 +28164,19 @@ function createSupervisor() {
|
|
|
28711
28164
|
});
|
|
28712
28165
|
let deadlineExceeded = false;
|
|
28713
28166
|
const rootDeadlineAtMs = pool.readout().deadlineMs;
|
|
28714
|
-
|
|
28715
|
-
const
|
|
28167
|
+
let clearAcceptedGrace;
|
|
28168
|
+
const reachRootDeadline = () => {
|
|
28169
|
+
if (clearAcceptedGrace !== void 0) return;
|
|
28170
|
+
if (rootResultAccepted?.() === true) {
|
|
28171
|
+
clearAcceptedGrace = armDeadlineTimer(ACCEPTED_RESULT_GRACE_MS, () => {
|
|
28172
|
+
deadlineExceeded = cascadeAbort(ROOT_DEADLINE_REASON);
|
|
28173
|
+
});
|
|
28174
|
+
return;
|
|
28175
|
+
}
|
|
28716
28176
|
deadlineExceeded = cascadeAbort(ROOT_DEADLINE_REASON);
|
|
28717
|
-
}
|
|
28177
|
+
};
|
|
28178
|
+
if (rootDeadlineAtMs > 0 && now() >= rootDeadlineAtMs) reachRootDeadline();
|
|
28179
|
+
const clearRootDeadline = opts.budget.deadlineMs === void 0 ? void 0 : armDeadlineTimer(Math.max(0, rootDeadlineAtMs - now()), reachRootDeadline);
|
|
28718
28180
|
let actOutcome = {
|
|
28719
28181
|
ok: false,
|
|
28720
28182
|
error: new RuntimeRunStateError("supervisor: root execution did not start")
|
|
@@ -28741,9 +28203,11 @@ function createSupervisor() {
|
|
|
28741
28203
|
allChildrenDownAtSettle = allSpawnedChildrenDown(runTree(openScope));
|
|
28742
28204
|
if (!controller.signal.aborted && rootDeadlineAtMs > 0 && now() >= rootDeadlineAtMs) deadlineExceeded = true;
|
|
28743
28205
|
clearRootDeadline?.();
|
|
28206
|
+
clearAcceptedGrace?.();
|
|
28744
28207
|
const settleGraceMs = !controller.signal.aborted ? boundedSettleGrace(opts.childSettleGraceMs, rootDeadlineAtMs, now) : 0;
|
|
28745
28208
|
try {
|
|
28746
28209
|
teardownUnconfirmed = await drainLiveChildren(openScope, controller, settleGraceMs);
|
|
28210
|
+
await settleManagerWork(openScope);
|
|
28747
28211
|
if (!persistence.failed && retainedAtSettlement === "release") teardownUnconfirmed = await releaseRetainedEnvironments(openScope);
|
|
28748
28212
|
const confirmWindowMs = opts.teardownConfirmMs ?? DEFAULT_TEARDOWN_CONFIRM_MS;
|
|
28749
28213
|
const releasing = !persistence.failed && retainedAtSettlement === "release";
|
|
@@ -29036,8 +28500,7 @@ function wrapJournalForBreaker(journal, breaker) {
|
|
|
29036
28500
|
* `allSettled`'d — a stray throw here is NOT the primary error (firstError precedence).
|
|
29037
28501
|
*/
|
|
29038
28502
|
async function drainLiveChildren(scope, controller, settleGraceMs = 0) {
|
|
29039
|
-
|
|
29040
|
-
if (!(view.inFlight > 0 || view.waiting > 0)) {
|
|
28503
|
+
if (scope.view.inFlight === 0) {
|
|
29041
28504
|
await drainCursor(scope);
|
|
29042
28505
|
return unconfirmedTeardowns(scope);
|
|
29043
28506
|
}
|
|
@@ -29050,7 +28513,7 @@ async function drainLiveChildren(scope, controller, settleGraceMs = 0) {
|
|
|
29050
28513
|
clearGraceTimer?.();
|
|
29051
28514
|
}
|
|
29052
28515
|
const after = scope.view;
|
|
29053
|
-
if (after.inFlight > 0
|
|
28516
|
+
if (after.inFlight > 0) throw new RuntimeRunStateError(`supervisor: cleanup ended with ${after.inFlight} running and ` + describeUnconfirmed(scope));
|
|
29054
28517
|
return unconfirmedTeardowns(scope);
|
|
29055
28518
|
}
|
|
29056
28519
|
/** Five minutes: long enough to outlast a provider's transient delete failures and a slow
|
|
@@ -29315,6 +28778,6 @@ function isNonEmptySpend(s) {
|
|
|
29315
28778
|
return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0 || s.tokensKnown === false || s.usdKnown === false || Object.values(s.resources ?? {}).some((resource) => resource.amount > 0 || !resource.known);
|
|
29316
28779
|
}
|
|
29317
28780
|
//#endregion
|
|
29318
|
-
export {
|
|
28781
|
+
export { scopeRetainedOwnerPriorSpend as $, sandboxProgressEvents as $i, bridgeStopSignalKey as $n, localHarnessExecutable as $r, harnessUsageIsEmpty as $t, isDriverSpec as A, runtimeOwnedExecutorExecutionBinding as Ai, insideCursorNamespace as An, buildRuntimeEventOtelSpans as Ar, readWorkerTraceContext as At, recordScopeOwnerMaterialization as B, detachedSnapshot as Bi, isTraceAnalysisStore as Bn, createRuntimeStreamEventCollector as Br, DEFAULT_PEER_MAIL_LIMITS as Bt, distillFindings as C, inheritRuntimeOwnedExecutorAttestation as Ci, sandboxSessionTraceSource as Cn, readTraceContextFromEnv as Cr, snapshotExecutorConfig as Ct, verdictFromJudgeScore as D, providerAttemptEvidence as Di, FileSpawnJournal as Dn, resolveRedactor as Dr, createRouterTranscript as Dt, passedItems as E, newExecutionAttemptId as Ei, FileResultBlobStore as En, defaultRedactorIdentityMaterial as Er, createSteerableSandboxSession as Et, createScope as F, unknownExecutionBindingReceipt as Fi, prepareJsonlAppend as Fn, loopEventToOtelSpan as Fr, DEFAULT_STALL_AFTER_MS as Ft, beginScopeRetainedOwnerDrive as G, createSandboxToolPartState as Gi, ReservationWaitRefused as Gn, runWorktreeHarness as Gr, peerMailTools as Gt, restoreScopeOwnerAcceptedExecution as H, readSandboxOutcome as Hi, workerTraceAnalysisStore as Hn, sanitizeKnowledgeReadinessReport as Hr, claimsAuthority as Ht, deriveNodeExecutionIdentity as I, unknownMaterializationReceipt as Ii, readCommittedJsonLines as In, padSpanId as Ir, createActivityLog as It, consumeScopeRetainedOwnerResult as J, isSandboxTerminalEvent as Ji, meterUsageEvent as Jn, removeWorktree as Jr, isTerminalNodeStatus as Jt, bindScopeRetainedOwnerEnvironmentId as K, createSandboxUsageLedger as Ki, assertValidBudget as Kn, captureWorktreeDiff as Kr, peerMailVerbNames as Kt, meterRuntimeOwnedAccounting as L, executableAgentProfileSnapshot as Li, writeAllBytes as Ln, padTraceId as Lr, readWorkerProgress as Lt, SUBTREE_RESULT_LIMIT as M, runtimeOwnedExecutorProviderEvidence as Mi, materializeTreeView as Mn, createOtelExporter as Mr, workerTraceHeaders as Mt, assertRecursiveReservationPolicy as N, runtimeOwnedPendingExecutorMaterialization as Ni, replaySpawnTree as Nn, flatOtelSpan as Nr, workerTraceSeamKey as Nt, driverChild as O, recordRuntimeOwnedDriveHarnessProviderEvidence as Oi, InMemoryResultBlobStore as On, buildLoopOtelSpans as Or, createInPlaceCliExecutor as Ot, beginScopeOwnerAttempt as P, runtimeOwnedScopeOwnerRuntime as Pi, isNoEntError as Pn, generateSpanId as Pr, taskToPrompt as Pt, scopeRetainedOwnerContext as Q, sandboxEventServedBackend as Qi, bridgeRuntimeAttachmentsKey as Qn, harnessSupportsReasoningEffort as Qr, createCodexRolloutStoreReader as Qt, meterRuntimeOwnedProviderAttempt as R, executableAgentSpecSnapshot as Ri, WORKER_TOOL_TRACE_SCHEMA_VERSION as Rn, toOtelAttributes as Rr, createInbox as Rt, createContinuationKeeper as S, finalizeRuntimeOwnedPendingExecutor as Si, decodeToolPart as Sn, mergeTraceEnv as Sr, createExecutorRegistry as St, failedItems as T, knownMaterializationReceipt as Ti, retainedExecutorSeamKey as Tn, defaultRedactor as Tr, DEFAULT_SANDBOX_STEERING_MAX_TURNS as Tt, scopeOwnerExecutorNodeContext as U, assertSandboxServedModel as Ui, executorFailure as Un, sanitizeRuntimeStreamEvent as Ur, createPeerMailbox as Ut, recordScopeOwnerPause as V, projectSandboxOutcome as Vi, parseWorkerToolTraceArtifact as Vn, sanitizeAgentRuntimeEvent as Vr, PEER_MAIL_WIRE_KEY as Vt, settledToIteration as W, canonicalStreamEventFromSandboxEvent as Wi, BudgetReconcileFault as Wn, runSettledCommand as Wr, isPeerMailEnvelope as Wt, prepareScopeRetainedOwnerTask as X, mapSandboxToolEvent as Xi, spendFromUsageEvents as Xn, LOCAL_HARNESSES as Xr, bridgeModelRouteRefusal as Xt, noteScopeRetainedOwnerCoordination as Y, mapSandboxEvent as Yi, newUsageTotals as Yn, DEFAULT_LOCAL_HARNESS as Yr, bridgeAdmissionRefusal as Yt, reconcileScopeRetainedOwnerEnvironment as Z, notifySandboxEventObserver as Zi, spendFromUsageTotals as Zn, harnessInvocation as Zr, addHarnessUsage as Zt, CheckUnavailableError as _, validateProfileMaterialization as _i, providerAsSandboxClient as _n, readHarnessTranscript as _r, bindReusableExecutorExecutionId as _t, collectDelivered as a, promptOptionsFromAgentTurnInput as aa, controlProfileMaterialization as ai, runAgentRounds as an, retainedCreateMaterial as ar, environmentReader as at, bestComposite as b, attestRuntimeOwnedScopeOwner as bi, unavailableSignalOfFailure as bn, contentAddress as br, cliWorktreeExecutor as bt, runTree as c, profileMaterializationAxes$1 as ci, SandboxEvidenceRetentionError as cn, createWorkerSlots as cr, interactiveAdmissionSeamKey as ct, classifyDriverFailure as d, promptOnlyProfileMaterialization as di, readPromptOptions as dn, abortError as dr, reconnectRetainedInteractiveRun as dt, sumSandboxUsage as ea, parseCodexTokenUsage as ei, readCodexRolloutSession as en, errMessage as er, scopeRetainedOwnerResourceReader as et, runDriverWithRetry as f, promptResourceProfileMaterialization as fi, routerBrain as fn, linkAbort as fr, recoverRetainedInteractiveRun as ft, CONTINUATION_FACTS as g, unsupportedProfileDimensions as gi, providerAsExecutor as gn, persistHarnessTranscript as gr, rollingDispatch as gt, CONTINUATIONS_DIR as h, sandboxActProfileMaterialization as hi, createAgentEnvironmentProviderRegistry as hn, harnessTranscriptArtifact as hr, queueOf as ht, bestDelivered as i, promptFromAgentTurnInput as ia, assertProfileMaterialization as ii, isTerminalDecision as in, startRetainedRunInEnvironment as ir, SPAWN_RESOURCE_PATH_MAX_BYTES as it, withDriverExecutor as j, runtimeOwnedExecutorMaterialization as ji, loadSpawnForest as jn, createOpenInferenceFileExporter as jr, workerTraceEnv as jt, driverExecutorFactory as k, runtimeOwnedDriveHarnessProviderEvidence as ki, InMemorySpawnJournal as kn, buildLoopSpanNodes as kr, WORKER_TRACE_PROPAGATION as kt, DriverAttemptsExhaustedError as l, promptControlProfileMaterialization as li, captureBeforeDestroy as ln, openSlotGroup as lr, attachWorker as lt, upstreamUnavailableSignal as m, renderUnsupported as mi, LEAF_CONTINUATION_TASK as mn, captureHarnessTranscript as mr, freeSlots as mt, createRootHandle as n, abortError$1 as na, terminateProcessTreeAndConfirm as ni, createSandboxForSpec as nn, recoverRetainedRun as nr, scopeRetainedOwnerResult as nt, pickBestDelivered as o, providerMessageText as oa, defineProfileMaterializationContract as oi, createSandboxLineage as on, destroyInteractiveEnvironment as or, hostDirectoryReader as ot, summarizeDriverAttempts as p, renderProfileMaterializationIssues as pi, runBrainLoop as pn, runAbortable as pr, startRetainedInteractiveRun as pt, bindScopeRetainedOwnerProvider as q, extractLlmCallEvent as qi, createBudgetPool as qn, createWorktree as qr, isLiveNodeStatus as qt, createSupervisor as r, awaitAbortable$1 as ra, AGENT_PROFILE_MATERIALIZATION_AXES as ri, defaultSelectWinner as rn, startRetainedRun as rr, scopeRetainedOwnerTurnEnded as rt, runFinalizer as s, fullProfileMaterialization as si, acquireSandbox as sn, environmentGone as sr, resolveSpawnResourcePaths as st, DEFAULT_MAX_DEPTH as t, decodeHarnessUsage as ta, CodexExecutionDiagnosticError as ti, TERMINAL_DECISIONS as tn, reconnectRetainedRun as tr, scopeRetainedOwnerRestorePoint as tt, HarnessTurnFailedError as u, promptModelProfileMaterialization as ui, assertBoxlessPromptOptions as un, RunCancellationReason as ur, readWorkerInteractiveBinding as ut, admitContinuationPolicy as v, worktreeCliProfileMaterialization as vi, resolveAgentEnvironmentProvider as vn, resolveHarnessTranscriptCapture as vr, captureReusableExecutorConfig as vt, expandQuestions as w, knownExecutionBindingReceipt as wi, registerRetainedExecutorPreparation as wn, traceContextToEnv as wr, createWorktreeCliExecutor as wt, checkVerdictOf as x, authoredProfileDigest as xi, createPushTraceSource as xn, createPropagatingTraceEmitter as xr, createExecutor as xt, admitFinding as y, attestRuntimeOwnedPendingExecutor as yi, unavailablePauseMs as yn, retainHarnessTranscript as yr, cliInPlaceExecutor as yt, recordScopeOwnerDriverAttempt as z, detachedFrozen as zi, captureWorkerTraceEvidence as zn, createRuntimeEventCollector as zr, AUTHORITY_MARKERS as zt };
|
|
29319
28782
|
|
|
29320
|
-
//# sourceMappingURL=supervisor-
|
|
28783
|
+
//# sourceMappingURL=supervisor-gCfG6K1-.js.map
|