@tangle-network/agent-runtime 0.301.0 → 0.306.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{activation-V6aaOlZV.js → activation-kaJzXM7Y.js} +3 -3
- package/dist/{activation-V6aaOlZV.js.map → activation-kaJzXM7Y.js.map} +1 -1
- package/dist/{activation-CRgEJutr.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-E6mbRX2P.js} +2 -2
- package/dist/{bundle-C9v5TH_H.js.map → bundle-E6mbRX2P.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-vNrdQBSH.js → coordination-driver-Y2gf8pBT.js} +9 -16
- package/dist/coordination-driver-Y2gf8pBT.js.map +1 -0
- package/dist/{delegate-B-jFJcbW.js → delegate-B7bwEFQl.js} +2 -2
- package/dist/{delegate-B-jFJcbW.js.map → delegate-B7bwEFQl.js.map} +1 -1
- package/dist/durable.d.ts +3 -15
- package/dist/durable.js +158 -124
- package/dist/durable.js.map +1 -1
- package/dist/{graph-Dwv_hQB_.js → graph-VUwNFYff.js} +4 -5
- package/dist/graph-VUwNFYff.js.map +1 -0
- package/dist/{improvement-cycle-Cf37Kexd.js → improvement-cycle-DuIGusyA.js} +1129 -10
- package/dist/improvement-cycle-DuIGusyA.js.map +1 -0
- package/dist/{index-CxBVmgqL.d.ts → index-DflDZq_I.d.ts} +50 -688
- 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-DiTNiJNs.d.ts → loop-runner-bin-DZfRUVWy.d.ts} +3 -3
- package/dist/{loop-runner-bin-C8MI8N1A.js → loop-runner-bin-Dp1V3P2W.js} +3 -3
- package/dist/{loop-runner-bin-C8MI8N1A.js.map → loop-runner-bin-Dp1V3P2W.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-BeogJ6V8.d.ts → openai-tools-YO30D2s3.d.ts} +2 -2
- package/dist/{prepare-CGVNMb1o.js → prepare-CulUz6Wm.js} +7 -13
- package/dist/prepare-CulUz6Wm.js.map +1 -0
- package/dist/{profile-BetOB4_Q.js → profile-DY9lT3gH.js} +2 -2
- package/dist/{profile-BetOB4_Q.js.map → profile-DY9lT3gH.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-CuVHkhti.js → protected-model-port-BT2iYAsU.js} +4 -4
- package/dist/{protected-model-port-CuVHkhti.js.map → protected-model-port-BT2iYAsU.js.map} +1 -1
- package/dist/{protected-model-port-CAa0t1MT.d.ts → protected-model-port-C9QtEf3y.d.ts} +2 -2
- package/dist/{provision-supervisor-CS0fBIhp.js → provision-supervisor-CfK8fla-.js} +4 -3
- package/dist/{provision-supervisor-CS0fBIhp.js.map → provision-supervisor-CfK8fla-.js.map} +1 -1
- package/dist/{runtime-6VPSJVeg.js → runtime-D452tieK.js} +67 -469
- package/dist/runtime-D452tieK.js.map +1 -0
- package/dist/{search-method-wTTS7mGG.js → search-executor-D3AT8_hb.js} +239 -1132
- package/dist/search-executor-D3AT8_hb.js.map +1 -0
- package/dist/{server-BfHD5iLD.js → server-D1Tfxo-v.js} +5 -12
- package/dist/server-D1Tfxo-v.js.map +1 -0
- package/dist/{stream-agent-turn-B1ipFtOc.d.ts → stream-agent-turn-B1giFgOg.d.ts} +2 -2
- package/dist/{substrate-DKBMd0Dl.d.ts → substrate-L_sC7jCD.d.ts} +2 -2
- package/dist/{supervise-BJrEC2Mh.js → supervise-CtewGpxh.js} +14 -24
- package/dist/supervise-CtewGpxh.js.map +1 -0
- package/dist/{supervisor-DA_nvF-h.js → supervisor-DYKPbcZa.js} +488 -1407
- package/dist/supervisor-DYKPbcZa.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-jD9mNnJM.d.ts → types-DeuEkBhI.d.ts} +114 -258
- package/dist/{workspace-archive-pz8YJ1fr.d.ts → workspace-streams-CpSmt3zx.d.ts} +67 -3
- package/dist/{workspace-streams-D5gHJdTb.js → workspace-streams-DaQceg2p.js} +699 -2
- package/dist/workspace-streams-DaQceg2p.js.map +1 -0
- package/package.json +8 -8
- package/skills/generate-eval/SKILL.md +1 -1
- package/skills/profile-authoring/SKILL.md +2 -2
- package/dist/coordination-driver-vNrdQBSH.js.map +0 -1
- package/dist/graph-Dwv_hQB_.js.map +0 -1
- package/dist/improvement-cycle-Cf37Kexd.js.map +0 -1
- package/dist/prepare-CGVNMb1o.js.map +0 -1
- package/dist/runtime-6VPSJVeg.js.map +0 -1
- package/dist/search-method-wTTS7mGG.js.map +0 -1
- package/dist/server-BfHD5iLD.js.map +0 -1
- package/dist/supervise-BJrEC2Mh.js.map +0 -1
- package/dist/supervisor-DA_nvF-h.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-DaQceg2p.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,130 +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
|
-
/**
|
|
113
|
-
* How long an abort waits for a manager whose result was already accepted to end the turn that
|
|
114
|
-
* submitted it. Acceptance asks the turn's harness to stop; what remains is the turn's final
|
|
115
|
-
* session copy (a bounded native capture), metering its usage and committing its result. Its
|
|
116
|
-
* workspace capture is not part of it: the manager's owner runs that after the result.
|
|
117
|
-
*/
|
|
118
|
-
const ACCEPTED_RESULT_GRACE_MS = 5 * 6e4;
|
|
119
|
-
/** Whether an abort reason is a deadline: the child's own or the root budget's. */
|
|
120
|
-
function isDeadlineAbortReason(reason) {
|
|
121
|
-
return reason === "child deadline exceeded" || reason === "root budget deadline exceeded";
|
|
122
|
-
}
|
|
123
|
-
const TEARDOWN_ACKNOWLEDGEMENT_MS = 250;
|
|
124
|
-
/** Arm a finite wall-clock deadline in safe chunks. Resource deadlines unref by default;
|
|
125
|
-
* a standalone wait opts into keeping the process alive until it settles or is cancelled. */
|
|
126
|
-
function armDeadlineTimer(delayMs, onDeadline, keepAlive = false) {
|
|
127
|
-
const deadlineAtMs = Date.now() + Math.max(0, delayMs);
|
|
128
|
-
if (!Number.isFinite(delayMs) || !Number.isSafeInteger(Math.ceil(deadlineAtMs))) throw new ValidationError$1("timer delay must produce a finite safe deadline");
|
|
129
|
-
let cleared = false;
|
|
130
|
-
let timer;
|
|
131
|
-
const arm = () => {
|
|
132
|
-
if (cleared) return;
|
|
133
|
-
const remainingMs = Math.max(0, deadlineAtMs - Date.now());
|
|
134
|
-
timer = setTimeout(() => {
|
|
135
|
-
if (cleared) return;
|
|
136
|
-
if (Date.now() >= deadlineAtMs) onDeadline();
|
|
137
|
-
else arm();
|
|
138
|
-
}, Math.min(remainingMs, MAX_TIMER_DELAY_MS));
|
|
139
|
-
if (!keepAlive && typeof timer.unref === "function") timer.unref();
|
|
140
|
-
};
|
|
141
|
-
arm();
|
|
142
|
-
return () => {
|
|
143
|
-
cleared = true;
|
|
144
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Resolve a spawned child's duration into one absolute cutoff. An omitted child deadline inherits
|
|
149
|
-
* the parent cutoff; an explicit child duration may shorten, but never extend, that cutoff.
|
|
150
|
-
*/
|
|
151
|
-
function boundedChildDeadlineAt(parentDeadlineAtMs, childDeadlineMs, nowMs) {
|
|
152
|
-
const parent = parentDeadlineAtMs > 0 ? parentDeadlineAtMs : void 0;
|
|
153
|
-
const child = childDeadlineMs === void 0 ? void 0 : nowMs + childDeadlineMs;
|
|
154
|
-
if (parent === void 0) return child;
|
|
155
|
-
if (child === void 0) return parent;
|
|
156
|
-
return Math.min(parent, child);
|
|
157
|
-
}
|
|
158
|
-
/** Invoke teardown once and bound how long the runtime waits for its acknowledgement. A hard
|
|
159
|
-
* execution deadline always wins; without one, numeric grace gets a short acknowledgement
|
|
160
|
-
* allowance and a brutal kill gets only that allowance. Explicit `infinity` remains unbounded
|
|
161
|
-
* only when no execution deadline exists. The losing promise stays observed. `ask` issues the
|
|
162
|
-
* request; a scope passes its per-child single-flight asker (see `singleFlightTeardown`). */
|
|
163
|
-
async function teardownExecutor(executor, grace, deadlineAtMs, now, ask = (requested) => Promise.resolve(executor.teardown(requested))) {
|
|
164
|
-
const work = Promise.resolve(ask(grace));
|
|
165
|
-
const requestedWaitMs = grace === "infinity" ? void 0 : grace === "brutalKill" ? executor.teardownTimeoutMs ?? TEARDOWN_ACKNOWLEDGEMENT_MS : grace + TEARDOWN_ACKNOWLEDGEMENT_MS;
|
|
166
|
-
const deadlineWaitMs = deadlineAtMs === void 0 ? void 0 : Math.max(0, deadlineAtMs - now()) + TEARDOWN_ACKNOWLEDGEMENT_MS;
|
|
167
|
-
const waitMs = requestedWaitMs === void 0 ? deadlineWaitMs : deadlineWaitMs === void 0 ? requestedWaitMs : Math.min(requestedWaitMs, deadlineWaitMs);
|
|
168
|
-
let receipt;
|
|
169
|
-
if (waitMs === void 0) receipt = await work;
|
|
170
|
-
else if (waitMs <= 0) {
|
|
171
|
-
work.catch(() => void 0);
|
|
172
|
-
throw new ValidationError$1("executor teardown did not acknowledge before its deadline");
|
|
173
|
-
} else {
|
|
174
|
-
let clearTimer;
|
|
175
|
-
const timedOut = new Promise((_resolve, reject) => {
|
|
176
|
-
clearTimer = armDeadlineTimer(waitMs, () => reject(new ValidationError$1(`executor teardown did not acknowledge within ${waitMs}ms`)), true);
|
|
177
|
-
});
|
|
178
|
-
try {
|
|
179
|
-
receipt = await Promise.race([work, timedOut]);
|
|
180
|
-
} finally {
|
|
181
|
-
clearTimer?.();
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
if (!receipt.destroyed) throw new ValidationError$1(`executor teardown reported destroyed=false${receipt.detail ? `: ${receipt.detail}` : ""}`);
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* The optional teardown surfaces a wrapping executor forwards, so wrapping never changes how a
|
|
188
|
-
* child is released: the acknowledgement window a remote teardown needs, the release of a
|
|
189
|
-
* retained execution at root settlement, and the environments an unconfirmed teardown names for
|
|
190
|
-
* a sweeper. A wrapper that dropped `releaseRetained` left every retained child it wrapped
|
|
191
|
-
* running after settlement. Each surface is present only when the inner executor implements it.
|
|
192
|
-
*/
|
|
193
|
-
function teardownSurfaces(inner) {
|
|
194
|
-
return {
|
|
195
|
-
...inner.teardownTimeoutMs === void 0 ? {} : { teardownTimeoutMs: inner.teardownTimeoutMs },
|
|
196
|
-
...inner.releaseRetained === void 0 ? {} : { releaseRetained: (signal) => inner.releaseRetained(signal) },
|
|
197
|
-
...inner.heldEnvironments === void 0 ? {} : { heldEnvironments: () => inner.heldEnvironments() }
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* An asker that sends an executor at most one teardown at a time. While a request is still
|
|
202
|
-
* running, every later ask returns that same request instead of sending another beside it.
|
|
203
|
-
*
|
|
204
|
-
* A settlement's own teardown has a short acknowledgement window, and a request that misses it
|
|
205
|
-
* keeps running. Without this, the first settlement retry sent a second teardown next to the
|
|
206
|
-
* first. An executor that does not deduplicate internally, such as one that re-issues its
|
|
207
|
-
* cancels, then ran two teardowns at once. `onRequest` runs once for each request actually sent.
|
|
208
|
-
*/
|
|
209
|
-
function singleFlightTeardown(executor, onRequest) {
|
|
210
|
-
let inFlight;
|
|
211
|
-
return (grace) => {
|
|
212
|
-
if (inFlight !== void 0) return inFlight;
|
|
213
|
-
onRequest();
|
|
214
|
-
let request;
|
|
215
|
-
try {
|
|
216
|
-
request = Promise.resolve(executor.teardown(grace));
|
|
217
|
-
} catch (error) {
|
|
218
|
-
request = Promise.reject(error);
|
|
219
|
-
}
|
|
220
|
-
inFlight = request;
|
|
221
|
-
const settle = () => {
|
|
222
|
-
if (inFlight === request) inFlight = void 0;
|
|
223
|
-
};
|
|
224
|
-
request.then(settle, settle);
|
|
225
|
-
return request;
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
//#endregion
|
|
229
105
|
//#region src/runtime/retained-run-binding.ts
|
|
230
106
|
function exactSession(environment, controlRef) {
|
|
231
107
|
if (environment.id !== controlRef.environmentId || environment.provider !== controlRef.provider) throw new Error("provider reconstructed a different retained environment");
|
|
@@ -322,7 +198,7 @@ async function awaitAbortable$1(work, signal) {
|
|
|
322
198
|
if (signal === void 0) return await work;
|
|
323
199
|
if (signal.aborted) {
|
|
324
200
|
Promise.resolve(work).catch(() => void 0);
|
|
325
|
-
throw abortError$
|
|
201
|
+
throw abortError$1(signal.reason);
|
|
326
202
|
}
|
|
327
203
|
return await new Promise((resolve, reject) => {
|
|
328
204
|
let settled = false;
|
|
@@ -332,7 +208,7 @@ async function awaitAbortable$1(work, signal) {
|
|
|
332
208
|
if (settled) return;
|
|
333
209
|
settled = true;
|
|
334
210
|
cleanup();
|
|
335
|
-
reject(abortError$
|
|
211
|
+
reject(abortError$1(signal.reason));
|
|
336
212
|
});
|
|
337
213
|
};
|
|
338
214
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -389,7 +265,7 @@ function isRecord$3(value) {
|
|
|
389
265
|
function assertStableText(value, label) {
|
|
390
266
|
if (value.length === 0 || value.trim() !== value) throw new Error(`${label} must be non-empty and have no outer whitespace`);
|
|
391
267
|
}
|
|
392
|
-
function abortError$
|
|
268
|
+
function abortError$1(reason) {
|
|
393
269
|
const error = new Error(reason instanceof Error ? reason.message : reason === void 0 ? "aborted" : String(reason));
|
|
394
270
|
error.name = "AbortError";
|
|
395
271
|
return error;
|
|
@@ -426,7 +302,7 @@ function isTerminalSessionStatus(status) {
|
|
|
426
302
|
return status !== null && sessionStatusIsTerminal[status];
|
|
427
303
|
}
|
|
428
304
|
function delay(ms, signal) {
|
|
429
|
-
if (signal?.aborted) return Promise.reject(abortError$
|
|
305
|
+
if (signal?.aborted) return Promise.reject(abortError$1(signal.reason));
|
|
430
306
|
return new Promise((resolveDelay, rejectDelay) => {
|
|
431
307
|
const clearTimer = armDeadlineTimer(ms, finish, true);
|
|
432
308
|
function finish() {
|
|
@@ -436,7 +312,7 @@ function delay(ms, signal) {
|
|
|
436
312
|
function onAbort() {
|
|
437
313
|
clearTimer();
|
|
438
314
|
signal?.removeEventListener("abort", onAbort);
|
|
439
|
-
rejectDelay(abortError$
|
|
315
|
+
rejectDelay(abortError$1(signal?.reason));
|
|
440
316
|
}
|
|
441
317
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
442
318
|
});
|
|
@@ -661,477 +537,6 @@ function isRecord$2(value) {
|
|
|
661
537
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
662
538
|
}
|
|
663
539
|
//#endregion
|
|
664
|
-
//#region src/runtime/supervise/resources.ts
|
|
665
|
-
/** Validate caller-owned names and units without assigning domain meaning to them. */
|
|
666
|
-
function assertResources(resources, kind, label) {
|
|
667
|
-
if (resources === void 0) return;
|
|
668
|
-
if (resources === null || typeof resources !== "object" || Array.isArray(resources)) throw new Error(`${label} must be a resource map`);
|
|
669
|
-
for (const [name, resource] of Object.entries(resources)) {
|
|
670
|
-
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`);
|
|
671
|
-
const value = kind === "limit" ? "limit" in resource ? resource.limit : void 0 : "amount" in resource ? resource.amount : void 0;
|
|
672
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new Error(`${label}.${name}.${kind} must be a non-negative safe integer`);
|
|
673
|
-
if (kind === "amount" && (!("known" in resource) || typeof resource.known !== "boolean")) throw new Error(`${label}.${name}.known must be a boolean`);
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
/** Missing contributors are not usage claims; enforced omissions are marked before aggregation.
|
|
677
|
-
* Overflow retains a safe lower bound and marks it unknown, preserving readable component receipts. */
|
|
678
|
-
function addResourceSpend(...maps) {
|
|
679
|
-
const totals = /* @__PURE__ */ new Map();
|
|
680
|
-
for (const resources of maps) {
|
|
681
|
-
assertResources(resources, "amount", "resource spend");
|
|
682
|
-
for (const [name, resource] of Object.entries(resources ?? {})) {
|
|
683
|
-
const prior = totals.get(name);
|
|
684
|
-
if (prior && prior.unit !== resource.unit) throw new Error(`resource ${name}: unit mismatch`);
|
|
685
|
-
const amount = (prior?.amount ?? 0) + resource.amount;
|
|
686
|
-
totals.set(name, {
|
|
687
|
-
unit: resource.unit,
|
|
688
|
-
amount: Math.min(amount, Number.MAX_SAFE_INTEGER),
|
|
689
|
-
known: (prior?.known ?? true) && resource.known && Number.isSafeInteger(amount)
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
return totals.size ? { resources: Object.fromEntries(totals) } : {};
|
|
694
|
-
}
|
|
695
|
-
/** A terminal omission under an enforced ceiling is unknown, never measured zero. */
|
|
696
|
-
function withBudgetResources(spend, budget, notStarted = false) {
|
|
697
|
-
const resources = new Map(Object.entries(addResourceSpend(spend.resources).resources ?? {}));
|
|
698
|
-
for (const [name, limit] of Object.entries(budget.resources ?? {})) {
|
|
699
|
-
const observed = resources.get(name);
|
|
700
|
-
if (observed && observed.unit !== limit.unit) throw new Error(`resource ${name}: unit mismatch`);
|
|
701
|
-
if (!observed) resources.set(name, {
|
|
702
|
-
unit: limit.unit,
|
|
703
|
-
amount: 0,
|
|
704
|
-
known: notStarted
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
return resources.size ? {
|
|
708
|
-
...spend,
|
|
709
|
-
resources: Object.fromEntries(resources)
|
|
710
|
-
} : spend;
|
|
711
|
-
}
|
|
712
|
-
/** Stream increments and a terminal total describe the same work; never sum them twice. */
|
|
713
|
-
function resourceTelemetry(streamed, terminal) {
|
|
714
|
-
const resources = new Map(Object.entries(addResourceSpend(terminal.resources).resources ?? {}));
|
|
715
|
-
for (const [name, value] of Object.entries(streamed.resources ?? {})) {
|
|
716
|
-
const other = resources.get(name);
|
|
717
|
-
if (other && other.unit !== value.unit) throw new Error(`resource ${name}: unit mismatch`);
|
|
718
|
-
resources.set(name, {
|
|
719
|
-
...value,
|
|
720
|
-
amount: Math.max(value.amount, other?.amount ?? 0),
|
|
721
|
-
known: value.known && (other?.known ?? true) && (other === void 0 || other.amount === value.amount)
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
return addResourceSpend(Object.fromEntries(resources));
|
|
725
|
-
}
|
|
726
|
-
//#endregion
|
|
727
|
-
//#region src/runtime/util.ts
|
|
728
|
-
/**
|
|
729
|
-
* Best-effort sandbox delete. Skips instances without a `delete` (test fakes);
|
|
730
|
-
* swallows errors (the platform reaps on expiry). Returns `false` when delete
|
|
731
|
-
* threw, `true` otherwise, so callers can surface a leak if they choose.
|
|
732
|
-
*/
|
|
733
|
-
async function deleteBoxSafe(box) {
|
|
734
|
-
if (!box || typeof box.delete !== "function") return true;
|
|
735
|
-
try {
|
|
736
|
-
await box.delete();
|
|
737
|
-
return true;
|
|
738
|
-
} catch {
|
|
739
|
-
return false;
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
/** Short base36 id for trace correlation. Not cryptographic, not collision-free. */
|
|
743
|
-
function randomSuffix(len = 8) {
|
|
744
|
-
return Math.random().toString(36).slice(2, 2 + len);
|
|
745
|
-
}
|
|
746
|
-
/** Collision-resistant id for sandbox naming (find-by-name recovery must be unique). */
|
|
747
|
-
function randomUuid() {
|
|
748
|
-
return crypto.randomUUID();
|
|
749
|
-
}
|
|
750
|
-
/** Construct an AbortError. Downstream code pattern-matches on `err.name`. */
|
|
751
|
-
function abortError$1() {
|
|
752
|
-
const err = /* @__PURE__ */ new Error("aborted");
|
|
753
|
-
err.name = "AbortError";
|
|
754
|
-
return err;
|
|
755
|
-
}
|
|
756
|
-
/** Throw an AbortError. */
|
|
757
|
-
function throwAbort() {
|
|
758
|
-
throw abortError$1();
|
|
759
|
-
}
|
|
760
|
-
/** Throw if the signal is already aborted; otherwise no-op. */
|
|
761
|
-
function throwIfAborted(signal) {
|
|
762
|
-
if (signal?.aborted) throw abortError$1();
|
|
763
|
-
}
|
|
764
|
-
/** True for any error whose `name` is `AbortError` (the cross-kernel abort contract). */
|
|
765
|
-
function isAbortError$2(err) {
|
|
766
|
-
return err instanceof Error && err.name === "AbortError";
|
|
767
|
-
}
|
|
768
|
-
/**
|
|
769
|
-
* Sleep that resolves early on abort and always clears its timer so it never
|
|
770
|
-
* keeps the event loop alive. Resolves (does not reject) on abort — callers
|
|
771
|
-
* re-check the signal explicitly after the sleep.
|
|
772
|
-
*/
|
|
773
|
-
function sleep(ms, signal, keepAlive = true) {
|
|
774
|
-
if (signal?.aborted) return Promise.resolve();
|
|
775
|
-
return new Promise((resolve) => {
|
|
776
|
-
const done = () => {
|
|
777
|
-
clearTimer();
|
|
778
|
-
signal?.removeEventListener("abort", done);
|
|
779
|
-
resolve();
|
|
780
|
-
};
|
|
781
|
-
const clearTimer = armDeadlineTimer(ms, done, keepAlive);
|
|
782
|
-
signal?.addEventListener("abort", done, { once: true });
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
|
-
/**
|
|
786
|
-
* Race a promise against a timeout. Resolves with the value if it settles in
|
|
787
|
-
* time, otherwise resolves with `undefined`. Always clears the timer.
|
|
788
|
-
*/
|
|
789
|
-
function withTimeout(promise, ms) {
|
|
790
|
-
return new Promise((resolve) => {
|
|
791
|
-
const clearTimer = armDeadlineTimer(ms, () => resolve(void 0), true);
|
|
792
|
-
promise.then((value) => {
|
|
793
|
-
clearTimer();
|
|
794
|
-
resolve(value);
|
|
795
|
-
}, () => {
|
|
796
|
-
clearTimer();
|
|
797
|
-
resolve(void 0);
|
|
798
|
-
});
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
/**
|
|
802
|
-
* `JSON.stringify` with a `String()` fallback on throw (cyclic / non-JSON).
|
|
803
|
-
* Strings pass through unstringified so a preview of a string output is the
|
|
804
|
-
* string itself, not a quoted re-encoding.
|
|
805
|
-
*/
|
|
806
|
-
function stringifySafe(value, opts = {}) {
|
|
807
|
-
let s;
|
|
808
|
-
try {
|
|
809
|
-
if (typeof value === "string") s = value;
|
|
810
|
-
else s = (opts.pretty ? JSON.stringify(value, null, 2) : JSON.stringify(value)) ?? String(value);
|
|
811
|
-
} catch {
|
|
812
|
-
s = String(value);
|
|
813
|
-
}
|
|
814
|
-
if (opts.max !== void 0 && s.length > opts.max) return `${s.slice(0, opts.max)}…`;
|
|
815
|
-
return s;
|
|
816
|
-
}
|
|
817
|
-
/** A fresh zero token-usage accumulator. */
|
|
818
|
-
function zeroTokenUsage() {
|
|
819
|
-
return {
|
|
820
|
-
input: 0,
|
|
821
|
-
output: 0
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
/**
|
|
825
|
-
* Sum the catalog-priced part of a dollar total across spends, as a field to spread.
|
|
826
|
-
*
|
|
827
|
-
* Returns nothing when no input carried one. A fold of pure provider receipts must not gain a
|
|
828
|
-
* `usdEstimated: 0`, which would read as "this runtime checked and priced none" on a path that
|
|
829
|
-
* never prices at all.
|
|
830
|
-
*/
|
|
831
|
-
function usdEstimatedOf(...spends) {
|
|
832
|
-
let total = 0;
|
|
833
|
-
let priced = false;
|
|
834
|
-
for (const spend of spends) {
|
|
835
|
-
if (spend.usdEstimated === void 0) continue;
|
|
836
|
-
priced = true;
|
|
837
|
-
total += spend.usdEstimated;
|
|
838
|
-
}
|
|
839
|
-
return priced ? { usdEstimated: total } : {};
|
|
840
|
-
}
|
|
841
|
-
/** A conserved spend with every channel at zero and no completeness claim on it. */
|
|
842
|
-
function zeroSpend() {
|
|
843
|
-
return {
|
|
844
|
-
iterations: 0,
|
|
845
|
-
tokens: zeroTokenUsage(),
|
|
846
|
-
usd: 0,
|
|
847
|
-
ms: 0
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
/**
|
|
851
|
-
* The spend of work that HAPPENED and reported no usage receipt.
|
|
852
|
-
*
|
|
853
|
-
* Not the same value as {@link zeroSpend} even though both carry `{0,0}` tokens and `$0`. A bare
|
|
854
|
-
* zero asserts a MEASUREMENT — "this ran and cost nothing" — and every consumer downstream reads it
|
|
855
|
-
* that way: the pool keeps reporting `readout().tokensKnown === true`, the journal totals stay
|
|
856
|
-
* clean, the OTEL span records a priced zero, and a caller's token-denominated ceiling can never
|
|
857
|
-
* fire no matter how much the work really burned. A ceiling that cannot fire is worse than no
|
|
858
|
-
* ceiling, because it reads as protection.
|
|
859
|
-
*
|
|
860
|
-
* `tokensKnown` is the marker the substrate already threads end to end for exactly this case
|
|
861
|
-
* (`budget.ts`, `otel-spans.ts`, `spawn-journal.ts`, `supervisor.ts`): the work is recorded, the
|
|
862
|
-
* zero is labelled a floor rather than a total, and every rollup that touches it reports its
|
|
863
|
-
* balance as a ceiling rather than a measurement. Use this — never a bare zero — whenever a runtime
|
|
864
|
-
* cannot see what its worker spent.
|
|
865
|
-
*
|
|
866
|
-
* The same rule applies to dollars. A dollar-capped run must refuse an executor whose billed spend
|
|
867
|
-
* is unknowable; `budgetExempt` does not authorize Runtime to relabel unknown spend as a measured
|
|
868
|
-
* zero. Callers that require dollar accounting must use a backend with a trusted billing receipt.
|
|
869
|
-
*/
|
|
870
|
-
function unmeteredSpend(ms) {
|
|
871
|
-
return {
|
|
872
|
-
iterations: 0,
|
|
873
|
-
tokens: zeroTokenUsage(),
|
|
874
|
-
tokensKnown: false,
|
|
875
|
-
usd: 0,
|
|
876
|
-
usdKnown: false,
|
|
877
|
-
ms
|
|
878
|
-
};
|
|
879
|
-
}
|
|
880
|
-
/** Copy a conserved spend without dropping a completeness marker or the catalog-priced part. */
|
|
881
|
-
function cloneSpend(spend) {
|
|
882
|
-
return {
|
|
883
|
-
...addResourceSpend(spend.resources),
|
|
884
|
-
iterations: spend.iterations,
|
|
885
|
-
tokens: cloneTokenUsage(spend.tokens),
|
|
886
|
-
...spend.tokensKnown === false ? { tokensKnown: false } : {},
|
|
887
|
-
usd: spend.usd,
|
|
888
|
-
...spend.usdKnown === false ? { usdKnown: false } : {},
|
|
889
|
-
...spend.usdEstimated !== void 0 ? { usdEstimated: spend.usdEstimated } : {},
|
|
890
|
-
ms: spend.ms,
|
|
891
|
-
...boxMinutesOf(spend),
|
|
892
|
-
...spend.tokensProvenance === void 0 ? {} : { tokensProvenance: spend.tokensProvenance }
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
|
-
/**
|
|
896
|
-
* Sum two conserved spends per channel. Tokens and dollars never fold into each other, an
|
|
897
|
-
* incomplete input keeps the sum incomplete on that channel, and the catalog-priced part of the
|
|
898
|
-
* dollar total is summed separately so `usd - usdEstimated` still names what a provider billed.
|
|
899
|
-
*/
|
|
900
|
-
function addSpend(a, b) {
|
|
901
|
-
const tokens = cloneTokenUsage(a.tokens);
|
|
902
|
-
addTokenUsage(tokens, b.tokens);
|
|
903
|
-
return {
|
|
904
|
-
...addResourceSpend(a.resources, b.resources),
|
|
905
|
-
iterations: a.iterations + b.iterations,
|
|
906
|
-
tokens,
|
|
907
|
-
...a.tokensKnown === false || b.tokensKnown === false ? { tokensKnown: false } : {},
|
|
908
|
-
usd: a.usd + b.usd,
|
|
909
|
-
...a.usdKnown === false || b.usdKnown === false ? { usdKnown: false } : {},
|
|
910
|
-
...usdEstimatedOf(a, b),
|
|
911
|
-
ms: a.ms + b.ms,
|
|
912
|
-
...boxMinutesOf(a, b),
|
|
913
|
-
...tokensProvenanceOf(a, b)
|
|
914
|
-
};
|
|
915
|
-
}
|
|
916
|
-
/**
|
|
917
|
-
* Fold the token-provenance channel across spends, as a field to spread.
|
|
918
|
-
*
|
|
919
|
-
* Returns nothing when no contributor stated one, because ABSENT already means "the executor's own
|
|
920
|
-
* live stream", which is what every path reported before a harness store was readable. A
|
|
921
|
-
* contributor that stated one and a contributor that did not therefore disagree, and the sum is
|
|
922
|
-
* `'mixed'` — the honest answer for a tree whose spend came from two kinds of receipt.
|
|
923
|
-
*/
|
|
924
|
-
function tokensProvenanceOf(...spends) {
|
|
925
|
-
let folded;
|
|
926
|
-
let sawStated = false;
|
|
927
|
-
for (const spend of spends) {
|
|
928
|
-
const stated = spend.tokensProvenance ?? "stream-receipt";
|
|
929
|
-
if (spend.tokensProvenance !== void 0) sawStated = true;
|
|
930
|
-
folded = folded === void 0 || folded === stated ? stated : "mixed";
|
|
931
|
-
}
|
|
932
|
-
return !sawStated || folded === void 0 || folded === "stream-receipt" ? {} : { tokensProvenance: folded };
|
|
933
|
-
}
|
|
934
|
-
/**
|
|
935
|
-
* Fold the platform box-time channel across spends, as a field to spread.
|
|
936
|
-
*
|
|
937
|
-
* Returns nothing when no input carried the channel, so a fold over router or cli-bridge work —
|
|
938
|
-
* which runs no box at all — never gains a `boxMinutesProvenance: 'uncaptured'` that would claim
|
|
939
|
-
* a box ran and went unmetered.
|
|
940
|
-
*
|
|
941
|
-
* Three rules, one per field:
|
|
942
|
-
* - `boxMinutes` sums the numbers that are present, and stays ABSENT when none is. A missing
|
|
943
|
-
* measurement never enters the sum as a zero.
|
|
944
|
-
* - `boxMinutesKnown` is sticky-false, like `tokensKnown`: one contributor that could not state
|
|
945
|
-
* its box time makes the whole sum a floor.
|
|
946
|
-
* - `boxMinutesProvenance` degrades. `'observed'` survives only when every contributor that
|
|
947
|
-
* carried a number was observed; one derived number makes the sum `'estimated'`; no number at
|
|
948
|
-
* all is `'uncaptured'`.
|
|
949
|
-
*/
|
|
950
|
-
function boxMinutesOf(...spends) {
|
|
951
|
-
const present = spends.filter((spend) => spend.boxMinutesProvenance !== void 0);
|
|
952
|
-
if (present.length === 0) return {};
|
|
953
|
-
let minutes = 0;
|
|
954
|
-
let sawNumber = false;
|
|
955
|
-
let known = true;
|
|
956
|
-
let observedOnly = true;
|
|
957
|
-
for (const spend of present) {
|
|
958
|
-
if (typeof spend.boxMinutes === "number" && Number.isFinite(spend.boxMinutes)) {
|
|
959
|
-
minutes += spend.boxMinutes;
|
|
960
|
-
sawNumber = true;
|
|
961
|
-
if (spend.boxMinutesProvenance !== "observed") observedOnly = false;
|
|
962
|
-
}
|
|
963
|
-
if (spend.boxMinutesKnown !== true) known = false;
|
|
964
|
-
}
|
|
965
|
-
return {
|
|
966
|
-
...sawNumber ? { boxMinutes: roundBoxMinutes(minutes) } : {},
|
|
967
|
-
boxMinutesKnown: known,
|
|
968
|
-
boxMinutesProvenance: sawNumber ? observedOnly ? "observed" : "estimated" : "uncaptured"
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
/**
|
|
972
|
-
* Box minutes at four decimals — a tenth of a second.
|
|
973
|
-
*
|
|
974
|
-
* Fine enough that a box alive for two seconds reports `0.0333` instead of a zero that would read
|
|
975
|
-
* as a measured free box, and coarse enough that summing floats does not print
|
|
976
|
-
* `1.5000000000000002`.
|
|
977
|
-
*/
|
|
978
|
-
function roundBoxMinutes(minutes) {
|
|
979
|
-
return Number(minutes.toFixed(4));
|
|
980
|
-
}
|
|
981
|
-
/** Copy a token subtotal without dropping optional provider cache telemetry. */
|
|
982
|
-
function cloneTokenUsage(usage) {
|
|
983
|
-
const cacheBreakdownUnknown = usage.cacheBreakdownKnown === false || usage.input > 0 && !hasClassifiedCacheBreakdown(usage);
|
|
984
|
-
return {
|
|
985
|
-
input: usage.input,
|
|
986
|
-
output: usage.output,
|
|
987
|
-
...usage.tokensKnown === false ? { tokensKnown: false } : {},
|
|
988
|
-
...usage.freshInput !== void 0 ? { freshInput: usage.freshInput } : {},
|
|
989
|
-
...usage.cacheRead !== void 0 ? { cacheRead: usage.cacheRead } : {},
|
|
990
|
-
...usage.cacheWrite !== void 0 ? { cacheWrite: usage.cacheWrite } : {},
|
|
991
|
-
...cacheBreakdownUnknown ? { cacheBreakdownKnown: false } : {}
|
|
992
|
-
};
|
|
993
|
-
}
|
|
994
|
-
/** True only when every positive prompt token has a consistent cache classification. */
|
|
995
|
-
function hasCompleteCacheBreakdown(usage) {
|
|
996
|
-
if (usage.cacheBreakdownKnown === false) return false;
|
|
997
|
-
return hasClassifiedCacheBreakdown(usage);
|
|
998
|
-
}
|
|
999
|
-
function hasClassifiedCacheBreakdown(usage) {
|
|
1000
|
-
if (usage.input === 0) return true;
|
|
1001
|
-
if (usage.freshInput === void 0 || usage.cacheRead === void 0 || usage.cacheWrite === void 0) return false;
|
|
1002
|
-
return usage.freshInput + usage.cacheRead + usage.cacheWrite === usage.input;
|
|
1003
|
-
}
|
|
1004
|
-
/**
|
|
1005
|
-
* The token charge for one observation: every token counted ONCE, at the moment it first enters
|
|
1006
|
-
* the context — `input − cacheRead + output`.
|
|
1007
|
-
*
|
|
1008
|
-
* `input` is a rolled-up prompt total that re-counts a cached prefix on every turn that reads it,
|
|
1009
|
-
* so charging `input` makes a 100K prefix read 40 times cost 4.1M for content authored once. A
|
|
1010
|
-
* cache read is content that was already charged when it was written, so it is the one class the
|
|
1011
|
-
* charge subtracts. Under a complete split the result is exactly `freshInput + cacheWrite`. No
|
|
1012
|
-
* price weight is involved: this counts work, and money is a separate channel.
|
|
1013
|
-
*
|
|
1014
|
-
* The subtraction form is what makes the charge ADDITIVE. `input` and `cacheRead` both accumulate
|
|
1015
|
-
* through `addTokenUsage`, so the charge on an aggregate equals the sum of the charges on the
|
|
1016
|
-
* records that built it — including an aggregate that mixes classified and unclassified turns,
|
|
1017
|
-
* where the unclassified remainder is charged in full and only the reported cache reads are
|
|
1018
|
-
* credited. A form that read `freshInput + cacheWrite` directly loses that: one unclassified turn
|
|
1019
|
-
* would drop the whole aggregate to `input + output` and over-charge every classified turn with it.
|
|
1020
|
-
*
|
|
1021
|
-
* The one arithmetic guarantee: a set of classes that does not FIT inside the prompt total it
|
|
1022
|
-
* partitions credits nothing, so the charge is never below `output` and a class that overflows its
|
|
1023
|
-
* own total buys no tokens. It is not a guarantee against a provider that reports a cache read it
|
|
1024
|
-
* never served — that provider can under-report `input` just as easily, and the token channel is an
|
|
1025
|
-
* accounting unit, not a trust boundary. Prompt tokens the provider left unclassified are charged
|
|
1026
|
-
* in full, so a spend with no readable split charges an upper bound on newly-presented work rather
|
|
1027
|
-
* than a measurement, and every caller that enforces a ceiling must report the difference
|
|
1028
|
-
* (`BudgetPool` does it through `readout().cacheBreakdownKnown`).
|
|
1029
|
-
*/
|
|
1030
|
-
function chargedTokens(usage) {
|
|
1031
|
-
return usage.input - creditedCacheRead(usage) + usage.output;
|
|
1032
|
-
}
|
|
1033
|
-
/** `cacheRead` if the reported classes fit inside `input`, else nothing. */
|
|
1034
|
-
function creditedCacheRead(usage) {
|
|
1035
|
-
const { cacheRead } = usage;
|
|
1036
|
-
if (cacheRead === void 0) return 0;
|
|
1037
|
-
return classifiedTotal(usage, cacheRead) > usage.input ? 0 : cacheRead;
|
|
1038
|
-
}
|
|
1039
|
-
function classifiedTotal(usage, cacheRead) {
|
|
1040
|
-
return (usage.freshInput ?? 0) + cacheRead + (usage.cacheWrite ?? 0);
|
|
1041
|
-
}
|
|
1042
|
-
/**
|
|
1043
|
-
* Translate one provider prompt-cache report into the prompt token classes `addTokenUsage` folds
|
|
1044
|
-
* and `chargedTokens` credits.
|
|
1045
|
-
*
|
|
1046
|
-
* The input is the open `promptCache` record an `llm_call` carries, keyed by the `PromptCacheUsage`
|
|
1047
|
-
* vocabulary (`readTokens`, `writeTokens`). Providers report that vocabulary unevenly, and the
|
|
1048
|
-
* three outcomes are different facts that must not collapse into one another:
|
|
1049
|
-
*
|
|
1050
|
-
* - **Nothing reported** — no classes and no marker. The caller's own unknown handling decides,
|
|
1051
|
-
* because a provider that says nothing about caching has not said the split is unknowable.
|
|
1052
|
-
* - **Read and write both reported, and fitting inside `input`** — a complete partition, so
|
|
1053
|
-
* `freshInput` is the remainder and the split is known.
|
|
1054
|
-
* - **One counter reported, or classes that overflow `input`** — the counters that ARE measured
|
|
1055
|
-
* pass through and `cacheBreakdownKnown` is false. A read with no write counter (OpenAI reports
|
|
1056
|
-
* no write) still credits the read it measured, while declaring the rest unclassified so the
|
|
1057
|
-
* charge stays a declared upper bound. An overflowing claim buys nothing: it cannot be a
|
|
1058
|
-
* partition of a total it exceeds.
|
|
1059
|
-
*
|
|
1060
|
-
* A counter the provider did not report stays absent. A zero would claim the provider measured no
|
|
1061
|
-
* cache, which is a different fact from a provider that did not report.
|
|
1062
|
-
*/
|
|
1063
|
-
function promptCacheTokenClasses(input, promptCache) {
|
|
1064
|
-
if (promptCache === void 0) return {};
|
|
1065
|
-
const read = tokenCount(promptCache.readTokens);
|
|
1066
|
-
const write = tokenCount(promptCache.writeTokens);
|
|
1067
|
-
if (read === void 0 && write === void 0) return {};
|
|
1068
|
-
if (input === void 0 || (read ?? 0) + (write ?? 0) > input) return { cacheBreakdownKnown: false };
|
|
1069
|
-
if (read !== void 0 && write !== void 0) return {
|
|
1070
|
-
freshInput: input - read - write,
|
|
1071
|
-
cacheRead: read,
|
|
1072
|
-
cacheWrite: write
|
|
1073
|
-
};
|
|
1074
|
-
return {
|
|
1075
|
-
...read !== void 0 ? { cacheRead: read } : {},
|
|
1076
|
-
...write !== void 0 ? { cacheWrite: write } : {},
|
|
1077
|
-
cacheBreakdownKnown: false
|
|
1078
|
-
};
|
|
1079
|
-
}
|
|
1080
|
-
function tokenCount(value) {
|
|
1081
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
1082
|
-
}
|
|
1083
|
-
/**
|
|
1084
|
-
* Add the observed subtotal into `acc`; token and cache incompleteness are sticky.
|
|
1085
|
-
*
|
|
1086
|
-
* A delta whose classes do not FIT inside its own `input` contributes its prompt total and NO
|
|
1087
|
-
* classes. Folding them in would hide the overflow: the accumulator's larger `input` can absorb a
|
|
1088
|
-
* class total that overflowed the one delta that reported it, and `chargedTokens` would then credit
|
|
1089
|
-
* at the aggregate a cache read it refuses at the record. That is the one way the charge could come
|
|
1090
|
-
* out below the sum of the charges on the records that built it.
|
|
1091
|
-
*/
|
|
1092
|
-
function addTokenUsage(acc, delta) {
|
|
1093
|
-
acc.input += delta.input ?? 0;
|
|
1094
|
-
acc.output += delta.output ?? 0;
|
|
1095
|
-
if (delta.tokensKnown === false) acc.tokensKnown = false;
|
|
1096
|
-
const input = delta.input ?? 0;
|
|
1097
|
-
const fits = classifiedTotal(delta, delta.cacheRead ?? 0) <= input;
|
|
1098
|
-
if (fits) {
|
|
1099
|
-
if (delta.freshInput !== void 0) acc.freshInput = (acc.freshInput ?? 0) + delta.freshInput;
|
|
1100
|
-
if (delta.cacheRead !== void 0) acc.cacheRead = (acc.cacheRead ?? 0) + delta.cacheRead;
|
|
1101
|
-
if (delta.cacheWrite !== void 0) acc.cacheWrite = (acc.cacheWrite ?? 0) + delta.cacheWrite;
|
|
1102
|
-
}
|
|
1103
|
-
const classified = fits && delta.freshInput !== void 0 && delta.cacheRead !== void 0 && delta.cacheWrite !== void 0 && delta.freshInput + delta.cacheRead + delta.cacheWrite === input;
|
|
1104
|
-
if (delta.cacheBreakdownKnown === false || input > 0 && !classified) acc.cacheBreakdownKnown = false;
|
|
1105
|
-
}
|
|
1106
|
-
/**
|
|
1107
|
-
* Map `items` through `fn` with at most `limit` calls in flight at once,
|
|
1108
|
-
* preserving input order in the result. On the first `fn` rejection no NEW
|
|
1109
|
-
* items are picked up; already-in-flight calls are awaited, then the first
|
|
1110
|
-
* error is rethrown. `limit` is clamped to ≥ 1.
|
|
1111
|
-
*
|
|
1112
|
-
* Used where a burst of provisioning (e.g. forking N child boxes) must respect
|
|
1113
|
-
* the loop's concurrency bound instead of firing all N at once.
|
|
1114
|
-
*/
|
|
1115
|
-
async function mapWithConcurrency(items, limit, fn) {
|
|
1116
|
-
const results = new Array(items.length);
|
|
1117
|
-
let next = 0;
|
|
1118
|
-
for await (const settled of runTaskQueue({
|
|
1119
|
-
concurrency: Math.max(1, Math.min(Math.floor(limit), items.length)),
|
|
1120
|
-
take: () => {
|
|
1121
|
-
const index = next++;
|
|
1122
|
-
return index < items.length ? {
|
|
1123
|
-
id: String(index),
|
|
1124
|
-
value: index
|
|
1125
|
-
} : void 0;
|
|
1126
|
-
},
|
|
1127
|
-
execute: ({ value: index }) => fn(items[index], index)
|
|
1128
|
-
})) {
|
|
1129
|
-
if (settled.status === "rejected") throw settled.reason;
|
|
1130
|
-
results[settled.task.value] = settled.value;
|
|
1131
|
-
}
|
|
1132
|
-
return results;
|
|
1133
|
-
}
|
|
1134
|
-
//#endregion
|
|
1135
540
|
//#region src/runtime/sandbox-events.ts
|
|
1136
541
|
const CANONICAL_STREAM_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
1137
542
|
"child-task",
|
|
@@ -2502,140 +1907,6 @@ function providerAttemptEvidence(model) {
|
|
|
2502
1907
|
});
|
|
2503
1908
|
}
|
|
2504
1909
|
//#endregion
|
|
2505
|
-
//#region src/durable/jsonl-file.ts
|
|
2506
|
-
/** Read a fixed file prefix one record at a time, never allocating the whole journal as a string.
|
|
2507
|
-
* A reader cannot chase concurrent appends forever. Missing files are empty only when requested;
|
|
2508
|
-
* corruption, short reads, and other I/O errors remain failures. Closing the iterator closes the fd. */
|
|
2509
|
-
async function* readCommittedJsonLines(path, options = {}) {
|
|
2510
|
-
const fs = await import("node:fs/promises");
|
|
2511
|
-
let handle;
|
|
2512
|
-
try {
|
|
2513
|
-
handle = await fs.open(path, "r");
|
|
2514
|
-
} catch (error) {
|
|
2515
|
-
if (options.allowMissing && isNoEntError(error)) return;
|
|
2516
|
-
throw error;
|
|
2517
|
-
}
|
|
2518
|
-
try {
|
|
2519
|
-
const { size } = await handle.stat();
|
|
2520
|
-
if (size === 0) return;
|
|
2521
|
-
const { StringDecoder } = await import("node:string_decoder");
|
|
2522
|
-
const decoder = new StringDecoder("utf8");
|
|
2523
|
-
const stream = handle.createReadStream({
|
|
2524
|
-
autoClose: false,
|
|
2525
|
-
end: size - 1
|
|
2526
|
-
});
|
|
2527
|
-
const fragments = [];
|
|
2528
|
-
let lineNumber = 1;
|
|
2529
|
-
try {
|
|
2530
|
-
for await (const chunk of stream) {
|
|
2531
|
-
options.onBytes?.(chunk);
|
|
2532
|
-
const text = decoder.write(chunk);
|
|
2533
|
-
let start = 0;
|
|
2534
|
-
let end = text.indexOf("\n", start);
|
|
2535
|
-
while (end !== -1) {
|
|
2536
|
-
fragments.push(text.slice(start, end));
|
|
2537
|
-
const record = parseRecord(fragments.join(""), path, lineNumber++, true);
|
|
2538
|
-
fragments.length = 0;
|
|
2539
|
-
if (record !== void 0) yield record.value;
|
|
2540
|
-
start = end + 1;
|
|
2541
|
-
end = text.indexOf("\n", start);
|
|
2542
|
-
}
|
|
2543
|
-
if (start < text.length) fragments.push(text.slice(start));
|
|
2544
|
-
}
|
|
2545
|
-
if (stream.bytesRead !== size) throw new Error(`${path}: journal changed while reading its committed prefix`);
|
|
2546
|
-
fragments.push(decoder.end());
|
|
2547
|
-
const record = parseRecord(fragments.join(""), path, lineNumber, false);
|
|
2548
|
-
if (record !== void 0) yield record.value;
|
|
2549
|
-
} finally {
|
|
2550
|
-
stream.destroy();
|
|
2551
|
-
}
|
|
2552
|
-
} finally {
|
|
2553
|
-
await handle.close();
|
|
2554
|
-
}
|
|
2555
|
-
}
|
|
2556
|
-
/** An invalid unterminated tail may be a torn write; other parse failures are not evidence of one. */
|
|
2557
|
-
function parseRecord(line, source, lineNumber, terminated) {
|
|
2558
|
-
if (line.length === 0) return void 0;
|
|
2559
|
-
try {
|
|
2560
|
-
return { value: JSON.parse(line) };
|
|
2561
|
-
} catch (cause) {
|
|
2562
|
-
if (!(cause instanceof SyntaxError)) throw cause;
|
|
2563
|
-
if (!terminated) return void 0;
|
|
2564
|
-
throw new Error(`${source}: malformed JSONL record at line ${lineNumber}`, { cause });
|
|
2565
|
-
}
|
|
2566
|
-
}
|
|
2567
|
-
/** FileHandle.write may legally make a short write. Loop until every byte is appended. */
|
|
2568
|
-
async function writeAllBytes(handle, value) {
|
|
2569
|
-
const bytes = typeof value === "string" ? Buffer.from(value) : Buffer.from(value);
|
|
2570
|
-
let offset = 0;
|
|
2571
|
-
while (offset < bytes.byteLength) {
|
|
2572
|
-
const { bytesWritten } = await handle.write(bytes, offset, bytes.byteLength - offset, null);
|
|
2573
|
-
if (bytesWritten <= 0) throw new Error(`append-only file write made no progress at byte ${offset}`);
|
|
2574
|
-
offset += bytesWritten;
|
|
2575
|
-
}
|
|
2576
|
-
}
|
|
2577
|
-
/** Prepare the tail under the journal owner's existing single-writer discipline.
|
|
2578
|
-
* Healthy appends read one byte, regardless of history size. Only recovery scans backward,
|
|
2579
|
-
* retaining a valid unterminated value or truncating an uncommitted torn tail. */
|
|
2580
|
-
async function prepareJsonlAppend(path) {
|
|
2581
|
-
const fs = await import("node:fs/promises");
|
|
2582
|
-
let handle;
|
|
2583
|
-
try {
|
|
2584
|
-
handle = await fs.open(path, "r+");
|
|
2585
|
-
} catch (error) {
|
|
2586
|
-
if (isNoEntError(error)) return false;
|
|
2587
|
-
throw error;
|
|
2588
|
-
}
|
|
2589
|
-
try {
|
|
2590
|
-
const size = (await handle.stat()).size;
|
|
2591
|
-
if (size === 0) return false;
|
|
2592
|
-
const lastByte = Buffer.allocUnsafe(1);
|
|
2593
|
-
await readAt(handle, lastByte, size - 1);
|
|
2594
|
-
if (lastByte[0] === 10) return false;
|
|
2595
|
-
const chunks = [];
|
|
2596
|
-
let end = size;
|
|
2597
|
-
let tailStart = 0;
|
|
2598
|
-
while (end > 0) {
|
|
2599
|
-
const start = Math.max(0, end - 65536);
|
|
2600
|
-
const chunk = Buffer.allocUnsafe(end - start);
|
|
2601
|
-
await readAt(handle, chunk, start);
|
|
2602
|
-
const newline = chunk.lastIndexOf(10);
|
|
2603
|
-
chunks.push(chunk.subarray(newline + 1));
|
|
2604
|
-
if (newline >= 0) {
|
|
2605
|
-
tailStart = start + newline + 1;
|
|
2606
|
-
break;
|
|
2607
|
-
}
|
|
2608
|
-
end = start;
|
|
2609
|
-
}
|
|
2610
|
-
const tail = Buffer.concat(chunks.reverse()).toString("utf8");
|
|
2611
|
-
try {
|
|
2612
|
-
JSON.parse(tail);
|
|
2613
|
-
return true;
|
|
2614
|
-
} catch (error) {
|
|
2615
|
-
if (!(error instanceof SyntaxError)) throw error;
|
|
2616
|
-
await handle.truncate(tailStart);
|
|
2617
|
-
await handle.sync();
|
|
2618
|
-
return false;
|
|
2619
|
-
}
|
|
2620
|
-
} finally {
|
|
2621
|
-
await handle.close();
|
|
2622
|
-
}
|
|
2623
|
-
}
|
|
2624
|
-
/** A short read is legal; an unexpected EOF means the owner's snapshot changed. */
|
|
2625
|
-
async function readAt(handle, buffer, position) {
|
|
2626
|
-
let offset = 0;
|
|
2627
|
-
while (offset < buffer.length) {
|
|
2628
|
-
const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, position + offset);
|
|
2629
|
-
if (bytesRead === 0) throw new Error("journal changed while preparing its append tail");
|
|
2630
|
-
offset += bytesRead;
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
/** True for a filesystem error meaning "the file is not there yet" — the one condition an
|
|
2634
|
-
* append-only reader treats as an empty log rather than a fault. */
|
|
2635
|
-
function isNoEntError(error) {
|
|
2636
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2637
|
-
}
|
|
2638
|
-
//#endregion
|
|
2639
1910
|
//#region src/agent/profile-materialization.ts
|
|
2640
1911
|
const KNOWN_AXIS_SET = new Set(AGENT_PROFILE_MATERIALIZATION_AXES);
|
|
2641
1912
|
/**
|
|
@@ -7321,6 +6592,49 @@ function normalizeWorkerSlotBound(value) {
|
|
|
7321
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)}`);
|
|
7322
6593
|
return value;
|
|
7323
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
|
+
}
|
|
7324
6638
|
/** Validate the executable retention port at the create boundary. */
|
|
7325
6639
|
function assertProviderWorkspaceRetentionPort(port, context) {
|
|
7326
6640
|
if (!Number.isSafeInteger(port.timeoutMs) || port.timeoutMs <= 0) throw new ValidationError$1(`${context}: workspaceRetention.timeoutMs must be a positive safe integer`);
|
|
@@ -7328,6 +6642,7 @@ function assertProviderWorkspaceRetentionPort(port, context) {
|
|
|
7328
6642
|
if (port.artifacts === null || typeof port.artifacts?.read !== "function") throw new ValidationError$1(`${context}: workspaceRetention.artifacts.read is required`);
|
|
7329
6643
|
if (typeof port.capture !== "function") throw new ValidationError$1(`${context}: workspaceRetention.capture is required`);
|
|
7330
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`);
|
|
7331
6646
|
for (const key of ["nativeIntervalMs", "nativeTimeoutMs"]) {
|
|
7332
6647
|
const value = port[key];
|
|
7333
6648
|
if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) throw new ValidationError$1(`${context}: workspaceRetention.${key} must be a positive safe integer`);
|
|
@@ -7592,7 +6907,7 @@ async function environmentGone(provider, environmentId, signal) {
|
|
|
7592
6907
|
}
|
|
7593
6908
|
/** Create one environment while retaining ownership if cancellation wins the race. */
|
|
7594
6909
|
async function createInteractiveEnvironment(create, signal) {
|
|
7595
|
-
if (signal?.aborted) throw abortError$
|
|
6910
|
+
if (signal?.aborted) throw abortError$1(signal.reason);
|
|
7596
6911
|
const creation = Promise.resolve().then(create);
|
|
7597
6912
|
if (signal === void 0) return creation;
|
|
7598
6913
|
return new Promise((resolve, reject) => {
|
|
@@ -7603,7 +6918,7 @@ async function createInteractiveEnvironment(create, signal) {
|
|
|
7603
6918
|
owner = "runtime";
|
|
7604
6919
|
removeAbortListener();
|
|
7605
6920
|
disposeInteractiveEnvironment(creation, signal.reason);
|
|
7606
|
-
reject(abortError$
|
|
6921
|
+
reject(abortError$1(signal.reason));
|
|
7607
6922
|
};
|
|
7608
6923
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
7609
6924
|
creation.then((environment) => {
|
|
@@ -7623,7 +6938,7 @@ async function createInteractiveEnvironment(create, signal) {
|
|
|
7623
6938
|
async function startInteractiveProcess(environment, start, signal) {
|
|
7624
6939
|
if (signal?.aborted) {
|
|
7625
6940
|
disposeInteractiveEnvironment(Promise.resolve(environment), signal.reason);
|
|
7626
|
-
throw abortError$
|
|
6941
|
+
throw abortError$1(signal.reason);
|
|
7627
6942
|
}
|
|
7628
6943
|
const pending = Promise.resolve().then(start);
|
|
7629
6944
|
if (signal === void 0) return pending;
|
|
@@ -7635,7 +6950,7 @@ async function startInteractiveProcess(environment, start, signal) {
|
|
|
7635
6950
|
owner = "runtime";
|
|
7636
6951
|
removeAbortListener();
|
|
7637
6952
|
disposeInteractiveEnvironment(Promise.resolve(environment), signal.reason);
|
|
7638
|
-
reject(abortError$
|
|
6953
|
+
reject(abortError$1(signal.reason));
|
|
7639
6954
|
};
|
|
7640
6955
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
7641
6956
|
pending.then((result) => {
|
|
@@ -7770,7 +7085,7 @@ async function* retainedRunEvents(session, controlRef, options, now) {
|
|
|
7770
7085
|
try {
|
|
7771
7086
|
Promise.resolve(iterator.return?.()).catch(() => void 0);
|
|
7772
7087
|
} catch {}
|
|
7773
|
-
throw abortError$
|
|
7088
|
+
throw abortError$1(options.signal.reason);
|
|
7774
7089
|
}
|
|
7775
7090
|
if (error instanceof RetainedRunProviderContractError) throw error;
|
|
7776
7091
|
const fromProvider = error instanceof Error && !runtimeChecks.has(error);
|
|
@@ -7821,13 +7136,13 @@ function createRetainedRunHandle(environment, session, initialControlRef, capabi
|
|
|
7821
7136
|
}
|
|
7822
7137
|
};
|
|
7823
7138
|
const snapshot = async (reason, signal) => {
|
|
7824
|
-
if (signal?.aborted) throw abortError$
|
|
7139
|
+
if (signal?.aborted) throw abortError$1(signal.reason);
|
|
7825
7140
|
synchronizeActiveControlRef();
|
|
7826
7141
|
let status;
|
|
7827
7142
|
try {
|
|
7828
7143
|
status = await awaitAbortable$1(Promise.resolve().then(() => session.status({ signal })), signal);
|
|
7829
7144
|
} catch {
|
|
7830
|
-
if (signal?.aborted) throw abortError$
|
|
7145
|
+
if (signal?.aborted) throw abortError$1(signal.reason);
|
|
7831
7146
|
status = "unknown";
|
|
7832
7147
|
}
|
|
7833
7148
|
synchronizeActiveControlRef();
|
|
@@ -8054,7 +7369,7 @@ function createRetainedRunHandle(environment, session, initialControlRef, capabi
|
|
|
8054
7369
|
synchronizeActiveControlRef();
|
|
8055
7370
|
assertStableText(options.operationId, "retained cancellation operation id");
|
|
8056
7371
|
if (options.reason !== void 0) assertStableText(options.reason, "retained cancellation reason");
|
|
8057
|
-
if (options.signal?.aborted) throw abortError$
|
|
7372
|
+
if (options.signal?.aborted) throw abortError$1(options.signal.reason);
|
|
8058
7373
|
if (!hasDurableCancel(session)) throw new Error(`provider "${activeControlRef.provider}" does not expose durable cancellation operations`);
|
|
8059
7374
|
const request = cancellationRequest(activeControlRef, options.operationId, options.reason);
|
|
8060
7375
|
let effect = "unknown";
|
|
@@ -8419,6 +7734,7 @@ async function recoverRetainedRun(options) {
|
|
|
8419
7734
|
if (options.admission.provider !== options.provider.name) throw new Error("retained run intent belongs to another provider");
|
|
8420
7735
|
return {
|
|
8421
7736
|
outcome: "recovered",
|
|
7737
|
+
via: "resumed",
|
|
8422
7738
|
handle: await startRetainedRun({
|
|
8423
7739
|
provider: options.provider,
|
|
8424
7740
|
...options.replay,
|
|
@@ -8459,6 +7775,7 @@ async function recoverRetainedRun(options) {
|
|
|
8459
7775
|
const exact = exactSession(environment, controlRef);
|
|
8460
7776
|
return {
|
|
8461
7777
|
outcome: "recovered",
|
|
7778
|
+
via: "reattached",
|
|
8462
7779
|
handle: createRetainedRunHandle(environment, exact.session, exact.controlRef, capabilities, options.now)
|
|
8463
7780
|
};
|
|
8464
7781
|
}
|
|
@@ -9571,12 +8888,7 @@ function contentRef$1(prefix, value) {
|
|
|
9571
8888
|
} catch {
|
|
9572
8889
|
str = String(value);
|
|
9573
8890
|
}
|
|
9574
|
-
|
|
9575
|
-
for (let i = 0; i < str.length; i += 1) {
|
|
9576
|
-
h ^= str.charCodeAt(i);
|
|
9577
|
-
h = Math.imul(h, 16777619);
|
|
9578
|
-
}
|
|
9579
|
-
return `${prefix}:${(h >>> 0).toString(16).padStart(8, "0")}`;
|
|
8891
|
+
return `${prefix}:${fnv1a32Hex(str)}`;
|
|
9580
8892
|
}
|
|
9581
8893
|
//#endregion
|
|
9582
8894
|
//#region src/runtime/supervise/trace-evidence.ts
|
|
@@ -9696,6 +9008,140 @@ function runtimeOwnedNestedDriverTreeRoot(owner, parentTreeRoot, driverNodeId) {
|
|
|
9696
9008
|
return nestedDriverTreeOwners.has(owner) ? nestedDriverTreeRoot(parentTreeRoot, driverNodeId) : void 0;
|
|
9697
9009
|
}
|
|
9698
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
|
|
9699
9145
|
//#region src/durable/spawn-journal.ts
|
|
9700
9146
|
/**
|
|
9701
9147
|
* Mint the content-addressed `outRef` for a result artifact: `sha256:<hex>` over a
|
|
@@ -10341,17 +9787,15 @@ var SpawnEventIndex = class {
|
|
|
10341
9787
|
}
|
|
10342
9788
|
};
|
|
10343
9789
|
/**
|
|
10344
|
-
* The records that CLOSE a node's cursor slot: a settlement
|
|
10345
|
-
* 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 —
|
|
10346
9791
|
* a kind missing here leaves a finished node looking live, so its budget is never reclaimed and
|
|
10347
9792
|
* its reservation is replayed as still in doubt.
|
|
10348
9793
|
*/
|
|
10349
9794
|
function closesCursorSlot(ev) {
|
|
10350
|
-
return ev.kind === "settled" || ev.kind === "cancelled"
|
|
9795
|
+
return ev.kind === "settled" || ev.kind === "cancelled";
|
|
10351
9796
|
}
|
|
10352
9797
|
const outsideKinds = /* @__PURE__ */ new Set([
|
|
10353
9798
|
"spawned",
|
|
10354
|
-
"waiting",
|
|
10355
9799
|
"metered",
|
|
10356
9800
|
"materialized",
|
|
10357
9801
|
"execution-bound",
|
|
@@ -10368,6 +9812,9 @@ const outsideKinds = /* @__PURE__ */ new Set([
|
|
|
10368
9812
|
"environment-teardown",
|
|
10369
9813
|
"workspace-checkpoint-requested",
|
|
10370
9814
|
"workspace-checkpoint",
|
|
9815
|
+
"workspace-checkpoint-fork-requested",
|
|
9816
|
+
"workspace-checkpoint-fork-teardown",
|
|
9817
|
+
"workspace-checkpoint-capture",
|
|
10371
9818
|
"workspace-checkpoint-cleanup",
|
|
10372
9819
|
"workspace-restored",
|
|
10373
9820
|
"workspace-capture",
|
|
@@ -10409,7 +9856,7 @@ async function replaySpawnTree(journal, blobs, root) {
|
|
|
10409
9856
|
const materializations = /* @__PURE__ */ new Map();
|
|
10410
9857
|
const executionBindings = /* @__PURE__ */ new Map();
|
|
10411
9858
|
for (const ev of ordered) {
|
|
10412
|
-
if (ev.kind === "spawned"
|
|
9859
|
+
if (ev.kind === "spawned") labels.set(ev.id, ev.label);
|
|
10413
9860
|
if (ev.kind === "spawned" && ev.assignmentId !== void 0) assignmentIds.set(ev.id, ev.assignmentId);
|
|
10414
9861
|
if (ev.kind === "spawned" && ev.identity !== void 0) identities.set(ev.id, copyFrozenIdentity(ev.identity));
|
|
10415
9862
|
if (ev.kind === "materialized") materializations.set(ev.id, ev.receipt);
|
|
@@ -10432,7 +9879,6 @@ async function replaySpawnTree(journal, blobs, root) {
|
|
|
10432
9879
|
const settled = [];
|
|
10433
9880
|
for (const ev of ordered) {
|
|
10434
9881
|
if (ev.kind === "spawned") continue;
|
|
10435
|
-
if (ev.kind === "waiting") continue;
|
|
10436
9882
|
if (ev.kind === "metered") continue;
|
|
10437
9883
|
if (ev.kind === "progress") continue;
|
|
10438
9884
|
if (ev.kind === "reconciled") continue;
|
|
@@ -10445,6 +9891,9 @@ async function replaySpawnTree(journal, blobs, root) {
|
|
|
10445
9891
|
if (ev.kind === "environment-teardown") continue;
|
|
10446
9892
|
if (ev.kind === "workspace-checkpoint-requested") continue;
|
|
10447
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;
|
|
10448
9897
|
if (ev.kind === "workspace-checkpoint-cleanup") continue;
|
|
10449
9898
|
if (ev.kind === "workspace-restored") continue;
|
|
10450
9899
|
if (ev.kind === "workspace-capture") continue;
|
|
@@ -10454,39 +9903,6 @@ async function replaySpawnTree(journal, blobs, root) {
|
|
|
10454
9903
|
if (ev.kind === "node-inputs-resolved") continue;
|
|
10455
9904
|
if (ev.kind === "edge-verdict") continue;
|
|
10456
9905
|
if (ev.kind === "join-state") continue;
|
|
10457
|
-
if (ev.kind === "woken") {
|
|
10458
|
-
if (ev.by === "cancelled" || ev.outRef === void 0) {
|
|
10459
|
-
settled.push({
|
|
10460
|
-
kind: "down",
|
|
10461
|
-
handle: handleFor(ev.id, "cancelled"),
|
|
10462
|
-
reason: "wait cancelled",
|
|
10463
|
-
infra: false,
|
|
10464
|
-
trace: {
|
|
10465
|
-
status: "unavailable",
|
|
10466
|
-
reason: "not-an-executor"
|
|
10467
|
-
},
|
|
10468
|
-
...settlementTime(ev.at),
|
|
10469
|
-
seq: ev.seq
|
|
10470
|
-
});
|
|
10471
|
-
continue;
|
|
10472
|
-
}
|
|
10473
|
-
const outcome = await blobs.get(ev.outRef);
|
|
10474
|
-
if (outcome === void 0) throw new Error(`replaySpawnTree: blob store has no wait outcome for outRef '${ev.outRef}' (node '${ev.id}', seq ${ev.seq})`);
|
|
10475
|
-
settled.push({
|
|
10476
|
-
kind: "done",
|
|
10477
|
-
handle: handleFor(ev.id, "done"),
|
|
10478
|
-
out: outcome,
|
|
10479
|
-
outRef: ev.outRef,
|
|
10480
|
-
spent: zeroSpend(),
|
|
10481
|
-
trace: {
|
|
10482
|
-
status: "unavailable",
|
|
10483
|
-
reason: "not-an-executor"
|
|
10484
|
-
},
|
|
10485
|
-
...settlementTime(ev.at),
|
|
10486
|
-
seq: ev.seq
|
|
10487
|
-
});
|
|
10488
|
-
continue;
|
|
10489
|
-
}
|
|
10490
9906
|
if (ev.kind === "cancelled") {
|
|
10491
9907
|
settled.push({
|
|
10492
9908
|
kind: "down",
|
|
@@ -10586,24 +10002,9 @@ function copyFrozenIdentity(identity) {
|
|
|
10586
10002
|
function materializeTreeView(events) {
|
|
10587
10003
|
const nodes = /* @__PURE__ */ new Map();
|
|
10588
10004
|
let root;
|
|
10589
|
-
const spawns = events.filter((ev) => ev.kind === "spawned"
|
|
10005
|
+
const spawns = events.filter((ev) => ev.kind === "spawned").sort((a, b) => a.seq - b.seq);
|
|
10590
10006
|
const settlements = events.filter((ev) => insideCursorNamespace(ev)).sort((a, b) => a.seq - b.seq);
|
|
10591
10007
|
for (const ev of spawns) {
|
|
10592
|
-
if (ev.kind === "waiting") {
|
|
10593
|
-
nodes.set(ev.id, {
|
|
10594
|
-
id: ev.id,
|
|
10595
|
-
parent: ev.parent,
|
|
10596
|
-
label: ev.label,
|
|
10597
|
-
status: "waiting",
|
|
10598
|
-
runtime: "wait",
|
|
10599
|
-
budget: {
|
|
10600
|
-
maxIterations: 0,
|
|
10601
|
-
maxTokens: 0
|
|
10602
|
-
},
|
|
10603
|
-
spent: zeroSpend()
|
|
10604
|
-
});
|
|
10605
|
-
continue;
|
|
10606
|
-
}
|
|
10607
10008
|
if (ev.parent === void 0 && root === void 0) root = ev.id;
|
|
10608
10009
|
nodes.set(ev.id, {
|
|
10609
10010
|
id: ev.id,
|
|
@@ -10631,16 +10032,6 @@ function materializeTreeView(events) {
|
|
|
10631
10032
|
if (ev.retainedPendingCause !== void 0) node.retainedPendingCause = ev.retainedPendingCause;
|
|
10632
10033
|
const settledAt = Date.parse(ev.at);
|
|
10633
10034
|
if (Number.isFinite(settledAt)) node.settledAt = settledAt;
|
|
10634
|
-
} else if (ev.kind === "woken") {
|
|
10635
|
-
const node = requireNode(nodes, ev.id);
|
|
10636
|
-
node.status = ev.by === "cancelled" ? "cancelled" : "done";
|
|
10637
|
-
node.outRef = ev.outRef;
|
|
10638
|
-
node.trace = {
|
|
10639
|
-
status: "unavailable",
|
|
10640
|
-
reason: "not-an-executor"
|
|
10641
|
-
};
|
|
10642
|
-
const settledAt = Date.parse(ev.at);
|
|
10643
|
-
if (Number.isFinite(settledAt)) node.settledAt = settledAt;
|
|
10644
10035
|
} else {
|
|
10645
10036
|
const node = requireNode(nodes, ev.id);
|
|
10646
10037
|
node.status = "cancelled";
|
|
@@ -10682,31 +10073,9 @@ function materializeTreeView(events) {
|
|
|
10682
10073
|
return {
|
|
10683
10074
|
root: root ?? snapshots[0]?.id ?? "",
|
|
10684
10075
|
nodes: snapshots,
|
|
10685
|
-
inFlight: snapshots.filter((n) => n.status === "running" || n.status === "acquiring").length
|
|
10686
|
-
waiting: snapshots.filter((n) => n.status === "waiting").length
|
|
10076
|
+
inFlight: snapshots.filter((n) => n.status === "running" || n.status === "acquiring").length
|
|
10687
10077
|
};
|
|
10688
10078
|
}
|
|
10689
|
-
/**
|
|
10690
|
-
* The waits a journaled tree shows as ARMED but never woken — what a resumed run re-arms with the
|
|
10691
|
-
* ORIGINAL absolute deadline. Reading it from the journal (rather than from any live state) is
|
|
10692
|
-
* what makes "SIGKILL a waiting tree, a new process keeps waiting to the same instant" true.
|
|
10693
|
-
*/
|
|
10694
|
-
function pendingWaits(events) {
|
|
10695
|
-
const woken = /* @__PURE__ */ new Set();
|
|
10696
|
-
for (const ev of events) if (ev.kind === "woken") woken.add(ev.id);
|
|
10697
|
-
const pending = [];
|
|
10698
|
-
for (const ev of events) {
|
|
10699
|
-
if (ev.kind !== "waiting" || woken.has(ev.id)) continue;
|
|
10700
|
-
pending.push({
|
|
10701
|
-
id: ev.id,
|
|
10702
|
-
label: ev.label,
|
|
10703
|
-
spec: ev.spec,
|
|
10704
|
-
armedAt: ev.armedAt,
|
|
10705
|
-
ordinal: ev.seq
|
|
10706
|
-
});
|
|
10707
|
-
}
|
|
10708
|
-
return pending.sort((a, b) => a.ordinal - b.ordinal);
|
|
10709
|
-
}
|
|
10710
10079
|
/** Copy a terminal record's overspend at the journal boundary; absent stays absent. */
|
|
10711
10080
|
function budgetViolationOf(event) {
|
|
10712
10081
|
if (event.budgetViolation === void 0) return {};
|
|
@@ -11234,6 +10603,9 @@ function jitteredMs(ms, random) {
|
|
|
11234
10603
|
const WORKSPACE_CHECKPOINT_MARKER_PATH = ".agent-runtime-checkpoint";
|
|
11235
10604
|
/** Bound on one checkpoint: environment lookup, marker write, and the provider operation. */
|
|
11236
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;
|
|
11237
10609
|
/** Write the marker. False when the environment cannot take it, so no restore can be verified. */
|
|
11238
10610
|
async function writeWorkspaceMarker(environment, marker, signal) {
|
|
11239
10611
|
if (typeof environment.write !== "function") return false;
|
|
@@ -14606,34 +13978,6 @@ function buildBackendOptions(profile, overrides) {
|
|
|
14606
13978
|
};
|
|
14607
13979
|
}
|
|
14608
13980
|
//#endregion
|
|
14609
|
-
//#region src/runtime/sandbox-capabilities.ts
|
|
14610
|
-
const probeCache = /* @__PURE__ */ new WeakMap();
|
|
14611
|
-
/**
|
|
14612
|
-
* Probe (and memoize per client) what the loop may rely on. A client without a
|
|
14613
|
-
* `criuStatus` method, or whose probe rejects, yields `canFork = false` — a
|
|
14614
|
-
* failed probe must never claim a capability the platform may not have. The
|
|
14615
|
-
* promise is cached so concurrent fanout branches share one round-trip.
|
|
14616
|
-
*
|
|
14617
|
-
* @experimental
|
|
14618
|
-
*/
|
|
14619
|
-
function probeSandboxCapabilities(client) {
|
|
14620
|
-
const key = client;
|
|
14621
|
-
const cached = probeCache.get(key);
|
|
14622
|
-
if (cached) return cached;
|
|
14623
|
-
const probe = resolveCapabilities(client);
|
|
14624
|
-
probeCache.set(key, probe);
|
|
14625
|
-
return probe;
|
|
14626
|
-
}
|
|
14627
|
-
async function resolveCapabilities(client) {
|
|
14628
|
-
const criuStatus = client.criuStatus;
|
|
14629
|
-
if (typeof criuStatus !== "function") return { canFork: false };
|
|
14630
|
-
try {
|
|
14631
|
-
return { canFork: (await criuStatus.call(client))?.available === true };
|
|
14632
|
-
} catch {
|
|
14633
|
-
return { canFork: false };
|
|
14634
|
-
}
|
|
14635
|
-
}
|
|
14636
|
-
//#endregion
|
|
14637
13981
|
//#region src/runtime/sandbox-lineage.ts
|
|
14638
13982
|
const TEARDOWN_TIMEOUT_MS$1 = 15e3;
|
|
14639
13983
|
const DEFAULT_FORK_CONCURRENCY = 4;
|
|
@@ -14699,13 +14043,11 @@ function promptEvents(streaming, box, prompt, sessionId, signal, promptOptions)
|
|
|
14699
14043
|
});
|
|
14700
14044
|
}
|
|
14701
14045
|
/**
|
|
14702
|
-
* Build a lineage bound to one client
|
|
14703
|
-
* capabilities are passed in (not re-probed) so the kernel probes once per run
|
|
14704
|
-
* 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.
|
|
14705
14047
|
*
|
|
14706
14048
|
* @experimental
|
|
14707
14049
|
*/
|
|
14708
|
-
function createSandboxLineage(client,
|
|
14050
|
+
function createSandboxLineage(client, options = {}) {
|
|
14709
14051
|
if (!client || typeof client.create !== "function") throw new ValidationError$1("createSandboxLineage: client.create is required");
|
|
14710
14052
|
const streaming = options.streaming ?? "sse";
|
|
14711
14053
|
const recordMount = options.recordMount ?? (() => {});
|
|
@@ -14795,30 +14137,10 @@ function createSandboxLineage(client, capabilities, options = {}) {
|
|
|
14795
14137
|
};
|
|
14796
14138
|
});
|
|
14797
14139
|
}
|
|
14798
|
-
const checkpointId = capabilities.canFork ? await checkpointForFork(parent.box, signal) : void 0;
|
|
14799
14140
|
return mapWithConcurrency(prompts, forkConcurrency, async (prompt, i) => {
|
|
14800
14141
|
throwIfAborted(signal);
|
|
14801
14142
|
const spec = specs[i % specs.length];
|
|
14802
14143
|
if (!spec) throw new ValidationError$1("SandboxLineage.fork: no AgentRunSpec for branch");
|
|
14803
|
-
if (checkpointId !== void 0) {
|
|
14804
|
-
const box = await forkFromCheckpoint(parent.box, checkpointId, signal);
|
|
14805
|
-
owned.push(box);
|
|
14806
|
-
const inheritedProfile = profiles.get(parent.box) ?? spec.profile;
|
|
14807
|
-
profiles.set(box, inheritedProfile);
|
|
14808
|
-
options.onAcquire?.(box, inheritedProfile);
|
|
14809
|
-
await spec.prepareBox?.(box, {
|
|
14810
|
-
signal,
|
|
14811
|
-
recordMount
|
|
14812
|
-
});
|
|
14813
|
-
const sessionId = mintSessionId();
|
|
14814
|
-
return {
|
|
14815
|
-
handle: {
|
|
14816
|
-
box,
|
|
14817
|
-
sessionId
|
|
14818
|
-
},
|
|
14819
|
-
events: promptEvents(streaming, box, prompt, sessionId, signal, optionsFor(i))
|
|
14820
|
-
};
|
|
14821
|
-
}
|
|
14822
14144
|
const box = await acquireFresh(spec, signal);
|
|
14823
14145
|
const sessionId = mintSessionId();
|
|
14824
14146
|
return {
|
|
@@ -14858,8 +14180,8 @@ function mintSessionId() {
|
|
|
14858
14180
|
}
|
|
14859
14181
|
/**
|
|
14860
14182
|
* Branch a running parent through the current Sandbox SDK, in bounded waves.
|
|
14861
|
-
* `undefined` means the box exposes
|
|
14862
|
-
*
|
|
14183
|
+
* `undefined` means the box exposes no branching API, so the caller starts
|
|
14184
|
+
* fresh boxes.
|
|
14863
14185
|
*/
|
|
14864
14186
|
async function branchParent(box, count, concurrency, signal) {
|
|
14865
14187
|
const branch = box.branch;
|
|
@@ -14880,34 +14202,6 @@ async function branchParent(box, count, concurrency, signal) {
|
|
|
14880
14202
|
}
|
|
14881
14203
|
}
|
|
14882
14204
|
/**
|
|
14883
|
-
* Checkpoint the parent leaving it running, returning the checkpoint id to fork
|
|
14884
|
-
* from, or `undefined` when the box exposes no legacy `checkpoint` method or
|
|
14885
|
-
* the call produced no id. `undefined` makes the caller use fresh boxes — a
|
|
14886
|
-
* fork that cannot preserve context must not pretend to share it.
|
|
14887
|
-
*/
|
|
14888
|
-
async function checkpointForFork(box, signal) {
|
|
14889
|
-
const checkpoint = box.checkpoint;
|
|
14890
|
-
if (typeof checkpoint !== "function") return void 0;
|
|
14891
|
-
if (signal.aborted) throwAbort();
|
|
14892
|
-
const id = (await checkpoint.call(box, { leaveRunning: true }))?.checkpointId;
|
|
14893
|
-
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
14894
|
-
}
|
|
14895
|
-
/**
|
|
14896
|
-
* Fork a child box from `checkpointId`. The box exposes `fork` whenever the
|
|
14897
|
-
* platform advertised `canFork`; a missing `fork` here is a contract violation
|
|
14898
|
-
* (probe said yes, box says no) and fails loud rather than silently degrading.
|
|
14899
|
-
*
|
|
14900
|
-
* `signal` gates entry only: the legacy SDK's `fork(checkpointId, options)`
|
|
14901
|
-
* takes no abort signal, so an in-flight fork cannot be interrupted. The
|
|
14902
|
-
* caller checks abort per branch, so cancellation is responsive at boundaries.
|
|
14903
|
-
*/
|
|
14904
|
-
async function forkFromCheckpoint(box, checkpointId, signal) {
|
|
14905
|
-
const fork = box.fork;
|
|
14906
|
-
if (typeof fork !== "function") throw new ValidationError$1("SandboxLineage.fork: capabilities report canFork but the box has no fork() method");
|
|
14907
|
-
if (signal.aborted) throwAbort();
|
|
14908
|
-
return fork.call(box, checkpointId);
|
|
14909
|
-
}
|
|
14910
|
-
/**
|
|
14911
14205
|
* Fail loud when a handle's session is no longer known to the sandbox. The real
|
|
14912
14206
|
* SDK box exposes `session(id).status()` which resolves `null` for an unknown /
|
|
14913
14207
|
* reaped id; a `null` here means a `continue` would run WITHOUT the prior
|
|
@@ -15151,9 +14445,8 @@ async function setUpLineage(options, maxConcurrency, recordMount, promptOptions,
|
|
|
15151
14445
|
const lineageOpts = options.lineage;
|
|
15152
14446
|
if (!lineageOpts || !lineageOpts.sessionContinuity && !lineageOpts.forkFanout) return void 0;
|
|
15153
14447
|
if (options.onWorkerBox) throw new ValidationError$1("runAgentRounds: `lineage` and `onWorkerBox` both own worker boxes — pass only one");
|
|
15154
|
-
const capabilities = await probeSandboxCapabilities(options.ctx.sandboxClient);
|
|
15155
14448
|
return {
|
|
15156
|
-
lineage: createSandboxLineage(options.ctx.sandboxClient,
|
|
14449
|
+
lineage: createSandboxLineage(options.ctx.sandboxClient, {
|
|
15157
14450
|
maxConcurrency,
|
|
15158
14451
|
streaming: lineageOpts.streaming,
|
|
15159
14452
|
recordMount,
|
|
@@ -15798,12 +15091,7 @@ function hashJson(value) {
|
|
|
15798
15091
|
} catch {
|
|
15799
15092
|
str = String(value);
|
|
15800
15093
|
}
|
|
15801
|
-
|
|
15802
|
-
for (let i = 0; i < str.length; i += 1) {
|
|
15803
|
-
h ^= str.charCodeAt(i);
|
|
15804
|
-
h = Math.imul(h, 16777619);
|
|
15805
|
-
}
|
|
15806
|
-
return (h >>> 0).toString(16).padStart(8, "0");
|
|
15094
|
+
return fnv1a32Hex(str);
|
|
15807
15095
|
}
|
|
15808
15096
|
//#endregion
|
|
15809
15097
|
//#region src/runtime/sandbox-executor-output.ts
|
|
@@ -17308,9 +16596,6 @@ async function cancelBridgeRunToTerminal(seam, run, grace, stopSignal) {
|
|
|
17308
16596
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
17309
16597
|
}
|
|
17310
16598
|
}
|
|
17311
|
-
function errorMessage(error) {
|
|
17312
|
-
return error instanceof Error ? error.message : String(error);
|
|
17313
|
-
}
|
|
17314
16599
|
/** Read only the Router-owned one-sided field from an error body. */
|
|
17315
16600
|
function providerDispatchFromErrorBody(body) {
|
|
17316
16601
|
let parsed;
|
|
@@ -17326,13 +16611,7 @@ function providerDispatchFromErrorBody(body) {
|
|
|
17326
16611
|
}
|
|
17327
16612
|
//#endregion
|
|
17328
16613
|
//#region src/runtime/supervise/node-status.ts
|
|
17329
|
-
/**
|
|
17330
|
-
* Which {@link NodeStatus} values mean the node is finished.
|
|
17331
|
-
*
|
|
17332
|
-
* `'waiting'` is deliberately NOT terminal: a wait-state node holds no executor, no box and no
|
|
17333
|
-
* conserved budget, so it is neither in flight nor settled. Callers that must also exclude it say
|
|
17334
|
-
* so at their own site, because "finished" and "not a live worker" are different questions.
|
|
17335
|
-
*/
|
|
16614
|
+
/** Which {@link NodeStatus} values mean the node is finished. */
|
|
17336
16615
|
const terminalNodeStatuses = /* @__PURE__ */ new Set([
|
|
17337
16616
|
"done",
|
|
17338
16617
|
"failed",
|
|
@@ -19309,8 +18588,7 @@ function createSteerableSandboxSession(args) {
|
|
|
19309
18588
|
let preservationFailure;
|
|
19310
18589
|
let preservationError;
|
|
19311
18590
|
let executionFailure;
|
|
19312
|
-
const
|
|
19313
|
-
const lineage = createSandboxLineage(args.sandboxClient, capabilities, {
|
|
18591
|
+
const lineage = createSandboxLineage(args.sandboxClient, {
|
|
19314
18592
|
maxConcurrency: 1,
|
|
19315
18593
|
failOnDestroyError: args.failOnDestroyError
|
|
19316
18594
|
});
|
|
@@ -21061,9 +20339,6 @@ function bridgeOutputText(out) {
|
|
|
21061
20339
|
return String(out);
|
|
21062
20340
|
}
|
|
21063
20341
|
}
|
|
21064
|
-
function isAsyncIterable$1(value) {
|
|
21065
|
-
return value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function";
|
|
21066
|
-
}
|
|
21067
20342
|
/**
|
|
21068
20343
|
* The leaf `createWorktreeCliExecutor` as a backend-as-data factory: a supervisor-authored
|
|
21069
20344
|
* `AgentProfile` driving claude / codex / opencode on its own worktree. `budgetExempt` like
|
|
@@ -21875,7 +21150,13 @@ async function startRetainedInteractiveRun(options) {
|
|
|
21875
21150
|
});
|
|
21876
21151
|
return createRetainedInteractiveRunHandle(environment, ref, capabilities, request);
|
|
21877
21152
|
}
|
|
21878
|
-
/**
|
|
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
|
+
*/
|
|
21879
21160
|
async function recoverRetainedInteractiveRun(options) {
|
|
21880
21161
|
options.signal?.throwIfAborted();
|
|
21881
21162
|
if (typeof options.onAdmission !== "function") throw new Error("recoverRetainedInteractiveRun requires an awaited onAdmission durability hook");
|
|
@@ -22307,27 +21588,6 @@ function workerInteractiveAdmissionFile(eventDir, workerId, phase) {
|
|
|
22307
21588
|
assertNoSymlinkDescendant(eventDir, dir, "interactive admission");
|
|
22308
21589
|
return join(dir, `${canonicalCandidateDigest({ workerId: id }).slice(7).slice(0, 32)}.${phase}.admission.json`);
|
|
22309
21590
|
}
|
|
22310
|
-
/** Read all durable admissions for one worker, oldest phase first. */
|
|
22311
|
-
function readWorkerInteractiveAdmissions(eventDir, workerId) {
|
|
22312
|
-
const phases = [
|
|
22313
|
-
"interactive_intent",
|
|
22314
|
-
"interactive_environment",
|
|
22315
|
-
"interactive_started"
|
|
22316
|
-
];
|
|
22317
|
-
const admissions = [];
|
|
22318
|
-
let missingPhase = false;
|
|
22319
|
-
for (const phase of phases) {
|
|
22320
|
-
const file = workerInteractiveAdmissionFile(eventDir, workerId, phase);
|
|
22321
|
-
if (!existsSync(file)) {
|
|
22322
|
-
missingPhase = true;
|
|
22323
|
-
continue;
|
|
22324
|
-
}
|
|
22325
|
-
if (missingPhase) throw new Error(`interactive admission phase '${phase}' exists after a missing earlier phase`);
|
|
22326
|
-
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
22327
|
-
admissions.push(parseAdmission(parsed, workerId, phase));
|
|
22328
|
-
}
|
|
22329
|
-
return admissions;
|
|
22330
|
-
}
|
|
22331
21591
|
/**
|
|
22332
21592
|
* Persist one retained-interactive admission without retaining sensitive start material.
|
|
22333
21593
|
*
|
|
@@ -22986,8 +22246,6 @@ async function prepareScopeResume(opts, events, signal, now, parentId = opts.run
|
|
|
22986
22246
|
view: materializeTreeView(prior),
|
|
22987
22247
|
maxSpawnOrdinal: maxSeqOf(prior, (event) => event.kind === "spawned"),
|
|
22988
22248
|
maxCursorSeq: reservedCursorFloor(prior),
|
|
22989
|
-
maxWaitOrdinal: maxSeqOf(prior, (event) => event.kind === "waiting"),
|
|
22990
|
-
waits: pendingWaits(prior),
|
|
22991
22249
|
keys: keyedAssignments(prior, settled),
|
|
22992
22250
|
priorSpend: sumMeasuredSpendFromEvents(prior)
|
|
22993
22251
|
}
|
|
@@ -23424,10 +22682,16 @@ function registerScopeRetainedOwner(scope, args) {
|
|
|
23424
22682
|
function scopeRetainedOwnerContext(scope) {
|
|
23425
22683
|
return owners.get(scope)?.context;
|
|
23426
22684
|
}
|
|
23427
|
-
/**
|
|
23428
|
-
|
|
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) {
|
|
23429
22691
|
const state = owners.get(scope);
|
|
23430
|
-
if (state
|
|
22692
|
+
if (state === void 0) return;
|
|
22693
|
+
state.provider = provider;
|
|
22694
|
+
if (checkpointCapture !== void 0) state.checkpointCapture = checkpointCapture;
|
|
23431
22695
|
}
|
|
23432
22696
|
/** Bind the owner cleanup policy selected by its provider executor. */
|
|
23433
22697
|
function bindScopeRetainedOwnerWorkspaceRetention(scope, enabled) {
|
|
@@ -23602,7 +22866,8 @@ async function checkpointOwnerWorkspace(scope, state) {
|
|
|
23602
22866
|
seq: state.nextSequence(),
|
|
23603
22867
|
at
|
|
23604
22868
|
});
|
|
23605
|
-
|
|
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);
|
|
23606
22871
|
}
|
|
23607
22872
|
/** Reconstruct authority for this source only; a replacement cannot own its snapshots. */
|
|
23608
22873
|
async function checkpointHandle(state, sourceEnvironmentId, signal) {
|
|
@@ -23695,10 +22960,35 @@ function pendingCheckpoints(events, state) {
|
|
|
23695
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])] : []));
|
|
23696
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])));
|
|
23697
22962
|
}
|
|
23698
|
-
/**
|
|
23699
|
-
|
|
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) {
|
|
23700
22971
|
const unconfirmed = [];
|
|
22972
|
+
const refused = [];
|
|
23701
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);
|
|
23702
22992
|
const material = {
|
|
23703
22993
|
kind: "checkpoint",
|
|
23704
22994
|
targetId: event.checkpoint.checkpointId,
|
|
@@ -23712,6 +23002,7 @@ async function deleteCheckpoints(state, branching, checkpoints, signal) {
|
|
|
23712
23002
|
let confirmed = false;
|
|
23713
23003
|
try {
|
|
23714
23004
|
if (branching === void 0) throw new Error("source-scoped checkpoint handle unavailable");
|
|
23005
|
+
const signal = AbortSignal.timeout(3e4);
|
|
23715
23006
|
const acknowledgement = await runAbortable(() => branching.deleteCheckpoint(request, { signal }), signal, "retained owner checkpoint cleanup timed out");
|
|
23716
23007
|
confirmed = workspaceCleanupAcknowledgementMatches(request, acknowledgement) && (acknowledgement.status === "deleted" || acknowledgement.status === "already_absent");
|
|
23717
23008
|
} catch {}
|
|
@@ -23723,11 +23014,178 @@ async function deleteCheckpoints(state, branching, checkpoints, signal) {
|
|
|
23723
23014
|
environmentId: event.environmentId,
|
|
23724
23015
|
checkpointId: event.checkpoint.checkpointId,
|
|
23725
23016
|
confirmed,
|
|
23017
|
+
capturedDigest: captured.digest,
|
|
23726
23018
|
seq: state.nextSequence(),
|
|
23727
23019
|
at: new Date(state.args.now()).toISOString()
|
|
23728
23020
|
});
|
|
23729
23021
|
}
|
|
23730
|
-
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;
|
|
23731
23189
|
}
|
|
23732
23190
|
/**
|
|
23733
23191
|
* The checkpoint a new environment of this owner starts from: the latest one journaled before
|
|
@@ -23860,19 +23318,23 @@ async function releaseOwnerEnvironment(state, provider) {
|
|
|
23860
23318
|
let lastDetail;
|
|
23861
23319
|
let retriable = false;
|
|
23862
23320
|
const checkpointFailures = [];
|
|
23321
|
+
const checkpointsKept = [];
|
|
23322
|
+
const uncapturedSources = /* @__PURE__ */ new Set();
|
|
23863
23323
|
const pending = pendingCheckpoints(events, state).filter((event) => !retentionFailures.has(event.environmentId));
|
|
23864
23324
|
for (const sourceEnvironmentId of new Set(pending.map((event) => event.environmentId))) {
|
|
23865
|
-
const
|
|
23866
|
-
|
|
23867
|
-
|
|
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);
|
|
23868
23329
|
}
|
|
23330
|
+
const forksUnresolved = await settleCaptureForks(state);
|
|
23869
23331
|
for (const event of checkpointFailures) pendingSources.add(event.environmentId);
|
|
23870
23332
|
for (const environmentId of environments) {
|
|
23871
23333
|
if (released.has(environmentId)) continue;
|
|
23872
23334
|
let destroyed = false;
|
|
23873
23335
|
let detail;
|
|
23874
|
-
if (retentionFailures.has(environmentId) || pendingSources.has(environmentId)) {
|
|
23875
|
-
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";
|
|
23876
23338
|
preserved.push(environmentId);
|
|
23877
23339
|
lastDetail = detail;
|
|
23878
23340
|
} else {
|
|
@@ -23928,19 +23390,36 @@ async function releaseOwnerEnvironment(state, provider) {
|
|
|
23928
23390
|
});
|
|
23929
23391
|
}
|
|
23930
23392
|
state.releaseRetriable = retriable;
|
|
23931
|
-
const checkpointTeardowns = [
|
|
23932
|
-
|
|
23933
|
-
|
|
23934
|
-
|
|
23935
|
-
|
|
23936
|
-
|
|
23937
|
-
|
|
23938
|
-
|
|
23939
|
-
|
|
23940
|
-
|
|
23941
|
-
|
|
23942
|
-
|
|
23943
|
-
|
|
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
|
+
];
|
|
23944
23423
|
return unconfirmed.length > 0 || preserved.length > 0 ? [{
|
|
23945
23424
|
id: args.nodeId,
|
|
23946
23425
|
label: "scope owner",
|
|
@@ -24245,179 +23724,6 @@ function consumeScopeRetainedOwnerResult(scope) {
|
|
|
24245
23724
|
state.acceptedConsumed = true;
|
|
24246
23725
|
}
|
|
24247
23726
|
//#endregion
|
|
24248
|
-
//#region src/runtime/supervise/wait.ts
|
|
24249
|
-
/**
|
|
24250
|
-
*
|
|
24251
|
-
* WAIT-STATES — a supervision-tree node that waits on wall-clock time or an external condition
|
|
24252
|
-
* without holding a worker, an executor, a sandbox, or a single LLM turn.
|
|
24253
|
-
*
|
|
24254
|
-
* A long-horizon run spends most of its wall-clock NOT computing: waiting for CI to finish,
|
|
24255
|
-
* for a nightly job to land, for a human to reply, for a rate-limit window to reopen. Before
|
|
24256
|
-
* this, the only way to express that was to keep something alive and re-ask — which spends
|
|
24257
|
-
* driver tokens per re-ask and pins a process to the wait.
|
|
24258
|
-
*
|
|
24259
|
-
* Two node kinds, both first-class:
|
|
24260
|
-
* - `timer` — wake at an ABSOLUTE wall-clock instant (`untilMs`).
|
|
24261
|
-
* - `poll` — re-run a named predicate every `intervalMs` until it returns true, or until an
|
|
24262
|
-
* absolute `timeoutAtMs` passes (CI status, file existence, an HTTP probe, an
|
|
24263
|
-
* inbox message).
|
|
24264
|
-
*
|
|
24265
|
-
* ── How this differs from a manager's wake (`./wake`) ──
|
|
24266
|
-
*
|
|
24267
|
-
* They look similar and are not the same mechanism. A wake is an IN-RUN RENDEZVOUS: a manager
|
|
24268
|
-
* that ends its turn with work open is resumed when the coordination bus delivers its next event.
|
|
24269
|
-
* It costs no model turn, but it lives in a process that must stay up, and nothing about that
|
|
24270
|
-
* wait is recorded: kill the process and the wait is simply gone.
|
|
24271
|
-
*
|
|
24272
|
-
* A wait-state is a NODE, not a call. It has a node id in the tree, a journal record carrying its
|
|
24273
|
-
* ABSOLUTE deadline, and it settles through the same `Scope.next()` cursor as any worker. Nobody
|
|
24274
|
-
* is blocked on it — the driver can stop reasoning entirely, and the process can die. Cost while
|
|
24275
|
-
* waiting: zero LLM calls, zero executor, zero sandbox, zero conserved budget (a wait reserves
|
|
24276
|
-
* nothing from the pool). The only in-process residue is one timer entry.
|
|
24277
|
-
*
|
|
24278
|
-
* ── Why probes are NAMED, not passed as closures ────────────────────────────────────────────
|
|
24279
|
-
*
|
|
24280
|
-
* A wait must survive a process restart with its original deadline intact, and a closure cannot
|
|
24281
|
-
* be journaled. So a `poll` names its predicate (`probe: 'ci-green'`) and the run resolves it
|
|
24282
|
-
* through a `WaitProbeRegistry`. A brand-new process re-resolves the SAME name against its own
|
|
24283
|
-
* registry and re-arms the wait — which is what makes "kill the box, the wait keeps waiting"
|
|
24284
|
-
* true rather than aspirational.
|
|
24285
|
-
*
|
|
24286
|
-
* Absolute instants for the same reason: `untilMs` / `timeoutAtMs` are epoch ms, not durations,
|
|
24287
|
-
* so a resumed wait counts down from the original arm, not from the restart. `timerAt`/`pollFor`
|
|
24288
|
-
* build them from a duration when that is what the caller has.
|
|
24289
|
-
*
|
|
24290
|
-
* @experimental
|
|
24291
|
-
*/
|
|
24292
|
-
/** Build a `timer` spec from a DURATION. The instant is resolved once, at arm time — a resumed
|
|
24293
|
-
* wait re-uses the journaled instant, never a fresh `now + ms`. */
|
|
24294
|
-
function timerAt(ms, now) {
|
|
24295
|
-
return {
|
|
24296
|
-
kind: "timer",
|
|
24297
|
-
untilMs: now + ms
|
|
24298
|
-
};
|
|
24299
|
-
}
|
|
24300
|
-
/** Build a bounded `poll` spec from a duration. */
|
|
24301
|
-
function pollFor(probe, opts, now) {
|
|
24302
|
-
return {
|
|
24303
|
-
kind: "poll",
|
|
24304
|
-
probe,
|
|
24305
|
-
intervalMs: opts.intervalMs,
|
|
24306
|
-
...opts.timeoutMs !== void 0 ? { timeoutAtMs: now + opts.timeoutMs } : {},
|
|
24307
|
-
...opts.args ? { args: opts.args } : {}
|
|
24308
|
-
};
|
|
24309
|
-
}
|
|
24310
|
-
/** Registry over a plain name→predicate record. */
|
|
24311
|
-
function createWaitProbes(entries) {
|
|
24312
|
-
const map = new Map(Object.entries(entries));
|
|
24313
|
-
return { resolve: (name) => map.get(name) };
|
|
24314
|
-
}
|
|
24315
|
-
/** Narrow a settlement's `out` to a wait outcome — a wait settles on the SAME cursor as workers,
|
|
24316
|
-
* so a driver that mixes them tags them apart with this. */
|
|
24317
|
-
function isWaitOutcome(value) {
|
|
24318
|
-
return typeof value === "object" && value !== null && value.waitOutcome === true;
|
|
24319
|
-
}
|
|
24320
|
-
/** The absolute instant a spec is bounded by, or `undefined` for an unbounded wait. */
|
|
24321
|
-
function waitUntil(spec) {
|
|
24322
|
-
if (spec.kind === "timer") return spec.untilMs;
|
|
24323
|
-
if (spec.kind === "token") return spec.expiresAtMs;
|
|
24324
|
-
return spec.timeoutAtMs;
|
|
24325
|
-
}
|
|
24326
|
-
/** Structural validation, independent of the run. Returns null when the spec is usable. */
|
|
24327
|
-
function validateWaitSpec(spec) {
|
|
24328
|
-
if (spec.kind === "timer") return Number.isFinite(spec.untilMs) ? null : "timer.untilMs must be a finite epoch-ms instant";
|
|
24329
|
-
if (spec.kind === "token") return "token waits are graph-engine-owned; deliver them through engine resume/expire";
|
|
24330
|
-
if (typeof spec.probe !== "string" || spec.probe.length === 0) return "poll.probe must be a non-empty probe name";
|
|
24331
|
-
if (!Number.isFinite(spec.intervalMs) || spec.intervalMs <= 0) return "poll.intervalMs must be > 0";
|
|
24332
|
-
if (spec.timeoutAtMs !== void 0 && !Number.isFinite(spec.timeoutAtMs)) return "poll.timeoutAtMs must be a finite epoch-ms instant";
|
|
24333
|
-
return null;
|
|
24334
|
-
}
|
|
24335
|
-
/**
|
|
24336
|
-
* A wait must not silently outlive the run's HARD wall-clock ceiling. When the conserved pool
|
|
24337
|
-
* carries an absolute `deadlineMs`, a wait bounded past it — or an unbounded poll — is REFUSED at
|
|
24338
|
-
* arm time (`deadline-exceeded`) rather than sleeping through a ceiling the caller already set.
|
|
24339
|
-
* The alternative (extending the pool's deadline to cover the wait) would let a wait override a
|
|
24340
|
-
* hard budget guard, which is exactly what mechanic D forbids; a run that intends week-long waits
|
|
24341
|
-
* either sets a deadline that covers them or sets none.
|
|
24342
|
-
*/
|
|
24343
|
-
function assertWaitWithinDeadline(spec, deadlineMs) {
|
|
24344
|
-
if (deadlineMs <= 0) return true;
|
|
24345
|
-
const until = waitUntil(spec);
|
|
24346
|
-
if (until === void 0) return false;
|
|
24347
|
-
return until <= deadlineMs;
|
|
24348
|
-
}
|
|
24349
|
-
/**
|
|
24350
|
-
* Drive one wait to its resolution. Spends nothing: a `timer` is one sleep, a `poll` is a sleep
|
|
24351
|
-
* between predicate checks. No LLM call, no executor, no box, no budget reservation.
|
|
24352
|
-
*/
|
|
24353
|
-
async function runWait(args) {
|
|
24354
|
-
const { spec, label, armedAt, resumed, signal, now } = args;
|
|
24355
|
-
const sleep = args.sleep ?? defaultSleep$1;
|
|
24356
|
-
if (signal.aborted) return {
|
|
24357
|
-
kind: "cancelled",
|
|
24358
|
-
reason: "aborted before arming"
|
|
24359
|
-
};
|
|
24360
|
-
if (spec.kind === "timer") {
|
|
24361
|
-
const remaining = spec.untilMs - now();
|
|
24362
|
-
if (remaining > 0) await sleep(remaining, signal);
|
|
24363
|
-
if (signal.aborted) return {
|
|
24364
|
-
kind: "cancelled",
|
|
24365
|
-
reason: "wait aborted"
|
|
24366
|
-
};
|
|
24367
|
-
return {
|
|
24368
|
-
kind: "woke",
|
|
24369
|
-
outcome: outcome("timer", "fired", label, spec.untilMs, armedAt, now(), 0, 0, resumed)
|
|
24370
|
-
};
|
|
24371
|
-
}
|
|
24372
|
-
if (spec.kind === "token") throw new ValidationError$1(`wait '${label}': token waits are graph-engine-owned; deliver them through engine resume/expire`);
|
|
24373
|
-
const probe = args.probes?.resolve(spec.probe);
|
|
24374
|
-
if (!probe) throw new ValidationError$1(`wait '${label}': poll probe '${spec.probe}' is not registered in this run's WaitProbeRegistry`);
|
|
24375
|
-
let polls = 0;
|
|
24376
|
-
let probeErrors = 0;
|
|
24377
|
-
for (;;) {
|
|
24378
|
-
if (signal.aborted) return {
|
|
24379
|
-
kind: "cancelled",
|
|
24380
|
-
reason: "wait aborted"
|
|
24381
|
-
};
|
|
24382
|
-
polls += 1;
|
|
24383
|
-
let fired = false;
|
|
24384
|
-
try {
|
|
24385
|
-
fired = await probe(spec.args, signal);
|
|
24386
|
-
} catch {
|
|
24387
|
-
probeErrors += 1;
|
|
24388
|
-
}
|
|
24389
|
-
if (fired) return {
|
|
24390
|
-
kind: "woke",
|
|
24391
|
-
outcome: outcome("poll", "fired", label, spec.timeoutAtMs, armedAt, now(), polls, probeErrors, resumed)
|
|
24392
|
-
};
|
|
24393
|
-
if (spec.timeoutAtMs !== void 0 && now() >= spec.timeoutAtMs) return {
|
|
24394
|
-
kind: "woke",
|
|
24395
|
-
outcome: outcome("poll", "timeout", label, spec.timeoutAtMs, armedAt, now(), polls, probeErrors, resumed)
|
|
24396
|
-
};
|
|
24397
|
-
await sleep(spec.timeoutAtMs === void 0 ? spec.intervalMs : Math.min(spec.intervalMs, Math.max(0, spec.timeoutAtMs - now())), signal);
|
|
24398
|
-
}
|
|
24399
|
-
}
|
|
24400
|
-
function outcome(kind, settled, label, untilMs, armedAt, wokenAt, polls, probeErrors, resumed) {
|
|
24401
|
-
return {
|
|
24402
|
-
waitOutcome: true,
|
|
24403
|
-
kind,
|
|
24404
|
-
settled,
|
|
24405
|
-
label,
|
|
24406
|
-
...untilMs !== void 0 ? { untilMs } : {},
|
|
24407
|
-
armedAt,
|
|
24408
|
-
wokenAt,
|
|
24409
|
-
polls,
|
|
24410
|
-
probeErrors,
|
|
24411
|
-
resumed
|
|
24412
|
-
};
|
|
24413
|
-
}
|
|
24414
|
-
/** `setTimeout` that resolves early (and clears) when the scope aborts, so a cancelled wait
|
|
24415
|
-
* releases the event loop immediately instead of holding the process to its deadline. */
|
|
24416
|
-
function defaultSleep$1(ms, signal) {
|
|
24417
|
-
if (ms <= 0 || signal?.aborted) return Promise.resolve();
|
|
24418
|
-
return sleep(ms, signal);
|
|
24419
|
-
}
|
|
24420
|
-
//#endregion
|
|
24421
23727
|
//#region src/runtime/supervise/scope.ts
|
|
24422
23728
|
/**
|
|
24423
23729
|
*
|
|
@@ -24751,13 +24057,11 @@ function createScope(args) {
|
|
|
24751
24057
|
} : void 0;
|
|
24752
24058
|
let spawnOrdinal = args.resumeFrom ? args.resumeFrom.maxSpawnOrdinal + 1 : 0;
|
|
24753
24059
|
let cursorSeq = args.resumeFrom ? args.resumeFrom.maxCursorSeq + 1 : 0;
|
|
24754
|
-
let waitOrdinal = args.resumeFrom ? args.resumeFrom.maxWaitOrdinal + 1 : 0;
|
|
24755
24060
|
let meterSeq = 0;
|
|
24756
24061
|
let ownerTurns = 0;
|
|
24757
24062
|
let ownerLastTurnAt = 0;
|
|
24758
24063
|
let progressSeq = 0;
|
|
24759
24064
|
const now = args.now ?? Date.now;
|
|
24760
|
-
const unclaimedWaits = new Map((args.resumeFrom?.waits ?? []).map((w) => [w.label, w]));
|
|
24761
24065
|
const keyed = /* @__PURE__ */ new Map();
|
|
24762
24066
|
for (const [key, prior] of args.resumeFrom?.keys ?? []) if (prior.state === "completed" && prior.settled?.kind === "done") {
|
|
24763
24067
|
if (prior.identity === void 0) continue;
|
|
@@ -25428,165 +24732,6 @@ function createScope(args) {
|
|
|
25428
24732
|
child.lastActivityAt = now();
|
|
25429
24733
|
return true;
|
|
25430
24734
|
}
|
|
25431
|
-
/**
|
|
25432
|
-
* Arm a wait-state node. Deliberately NOT a `spawn` with a sleeping executor: it resolves no
|
|
25433
|
-
* `AgentSpec`, constructs no `Executor`, and — the point of the whole mechanic — reserves NOTHING
|
|
25434
|
-
* from the conserved pool, so a week-long wait costs zero tokens and zero dollars and cannot
|
|
25435
|
-
* starve a worker of budget.
|
|
25436
|
-
*
|
|
25437
|
-
* Fail-closed admission, mirroring `spawn`'s typed outcome. The deadline check is the one that
|
|
25438
|
-
* matters: a wait may not silently sleep past the pool's hard wall-clock ceiling, and it may not
|
|
25439
|
-
* extend that ceiling either (that would make a wait override a budget guard).
|
|
25440
|
-
*/
|
|
25441
|
-
function wait(spec, opts) {
|
|
25442
|
-
if (args.signal.aborted) return {
|
|
25443
|
-
ok: false,
|
|
25444
|
-
reason: "deadline-exceeded"
|
|
25445
|
-
};
|
|
25446
|
-
if (validateWaitSpec(spec) !== null) return {
|
|
25447
|
-
ok: false,
|
|
25448
|
-
reason: "invalid-spec"
|
|
25449
|
-
};
|
|
25450
|
-
if (spec.kind === "poll" && args.probes?.resolve(spec.probe) === void 0) return {
|
|
25451
|
-
ok: false,
|
|
25452
|
-
reason: "unknown-probe"
|
|
25453
|
-
};
|
|
25454
|
-
const adopted = unclaimedWaits.get(opts.label);
|
|
25455
|
-
if (adopted) unclaimedWaits.delete(opts.label);
|
|
25456
|
-
const effectiveSpec = adopted?.spec ?? spec;
|
|
25457
|
-
const armedAt = adopted?.armedAt ?? now();
|
|
25458
|
-
if (!assertWaitWithinDeadline(effectiveSpec, args.pool.readout().deadlineMs)) return {
|
|
25459
|
-
ok: false,
|
|
25460
|
-
reason: "deadline-exceeded"
|
|
25461
|
-
};
|
|
25462
|
-
const id = adopted ? adopted.id : `${args.parentId}:w${waitOrdinal}`;
|
|
25463
|
-
const ordinal = adopted ? adopted.ordinal : waitOrdinal;
|
|
25464
|
-
if (!adopted) waitOrdinal += 1;
|
|
25465
|
-
const waitAbort = new AbortController();
|
|
25466
|
-
const cascadeAbort = () => waitAbort.abort();
|
|
25467
|
-
if (args.signal.aborted) waitAbort.abort();
|
|
25468
|
-
else args.signal.addEventListener("abort", cascadeAbort, { once: true });
|
|
25469
|
-
const handle = {
|
|
25470
|
-
id,
|
|
25471
|
-
label: opts.label,
|
|
25472
|
-
get status() {
|
|
25473
|
-
return children.get(id)?.status ?? "cancelled";
|
|
25474
|
-
},
|
|
25475
|
-
abort(reason) {
|
|
25476
|
-
waitAbort.abort(reason);
|
|
25477
|
-
}
|
|
25478
|
-
};
|
|
25479
|
-
const live = {
|
|
25480
|
-
id,
|
|
25481
|
-
status: "waiting",
|
|
25482
|
-
runtime: "wait",
|
|
25483
|
-
abortChild: (reason) => waitAbort.abort(reason),
|
|
25484
|
-
budget: {
|
|
25485
|
-
maxIterations: 0,
|
|
25486
|
-
maxTokens: 0
|
|
25487
|
-
},
|
|
25488
|
-
label: opts.label,
|
|
25489
|
-
spent: zeroSpend(),
|
|
25490
|
-
settled: void 0,
|
|
25491
|
-
delivered: false,
|
|
25492
|
-
executorDone: false,
|
|
25493
|
-
cleanupConfirmed: true,
|
|
25494
|
-
teardownAttempts: 0,
|
|
25495
|
-
executionBindings: [],
|
|
25496
|
-
startedAt: armedAt,
|
|
25497
|
-
lastActivityAt: now(),
|
|
25498
|
-
wait: {
|
|
25499
|
-
spec: effectiveSpec,
|
|
25500
|
-
armedAt,
|
|
25501
|
-
label: opts.label,
|
|
25502
|
-
armCommitted: adopted !== void 0
|
|
25503
|
-
}
|
|
25504
|
-
};
|
|
25505
|
-
children.set(id, live);
|
|
25506
|
-
live.settled = (adopted ? Promise.resolve() : args.journal.appendEvent(args.root, {
|
|
25507
|
-
kind: "waiting",
|
|
25508
|
-
id,
|
|
25509
|
-
parent: args.parentId,
|
|
25510
|
-
label: opts.label,
|
|
25511
|
-
spec: effectiveSpec,
|
|
25512
|
-
armedAt,
|
|
25513
|
-
seq: ordinal,
|
|
25514
|
-
at: new Date(now()).toISOString()
|
|
25515
|
-
})).then(() => {
|
|
25516
|
-
if (live.wait) live.wait.armCommitted = true;
|
|
25517
|
-
notifyRuntimeHookEvent(args.hooks, {
|
|
25518
|
-
id: `${id}:waiting`,
|
|
25519
|
-
runId: args.root,
|
|
25520
|
-
target: "agent.spawn",
|
|
25521
|
-
phase: "after",
|
|
25522
|
-
timestamp: now(),
|
|
25523
|
-
stepIndex: ordinal,
|
|
25524
|
-
parentId: args.parentId,
|
|
25525
|
-
payload: {
|
|
25526
|
-
childId: id,
|
|
25527
|
-
label: opts.label,
|
|
25528
|
-
runtime: "wait",
|
|
25529
|
-
wait: effectiveSpec,
|
|
25530
|
-
armedAt,
|
|
25531
|
-
resumed: adopted !== void 0
|
|
25532
|
-
}
|
|
25533
|
-
}, { signal: args.signal });
|
|
25534
|
-
return runWait({
|
|
25535
|
-
spec: effectiveSpec,
|
|
25536
|
-
label: opts.label,
|
|
25537
|
-
armedAt,
|
|
25538
|
-
resumed: adopted !== void 0,
|
|
25539
|
-
signal: waitAbort.signal,
|
|
25540
|
-
...args.probes ? { probes: args.probes } : {},
|
|
25541
|
-
now,
|
|
25542
|
-
...args.waitSleep ? { sleep: args.waitSleep } : {}
|
|
25543
|
-
});
|
|
25544
|
-
}).then(async (resolution) => {
|
|
25545
|
-
live.executorDone = true;
|
|
25546
|
-
live.lastActivityAt = now();
|
|
25547
|
-
if (resolution.kind === "cancelled") return {
|
|
25548
|
-
kind: "down",
|
|
25549
|
-
reason: resolution.reason,
|
|
25550
|
-
infra: false,
|
|
25551
|
-
trace: {
|
|
25552
|
-
status: "unavailable",
|
|
25553
|
-
reason: "not-an-executor"
|
|
25554
|
-
}
|
|
25555
|
-
};
|
|
25556
|
-
const outRef = contentAddress(resolution.outcome);
|
|
25557
|
-
await args.blobs.put(outRef, resolution.outcome);
|
|
25558
|
-
return {
|
|
25559
|
-
kind: "done",
|
|
25560
|
-
out: resolution.outcome,
|
|
25561
|
-
outRef,
|
|
25562
|
-
spent: zeroSpend(),
|
|
25563
|
-
trace: {
|
|
25564
|
-
status: "unavailable",
|
|
25565
|
-
reason: "not-an-executor"
|
|
25566
|
-
}
|
|
25567
|
-
};
|
|
25568
|
-
}).catch((err) => {
|
|
25569
|
-
live.executorDone = true;
|
|
25570
|
-
return {
|
|
25571
|
-
kind: "down",
|
|
25572
|
-
reason: errMessage(err),
|
|
25573
|
-
infra: true,
|
|
25574
|
-
trace: {
|
|
25575
|
-
status: "unavailable",
|
|
25576
|
-
reason: "not-an-executor"
|
|
25577
|
-
}
|
|
25578
|
-
};
|
|
25579
|
-
}).then((s) => {
|
|
25580
|
-
live.resolved = s;
|
|
25581
|
-
return s;
|
|
25582
|
-
}).finally(() => {
|
|
25583
|
-
args.signal.removeEventListener("abort", cascadeAbort);
|
|
25584
|
-
});
|
|
25585
|
-
return {
|
|
25586
|
-
ok: true,
|
|
25587
|
-
handle
|
|
25588
|
-
};
|
|
25589
|
-
}
|
|
25590
24735
|
function progress(nodeId, opts = {}) {
|
|
25591
24736
|
const child = children.get(nodeId);
|
|
25592
24737
|
if (!child) return void 0;
|
|
@@ -25636,7 +24781,7 @@ function createScope(args) {
|
|
|
25636
24781
|
function interactive(nodeId) {
|
|
25637
24782
|
const child = children.get(nodeId);
|
|
25638
24783
|
if (!child) return noInteractiveSession("unknown-node");
|
|
25639
|
-
if (child.
|
|
24784
|
+
if (child.executorDone || isTerminalNodeStatus(child.status)) return noInteractiveSession("not-live");
|
|
25640
24785
|
const session = readInteractiveSession(child);
|
|
25641
24786
|
if (args.interactiveBindingDir !== void 0 && session.status === "available") writeWorkerInteractiveBinding(args.interactiveBindingDir, child.id, child.label, args.root, session, now);
|
|
25642
24787
|
return session;
|
|
@@ -25680,7 +24825,6 @@ function createScope(args) {
|
|
|
25680
24825
|
const resume = args.resumeFrom ? {
|
|
25681
24826
|
settled: args.resumeFrom.settled,
|
|
25682
24827
|
view: args.resumeFrom.view,
|
|
25683
|
-
waits: args.resumeFrom.waits,
|
|
25684
24828
|
keys: args.resumeFrom.keys,
|
|
25685
24829
|
priorSpend: args.resumeFrom.priorSpend
|
|
25686
24830
|
} : void 0;
|
|
@@ -25689,7 +24833,6 @@ function createScope(args) {
|
|
|
25689
24833
|
next,
|
|
25690
24834
|
nextResolved,
|
|
25691
24835
|
send,
|
|
25692
|
-
wait,
|
|
25693
24836
|
progress,
|
|
25694
24837
|
traceSource,
|
|
25695
24838
|
interactive,
|
|
@@ -26007,6 +25150,15 @@ async function publishOwnerMaterialization(state, runtime, declaration, bindingI
|
|
|
26007
25150
|
state.publishedThisProcess = true;
|
|
26008
25151
|
return;
|
|
26009
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
|
+
}
|
|
26010
25162
|
if (state.receipt !== void 0) {
|
|
26011
25163
|
if (canonicalCandidateDigest(state.receipt) !== canonicalCandidateDigest(receipt)) {
|
|
26012
25164
|
const changed = changedRunIdentityFields(state.receipt, receipt);
|
|
@@ -26274,7 +25426,6 @@ async function appendAcknowledged(journal, root, event) {
|
|
|
26274
25426
|
* `PreSeqSettled` into the frozen `Settled` the driver branches on. */
|
|
26275
25427
|
async function finalizeSettlement(child, settlement, seq, args, now) {
|
|
26276
25428
|
const handle = frozenHandle(child);
|
|
26277
|
-
if (child.wait) return finalizeWait(child, settlement, seq, args, now, handle);
|
|
26278
25429
|
const nestedScope = child.readNestedScope?.();
|
|
26279
25430
|
const subtree = nestedScope === void 0 ? void 0 : subtreeSummaryOf(nestedScope);
|
|
26280
25431
|
if (subtree !== void 0) child.subtree = subtree;
|
|
@@ -26395,68 +25546,6 @@ async function finalizeSettlement(child, settlement, seq, args, now) {
|
|
|
26395
25546
|
seq
|
|
26396
25547
|
};
|
|
26397
25548
|
}
|
|
26398
|
-
/** Journal a wait-state's settlement as a `woken` event and project it onto the same `Settled`
|
|
26399
|
-
* the driver branches on. `by` names WHY it woke — `fired` / `timeout` / `cancelled` — which is
|
|
26400
|
-
* the fact a resumed reader needs and cannot recover from a payload it may never fetch. */
|
|
26401
|
-
async function finalizeWait(child, settlement, seq, args, now, handle) {
|
|
26402
|
-
const settledAt = now();
|
|
26403
|
-
child.settledAt = settledAt;
|
|
26404
|
-
const at = new Date(settledAt).toISOString();
|
|
26405
|
-
if (settlement.kind === "down") {
|
|
26406
|
-
child.status = "cancelled";
|
|
26407
|
-
if (child.wait?.armCommitted) await args.journal.appendEvent(args.root, {
|
|
26408
|
-
kind: "woken",
|
|
26409
|
-
id: child.id,
|
|
26410
|
-
by: "cancelled",
|
|
26411
|
-
seq,
|
|
26412
|
-
at
|
|
26413
|
-
});
|
|
26414
|
-
return {
|
|
26415
|
-
kind: "down",
|
|
26416
|
-
handle,
|
|
26417
|
-
reason: settlement.reason,
|
|
26418
|
-
...settlement.infra === void 0 ? {} : { infra: settlement.infra },
|
|
26419
|
-
trace: settlement.trace,
|
|
26420
|
-
settledAt,
|
|
26421
|
-
seq
|
|
26422
|
-
};
|
|
26423
|
-
}
|
|
26424
|
-
child.status = "done";
|
|
26425
|
-
child.outRef = settlement.outRef;
|
|
26426
|
-
const out = settlement.out;
|
|
26427
|
-
await args.journal.appendEvent(args.root, {
|
|
26428
|
-
kind: "woken",
|
|
26429
|
-
id: child.id,
|
|
26430
|
-
by: out.settled,
|
|
26431
|
-
outRef: settlement.outRef,
|
|
26432
|
-
seq,
|
|
26433
|
-
at
|
|
26434
|
-
});
|
|
26435
|
-
notifyRuntimeHookEvent(args.hooks, {
|
|
26436
|
-
id: `${child.id}:woken`,
|
|
26437
|
-
runId: args.root,
|
|
26438
|
-
target: "agent.child",
|
|
26439
|
-
phase: "after",
|
|
26440
|
-
timestamp: settledAt,
|
|
26441
|
-
stepIndex: seq,
|
|
26442
|
-
parentId: args.parentId,
|
|
26443
|
-
payload: {
|
|
26444
|
-
childId: child.id,
|
|
26445
|
-
status: "done",
|
|
26446
|
-
wait: out
|
|
26447
|
-
}
|
|
26448
|
-
}, { signal: args.signal });
|
|
26449
|
-
return {
|
|
26450
|
-
kind: "done",
|
|
26451
|
-
handle,
|
|
26452
|
-
out: settlement.out,
|
|
26453
|
-
outRef: settlement.outRef,
|
|
26454
|
-
spent: settlement.spent,
|
|
26455
|
-
trace: settlement.trace,
|
|
26456
|
-
settledAt,
|
|
26457
|
-
seq
|
|
26458
|
-
};
|
|
26459
|
-
}
|
|
26460
25549
|
/**
|
|
26461
25550
|
* Drive one child's `Executor` to a terminal `PreSeqSettled`, folding usage into the
|
|
26462
25551
|
* conserved `Spend`, reconciling the reservation, and persisting the result blob. Both
|
|
@@ -26733,8 +25822,6 @@ function settledToIteration(settled) {
|
|
|
26733
25822
|
tokenUsage: cloneTokenUsage(settled.spent.tokens)
|
|
26734
25823
|
};
|
|
26735
25824
|
}
|
|
26736
|
-
/** Summarize one scope's children and everything below them, listing the best direct children
|
|
26737
|
-
* and counting the rest. Wait-states are not agents and are left out. */
|
|
26738
25825
|
/** The live team a child manager leads, read through the nested scope it owns. */
|
|
26739
25826
|
function liveTeamOf(child) {
|
|
26740
25827
|
let nested;
|
|
@@ -26769,7 +25856,6 @@ function readLiveTeam(children, ownerTurns, ownerLastTurnAt) {
|
|
|
26769
25856
|
let down = 0;
|
|
26770
25857
|
let lastActivityAt = ownerLastTurnAt;
|
|
26771
25858
|
for (const child of children) {
|
|
26772
|
-
if (child.wait) continue;
|
|
26773
25859
|
agents += 1;
|
|
26774
25860
|
lastActivityAt = Math.max(lastActivityAt, child.lastActivityAt, child.settledAt ?? 0, executorActivityAt(child));
|
|
26775
25861
|
if (child.status === "done") done += 1;
|
|
@@ -26807,7 +25893,6 @@ function summarizeTeam(children) {
|
|
|
26807
25893
|
let members = 0;
|
|
26808
25894
|
const results = [];
|
|
26809
25895
|
for (const child of children) {
|
|
26810
|
-
if (child.wait) continue;
|
|
26811
25896
|
members += 1;
|
|
26812
25897
|
const below = child.subtree;
|
|
26813
25898
|
agents += 1 + (below?.agents ?? 0);
|
|
@@ -26867,8 +25952,7 @@ function makeTreeView(root, children) {
|
|
|
26867
25952
|
return {
|
|
26868
25953
|
root,
|
|
26869
25954
|
nodes,
|
|
26870
|
-
inFlight: nodes.filter((n) => n.status === "running" || n.status === "acquiring" || n.status === "queued").length
|
|
26871
|
-
waiting: nodes.filter((n) => n.status === "waiting").length
|
|
25955
|
+
inFlight: nodes.filter((n) => n.status === "running" || n.status === "acquiring" || n.status === "queued").length
|
|
26872
25956
|
};
|
|
26873
25957
|
}
|
|
26874
25958
|
function frozenHandle(child) {
|
|
@@ -27366,7 +26450,7 @@ async function closeNestedScope(scope, controller, reason) {
|
|
|
27366
26450
|
if (!controller.signal.aborted) controller.abort(reason);
|
|
27367
26451
|
for (;;) if (await scope.next() === null) break;
|
|
27368
26452
|
const view = scope.view;
|
|
27369
|
-
if (view.inFlight > 0
|
|
26453
|
+
if (view.inFlight > 0) throw new ValidationError$1(`driverExecutor: nested cleanup left ${view.inFlight} running nodes`);
|
|
27370
26454
|
}
|
|
27371
26455
|
/** The nested tree's full event list — the one evidence the spend, verdict, AND driver-inference
|
|
27372
26456
|
* roll-ups read off the same journal the supervisor sums. */
|
|
@@ -28914,7 +27998,7 @@ function describeRejection(error) {
|
|
|
28914
27998
|
function createSupervisor() {
|
|
28915
27999
|
let attached;
|
|
28916
28000
|
async function run(root, task, opts) {
|
|
28917
|
-
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;
|
|
28918
28002
|
const input = detachedSnapshot({
|
|
28919
28003
|
task,
|
|
28920
28004
|
options: {
|
|
@@ -28949,7 +28033,6 @@ function createSupervisor() {
|
|
|
28949
28033
|
blobs: persistence.blobs,
|
|
28950
28034
|
executors: executorRegistry,
|
|
28951
28035
|
...recoverExecutor ? { recoverExecutor } : {},
|
|
28952
|
-
...probes === void 0 ? {} : { probes },
|
|
28953
28036
|
...suppliedNow === void 0 ? {} : { now: suppliedNow },
|
|
28954
28037
|
...signal === void 0 ? {} : { signal },
|
|
28955
28038
|
...hooks === void 0 ? {} : { hooks },
|
|
@@ -29067,7 +28150,6 @@ function createSupervisor() {
|
|
|
29067
28150
|
requiredKnown: true,
|
|
29068
28151
|
...priorRootMaterialization === void 0 ? {} : { prior: priorRootMaterialization }
|
|
29069
28152
|
} } : {},
|
|
29070
|
-
...opts.probes ? { probes: opts.probes } : {},
|
|
29071
28153
|
...opts.workerTrace ? { workerTrace: opts.workerTrace } : {},
|
|
29072
28154
|
...opts.workerTraceUnpropagated ? { workerTraceUnpropagated: opts.workerTraceUnpropagated } : {},
|
|
29073
28155
|
...opts.interactiveBindingDir ? { interactiveBindingDir: opts.interactiveBindingDir } : {},
|
|
@@ -29418,8 +28500,7 @@ function wrapJournalForBreaker(journal, breaker) {
|
|
|
29418
28500
|
* `allSettled`'d — a stray throw here is NOT the primary error (firstError precedence).
|
|
29419
28501
|
*/
|
|
29420
28502
|
async function drainLiveChildren(scope, controller, settleGraceMs = 0) {
|
|
29421
|
-
|
|
29422
|
-
if (!(view.inFlight > 0 || view.waiting > 0)) {
|
|
28503
|
+
if (scope.view.inFlight === 0) {
|
|
29423
28504
|
await drainCursor(scope);
|
|
29424
28505
|
return unconfirmedTeardowns(scope);
|
|
29425
28506
|
}
|
|
@@ -29432,7 +28513,7 @@ async function drainLiveChildren(scope, controller, settleGraceMs = 0) {
|
|
|
29432
28513
|
clearGraceTimer?.();
|
|
29433
28514
|
}
|
|
29434
28515
|
const after = scope.view;
|
|
29435
|
-
if (after.inFlight > 0
|
|
28516
|
+
if (after.inFlight > 0) throw new RuntimeRunStateError(`supervisor: cleanup ended with ${after.inFlight} running and ` + describeUnconfirmed(scope));
|
|
29436
28517
|
return unconfirmedTeardowns(scope);
|
|
29437
28518
|
}
|
|
29438
28519
|
/** Five minutes: long enough to outlast a provider's transient delete failures and a slow
|
|
@@ -29697,6 +28778,6 @@ function isNonEmptySpend(s) {
|
|
|
29697
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);
|
|
29698
28779
|
}
|
|
29699
28780
|
//#endregion
|
|
29700
|
-
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 };
|
|
29701
28782
|
|
|
29702
|
-
//# sourceMappingURL=supervisor-
|
|
28783
|
+
//# sourceMappingURL=supervisor-DYKPbcZa.js.map
|