@tangle-network/agent-runtime 7.0.0 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +74 -0
- package/README.md +0 -3
- package/dist/{activation-Ba9IAgQ3.js → activation-8AdN-3r4.js} +4 -4
- package/dist/{activation-Ba9IAgQ3.js.map → activation-8AdN-3r4.js.map} +1 -1
- package/dist/{activation-DIkTEBWF.d.ts → activation-xxvLF1-1.d.ts} +2 -2
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +3 -3
- package/dist/{bundle-Cy5igJdv.js → bundle-CcpL2lfr.js} +2 -2
- package/dist/{bundle-Cy5igJdv.js.map → bundle-CcpL2lfr.js.map} +1 -1
- package/dist/candidate-execution/index.js +4 -4
- package/dist/continuation-CIyKnS1Q.js +451 -0
- package/dist/continuation-CIyKnS1Q.js.map +1 -0
- package/dist/{coordination-manager-DQZ2u6L-.js → coordination-manager-DAGPerJy.js} +194 -148
- package/dist/coordination-manager-DAGPerJy.js.map +1 -0
- package/dist/{coordination-map-routing-CKRNZSaV.js → coordination-map-routing-XZXTlCmn.js} +2 -4
- package/dist/coordination-map-routing-XZXTlCmn.js.map +1 -0
- package/dist/{delegate-DFLSVjFG.js → delegate-CoaNr0hO.js} +3 -3
- package/dist/{delegate-DFLSVjFG.js.map → delegate-CoaNr0hO.js.map} +1 -1
- package/dist/durable.d.ts +1 -1
- package/dist/durable.js +13 -12
- package/dist/durable.js.map +1 -1
- package/dist/{errors-AzSuGI23.js → errors-DG8yufqX.js} +8 -1
- package/dist/errors-DG8yufqX.js.map +1 -0
- package/dist/{failure-cause-rJTm_Tx8.js → failure-cause-CVB-YE88.js} +12 -8
- package/dist/{failure-cause-rJTm_Tx8.js.map → failure-cause-CVB-YE88.js.map} +1 -1
- package/dist/graph-pFTeNZ-G.js +1793 -0
- package/dist/graph-pFTeNZ-G.js.map +1 -0
- package/dist/{graph-registry-7X2nrreR.js → graph-registry-czAwYwNj.js} +133 -9
- package/dist/graph-registry-czAwYwNj.js.map +1 -0
- package/dist/{graph-registry-gYbq4L5Z.js → graph-registry-uTIyyy7A.js} +1 -1
- package/dist/{improve-BolbYegO.js → improve-I43jex8v.js} +19 -48
- package/dist/improve-I43jex8v.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +16 -16
- package/dist/intelligence.d.ts +2 -2
- package/dist/intelligence.js +8 -8
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +2 -2
- package/dist/kernel.js +16 -15
- package/dist/{loop-runner-Cs9IJ6Nk.d.ts → loop-runner-BGwhcn1p.d.ts} +2 -2
- package/dist/{loop-runner-C9W3T_3T.js → loop-runner-BN4KozR0.js} +3 -3
- package/dist/{loop-runner-C9W3T_3T.js.map → loop-runner-BN4KozR0.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 +7 -5
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +9 -9
- package/dist/mcp/memory-bin.js +2 -2
- package/dist/{model-policy-JOcg3NRs.js → model-policy-CZwhXp8O.js} +15 -10
- package/dist/model-policy-CZwhXp8O.js.map +1 -0
- package/dist/{prepare-Dtfk_Wi_.js → prepare-BnF8nGIg.js} +4 -4
- package/dist/{prepare-Dtfk_Wi_.js.map → prepare-BnF8nGIg.js.map} +1 -1
- package/dist/{profile-CNSQjOes.js → profile-CD25rTJF.js} +4 -4
- package/dist/{profile-CNSQjOes.js.map → profile-CD25rTJF.js.map} +1 -1
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.js +2 -2
- package/dist/{provision-supervisor-BQFHjCtN.js → provision-supervisor-DvzayGpe.js} +8 -8
- package/dist/{provision-supervisor-BQFHjCtN.js.map → provision-supervisor-DvzayGpe.js.map} +1 -1
- package/dist/{run-cancellation-B84X8ZAP.js → run-cancellation-C5__aBKW.js} +4 -4
- package/dist/{run-cancellation-B84X8ZAP.js.map → run-cancellation-C5__aBKW.js.map} +1 -1
- package/dist/{run-state-Dh1bGERI.js → run-state-CJAaxqfp.js} +5 -5
- package/dist/{run-state-Dh1bGERI.js.map → run-state-CJAaxqfp.js.map} +1 -1
- package/dist/{runtime-C9F1PG2A.js → runtime-CuwHe8_K.js} +76 -987
- package/dist/runtime-CuwHe8_K.js.map +1 -0
- package/dist/{sandbox-profile-compat-oFGWESLn.js → sandbox-profile-compat-l-SLQnGb.js} +2 -2
- package/dist/{sandbox-profile-compat-oFGWESLn.js.map → sandbox-profile-compat-l-SLQnGb.js.map} +1 -1
- package/dist/{search-executor-CKR9_6NR.js → search-executor-DAjm-NlG.js} +17 -46
- package/dist/search-executor-DAjm-NlG.js.map +1 -0
- package/dist/{server-Dh3SQGQT.js → server-BUpNUn5D.js} +32 -8
- package/dist/{server-Dh3SQGQT.js.map → server-BUpNUn5D.js.map} +1 -1
- package/dist/{settle-record-C6LdVvok.js → settle-record-CeBdBuxP.js} +2 -2
- package/dist/{settle-record-C6LdVvok.js.map → settle-record-CeBdBuxP.js.map} +1 -1
- package/dist/{snapshot-Cgj2Wzi6.js → snapshot-BKI_w3JD.js} +19 -5
- package/dist/snapshot-BKI_w3JD.js.map +1 -0
- package/dist/{structural-rollout-Dapf2aZY.js → structural-rollout-CqxBKxKY.js} +18 -54
- package/dist/structural-rollout-CqxBKxKY.js.map +1 -0
- package/dist/{supervise-xwl2D6q3.js → supervise-Cdqj_oe4.js} +101 -111
- package/dist/supervise-Cdqj_oe4.js.map +1 -0
- package/dist/{supervision-discovery-B47U8TH5.js → supervision-discovery-CW-ildZ-.js} +4 -4
- package/dist/{supervision-discovery-B47U8TH5.js.map → supervision-discovery-CW-ildZ-.js.map} +1 -1
- package/dist/{supervisor-gC-u_PRk.js → supervisor-D-eT-6Tu.js} +206 -693
- package/dist/supervisor-D-eT-6Tu.js.map +1 -0
- package/dist/{supervisor-accounting-DxLQ_TQe.js → supervisor-accounting-Ow6CChpB.js} +434 -423
- package/dist/supervisor-accounting-Ow6CChpB.js.map +1 -0
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +61 -61
- package/dist/{tool-server-Dg350Bgz.js → tool-server-DYwRbi0I.js} +3 -3
- package/dist/{tool-server-Dg350Bgz.js.map → tool-server-DYwRbi0I.js.map} +1 -1
- package/dist/{top-web-BJLnFQdr.js → top-web-BuYMkJN4.js} +9 -7
- package/dist/{top-web-BJLnFQdr.js.map → top-web-BuYMkJN4.js.map} +1 -1
- package/dist/tui/bin.js +3 -3
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.js +2 -2
- package/dist/{types-Jg274chm.d.ts → types-C_qbnw9N.d.ts} +1335 -1413
- package/dist/{util-C_cdPNC4.js → util-fcckrkKp.js} +39 -113
- package/dist/util-fcckrkKp.js.map +1 -0
- package/dist/viewer/report-viewer.js +90 -90
- package/dist/viewer/runtime-viewer/AgentGraphCanvas-P_PFS2Ko-B7S6Fwwy.js +563 -0
- package/dist/viewer/runtime-viewer/latest-events-wofY6ifW-B8_bfLwI.js +17870 -0
- package/dist/viewer/styles.css +1592 -7027
- package/dist/viewer/workspace-viewer.js +10926 -10522
- package/dist/{workspace-streams-B7yNMahl.js → workspace-streams-DA9R3j-p.js} +2 -2
- package/dist/{workspace-streams-B7yNMahl.js.map → workspace-streams-DA9R3j-p.js.map} +1 -1
- package/package.json +9 -9
- package/dist/coordination-manager-DQZ2u6L-.js.map +0 -1
- package/dist/coordination-map-routing-CKRNZSaV.js.map +0 -1
- package/dist/errors-AzSuGI23.js.map +0 -1
- package/dist/graph-BjQF2syl.js +0 -579
- package/dist/graph-BjQF2syl.js.map +0 -1
- package/dist/graph-registry-7X2nrreR.js.map +0 -1
- package/dist/improve-BolbYegO.js.map +0 -1
- package/dist/model-policy-JOcg3NRs.js.map +0 -1
- package/dist/runtime-C9F1PG2A.js.map +0 -1
- package/dist/search-executor-CKR9_6NR.js.map +0 -1
- package/dist/snapshot-Cgj2Wzi6.js.map +0 -1
- package/dist/structural-rollout-Dapf2aZY.js.map +0 -1
- package/dist/supervise-xwl2D6q3.js.map +0 -1
- package/dist/supervisor-accounting-DxLQ_TQe.js.map +0 -1
- package/dist/supervisor-gC-u_PRk.js.map +0 -1
- package/dist/util-C_cdPNC4.js.map +0 -1
- package/dist/viewer/runtime-viewer/AgentGraphCanvas-DGq7_u8--C6yDfIUT.js +0 -8555
- package/dist/viewer/runtime-viewer/latest-events-CRq9sGkK-CfoBl1dP.js +0 -9592
- package/dist/viewer/runtime-viewer/sandbox-ui-BEziPi7D.css +0 -2
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { c as RetainedInteractiveAdmissionError, d as RetainedRunDispatchBindingError, f as RuntimeRunStateError, l as RetainedInteractiveBindingError, m as ValidationError, u as RetainedRunAdmissionError } from "./errors-
|
|
2
|
-
import { A as withBudgetResources, B as singleFlightTeardown, C as unreportedSpend, D as addResourceSpend, I as armDeadlineTimer, L as boundedChildDeadlineAt, M as CHILD_DEADLINE_REASON, R as checkInIdleMs, T as zeroSpend, V as teardownExecutor, a as cloneSpend, c as errorMessage, f as isAsyncIterable, i as chargedTokens, k as resourceTelemetry, n as addSpend, o as cloneTokenUsage, t as abortError, v as sleep } from "./util-
|
|
3
|
-
import { $ as
|
|
4
|
-
import { a as errMessage,
|
|
5
|
-
import { t as assertExecutableAgentProfile } from "./model-policy-
|
|
1
|
+
import { c as RetainedInteractiveAdmissionError, d as RetainedRunDispatchBindingError, f as RuntimeRunStateError, l as RetainedInteractiveBindingError, m as ValidationError, r as BackendTransportError, u as RetainedRunAdmissionError } from "./errors-DG8yufqX.js";
|
|
2
|
+
import { A as withBudgetResources, B as singleFlightTeardown, C as unreportedSpend, D as addResourceSpend, I as armDeadlineTimer, L as boundedChildDeadlineAt, M as CHILD_DEADLINE_REASON, R as checkInIdleMs, T as zeroSpend, V as teardownExecutor, a as cloneSpend, c as errorMessage, f as isAsyncIterable, i as chargedTokens, k as resourceTelemetry, n as addSpend, o as cloneTokenUsage, t as abortError, v as sleep } from "./util-fcckrkKp.js";
|
|
3
|
+
import { $ as spendFromUsageTotals, F as executorFailureInfra, G as budgetLeases, I as executorFailureReason, J as createBudgetPool, K as budgetRecoverySeeds, M as contentAddress, P as executorFailure, Tt as recordValue, U as BudgetReconcileFault, W as budgetLeaseUpdates, X as meterUsageEvent, Y as refreshBudgetLease, Z as newUsageTotals, dt as parseCanonicalTransportEvent, et as assertValidBudget, f as executorEvidenceWriter, g as prepareRetainedExecutor, h as leafContinuationExecutionId, j as traceRootWriter, n as runtimeFailureCause, o as settlementFailureCause, q as completedLeafBacking, s as BUDGET_CONTINUATION_TASK, t as reportedFailureCause, tt as assertValidSpend, u as RetainedExecutionPendingError, ut as extractTransportEventIdentity, y as retainedExecutorSeamKey } from "./failure-cause-CVB-YE88.js";
|
|
4
|
+
import { a as errMessage, f as resolveRedactor, l as errorText, n as detachedSnapshot, r as shareParsedStrings, u as defaultRedactor } from "./snapshot-BKI_w3JD.js";
|
|
5
|
+
import { t as assertExecutableAgentProfile } from "./model-policy-CZwhXp8O.js";
|
|
6
6
|
import { a as readCommittedJsonLines, c as appendCommittedJsonLine, l as sha256, r as prepareJsonlAppend, s as withJsonlFileLock, t as isNoEntError } from "./jsonl-file-DsxVI0E_.js";
|
|
7
|
-
import { F as deepFreezeCandidate, h as workspaceLimits, i as readAgentCandidateWorkspaceArtifactFiles, j as verifiedArtifactChunks, m as safeArchivePath, z as sha256Bytes$1 } from "./workspace-streams-
|
|
7
|
+
import { F as deepFreezeCandidate, h as workspaceLimits, i as readAgentCandidateWorkspaceArtifactFiles, j as verifiedArtifactChunks, m as safeArchivePath, z as sha256Bytes$1 } from "./workspace-streams-DA9R3j-p.js";
|
|
8
8
|
import { a as writeAtomicDurableFile, n as assertNoSymlinkDescendant, o as captureRunWriteContext, r as publishExclusiveDurableFile, s as fenceRunWrite, t as appendDurableFile } from "./durable-file-DstVeHcj.js";
|
|
9
9
|
import { a as withPursuitContext, i as notifyRuntimeHookEvent } from "./runtime-hooks-CsW2pfFk.js";
|
|
10
10
|
import * as traceContract from "@tangle-network/agent-trace-contract";
|
|
@@ -130,6 +130,11 @@ function signalAbortError(signal, fallback) {
|
|
|
130
130
|
error.name = "AbortError";
|
|
131
131
|
return error;
|
|
132
132
|
}
|
|
133
|
+
/** A parent abort stops the call at once. The timeout still bounds a parent that stays open. */
|
|
134
|
+
function deadlineSignal(parent, ms) {
|
|
135
|
+
const timeout = AbortSignal.timeout(ms);
|
|
136
|
+
return parent === void 0 ? timeout : AbortSignal.any([parent, timeout]);
|
|
137
|
+
}
|
|
133
138
|
/** Cascade source aborts and preserve their reason; release removes the source listeners. */
|
|
134
139
|
function linkAbort(...sources) {
|
|
135
140
|
const controller = new AbortController();
|
|
@@ -220,6 +225,24 @@ function exactSession(environment, controlRef) {
|
|
|
220
225
|
controlRef: currentControlRef
|
|
221
226
|
};
|
|
222
227
|
}
|
|
228
|
+
/** The provider returned a different session than the one this execution admitted. A lost
|
|
229
|
+
* reply, which keeps the same coordinates and no session, is not this case. */
|
|
230
|
+
const replacedSessionMessage = /* @__PURE__ */ new Set([
|
|
231
|
+
"provider reconstructed a different retained environment",
|
|
232
|
+
"provider reconstructed a different session",
|
|
233
|
+
"provider session reconstructed a different retained session",
|
|
234
|
+
"provider session returned a different exact run control reference"
|
|
235
|
+
]);
|
|
236
|
+
function retainedSessionReplaced(error) {
|
|
237
|
+
const seen = /* @__PURE__ */ new Set();
|
|
238
|
+
let current = error;
|
|
239
|
+
for (let depth = 0; depth < 8 && current !== void 0 && !seen.has(current); depth += 1) {
|
|
240
|
+
seen.add(current);
|
|
241
|
+
if (current instanceof Error && replacedSessionMessage.has(current.message)) return true;
|
|
242
|
+
current = current instanceof Error ? current.cause : void 0;
|
|
243
|
+
}
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
223
246
|
function sameControlCoordinates(left, right) {
|
|
224
247
|
return canonicalCandidateDigest(left) === canonicalCandidateDigest(right);
|
|
225
248
|
}
|
|
@@ -265,6 +288,24 @@ var RetainedRunProviderContractError = class extends Error {
|
|
|
265
288
|
this.code = options.code ?? "RETAINED_RUN_PROVIDER_CONTRACT_ERROR";
|
|
266
289
|
}
|
|
267
290
|
};
|
|
291
|
+
const HTML_PAGE_START = /^<!doctype html|^<html|^<body/i;
|
|
292
|
+
/** A gateway's HTML error page that reached a retained-run read as the failure text: a replay
|
|
293
|
+
* endpoint behind Cloudflare answers 5xx with markup, which is a transport failure (retry,
|
|
294
|
+
* outage window), never a contract violation inviting reconciliation of an execution that never
|
|
295
|
+
* started (#1203). Extracts the printed HTTP status and a bounded, tag-free excerpt. */
|
|
296
|
+
function htmlTransportFailure(error) {
|
|
297
|
+
if (!(error instanceof Error)) return void 0;
|
|
298
|
+
const text = error.message ?? "";
|
|
299
|
+
if (!HTML_PAGE_START.test(text.trim())) return void 0;
|
|
300
|
+
const headline = /<title>([^<]*)<\/title>/i.exec(text)?.[1]?.trim() ?? text.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
301
|
+
const status = /\b([45]\d\d)\b/.exec(headline)?.[1];
|
|
302
|
+
const excerpt = headline.slice(0, 200);
|
|
303
|
+
return new BackendTransportError("provider", `provider answered an HTML error page${status === void 0 ? "" : ` (HTTP ${status})`}: ${excerpt}`, {
|
|
304
|
+
...status === void 0 ? {} : { status: Number(status) },
|
|
305
|
+
body: excerpt,
|
|
306
|
+
cause: error
|
|
307
|
+
});
|
|
308
|
+
}
|
|
268
309
|
function cancellationRequest(controlRef, operationId, reason) {
|
|
269
310
|
const material = {
|
|
270
311
|
operationId,
|
|
@@ -403,6 +444,14 @@ function runtimeFlowAgent(agent, brain) {
|
|
|
403
444
|
function isRuntimeFlowAgent(agent) {
|
|
404
445
|
return typeof agent === "object" && agent !== null && flowOwners.has(agent);
|
|
405
446
|
}
|
|
447
|
+
/** A check node's executor factory runs a command, not a model, so it carries no model identity. */
|
|
448
|
+
function runtimeCheckFactory(factory) {
|
|
449
|
+
flowOwners.add(factory);
|
|
450
|
+
return factory;
|
|
451
|
+
}
|
|
452
|
+
function isRuntimeCheckFactory(factory) {
|
|
453
|
+
return typeof factory === "function" && flowOwners.has(factory);
|
|
454
|
+
}
|
|
406
455
|
// src/runtime/supervise/executable-spec.ts
|
|
407
456
|
/** Read untrusted fields once, then parse and seal the exact executable profile. */
|
|
408
457
|
function executableAgentProfileSnapshot(raw, context) {
|
|
@@ -419,7 +468,7 @@ function authoredAgentProfileSnapshot(raw, context) {
|
|
|
419
468
|
/** Snapshot an executable spec while retaining trusted callbacks by reference. */
|
|
420
469
|
function executableAgentSpecSnapshot(raw, context) {
|
|
421
470
|
const { profile: rawProfile, harness, execution: rawExecution, ...runtimeExtensions } = raw;
|
|
422
|
-
const profile = raw.executor === void 0 && !isRuntimeFlowAgent(runtimeExtensions.driver) ? executableAgentProfileSnapshot(rawProfile, context) : authoredAgentProfileSnapshot(rawProfile, context);
|
|
471
|
+
const profile = raw.executor === void 0 && !isRuntimeFlowAgent(runtimeExtensions.driver) && !isRuntimeCheckFactory(raw.executorFactory) ? executableAgentProfileSnapshot(rawProfile, context) : authoredAgentProfileSnapshot(rawProfile, context);
|
|
423
472
|
const execution = rawExecution === void 0 ? void 0 : detachedSnapshot(rawExecution, `${context}: execution attribution`);
|
|
424
473
|
return Object.freeze({
|
|
425
474
|
...runtimeExtensions,
|
|
@@ -447,6 +496,7 @@ const declarationKeys = /* @__PURE__ */ new Set([
|
|
|
447
496
|
"authoredProfile",
|
|
448
497
|
"backend",
|
|
449
498
|
"model",
|
|
499
|
+
"resources",
|
|
450
500
|
"execution",
|
|
451
501
|
"materializer",
|
|
452
502
|
"plan",
|
|
@@ -645,6 +695,7 @@ function knownMaterializationReceipt(input) {
|
|
|
645
695
|
assertNonEmpty(declaration.execution?.kind, "execution.kind");
|
|
646
696
|
assertNonEmpty(declaration.execution?.id, "execution.id");
|
|
647
697
|
assertModel(declaration.model);
|
|
698
|
+
assertResources(declaration.resources);
|
|
648
699
|
let effectiveProfileDigest;
|
|
649
700
|
let materializationPlanDigest;
|
|
650
701
|
let platformAttachmentsDigest;
|
|
@@ -665,6 +716,10 @@ function knownMaterializationReceipt(input) {
|
|
|
665
716
|
runtime: input.runtime,
|
|
666
717
|
backend: declaration.backend,
|
|
667
718
|
model: declaration.model,
|
|
719
|
+
resources: declaration.resources ?? {
|
|
720
|
+
status: "unknown",
|
|
721
|
+
reason: "executor-did-not-report-applied-resources"
|
|
722
|
+
},
|
|
668
723
|
execution: declaration.execution,
|
|
669
724
|
materializer: declaration.materializer
|
|
670
725
|
});
|
|
@@ -770,6 +825,46 @@ function assertModel(model) {
|
|
|
770
825
|
}
|
|
771
826
|
throw new ValidationError("executor materialization: model.status must be known or unknown");
|
|
772
827
|
}
|
|
828
|
+
function assertResources(resources) {
|
|
829
|
+
if (resources === void 0) return;
|
|
830
|
+
if (typeof resources !== "object" || resources === null || Array.isArray(resources)) throw new ValidationError("executor materialization: resources must be a known/unknown report");
|
|
831
|
+
const keys = Object.keys(resources).sort().join(",");
|
|
832
|
+
if (resources.status === "unknown") {
|
|
833
|
+
if (keys !== "reason,status") throw new ValidationError("executor materialization: unknown resources accepts only status and reason");
|
|
834
|
+
assertNonEmpty(resources.reason, "resources.reason");
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (resources.status !== "known") throw new ValidationError("executor materialization: resources.status must be known or unknown");
|
|
838
|
+
const sizes = {
|
|
839
|
+
cpu: resources.cpu,
|
|
840
|
+
memoryMb: resources.memoryMb,
|
|
841
|
+
diskMb: resources.diskMb
|
|
842
|
+
};
|
|
843
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
844
|
+
...Object.keys(sizes),
|
|
845
|
+
"accelerator",
|
|
846
|
+
"origin",
|
|
847
|
+
"observedAt",
|
|
848
|
+
"status"
|
|
849
|
+
]);
|
|
850
|
+
const stray = Object.keys(resources).filter((key) => !allowed.has(key));
|
|
851
|
+
if (stray.length > 0) throw new ValidationError(`executor materialization: known resources accepts only ${[...allowed].sort().join(", ")}: ${stray.sort().join(", ")}`);
|
|
852
|
+
const positive = (value, integer) => Number.isFinite(value) && value > 0 && (!integer || Number.isInteger(value));
|
|
853
|
+
for (const [field, value] of Object.entries(sizes).filter(([, v]) => v !== void 0)) if (!positive(value, field !== "cpu")) throw new ValidationError(`executor materialization: resources.${field} must be a positive ${field === "cpu" ? "number" : "integer"}`);
|
|
854
|
+
if (resources.accelerator !== void 0) {
|
|
855
|
+
const accelerator = resources.accelerator;
|
|
856
|
+
assertNonEmpty(accelerator.kind, "resources.accelerator.kind");
|
|
857
|
+
if (!positive(accelerator.count, true)) throw new ValidationError("executor materialization: resources.accelerator.count must be a positive integer");
|
|
858
|
+
if (accelerator.memoryMb !== void 0 && !positive(accelerator.memoryMb, true)) throw new ValidationError("executor materialization: resources.accelerator.memoryMb must be a positive integer");
|
|
859
|
+
}
|
|
860
|
+
if (Object.values(sizes).every((v) => v === void 0) && resources.accelerator === void 0) throw new ValidationError("executor materialization: known resources needs at least one of cpu, memoryMb, diskMb, accelerator");
|
|
861
|
+
if (![
|
|
862
|
+
"estimated",
|
|
863
|
+
"measured",
|
|
864
|
+
"reported"
|
|
865
|
+
].includes(resources.origin)) throw new ValidationError("executor materialization: resources.origin must be estimated, measured or reported");
|
|
866
|
+
assertNonEmpty(resources.observedAt, "resources.observedAt");
|
|
867
|
+
}
|
|
773
868
|
function assertNonEmpty(value, field) {
|
|
774
869
|
if (typeof value !== "string" || value.trim().length === 0) throw new ValidationError(`executor materialization: ${field} must be a non-empty string`);
|
|
775
870
|
}
|
|
@@ -1261,6 +1356,12 @@ function retainHarnessTranscript(receipt, harness) {
|
|
|
1261
1356
|
throw new ValidationError(`Invalid retained transcript receipt: ${error instanceof Error ? error.message : String(error)}`);
|
|
1262
1357
|
}
|
|
1263
1358
|
}
|
|
1359
|
+
/** The sidecar's native inventory has written both `sha256:<hex>` and bare `<hex>` digests
|
|
1360
|
+
* (#1264: whole runs skipped every file for the bare form); accept either and keep the
|
|
1361
|
+
* canonical prefix. `null` still means no digest and stays unverifiable. */
|
|
1362
|
+
function nativeDigest(value) {
|
|
1363
|
+
return typeof value === "string" && /^[0-9a-f]{64}$/.test(value) ? `sha256:${value}` : value;
|
|
1364
|
+
}
|
|
1264
1365
|
function retainedCapture(receipt, harness) {
|
|
1265
1366
|
const { snapshot, provenance, ...source } = receipt;
|
|
1266
1367
|
const files = [];
|
|
@@ -1284,7 +1385,7 @@ function retainedCapture(receipt, harness) {
|
|
|
1284
1385
|
const parsed = fileSchema.safeParse({
|
|
1285
1386
|
path: nativeIdentity(attribution),
|
|
1286
1387
|
bytes: entry.sizeBytes,
|
|
1287
|
-
sha256: entry.sha256,
|
|
1388
|
+
sha256: nativeDigest(entry.sha256),
|
|
1288
1389
|
attribution
|
|
1289
1390
|
});
|
|
1290
1391
|
if (parsed.success) files.push(parsed.data);
|
|
@@ -3092,73 +3193,18 @@ function generateTraceId() {
|
|
|
3092
3193
|
}
|
|
3093
3194
|
// src/runtime/supervise/worker-trace.ts
|
|
3094
3195
|
/**
|
|
3095
|
-
* Trace context handed
|
|
3096
|
-
*
|
|
3097
|
-
*
|
|
3098
|
-
*
|
|
3099
|
-
*
|
|
3100
|
-
*
|
|
3101
|
-
*
|
|
3102
|
-
*
|
|
3103
|
-
*
|
|
3104
|
-
*
|
|
3105
|
-
*
|
|
3106
|
-
* `
|
|
3107
|
-
* reads `TRACEPARENT` first with the legacy pair as fallback. The bespoke pair is the fork, not
|
|
3108
|
-
* the standard; it is kept for one release so children on the previous package version still join,
|
|
3109
|
-
* then removed in the next major.
|
|
3110
|
-
*
|
|
3111
|
-
* WHICH SPAN. The stamped `PARENT_SPAN_ID` is the span of the node that DID the spawning — the
|
|
3112
|
-
* scope's own `parentId` — not the span of the worker's own node. At depth 0 that is the run's root
|
|
3113
|
-
* span; inside a driver's nested scope it is that driver child's span, so a depth-2 worker joins
|
|
3114
|
-
* under the middle node rather than the root. That is also the only span guaranteed to exist at the
|
|
3115
|
-
* moment the child's `ExecutorContext` is built (the worker's own node span is opened by the
|
|
3116
|
-
* `agent.spawn` hook that fires just after), so the wire needs no ordering assumption.
|
|
3117
|
-
*
|
|
3118
|
-
* OFF WHEN TRACING IS OFF. The context reaches an executor only through the `ExecutorContext` seam
|
|
3119
|
-
* this module names, and that seam is seeded only when a run configured `SuperviseOptions.otel` AND
|
|
3120
|
-
* an exporter resolved. With tracing off there is no seam, {@link workerTraceEnv} returns an empty
|
|
3121
|
-
* record, and every spawn environment is byte-identical to what it was before this module existed.
|
|
3122
|
-
*
|
|
3123
|
-
* TWO SEEDING SITES, because there are two ways a worker's `ExecutorContext` gets built. `Scope`
|
|
3124
|
-
* seeds it per child for anything the executor registry resolves — that is the path that knows the
|
|
3125
|
-
* spawning node and reaches arbitrary depth. `workerFromBackend` (what `supervise({ backend })`
|
|
3126
|
-
* uses) instead builds its leaf executor eagerly and hands it back as a bring-your-own `Executor`,
|
|
3127
|
-
* which the registry resolves WITHOUT consulting the scope's context at all; `supervise()` therefore
|
|
3128
|
-
* passes the seam to `workerFromBackend` itself, resolved against the run root, which is the node
|
|
3129
|
-
* that spawns every front-door worker. Miss that second site and the front door looks wired and
|
|
3130
|
-
* stamps nothing.
|
|
3131
|
-
*
|
|
3132
|
-
* PRECEDENCE, from lowest to highest:
|
|
3133
|
-
* 1. the supervisor process's own `process.env` (ambient inheritance),
|
|
3134
|
-
* 2. the trace context stamped here,
|
|
3135
|
-
* 3. the caller's own seam env (`CliSeam.env`).
|
|
3136
|
-
* A caller who sets `TRACE_ID` / `PARENT_SPAN_ID` on a seam wins — theirs is a deliberate
|
|
3137
|
-
* declaration about the worker — and the W3C wire follows: a seam that overrides the legacy pair
|
|
3138
|
-
* without its own `TRACEPARENT` gets `TRACEPARENT` rebuilt from ITS ids (`mergeTraceEnv` performs
|
|
3139
|
-
* the dual-write on the caller's behalf), never left as the recorder's, so both spellings always
|
|
3140
|
-
* name the same trace. Ambient `process.env` does NOT win: when the supervisor process was
|
|
3141
|
-
* itself launched as someone's worker, its inherited ids describe the SUPERVISOR's place in an
|
|
3142
|
-
* outer trace, and reusing them for a child would file the child's spans under the wrong parent.
|
|
3143
|
-
* The supported way to join that outer trace is `SupervisorSpanOptions.traceId` / `parentSpanId`
|
|
3144
|
-
* (feed them from `readTraceContextFromEnv()`), which makes the recorder's own ids the outer ones
|
|
3145
|
-
* and so makes every stamped child inherit them too.
|
|
3146
|
-
*
|
|
3147
|
-
* WHICH BACKENDS PROPAGATE. Only a backend with a real channel to the worker can carry this, and
|
|
3148
|
-
* the ones that have none say so here rather than dropping it silently:
|
|
3149
|
-
* - `cli` YES — `CliSeam.env` → the spawned subprocess.
|
|
3150
|
-
* - `sandbox` YES — `CreateSandboxOptions.env` on the box the worker runs in (single-shot
|
|
3151
|
-
* and steerable). This is the cross-MACHINE case the feature exists for.
|
|
3152
|
-
* - `bridge` YES — request headers on the bridge POST (`traceparent` plus the legacy
|
|
3153
|
-
* `x-trace-id` / `x-parent-span-id` pair, {@link workerTraceHeaders}); the
|
|
3154
|
-
* bridge stamps them into the harness child's environment at spawn.
|
|
3155
|
-
* - `router`,
|
|
3156
|
-
* `router-tools` NO — a direct model HTTP call. There is no worker process to inherit anything.
|
|
3157
|
-
* - `cli-worktree`,
|
|
3158
|
-
* `cli-in-place` NO — the work is dispatched through a local harness transport that exposes
|
|
3159
|
-
* no environment channel. Wiring it means adding that channel to the transport
|
|
3160
|
-
* first; until then it is honestly unpropagated.
|
|
3161
|
-
* - `provider` NO — `AgentEnvironmentProvider` has no environment field on its port.
|
|
3196
|
+
* Trace context handed to a spawned worker so one supervised tree spans machines. `traceContextToEnv`
|
|
3197
|
+
* writes W3C `TRACEPARENT` and the legacy `TRACE_ID` / `PARENT_SPAN_ID` pair; readers take
|
|
3198
|
+
* `TRACEPARENT` first. The legacy pair stays so a child on the previous package still joins.
|
|
3199
|
+
* `PARENT_SPAN_ID` is the spawning node's span (`parentId`): at depth 0 the run root, otherwise the
|
|
3200
|
+
* driver child, and the only span that exists when the child's `ExecutorContext` is built. With
|
|
3201
|
+
* `SuperviseOptions.otel` unset, {@link workerTraceEnv} is empty and spawn environments stay
|
|
3202
|
+
* byte-identical. `Scope` stamps registry children. `supervise({ backend })` must pass the same seam
|
|
3203
|
+
* into `workerFromBackend` or the front door stamps nothing. Precedence is ambient `process.env`,
|
|
3204
|
+
* then this stamp, then `CliSeam.env`. A seam that sets the legacy pair gets `TRACEPARENT` rebuilt
|
|
3205
|
+
* from those ids. Ambient ids do not win; join an outer trace with `SupervisorSpanOptions.traceId`
|
|
3206
|
+
* and `parentSpanId`. `cli`, `sandbox`, and `bridge` propagate. `router`, `router-tools`,
|
|
3207
|
+
* `cli-worktree`, `cli-in-place`, and `provider` have no worker environment channel and do not.
|
|
3162
3208
|
*/
|
|
3163
3209
|
/**
|
|
3164
3210
|
* The census above, as a value the compiler checks. `satisfies` against every `ExecutorConfig`
|
|
@@ -3814,6 +3860,8 @@ async function* retainedRunEvents(session, controlRef, options, now) {
|
|
|
3814
3860
|
throw abortError(options.signal.reason);
|
|
3815
3861
|
}
|
|
3816
3862
|
if (error instanceof RetainedRunProviderContractError) throw error;
|
|
3863
|
+
const html = htmlTransportFailure(error);
|
|
3864
|
+
if (html) throw html;
|
|
3817
3865
|
const fromProvider = error instanceof Error && !runtimeChecks.has(error);
|
|
3818
3866
|
throw new RetainedRunProviderContractError(error instanceof Error ? error.message : "provider retained event stream failed", {
|
|
3819
3867
|
code: fromProvider ? "RETAINED_EVENT_STREAM_READ_FAILED" : "RETAINED_EVENT_STREAM_INVALID",
|
|
@@ -4012,6 +4060,8 @@ async function retainedResult(control) {
|
|
|
4012
4060
|
candidate = await exactSessionResult(control.session);
|
|
4013
4061
|
} catch (error) {
|
|
4014
4062
|
if (error instanceof RetainedRunProviderContractError) throw error;
|
|
4063
|
+
const html = htmlTransportFailure(error);
|
|
4064
|
+
if (html) throw html;
|
|
4015
4065
|
throw new RetainedRunProviderContractError(error instanceof Error ? error.message : "provider retained result read failed", {
|
|
4016
4066
|
code: "RETAINED_RESULT_READ_FAILED",
|
|
4017
4067
|
cause: error
|
|
@@ -4231,13 +4281,7 @@ function retainedTurnForDispatch(turn, env) {
|
|
|
4231
4281
|
};
|
|
4232
4282
|
}
|
|
4233
4283
|
// src/runtime/retained-run-start.ts
|
|
4234
|
-
/**
|
|
4235
|
-
* Mint deterministic dispatch coordinates from the two caller-supplied keys.
|
|
4236
|
-
* The same `(idempotencyKey, turnId)` pair yields the same coordinates in
|
|
4237
|
-
* every process, so a pre-dispatch admission record always names the exact
|
|
4238
|
-
* session and execution the dispatch will request. A full SHA-256 digest
|
|
4239
|
-
* keeps provider session identifiers bounded and safe for workspace paths.
|
|
4240
|
-
*/
|
|
4284
|
+
/** Mint the same session and execution from `(idempotencyKey, turnId)` in every process. */
|
|
4241
4285
|
function mintRetainedIdentity(idempotencyKey, turnId) {
|
|
4242
4286
|
const base = `${encodeURIComponent(idempotencyKey)}:${encodeURIComponent(turnId)}`;
|
|
4243
4287
|
const digest = canonicalCandidateDigest({
|
|
@@ -4249,17 +4293,44 @@ function mintRetainedIdentity(idempotencyKey, turnId) {
|
|
|
4249
4293
|
executionId: `retained-execution-${digest}`
|
|
4250
4294
|
};
|
|
4251
4295
|
}
|
|
4252
|
-
/**
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4296
|
+
/** Bound from a durable intent until `provider.create` resolves. */
|
|
4297
|
+
const RETAINED_ENVIRONMENT_MATERIALIZATION_MS = 12e4;
|
|
4298
|
+
/** Create never reached an environment. The message names the stage; the driver stops. */
|
|
4299
|
+
var RetainedMaterializationTimeoutError = class extends Error {
|
|
4300
|
+
stage = "intent";
|
|
4301
|
+
constructor() {
|
|
4302
|
+
super("retained run reached stage intent and did not materialize an environment");
|
|
4303
|
+
this.name = "RetainedMaterializationTimeoutError";
|
|
4304
|
+
}
|
|
4305
|
+
};
|
|
4306
|
+
function createRetainedEnvironment(options, parent) {
|
|
4307
|
+
const timeout = AbortSignal.timeout(options.materializationTimeoutMs ?? RETAINED_ENVIRONMENT_MATERIALIZATION_MS);
|
|
4308
|
+
const signal = parent === void 0 ? timeout : AbortSignal.any([parent, timeout]);
|
|
4309
|
+
const create = options.provider.create({
|
|
4310
|
+
...options.environment,
|
|
4311
|
+
signal,
|
|
4312
|
+
metadata: retainedEnvironmentMetadata(options.environment.metadata, options.environment.idempotencyKey)
|
|
4313
|
+
});
|
|
4314
|
+
const timedOut = () => timeout.aborted && parent?.aborted !== true;
|
|
4315
|
+
return new Promise((resolve, reject) => {
|
|
4316
|
+
const fail = () => {
|
|
4317
|
+
reject(timedOut() ? new RetainedMaterializationTimeoutError() : parent?.reason instanceof Error ? parent.reason : /* @__PURE__ */ new Error("retained run materialization aborted"));
|
|
4318
|
+
};
|
|
4319
|
+
if (signal.aborted) {
|
|
4320
|
+
fail();
|
|
4321
|
+
return;
|
|
4322
|
+
}
|
|
4323
|
+
signal.addEventListener("abort", fail, { once: true });
|
|
4324
|
+
create.then((environment) => {
|
|
4325
|
+
signal.removeEventListener("abort", fail);
|
|
4326
|
+
resolve(environment);
|
|
4327
|
+
}, (error) => {
|
|
4328
|
+
signal.removeEventListener("abort", fail);
|
|
4329
|
+
reject(timedOut() ? new RetainedMaterializationTimeoutError() : error);
|
|
4330
|
+
});
|
|
4331
|
+
});
|
|
4332
|
+
}
|
|
4333
|
+
/** Dispatch one replayable run. Intent is durable before create; the promise resolves after dispatch is durable. @stable */
|
|
4263
4334
|
async function startRetainedRun(options) {
|
|
4264
4335
|
assertStableText(options.environment.idempotencyKey, "environment idempotency key");
|
|
4265
4336
|
assertStableText(options.turn.turnId, "turn idempotency key");
|
|
@@ -4310,10 +4381,7 @@ async function startRetainedRun(options) {
|
|
|
4310
4381
|
if (!existing || existing.id !== options.existingEnvironmentId || existing.provider !== options.provider.name) throw new Error("retained provider environment is unavailable or has another identity");
|
|
4311
4382
|
await assertRetainedEnvironmentOwnership(options.provider, existing, options.environment.idempotencyKey);
|
|
4312
4383
|
environment = existing;
|
|
4313
|
-
} else environment = await options
|
|
4314
|
-
...options.environment,
|
|
4315
|
-
metadata: retainedEnvironmentMetadata(options.environment.metadata, options.environment.idempotencyKey)
|
|
4316
|
-
});
|
|
4384
|
+
} else environment = await createRetainedEnvironment(options, attachmentSignal);
|
|
4317
4385
|
let capabilities;
|
|
4318
4386
|
let teardownFailure = "retained run environment published invalid capabilities and could not be destroyed";
|
|
4319
4387
|
try {
|
|
@@ -4349,15 +4417,7 @@ async function startRetainedRun(options) {
|
|
|
4349
4417
|
preserveEnvironment: options.preserveEnvironment === true || transferReceipt !== void 0 || options.existingEnvironmentId !== void 0
|
|
4350
4418
|
});
|
|
4351
4419
|
}
|
|
4352
|
-
/**
|
|
4353
|
-
* Dispatch a fresh retained session inside an existing provider environment.
|
|
4354
|
-
*
|
|
4355
|
-
* This operation reuses only the environment. It does not append to a prior
|
|
4356
|
-
* harness chat and does not claim native conversation continuity. The caller
|
|
4357
|
-
* must use `RetainedRunHandle.continueNative` for a verified same-chat turn.
|
|
4358
|
-
*
|
|
4359
|
-
* @stable
|
|
4360
|
-
*/
|
|
4420
|
+
/** Fresh session in an existing environment. A same-chat turn uses `continueNative`. @stable */
|
|
4361
4421
|
async function startRetainedRunInEnvironment(options) {
|
|
4362
4422
|
if (options.turn.contextTransfer !== void 0) throw new Error("accepted context transfer requires startRetainedRun with its exact profile and receipt");
|
|
4363
4423
|
assertStableText(options.environment.id, "retained environment id");
|
|
@@ -4398,16 +4458,7 @@ async function startRetainedRunInEnvironment(options) {
|
|
|
4398
4458
|
preserveEnvironment: true
|
|
4399
4459
|
});
|
|
4400
4460
|
}
|
|
4401
|
-
/**
|
|
4402
|
-
* Bind an existing environment to the retained key it was created under, from the metadata the
|
|
4403
|
-
* provider's own `get` or `create` record carries. One read, no listing.
|
|
4404
|
-
*
|
|
4405
|
-
* A metadata-filtered `list` used to stand in when that record had none. The only provider with
|
|
4406
|
-
* detached session control and a `list` (Tangle Sandbox) filters client-side, so the fallback
|
|
4407
|
-
* walked the caller's whole inventory: on Braid's ~68.5k-sandbox account, ~55 pages at ~1.4 s
|
|
4408
|
-
* each before admission (2026-10-28), and past 2,000 sandboxes the route answered 400
|
|
4409
|
-
* (2026-10-04). A record without metadata cannot prove ownership, so it is refused at once.
|
|
4410
|
-
*/
|
|
4461
|
+
/** Bind an environment to the retained key on its own get/create record. Missing metadata is a refusal; do not list the account. */
|
|
4411
4462
|
async function assertRetainedEnvironmentOwnership(provider, environment, idempotencyKey) {
|
|
4412
4463
|
if (environment.metadata === void 0) throw new Error(`provider "${provider.name}" returned environment "${environment.id}" without metadata, so it cannot prove retained environment ownership`);
|
|
4413
4464
|
if (environment.metadata.retainedIdempotencyKey !== idempotencyKey) throw new Error(`provider "${provider.name}" could not bind environment "${environment.id}" to its retained idempotency key`);
|
|
@@ -4482,12 +4533,7 @@ async function dispatchRetainedRun(options) {
|
|
|
4482
4533
|
});
|
|
4483
4534
|
return createRetainedRunHandle(environment, exact.session, exact.controlRef, options.capabilities, options.now);
|
|
4484
4535
|
}
|
|
4485
|
-
/**
|
|
4486
|
-
* Await one admission record through the caller's durability hook. A rejection
|
|
4487
|
-
* keeps the environment alive — the provider state plus any partial admission
|
|
4488
|
-
* record is the recovery path — and surfaces as `RetainedRunAdmissionError` so
|
|
4489
|
-
* callers can distinguish a persistence failure from a provider failure.
|
|
4490
|
-
*/
|
|
4536
|
+
/** Await the durability hook. A rejection keeps the environment and throws the admission error, not a provider error. */
|
|
4491
4537
|
async function admitDurably(onAdmission, admission) {
|
|
4492
4538
|
const stableAdmission = detachedSnapshot(admission, "retained run admission");
|
|
4493
4539
|
try {
|
|
@@ -4497,11 +4543,7 @@ async function admitDurably(onAdmission, admission) {
|
|
|
4497
4543
|
throw new RetainedRunAdmissionError(stableAdmission, { cause: error });
|
|
4498
4544
|
}
|
|
4499
4545
|
}
|
|
4500
|
-
/**
|
|
4501
|
-
* Mirror each admission into `<journalRoot>/<runId>/observer.jsonl`, the observer format
|
|
4502
|
-
* agent-runtime-top reads. The caller's hook stays authoritative; an observer append that
|
|
4503
|
-
* fails never fails the run, matching `FileObserverJournal`'s contract.
|
|
4504
|
-
*/
|
|
4546
|
+
/** Mirror admissions into the observer journal. The caller's hook stays authoritative; an observer failure does not fail the run. */
|
|
4505
4547
|
function journaledAdmissions(journalRoot, replayRunId, onAdmission) {
|
|
4506
4548
|
if (journalRoot === void 0) return onAdmission;
|
|
4507
4549
|
let runId = replayRunId;
|
|
@@ -4515,12 +4557,7 @@ function journaledAdmissions(journalRoot, replayRunId, onAdmission) {
|
|
|
4515
4557
|
});
|
|
4516
4558
|
};
|
|
4517
4559
|
}
|
|
4518
|
-
/**
|
|
4519
|
-
* The interactive handle with its accepted turns and its end journaled beside its admissions, so a
|
|
4520
|
-
* viewer sees the run take turns and settle rather than run forever. A turn records its operation id,
|
|
4521
|
-
* never the prompt, so handles in different processes agree. The end is the first exited status
|
|
4522
|
-
* read or accepted stop.
|
|
4523
|
-
*/
|
|
4560
|
+
/** Journal accepted turns and the end beside admissions. A turn records its operation id, never the prompt. */
|
|
4524
4561
|
function journaledInteractiveHandle(handle, journalRoot, runId) {
|
|
4525
4562
|
if (journalRoot === void 0) return handle;
|
|
4526
4563
|
const settle = async (failed, detail) => {
|
|
@@ -4724,13 +4761,7 @@ async function assertRetainedCapabilities(provider) {
|
|
|
4724
4761
|
assertRetainedCapabilityRequirements(provider.name, capabilities);
|
|
4725
4762
|
return capabilities;
|
|
4726
4763
|
}
|
|
4727
|
-
/**
|
|
4728
|
-
* Select the capability document for one concrete environment.
|
|
4729
|
-
*
|
|
4730
|
-
* A measured environment document is authoritative. An absent document uses
|
|
4731
|
-
* the provider document because that provider claims the same guarantee for
|
|
4732
|
-
* every environment it creates.
|
|
4733
|
-
*/
|
|
4764
|
+
/** A measured environment capability document wins; an absent one uses the provider document. */
|
|
4734
4765
|
function retainedCapabilitiesForEnvironment(providerName, providerCapabilities, environment) {
|
|
4735
4766
|
if (environment.capabilities === void 0) return providerCapabilities;
|
|
4736
4767
|
const measured = AgentEnvironmentCapabilitiesSchema.parse(environment.capabilities);
|
|
@@ -4745,10 +4776,7 @@ function assertRetainedCapabilityRequirements(providerName, capabilities) {
|
|
|
4745
4776
|
const WORKSPACE_CHECKPOINT_MARKER_PATH = ".agent-runtime-checkpoint";
|
|
4746
4777
|
/** Bound on one checkpoint: environment lookup, marker write, and the provider operation. */
|
|
4747
4778
|
const WORKSPACE_CHECKPOINT_TIMEOUT_MS = 12e4;
|
|
4748
|
-
/** Bound on the captures one checkpoint rotation runs
|
|
4749
|
-
* A turn's own workspace capture also cuts them short when it starts. A capture cut short keeps
|
|
4750
|
-
* its checkpoint, and the owner's release, bounded by `RELEASE_CHECKPOINT_CAPTURES_MS`, captures
|
|
4751
|
-
* it again. */
|
|
4779
|
+
/** Bound on the captures one checkpoint rotation runs. A turn capture cuts them short; the release captures again. */
|
|
4752
4780
|
const WORKSPACE_CHECKPOINT_ROTATION_CAPTURES_MS = 12e4;
|
|
4753
4781
|
/** Write the marker. False when the environment cannot take it, so no restore can be verified. */
|
|
4754
4782
|
async function writeWorkspaceMarker(environment, marker, signal) {
|
|
@@ -5558,19 +5586,23 @@ async function checkpointHandle(state, sourceEnvironmentId, signal) {
|
|
|
5558
5586
|
}
|
|
5559
5587
|
/**
|
|
5560
5588
|
* An aborted observation cannot prove that the provider never created a snapshot, so a request
|
|
5561
|
-
* whose lookup does not find it keeps its source
|
|
5562
|
-
*
|
|
5563
|
-
* never created it. No operation that request started can still be in flight, and keeping the
|
|
5564
|
-
* source then holds a sandbox for nothing: on Discovery Lab runs terraform-economics-20261005e and
|
|
5565
|
-
* f, each root was kept after its run settled over requests made hours earlier.
|
|
5589
|
+
* whose lookup does not find it keeps its source. A `not_found` older than
|
|
5590
|
+
* {@link WORKSPACE_CHECKPOINT_ABSENT_AFTER_MS} never created one. A parent abort starts no lookup.
|
|
5566
5591
|
*/
|
|
5567
|
-
async function reconcileCheckpointRequests(state, events) {
|
|
5592
|
+
async function reconcileCheckpointRequests(state, events, parent) {
|
|
5568
5593
|
const operationKey = (operation) => JSON.stringify([operation.idempotencyKey, operation.requestDigest]);
|
|
5569
5594
|
const resolved = new Set(events.flatMap((event) => event.kind === "workspace-checkpoint" && event.id === state.args.nodeId && event.provider === state.provider?.name ? [operationKey(event.checkpoint)] : []));
|
|
5570
5595
|
const pending = events.filter((event) => event.kind === "workspace-checkpoint-requested" && event.id === state.args.nodeId && event.provider === state.provider?.name && !resolved.has(operationKey(event.request)));
|
|
5571
5596
|
const unconfirmed = [];
|
|
5572
5597
|
for (const event of pending) {
|
|
5573
|
-
|
|
5598
|
+
if (parent?.aborted) {
|
|
5599
|
+
unconfirmed.push({
|
|
5600
|
+
event,
|
|
5601
|
+
reason: "cancelled before checkpoint lookup"
|
|
5602
|
+
});
|
|
5603
|
+
continue;
|
|
5604
|
+
}
|
|
5605
|
+
const signal = deadlineSignal(parent, 3e4);
|
|
5574
5606
|
try {
|
|
5575
5607
|
const branching = await checkpointHandle(state, event.environmentId, signal);
|
|
5576
5608
|
if (branching === void 0) throw new Error("source-scoped checkpoint handle unavailable");
|
|
@@ -5627,9 +5659,8 @@ function pendingCheckpoints(events, state) {
|
|
|
5627
5659
|
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])));
|
|
5628
5660
|
}
|
|
5629
5661
|
// src/runtime/supervise/retained-owner-checkpoint-capture.ts
|
|
5630
|
-
/** Delete
|
|
5631
|
-
|
|
5632
|
-
async function cleanCheckpoints(state, branching, checkpoints, events, _parent, uncapturable = false) {
|
|
5662
|
+
/** Delete captured checkpoints. An aborted parent starts no further provider call. */
|
|
5663
|
+
async function cleanCheckpoints(state, branching, checkpoints, events, parent, uncapturable = false) {
|
|
5633
5664
|
const unconfirmed = [];
|
|
5634
5665
|
const refused = [];
|
|
5635
5666
|
for (const event of checkpoints) {
|
|
@@ -5663,13 +5694,23 @@ async function cleanCheckpoints(state, branching, checkpoints, events, _parent,
|
|
|
5663
5694
|
requestDigest: workspaceCleanupRequestDigest(material)
|
|
5664
5695
|
};
|
|
5665
5696
|
let confirmed = false;
|
|
5697
|
+
let reason;
|
|
5698
|
+
const signal = deadlineSignal(parent, 3e4);
|
|
5666
5699
|
try {
|
|
5667
|
-
if (
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5700
|
+
if (parent?.aborted) reason = "cancelled before checkpoint cleanup";
|
|
5701
|
+
else if (branching === void 0) throw new Error("source-scoped checkpoint handle unavailable");
|
|
5702
|
+
else {
|
|
5703
|
+
const acknowledgement = await runAbortable(() => branching.deleteCheckpoint(request, { signal }), signal, "retained owner checkpoint cleanup timed out");
|
|
5704
|
+
confirmed = workspaceCleanupAcknowledgementMatches(request, acknowledgement) && (acknowledgement.status === "deleted" || acknowledgement.status === "already_absent");
|
|
5705
|
+
if (!confirmed) reason = `provider acknowledged '${acknowledgement.status}'`;
|
|
5706
|
+
}
|
|
5707
|
+
} catch (error) {
|
|
5708
|
+
reason = errMessage(error).slice(0, 1e3);
|
|
5709
|
+
}
|
|
5710
|
+
if (!confirmed) unconfirmed.push({
|
|
5711
|
+
event,
|
|
5712
|
+
reason: reason ?? "delete was not confirmed"
|
|
5713
|
+
});
|
|
5673
5714
|
await state.args.journal.appendEvent(state.args.rootId, {
|
|
5674
5715
|
kind: "workspace-checkpoint-cleanup",
|
|
5675
5716
|
id: state.args.nodeId,
|
|
@@ -5677,6 +5718,7 @@ async function cleanCheckpoints(state, branching, checkpoints, events, _parent,
|
|
|
5677
5718
|
environmentId: event.environmentId,
|
|
5678
5719
|
checkpointId: event.checkpoint.checkpointId,
|
|
5679
5720
|
confirmed,
|
|
5721
|
+
...reason === void 0 ? {} : { refused: reason.slice(0, 1e3) },
|
|
5680
5722
|
...captured.digest === void 0 ? {} : { capturedDigest: captured.digest },
|
|
5681
5723
|
seq: state.nextSequence(),
|
|
5682
5724
|
at: new Date(state.args.now()).toISOString()
|
|
@@ -5701,7 +5743,7 @@ function captureBeforeCleanup(state, event, events, uncapturable = false) {
|
|
|
5701
5743
|
return uncapturable ? {} : refuse("client checkpoint capture writers are retired; no persisted capture holds this checkpoint", false);
|
|
5702
5744
|
}
|
|
5703
5745
|
/** Destroy a capture's fork and journal whether the provider confirmed it. */
|
|
5704
|
-
async function destroyCaptureFork(state, branching, target) {
|
|
5746
|
+
async function destroyCaptureFork(state, branching, target, parent) {
|
|
5705
5747
|
const material = {
|
|
5706
5748
|
kind: "fork",
|
|
5707
5749
|
targetId: target.fork.environmentId,
|
|
@@ -5714,7 +5756,7 @@ async function destroyCaptureFork(state, branching, target) {
|
|
|
5714
5756
|
};
|
|
5715
5757
|
let destroyed = false;
|
|
5716
5758
|
try {
|
|
5717
|
-
const signal =
|
|
5759
|
+
const signal = deadlineSignal(parent, 3e4);
|
|
5718
5760
|
const acknowledgement = await runAbortable(() => branching.destroyFork(request, { signal }), signal, "checkpoint capture fork cleanup timed out");
|
|
5719
5761
|
destroyed = workspaceCleanupAcknowledgementMatches(request, acknowledgement) && (acknowledgement.status === "deleted" || acknowledgement.status === "already_absent");
|
|
5720
5762
|
} catch {}
|
|
@@ -5728,12 +5770,8 @@ async function destroyCaptureFork(state, branching, target) {
|
|
|
5728
5770
|
});
|
|
5729
5771
|
return destroyed;
|
|
5730
5772
|
}
|
|
5731
|
-
/**
|
|
5732
|
-
|
|
5733
|
-
* request and its teardown, or whose teardown the provider did not confirm. Returns the forks
|
|
5734
|
-
* still not confirmed destroyed, by environment or, when none was ever named, by request key.
|
|
5735
|
-
*/
|
|
5736
|
-
async function settleCaptureForks(state) {
|
|
5773
|
+
/** Destroy capture forks the journal does not show destroyed. An aborted parent starts no further fork call. Returns forks still unconfirmed. */
|
|
5774
|
+
async function settleCaptureForks(state, parent) {
|
|
5737
5775
|
const { args } = state;
|
|
5738
5776
|
const providerName = state.provider?.name;
|
|
5739
5777
|
const events = (await args.journal.loadTree(args.rootId) ?? []).filter((event) => event.id === args.nodeId);
|
|
@@ -5745,7 +5783,11 @@ async function settleCaptureForks(state) {
|
|
|
5745
5783
|
for (const request of events) {
|
|
5746
5784
|
if (request.kind !== "workspace-checkpoint-fork-requested" || request.provider !== providerName) continue;
|
|
5747
5785
|
if (destroyed.has(request.idempotencyKey)) continue;
|
|
5748
|
-
|
|
5786
|
+
if (parent?.aborted) {
|
|
5787
|
+
unresolved.push(named(request.idempotencyKey) ?? request.idempotencyKey);
|
|
5788
|
+
continue;
|
|
5789
|
+
}
|
|
5790
|
+
const signal = deadlineSignal(parent, 3e4);
|
|
5749
5791
|
const branching = await checkpointHandle(state, request.environmentId, signal);
|
|
5750
5792
|
let forkEnvironmentId = named(request.idempotencyKey);
|
|
5751
5793
|
if (forkEnvironmentId === void 0 && branching !== void 0) try {
|
|
@@ -5764,7 +5806,7 @@ async function settleCaptureForks(state) {
|
|
|
5764
5806
|
idempotencyKey: request.idempotencyKey,
|
|
5765
5807
|
environmentId: forkEnvironmentId
|
|
5766
5808
|
}
|
|
5767
|
-
})) unresolved.push(forkEnvironmentId ?? request.idempotencyKey);
|
|
5809
|
+
}, parent)) unresolved.push(forkEnvironmentId ?? request.idempotencyKey);
|
|
5768
5810
|
}
|
|
5769
5811
|
return unresolved;
|
|
5770
5812
|
}
|
|
@@ -5838,28 +5880,13 @@ function bindScopeRetainedOwnerProvider(scope, provider) {
|
|
|
5838
5880
|
const state = owners.get(scope);
|
|
5839
5881
|
if (state) state.provider = provider;
|
|
5840
5882
|
}
|
|
5841
|
-
/**
|
|
5842
|
-
* Bind where the owner's CURRENT environment id can be read from. The drive harness supplies a
|
|
5843
|
-
* resolver over its active executor's materialization receipt, which every provider path
|
|
5844
|
-
* publishes once the environment exists. The `environment` admission is written only on the
|
|
5845
|
-
* retained path, and the production tangle provider declares no `retainedControl`, so a reader
|
|
5846
|
-
* that scanned admissions alone would refuse every read on a real sandbox root while looking
|
|
5847
|
-
* correct in every test that uses the retained fixture.
|
|
5848
|
-
*/
|
|
5883
|
+
/** Bind the resolver for the owner's current environment id. Admissions alone miss a provider that never writes an environment admission. */
|
|
5849
5884
|
function bindScopeRetainedOwnerEnvironmentId(scope, liveEnvironmentId) {
|
|
5850
5885
|
const state = owners.get(scope);
|
|
5851
5886
|
if (state) state.liveEnvironmentId = liveEnvironmentId;
|
|
5852
5887
|
}
|
|
5853
5888
|
// src/runtime/supervise/retained-owner-checkpoint-rotation.ts
|
|
5854
|
-
/**
|
|
5855
|
-
* Note that the owner's manager made a coordination call, and checkpoint its workspace when the
|
|
5856
|
-
* last checkpoint is older than {@link WORKSPACE_CHECKPOINT_MIN_INTERVAL_MS}.
|
|
5857
|
-
*
|
|
5858
|
-
* A coordination call is where a manager commits work: it spawns, waits for, reads, or submits.
|
|
5859
|
-
* The checkpoint runs in the background and never delays the call. A failed checkpoint leaves the
|
|
5860
|
-
* previous one as the restore point; a provider or environment that cannot checkpoint stops the
|
|
5861
|
-
* attempts for this owner.
|
|
5862
|
-
*/
|
|
5889
|
+
/** Checkpoint after a coordination call once the last one is older than {@link WORKSPACE_CHECKPOINT_MIN_INTERVAL_MS}. It never delays the call, and a failure leaves the previous restore point. */
|
|
5863
5890
|
function noteScopeRetainedOwnerCoordination(scope) {
|
|
5864
5891
|
const state = owners.get(scope);
|
|
5865
5892
|
if (state?.provider === void 0 || state.checkpointsUnavailable !== void 0) return;
|
|
@@ -5976,12 +6003,7 @@ async function requestCheckpoint(scope, state, providerName, environment, branch
|
|
|
5976
6003
|
});
|
|
5977
6004
|
return true;
|
|
5978
6005
|
}
|
|
5979
|
-
/**
|
|
5980
|
-
* Delete this environment's oldest checkpoint beyond {@link WORKSPACE_CHECKPOINTS_KEPT} when no
|
|
5981
|
-
* capture could ever hold its files, to make room for a newer one. Keeping them all stopped the
|
|
5982
|
-
* restore point instead: zro-game-20261104b's box stored 25 of its 61 requests, its plan's limit,
|
|
5983
|
-
* and its last restore point was 7 h old. A backend that captures deletes through its rotation.
|
|
5984
|
-
*/
|
|
6006
|
+
/** Delete the oldest checkpoint past {@link WORKSPACE_CHECKPOINTS_KEPT} when no capture can hold it, so a snapshot limit cannot freeze the restore point. */
|
|
5985
6007
|
async function deleteOldestUncapturable(state, branching, environmentId, signal) {
|
|
5986
6008
|
const events = await state.args.journal.loadTree(state.args.rootId) ?? [];
|
|
5987
6009
|
const oldest = pendingCheckpoints(events, state).filter((event) => event.environmentId === environmentId).slice(0, -2).slice(0, 1);
|
|
@@ -5989,13 +6011,6 @@ async function deleteOldestUncapturable(state, branching, environmentId, signal)
|
|
|
5989
6011
|
const { unconfirmed } = await cleanCheckpoints(state, branching, oldest, events, signal, true);
|
|
5990
6012
|
return unconfirmed.length === 0;
|
|
5991
6013
|
}
|
|
5992
|
-
/**
|
|
5993
|
-
* The owner's workspace-capture journal writers. A turn's capture starting cuts short the
|
|
5994
|
-
* checkpoint rotation's captures: on Discovery canary test-acceptance-canary-20261019j (Runtime
|
|
5995
|
-
* 0.327.0) a rotation capture of an older checkpoint started 4 s before the root's final capture,
|
|
5996
|
-
* on the same evidence store, and the final capture spent 26.6 of its 32.6 minutes storing its
|
|
5997
|
-
* archive until the root's budget deadline aborted the rotation.
|
|
5998
|
-
*/
|
|
5999
6014
|
// src/runtime/supervise/retained-owner-evidence.ts
|
|
6000
6015
|
/** Retained-owner capture receipts, source verification before reuse, and accepted-turn evidence debt. */
|
|
6001
6016
|
/** One grouping for release and the evidence gate before another owner invocation. */
|
|
@@ -6038,28 +6053,22 @@ function ownerWorkspaceAttempts(events, nodeId) {
|
|
|
6038
6053
|
flush();
|
|
6039
6054
|
return attempts;
|
|
6040
6055
|
}
|
|
6041
|
-
/**
|
|
6042
|
-
|
|
6043
|
-
*
|
|
6044
|
-
* The receipt is the attempt's committed result, or an evidence record of the same attempt: a
|
|
6045
|
-
* turn its manager stopped commits its result first and journals its capture as evidence after
|
|
6046
|
-
* it, and a turn that failed journals the capture it took of the stopped session. A root capture
|
|
6047
|
-
* failure can enter this cleanup barrier and still be resumed, so any receipt refused before
|
|
6048
|
-
* reuse must also preserve its source here.
|
|
6049
|
-
*/
|
|
6056
|
+
/** Group owner admissions by input. A source with no verified workspace receipt is preserved, including one a later resume can still use. */
|
|
6057
|
+
const UNVERIFIED_SOURCE = "provider workspace retention: source preserved because the owner execution has no verified workspace receipt";
|
|
6050
6058
|
async function ownerWorkspaceRetentionFailures(events, owner) {
|
|
6051
6059
|
const attempts = ownerWorkspaceAttempts(events, owner.nodeId);
|
|
6052
|
-
const failures = /* @__PURE__ */ new
|
|
6060
|
+
const failures = /* @__PURE__ */ new Map();
|
|
6053
6061
|
for (const index of attempts.keys()) {
|
|
6054
6062
|
const attempt = withLaterEvidence(attempts, index);
|
|
6055
6063
|
if (attempt.workspaceRetention !== true) continue;
|
|
6056
6064
|
if (attempt.environmentIds.size === 0) continue;
|
|
6057
6065
|
if (attempt.environmentIds.size > 1) {
|
|
6058
|
-
for (const environmentId of attempt.environmentIds) failures.
|
|
6066
|
+
for (const environmentId of attempt.environmentIds) failures.set(environmentId, UNVERIFIED_SOURCE);
|
|
6059
6067
|
continue;
|
|
6060
6068
|
}
|
|
6061
6069
|
const environmentId = [...attempt.environmentIds][0];
|
|
6062
|
-
|
|
6070
|
+
const reason = await ownerAttemptHasCapture(attempt, environmentId, owner.blobs, owner);
|
|
6071
|
+
if (reason !== void 0) failures.set(environmentId, reason);
|
|
6063
6072
|
}
|
|
6064
6073
|
return failures;
|
|
6065
6074
|
}
|
|
@@ -6076,18 +6085,33 @@ function withLaterEvidence(attempts, index) {
|
|
|
6076
6085
|
evidence: [...attempt.evidence, ...later]
|
|
6077
6086
|
};
|
|
6078
6087
|
}
|
|
6088
|
+
/** Undefined when a receipt authorizes destroying the source. Otherwise why it stays. */
|
|
6079
6089
|
async function ownerAttemptHasCapture(attempt, environmentId, blobs, owner, allowPartialProcessCapture = false) {
|
|
6080
6090
|
const controlRef = attempt.controlByEnvironment.get(environmentId);
|
|
6081
6091
|
const receipts = [...attempt.result === void 0 || attempt.result.outputFormat === "json" ? [] : [attempt.result.outRef], ...attempt.evidence.map((event) => event.outRef)];
|
|
6092
|
+
const identity = owner === void 0 ? void 0 : ownerCaptureIdentity(attempt, owner);
|
|
6082
6093
|
let verified = false;
|
|
6094
|
+
let nativeHold;
|
|
6083
6095
|
for (const outRef of receipts) {
|
|
6084
6096
|
const output = await storedOutput(blobs, outRef);
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6097
|
+
const snapshot = output !== null && typeof output === "object" ? output.workspaceSnapshot : void 0;
|
|
6098
|
+
const capture = output !== null && typeof output === "object" ? output.workspaceCapture : void 0;
|
|
6099
|
+
const receipt = capture !== null && typeof capture === "object" ? capture : void 0;
|
|
6100
|
+
const matches = receipt !== void 0 && receipt.environmentId === environmentId && receipt.executionId === attempt.executionId;
|
|
6101
|
+
const durable = hasDurableWorkspaceCapture(output, snapshot, environmentId, attempt.executionId, controlRef, identity, allowPartialProcessCapture);
|
|
6102
|
+
if (matches && receipt !== void 0) {
|
|
6103
|
+
const hold = nativeSessionCopyHoldReason(receipt);
|
|
6104
|
+
if (hold !== void 0) {
|
|
6105
|
+
nativeHold = hold;
|
|
6106
|
+
verified = false;
|
|
6107
|
+
} else if (durable) {
|
|
6108
|
+
nativeHold = void 0;
|
|
6109
|
+
verified = true;
|
|
6110
|
+
}
|
|
6111
|
+
} else if (nativeHold === void 0 && durable) verified = true;
|
|
6089
6112
|
}
|
|
6090
|
-
return
|
|
6113
|
+
if (nativeHold !== void 0) return nativeHold;
|
|
6114
|
+
return verified ? void 0 : UNVERIFIED_SOURCE;
|
|
6091
6115
|
}
|
|
6092
6116
|
function ownerCaptureIdentity(attempt, owner) {
|
|
6093
6117
|
const intent = attempt.admissions.find((admission) => admission.phase === "intent");
|
|
@@ -6118,7 +6142,7 @@ async function reconcileOwnerWorkspaceBeforeReuse(state, signal) {
|
|
|
6118
6142
|
if (attempt.workspaceRetention !== true || attempt.result === void 0) continue;
|
|
6119
6143
|
for (const environmentId of attempt.environmentIds) {
|
|
6120
6144
|
if (!reusing.has(environmentId) || unusable.has(environmentId)) continue;
|
|
6121
|
-
if (await ownerAttemptHasCapture(attempt, environmentId, args.blobs, args, true)) continue;
|
|
6145
|
+
if (await ownerAttemptHasCapture(attempt, environmentId, args.blobs, args, true) === void 0) continue;
|
|
6122
6146
|
signal.throwIfAborted();
|
|
6123
6147
|
const failure = capturePendingMessage(attempt.executionId ?? args.nodeId, /* @__PURE__ */ new Error("client capture writers are retired; a persisted verified receipt is required"));
|
|
6124
6148
|
if (executorFailureReason(attempt.result) === void 0) await owePendingEvidence(state, events, attempt, environmentId, failure);
|
|
@@ -6177,38 +6201,73 @@ function hasDurableWorkspaceCapture(output, snapshot, environmentId, executionId
|
|
|
6177
6201
|
const receipt = capture;
|
|
6178
6202
|
if (identity !== void 0 && (identity.profileDigest === void 0 || receipt.profileDigest !== identity.profileDigest || (identity.node === void 0 ? receipt.node !== void 0 : receipt.node === void 0 || contentAddress(receipt.node) !== contentAddress(identity.node)))) return false;
|
|
6179
6203
|
const capturedControl = AgentExactRunControlRefSchema.safeParse(receipt.controlRef);
|
|
6180
|
-
if (executionId === void 0 || controlRef === void 0 || !capturedControl.success || !sameControlCoordinates(capturedControl.data, controlRef) || receipt.environmentId !== environmentId || receipt.executionId !== executionId || receipt
|
|
6204
|
+
if (executionId === void 0 || controlRef === void 0 || !capturedControl.success || !sameControlCoordinates(capturedControl.data, controlRef) || receipt.environmentId !== environmentId || receipt.executionId !== executionId || !captureAuthorizesDestroy(receipt, allowPartialProcessCapture)) return false;
|
|
6181
6205
|
const linked = agentCandidateWorkspaceSnapshotEvidenceSchema.safeParse(receipt.snapshot);
|
|
6182
6206
|
return linked.success && linked.data.digest === parsed.data.digest && JSON.stringify(linked.data.manifest) === JSON.stringify(parsed.data.manifest) && JSON.stringify(linked.data.archive) === JSON.stringify(parsed.data.archive);
|
|
6183
6207
|
}
|
|
6208
|
+
const NATIVE_STORE_GAP = /(?:^|\s)((?:over_bound|store_file_):[^\s;]+)/g;
|
|
6209
|
+
const COVERAGE_GAP_PREFIX = "provider workspace retention coverage incomplete: ";
|
|
6210
|
+
const PROCESS_GAP = [
|
|
6211
|
+
/^process_(?:io_missing|io_unavailable|source_unavailable)(?::[\w-]+)?$/,
|
|
6212
|
+
/^Sandbox session [\w-]+ declared partial raw capture$/,
|
|
6213
|
+
/^Sandbox session [\w-]+ attempt [\w-]+\/\d+: process_(?:io_missing|io_unavailable|source_unavailable)(?::[\w-]+)?$/,
|
|
6214
|
+
/^Sandbox session [\w-]+ has no original process source: [\w-]+$/,
|
|
6215
|
+
/^Sandbox session [\w-]+: process_(?:io_missing|io_unavailable|source_unavailable)(?::[\w-]+)?$/,
|
|
6216
|
+
/^Raw native session capture for Sandbox session [\w-]+ is unavailable: process_(?:io_missing|io_unavailable|source_unavailable)(?::[\w-]+)?$/,
|
|
6217
|
+
/^Session [\w-]+ process streams incomplete$/,
|
|
6218
|
+
/^Attempt [\w-]+\/\d+: process_(?:io_missing|io_unavailable|source_unavailable)(?::[\w-]+)?$/
|
|
6219
|
+
];
|
|
6220
|
+
const EVENT_GAP = /(?:^|:\s)events_incomplete(?::[\w.-]+)?$/;
|
|
6221
|
+
/** Keep the box when the final native copy is incomplete. Process I/O or event-log gaps do not. */
|
|
6222
|
+
function nativeSessionCopyHoldReason(receipt) {
|
|
6223
|
+
const provenance = receipt.provenance;
|
|
6224
|
+
if (provenance === null || typeof provenance !== "object") return void 0;
|
|
6225
|
+
const reported = provenance;
|
|
6226
|
+
const sessions = Array.isArray(reported.sessions) ? reported.sessions : [];
|
|
6227
|
+
let incomplete = false;
|
|
6228
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
6229
|
+
const take = (value) => {
|
|
6230
|
+
const text = Array.isArray(value) ? value.filter((item) => typeof item === "string").join("; ") : typeof value === "string" ? value : "";
|
|
6231
|
+
for (const match of text.matchAll(NATIVE_STORE_GAP)) tokens.add(match[1]);
|
|
6232
|
+
};
|
|
6233
|
+
for (const session of sessions) {
|
|
6234
|
+
if (session === null || typeof session !== "object") continue;
|
|
6235
|
+
const row = session;
|
|
6236
|
+
const store = row.nativeStore;
|
|
6237
|
+
if (store !== null && typeof store === "object" && store.complete === false) incomplete = true;
|
|
6238
|
+
take(row.nativeReason);
|
|
6239
|
+
if (store !== null && typeof store === "object") take(store.missingReasons);
|
|
6240
|
+
}
|
|
6241
|
+
take(reported.missing);
|
|
6242
|
+
if (!incomplete && tokens.size === 0) return void 0;
|
|
6243
|
+
return `native session copy incomplete: ${tokens.size > 0 ? [...tokens].join("; ") : "nativeStore incomplete"}`;
|
|
6244
|
+
}
|
|
6245
|
+
function captureAuthorizesDestroy(receipt, allowPartialProcessCapture) {
|
|
6246
|
+
if (nativeSessionCopyHoldReason(receipt) !== void 0) return false;
|
|
6247
|
+
if (receipt.coverageComplete === true) return true;
|
|
6248
|
+
return hasOnlyRawProcessCaptureGap(receipt, !allowPartialProcessCapture);
|
|
6249
|
+
}
|
|
6250
|
+
function captureGapTexts(receipt) {
|
|
6251
|
+
const reason = receipt.incompleteReason;
|
|
6252
|
+
const named = typeof reason === "string" && reason.startsWith(COVERAGE_GAP_PREFIX) ? reason.slice(50).split("; ") : [];
|
|
6253
|
+
const provenance = receipt.provenance;
|
|
6254
|
+
const missing = provenance !== null && typeof provenance === "object" && Array.isArray(provenance.missing) ? provenance.missing.filter((item) => typeof item === "string") : [];
|
|
6255
|
+
return [...named, ...missing].map((gap) => gap.trim()).filter((gap) => gap.length > 0);
|
|
6256
|
+
}
|
|
6184
6257
|
/**
|
|
6185
|
-
* A process I/O gap does not invalidate a verified workspace
|
|
6186
|
-
*
|
|
6187
|
-
* capture. The historical producer assembled this reason after identity, inventory,
|
|
6188
|
-
* and archive checks, so any other gap keeps the accepted turn pending.
|
|
6258
|
+
* A process I/O gap does not invalidate a verified workspace. `includeEvents` is the release
|
|
6259
|
+
* rule: an event-log gap alone also authorizes destroy. Any other gap keeps the source.
|
|
6189
6260
|
*/
|
|
6190
|
-
function hasOnlyRawProcessCaptureGap(receipt) {
|
|
6191
|
-
const reason = receipt.incompleteReason;
|
|
6192
|
-
if (typeof reason !== "string" || !reason.startsWith("provider workspace retention coverage incomplete: ")) return false;
|
|
6261
|
+
function hasOnlyRawProcessCaptureGap(receipt, includeEvents = false) {
|
|
6193
6262
|
const provenance = receipt.provenance;
|
|
6194
6263
|
if (provenance === null || typeof provenance !== "object") return false;
|
|
6195
6264
|
const reported = provenance;
|
|
6196
6265
|
const workspace = reported.workspace;
|
|
6197
6266
|
if (reported.status !== "reported" || workspace === null || typeof workspace !== "object" || workspace.complete !== true) return false;
|
|
6198
6267
|
const sessions = reported.sessions;
|
|
6199
|
-
if (!Array.isArray(sessions) || !sessions.some((session) => session !== null && typeof session === "object" && session.processStreams?.complete === false)) return false;
|
|
6200
|
-
const
|
|
6201
|
-
|
|
6202
|
-
/^Sandbox session [\w-]+ declared partial raw capture$/,
|
|
6203
|
-
/^Sandbox session [\w-]+ attempt [\w-]+\/\d+: process_(?:io_missing|io_unavailable|source_unavailable)(?::[\w-]+)?$/,
|
|
6204
|
-
/^Sandbox session [\w-]+ has no original process source: [\w-]+$/,
|
|
6205
|
-
/^Sandbox session [\w-]+: process_(?:io_missing|io_unavailable|source_unavailable)(?::[\w-]+)?$/,
|
|
6206
|
-
/^Raw native session capture for Sandbox session [\w-]+ is unavailable: process_(?:io_missing|io_unavailable|source_unavailable)(?::[\w-]+)?$/,
|
|
6207
|
-
/^Session [\w-]+ process streams incomplete$/,
|
|
6208
|
-
/^Attempt [\w-]+\/\d+: process_(?:io_missing|io_unavailable|source_unavailable)(?::[\w-]+)?$/
|
|
6209
|
-
];
|
|
6210
|
-
const gaps = reason.slice(50).split("; ");
|
|
6211
|
-
return gaps.length > 0 && gaps.every((gap) => allowed.some((pattern) => pattern.test(gap)));
|
|
6268
|
+
if (!Array.isArray(sessions) || !sessions.some((session) => session !== null && typeof session === "object" && (session.processStreams?.complete === false || includeEvents && session.nativeEvents?.complete === false))) return false;
|
|
6269
|
+
const gaps = captureGapTexts(receipt);
|
|
6270
|
+
return gaps.length > 0 && gaps.every((gap) => PROCESS_GAP.some((pattern) => pattern.test(gap)) || includeEvents && EVENT_GAP.test(gap));
|
|
6212
6271
|
}
|
|
6213
6272
|
/** Environments that cannot execute another turn, whether absent or terminal at the provider. */
|
|
6214
6273
|
function unusableEnvironmentIds(owned) {
|
|
@@ -6919,9 +6978,7 @@ function registerScopeRetainedOwner(scope, args) {
|
|
|
6919
6978
|
};
|
|
6920
6979
|
owners.set(scope, state);
|
|
6921
6980
|
}
|
|
6922
|
-
/**
|
|
6923
|
-
* committed invocation's, past abandoned ones. A live process keeps its session across an
|
|
6924
|
-
* abandonment, so a resumed one derives it the same way or its replay would not match. */
|
|
6981
|
+
/** Continue the latest committed environment, past abandoned ones. A live session stays. */
|
|
6925
6982
|
function committedSession(events, before) {
|
|
6926
6983
|
let end = before;
|
|
6927
6984
|
for (;;) {
|
|
@@ -6936,12 +6993,19 @@ function committedSession(events, before) {
|
|
|
6936
6993
|
end = start;
|
|
6937
6994
|
}
|
|
6938
6995
|
}
|
|
6939
|
-
/** Bounds the stop Runtime asks for before abandoning a dispatched invocation. */
|
|
6940
6996
|
const ABANDON_STOP_TIMEOUT_MS = 3e4;
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6997
|
+
async function releaseDeadRetainedExecution(state, events, scope) {
|
|
6998
|
+
if (state.provider === void 0) return;
|
|
6999
|
+
const dispatched = [...events].reverse().find((event) => event.kind === "execution-admitted" && event.admission.phase === "dispatched");
|
|
7000
|
+
if (dispatched?.kind !== "execution-admitted" || dispatched.admission.phase !== "dispatched") return;
|
|
7001
|
+
await stopRetainedNativeExecution({
|
|
7002
|
+
provider: state.provider,
|
|
7003
|
+
controlRef: dispatched.admission.controlRef,
|
|
7004
|
+
signal: AbortSignal.any([scope.signal, AbortSignal.timeout(ABANDON_STOP_TIMEOUT_MS)]),
|
|
7005
|
+
now: state.args.now
|
|
7006
|
+
});
|
|
7007
|
+
}
|
|
7008
|
+
/** Abandon only after a confirmed stop. Without one, journal nothing. */
|
|
6945
7009
|
async function abandonScopeRetainedOwnerInvocation(scope, failure, failures) {
|
|
6946
7010
|
const state = owners.get(scope);
|
|
6947
7011
|
if (state === void 0 || state.inputSequence === void 0) return false;
|
|
@@ -6979,9 +7043,7 @@ async function abandonScopeRetainedOwnerInvocation(scope, failure, failures) {
|
|
|
6979
7043
|
});
|
|
6980
7044
|
return true;
|
|
6981
7045
|
}
|
|
6982
|
-
/** Resume the original
|
|
6983
|
-
* `session: 'fresh'` makes every invocation after the first start a new harness session in the
|
|
6984
|
-
* environment it continues. */
|
|
7046
|
+
/** Resume the original prompt. A fresh session starts a new harness session in the environment. */
|
|
6985
7047
|
async function prepareScopeRetainedOwnerTask(scope, task, session = "native") {
|
|
6986
7048
|
const state = owners.get(scope);
|
|
6987
7049
|
if (!state) return task;
|
|
@@ -7044,6 +7106,10 @@ async function prepareScopeRetainedOwnerTask(scope, task, session = "native") {
|
|
|
7044
7106
|
...point.marker === void 0 ? {} : { marker: point.marker }
|
|
7045
7107
|
};
|
|
7046
7108
|
};
|
|
7109
|
+
if (state.replaceDeadExecution === true) {
|
|
7110
|
+
state.replaceDeadExecution = false;
|
|
7111
|
+
if (state.taskRef === void 0) await releaseDeadRetainedExecution(state, owned, scope);
|
|
7112
|
+
}
|
|
7047
7113
|
if (state.taskRef !== void 0) {
|
|
7048
7114
|
const original = await args.blobs.get(state.taskRef);
|
|
7049
7115
|
if (original === void 0 || contentAddress(original) !== state.taskRef) throw new ValidationError("retained owner task is missing or corrupt");
|
|
@@ -7108,22 +7174,11 @@ function consumeScopeRetainedOwnerResult(scope) {
|
|
|
7108
7174
|
function retainedOwnerReleaseRetriable(scope) {
|
|
7109
7175
|
return owners.get(scope)?.releaseRetriable ?? false;
|
|
7110
7176
|
}
|
|
7111
|
-
/**
|
|
7112
|
-
* How long one release spends capturing the owner's pending checkpoints, all of them together. A
|
|
7113
|
-
* capture it cuts short keeps its checkpoint and the source, as a failed capture does, and the
|
|
7114
|
-
* release goes on to settle. Each capture is otherwise bounded only by its port, and one at a
|
|
7115
|
-
* time: on Discovery run terraform-dc-model-20261010h-c1 (Runtime 0.314.0) a director's release
|
|
7116
|
-
* waited 00:19:18Z to 01:19:38Z for one capture to reach the port's 60-minute bound, and the run
|
|
7117
|
-
* settled 98 minutes after its deadline. The release captures that finished there took 67 seconds
|
|
7118
|
-
* and 7 and 34 minutes, beside live captures of abandoned directors on the same slots.
|
|
7119
|
-
*/
|
|
7177
|
+
/** One budget for every checkpoint lookup, delete and fork this release starts. A cut-short capture keeps its checkpoint and source. */
|
|
7120
7178
|
const RELEASE_CHECKPOINT_CAPTURES_MS = 9e5;
|
|
7121
7179
|
/**
|
|
7122
|
-
* Release the owner's environment once no turn of it will run again
|
|
7123
|
-
*
|
|
7124
|
-
* one is in flight joins it. The release first waits for the end-of-turn capture of the owner's
|
|
7125
|
-
* last turn, so the capture always precedes the destroy. `capturesMs` bounds the checkpoint
|
|
7126
|
-
* captures it runs ({@link RELEASE_CHECKPOINT_CAPTURES_MS}).
|
|
7180
|
+
* Release the owner's environment once no turn of it will run again. A second call joins the one
|
|
7181
|
+
* in flight. The end-of-turn capture finishes before destroy. `capturesMs` bounds checkpoint work.
|
|
7127
7182
|
*/
|
|
7128
7183
|
function releaseScopeRetainedOwnerEnvironment(scope, { capturesMs = RELEASE_CHECKPOINT_CAPTURES_MS } = {}) {
|
|
7129
7184
|
const state = owners.get(scope);
|
|
@@ -7146,8 +7201,10 @@ async function releaseOwnerEnvironment(state, provider, capturesMs) {
|
|
|
7146
7201
|
await state.checkpointing?.catch(() => void 0);
|
|
7147
7202
|
const capturesDeadline = Date.now() + capturesMs;
|
|
7148
7203
|
const events = [...await args.journal.loadTree(args.rootId) ?? []];
|
|
7149
|
-
const
|
|
7150
|
-
const
|
|
7204
|
+
const captures = new AbortController();
|
|
7205
|
+
const capturesTimer = setTimeout(() => captures.abort(/* @__PURE__ */ new Error(`the release's ${capturesMs} ms for checkpoint captures ran out; checkpoint and source kept`)), Math.max(0, capturesDeadline - Date.now()));
|
|
7206
|
+
let requestFailures = [];
|
|
7207
|
+
const pendingSources = /* @__PURE__ */ new Set();
|
|
7151
7208
|
const released = new Set(events.flatMap((event) => event.kind === "environment-teardown" && event.id === args.nodeId && event.provider === provider.name && event.destroyed ? [event.environmentId] : []));
|
|
7152
7209
|
const environments = new Set(events.flatMap((event) => event.id === args.nodeId && event.kind === "execution-admitted" && event.admission.phase === "environment" ? [event.admission.environmentId] : []));
|
|
7153
7210
|
const retentionFailures = await ownerWorkspaceRetentionFailures(events, args);
|
|
@@ -7158,10 +7215,10 @@ async function releaseOwnerEnvironment(state, provider, capturesMs) {
|
|
|
7158
7215
|
const checkpointFailures = [];
|
|
7159
7216
|
const checkpointsKept = [];
|
|
7160
7217
|
const uncapturedSources = /* @__PURE__ */ new Set();
|
|
7161
|
-
const pending = pendingCheckpoints(events, state).filter((event) => !retentionFailures.has(event.environmentId));
|
|
7162
|
-
const captures = new AbortController();
|
|
7163
|
-
const capturesTimer = setTimeout(() => captures.abort(/* @__PURE__ */ new Error(`the release's ${capturesMs} ms for checkpoint captures ran out; checkpoint and source kept`)), Math.max(0, capturesDeadline - Date.now()));
|
|
7164
7218
|
try {
|
|
7219
|
+
requestFailures = await reconcileCheckpointRequests(state, events, captures.signal);
|
|
7220
|
+
for (const { event } of requestFailures) pendingSources.add(event.environmentId);
|
|
7221
|
+
const pending = pendingCheckpoints(events, state).filter((event) => !retentionFailures.has(event.environmentId));
|
|
7165
7222
|
for (const sourceEnvironmentId of new Set(pending.map((event) => event.environmentId))) {
|
|
7166
7223
|
const cleaned = await cleanCheckpoints(state, await checkpointHandle(state, sourceEnvironmentId, AbortSignal.any([captures.signal, AbortSignal.timeout(3e4)])), pending.filter((event) => event.environmentId === sourceEnvironmentId), events, captures.signal);
|
|
7167
7224
|
checkpointFailures.push(...cleaned.unconfirmed);
|
|
@@ -7171,14 +7228,16 @@ async function releaseOwnerEnvironment(state, provider, capturesMs) {
|
|
|
7171
7228
|
} finally {
|
|
7172
7229
|
clearTimeout(capturesTimer);
|
|
7173
7230
|
}
|
|
7174
|
-
const forksUnresolved = await settleCaptureForks(state);
|
|
7175
|
-
for (const event of checkpointFailures) pendingSources.add(event.environmentId);
|
|
7231
|
+
const forksUnresolved = await settleCaptureForks(state, captures.signal);
|
|
7232
|
+
for (const { event } of checkpointFailures) pendingSources.add(event.environmentId);
|
|
7176
7233
|
for (const environmentId of environments) {
|
|
7177
7234
|
if (released.has(environmentId)) continue;
|
|
7178
7235
|
let destroyed = false;
|
|
7179
7236
|
let detail;
|
|
7180
|
-
|
|
7181
|
-
|
|
7237
|
+
const retentionReason = retentionFailures.get(environmentId);
|
|
7238
|
+
const nativeHold = retentionReason?.startsWith("native session copy incomplete:") === true;
|
|
7239
|
+
if (retentionReason !== void 0 || pendingSources.has(environmentId) || uncapturedSources.has(environmentId)) {
|
|
7240
|
+
detail = nativeHold ? retentionReason : 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" : retentionReason;
|
|
7182
7241
|
preserved.push(environmentId);
|
|
7183
7242
|
lastDetail = detail;
|
|
7184
7243
|
} else {
|
|
@@ -7235,12 +7294,12 @@ async function releaseOwnerEnvironment(state, provider, capturesMs) {
|
|
|
7235
7294
|
}
|
|
7236
7295
|
state.releaseRetriable = retriable;
|
|
7237
7296
|
const checkpointTeardowns = [
|
|
7238
|
-
...checkpointFailures.map((event) => ({
|
|
7297
|
+
...checkpointFailures.map(({ event, reason }) => ({
|
|
7239
7298
|
id: args.nodeId,
|
|
7240
7299
|
label: "scope owner workspace checkpoint",
|
|
7241
7300
|
runtime: provider.name,
|
|
7242
7301
|
status: "done",
|
|
7243
|
-
detail: `checkpoint cleanup unconfirmed: ${event.checkpoint.checkpointId} from ${event.environmentId}`
|
|
7302
|
+
detail: `checkpoint cleanup unconfirmed: ${event.checkpoint.checkpointId} from ${event.environmentId} (${reason})`
|
|
7244
7303
|
})),
|
|
7245
7304
|
...requestFailures.map(({ event, reason }) => ({
|
|
7246
7305
|
id: args.nodeId,
|
|
@@ -7303,46 +7362,14 @@ function unsettledOwnerRuns(events, nodeId, environmentId) {
|
|
|
7303
7362
|
}
|
|
7304
7363
|
// src/mcp/tools/spawn-resource-paths.ts
|
|
7305
7364
|
/**
|
|
7306
|
-
* Inline resources a manager hands a child by
|
|
7307
|
-
*
|
|
7308
|
-
*
|
|
7309
|
-
*
|
|
7310
|
-
*
|
|
7311
|
-
* not
|
|
7312
|
-
*
|
|
7313
|
-
*
|
|
7314
|
-
* manager meant to replace. Seven blind-check attempts across two runs delivered no data.
|
|
7315
|
-
*
|
|
7316
|
-
* `{ kind: 'inline', name, path }` lets the coordination server be the transport: it reads the
|
|
7317
|
-
* file from the manager's workspace, fills `content`, and the profile that reaches the schema,
|
|
7318
|
-
* the journal, and the provider is an ordinary inline resource. Nothing downstream changes; the
|
|
7319
|
-
* model's output stops carrying the bytes.
|
|
7320
|
-
*
|
|
7321
|
-
* Where the bytes come from is a {@link SpawnResourceReader}, and there are two:
|
|
7322
|
-
* - a HOST DIRECTORY, for a manager whose driver runs on this host (a loopback bridge with a
|
|
7323
|
-
* `cwd`), read through the descriptor-identity checks below;
|
|
7324
|
-
* - the manager's OWN EXECUTION ENVIRONMENT, for a manager inside a provider sandbox, read
|
|
7325
|
-
* through the environment's `read()`. The coordination server cannot see a sandbox filesystem,
|
|
7326
|
-
* but the provider that created the sandbox can, and it is already in hand.
|
|
7327
|
-
*
|
|
7328
|
-
* The second reader exists because the first one's absence was not a refusal a manager could
|
|
7329
|
-
* work around. Measured 2026-09-17 (discovery-lab `mech-interp-foundations-sandbox-a-20260917h`):
|
|
7330
|
-
* a sandbox-rooted director, refused by path, fell back to emitting its files as content and got
|
|
7331
|
-
* 5 characters wrong in 24,008, destroying 3 of 7 files including its instrument and both
|
|
7332
|
-
* drivers. At that rate 20 KB of mounts all arrive intact about 2% of the time, and gzip makes it
|
|
7333
|
-
* worse by turning one wrong character into total loss. Four consecutive runs in that lane lost
|
|
7334
|
-
* their research children to this. There is no encoding that fixes a transcription channel; the
|
|
7335
|
-
* only fix is a channel with no model in it.
|
|
7336
|
-
*
|
|
7337
|
-
* Fail-closed rules, each with the reason a manager needs to fix its call:
|
|
7338
|
-
* - no reader available → refused (neither a host directory nor an environment with `read`)
|
|
7339
|
-
* - absolute path or `..` escape → refused by both readers; a symlink that resolves outside the
|
|
7340
|
-
* root → refused by the host reader (the environment reader delegates containment to the
|
|
7341
|
-
* provider, which serves only the sandbox's own workspace)
|
|
7342
|
-
* - not a regular file → refused
|
|
7343
|
-
* - over {@link SPAWN_RESOURCE_PATH_MAX_BYTES} → refused naming the bound
|
|
7344
|
-
* - bytes that are not valid UTF-8 → refused; `content` is a string, so binary data is encoded
|
|
7345
|
-
* (base64) by the manager first, exactly as it would be for a content resource
|
|
7365
|
+
* Inline resources a manager hands a child by path. `{ kind: 'inline', name, path }` is read by the
|
|
7366
|
+
* coordination server into ordinary `content`, so the model does not transcribe the bytes.
|
|
7367
|
+
* {@link SpawnResourceReader} is either a host directory (descriptor-identity checks) or the
|
|
7368
|
+
* manager's own execution environment (`read()`), because the coordination server cannot see a
|
|
7369
|
+
* sandbox filesystem. Refuse when no reader exists, the path is absolute or escapes with `..`, a
|
|
7370
|
+
* host symlink resolves outside the root, the path is not a regular file, the file exceeds
|
|
7371
|
+
* {@link SPAWN_RESOURCE_PATH_MAX_BYTES}, or the bytes are not UTF-8. Binary content is base64,
|
|
7372
|
+
* encoded by the manager as it would be for a content resource.
|
|
7346
7373
|
*/
|
|
7347
7374
|
const SPAWN_RESOURCE_PATH_MAX_BYTES = 4194304;
|
|
7348
7375
|
/** The host-directory reader: a manager whose workspace is a directory this process can open. */
|
|
@@ -7354,9 +7381,14 @@ function hostDirectoryReader(root) {
|
|
|
7354
7381
|
}
|
|
7355
7382
|
/**
|
|
7356
7383
|
* The environment reader: a manager running inside a provider environment whose `read()` serves
|
|
7357
|
-
* its own workspace.
|
|
7358
|
-
*
|
|
7359
|
-
*
|
|
7384
|
+
* its own workspace. Containment of relative paths is the provider's property, not this
|
|
7385
|
+
* function's: the tangle sidecar enforces it in `resolveWorkspacePath`
|
|
7386
|
+
* (apps/sidecar/src/lib/workspace.ts:98-155, agent-dev-container) — relative paths stay inside
|
|
7387
|
+
* the sandbox workspace, absolute paths only through an allowlist, `.sidecar/` refused at any
|
|
7388
|
+
* depth because that tree holds the guest's orchestrator token. If that enforcement widened,
|
|
7389
|
+
* this reader would serve files from outside the workspace; the absolute, `..`, and `.sidecar`
|
|
7390
|
+
* refusals below are belt-and-braces so it stays correct for every path shape they can name
|
|
7391
|
+
* (#1284).
|
|
7360
7392
|
*
|
|
7361
7393
|
* `read()` returns a string, so an environment cannot hand back bytes that are not UTF-8; the
|
|
7362
7394
|
* provider has already decoded. The size bound is checked on what arrives.
|
|
@@ -7377,6 +7409,10 @@ function environmentReader(environment) {
|
|
|
7377
7409
|
ok: false,
|
|
7378
7410
|
reason: `path ${requested} leaves the manager's workspace`
|
|
7379
7411
|
};
|
|
7412
|
+
if (requested.split(/[\\/]/u).includes(".sidecar")) return {
|
|
7413
|
+
ok: false,
|
|
7414
|
+
reason: `path ${requested} names .sidecar, the guest's control tree; a spawn resource cannot read it`
|
|
7415
|
+
};
|
|
7380
7416
|
let content;
|
|
7381
7417
|
try {
|
|
7382
7418
|
content = await environment.read(requested);
|
|
@@ -7606,20 +7642,7 @@ async function resolveSpawnResourcePaths(profile, source) {
|
|
|
7606
7642
|
};
|
|
7607
7643
|
}
|
|
7608
7644
|
// src/runtime/supervise/retained-owner-resource-reader.ts
|
|
7609
|
-
/**
|
|
7610
|
-
* The owner's environment as a source of by-path spawn resources.
|
|
7611
|
-
*
|
|
7612
|
-
* Built once per scope, before the environment exists: the coordination tools are created ahead
|
|
7613
|
-
* of the owner's first turn, and the environment is admitted during it. So the reader resolves
|
|
7614
|
-
* its target on every read — the latest environment admission on this node, reconstructed through
|
|
7615
|
-
* the bound provider the same way cleanup reconstructs it, with the same identity check. A manager
|
|
7616
|
-
* that has not yet been admitted, or whose provider was never bound, gets a refusal that names the
|
|
7617
|
-
* missing piece rather than a read from nowhere.
|
|
7618
|
-
*
|
|
7619
|
-
* The environment's `read()` serves the sandbox's own workspace; containment against that
|
|
7620
|
-
* workspace is the provider's. The relative-path and `..` rules are the reader's, in
|
|
7621
|
-
* `environmentReader`, so a manager is refused for the rule it broke.
|
|
7622
|
-
*/
|
|
7645
|
+
/** Read spawn resources from the owner's latest environment. Path rules live in `environmentReader`. A missing admission or provider is a refusal, not a read from nowhere. */
|
|
7623
7646
|
function scopeRetainedOwnerResourceReader(scope) {
|
|
7624
7647
|
const state = owners.get(scope);
|
|
7625
7648
|
if (state === void 0) return void 0;
|
|
@@ -8023,40 +8046,10 @@ function meterRuntimeOwnedAccounting(scope, spend, detail, options) {
|
|
|
8023
8046
|
}
|
|
8024
8047
|
// src/runtime/supervise/dispatch.ts
|
|
8025
8048
|
/**
|
|
8026
|
-
*
|
|
8027
|
-
*
|
|
8028
|
-
*
|
|
8029
|
-
*
|
|
8030
|
-
*
|
|
8031
|
-
* The gap it closes: `fanout` opens every item at once and then drains — one round, no refill —
|
|
8032
|
-
* and a driver brain's manual loop opens one worker per model turn. Between those two there was
|
|
8033
|
-
* nothing that holds N slots full, which is why a 5-worker run can peak at 2 live workers and sit
|
|
8034
|
-
* ~50% idle. This is a policy over the EXISTING `Scope.spawn` / `Scope.next` primitives: it adds
|
|
8035
|
-
* no second admission path, so the conserved budget pool stays the only fence on total work and
|
|
8036
|
-
* `width` is only a fence on simultaneous work.
|
|
8037
|
-
*
|
|
8038
|
-
* Fail-closed by construction: an admission rejection (`budget-exhausted` / `depth-exceeded`) is
|
|
8039
|
-
* recorded and STOPS further admission — the loop then drains what is already live and returns.
|
|
8040
|
-
* It never retries a rejected spawn against the same pool, and it never spawns past `width`.
|
|
8041
|
-
*
|
|
8042
|
-
* ── Concurrency ────────────────────────────────────────────────────────────────────────────────
|
|
8043
|
-
*
|
|
8044
|
-
* `width` bounds the children THIS dispatcher holds in flight. The tree-wide bound on working
|
|
8045
|
-
* agents is `SuperviseOptions.workerSlots` (`./worker-slots`): a spawn past it is queued by the
|
|
8046
|
-
* scope, not refused, so a `width` above the free slots only lengthens the allocator's queue.
|
|
8047
|
-
* `SandboxLineage`'s fork concurrency bounds boxes inside ONE leaf's fork wave, a different unit.
|
|
8048
|
-
*
|
|
8049
|
-
* ── Why this is not a copy of the kernel's batch loop ──────────────────────────────────────────
|
|
8050
|
-
*
|
|
8051
|
-
* `runBatch` (`src/runtime/run-loop.ts`) runs the same hold-N-slots-full shape over the KERNEL's
|
|
8052
|
-
* substrate: bare promises in a `Set`, raced with `Promise.race`, bounded by `maxConcurrency`.
|
|
8053
|
-
* This one runs it over the SUPERVISOR's substrate — `Scope.spawn`'s atomic reservation against
|
|
8054
|
-
* the conserved pool, `Scope.next`'s journaled settlement cursor, and the blob store behind each
|
|
8055
|
-
* result. Neither can be expressed in the other's terms without dragging its whole substrate
|
|
8056
|
-
* along: the kernel loop has no budget to reserve and no cursor to order settlements by, and this
|
|
8057
|
-
* one cannot race raw promises because a settlement is only real once it is journaled. The shape
|
|
8058
|
-
* repeating across the two deliberate layers is resonance, not duplication — do not "unify" them
|
|
8059
|
-
* into a shared helper that would have to know about both.
|
|
8049
|
+
* `rollingDispatch` keeps `width` children in flight over `Scope.spawn` and `Scope.next`.
|
|
8050
|
+
* An admission rejection stops further admission. `width` bounds this dispatcher; `workerSlots`
|
|
8051
|
+
* queues past the tree-wide bound; a sandbox fork wave is a different unit. Do not fold this
|
|
8052
|
+
* into `runBatch`: a Scope settlement is real only once it is journaled.
|
|
8060
8053
|
*
|
|
8061
8054
|
* @experimental
|
|
8062
8055
|
*/
|
|
@@ -10389,13 +10382,8 @@ async function runChild(live, executor, childAbort, task, opts, pool, ticket, bl
|
|
|
10389
10382
|
await closeRetainedWrites();
|
|
10390
10383
|
}
|
|
10391
10384
|
}
|
|
10392
|
-
/**
|
|
10393
|
-
*
|
|
10394
|
-
* The fold never aborts at the reservation: usage arrives after the call it measures, often last,
|
|
10395
|
-
* so aborting would discard completed work and save no spend; reconciliation records overspend.
|
|
10396
|
-
* `progress` rows, replaced by the settlement, publish on a leading-edge second (#2337, #2339);
|
|
10397
|
-
* awaiting-result rows always publish.
|
|
10398
|
-
*/
|
|
10385
|
+
/** Publish the running total. Do not abort the fold: usage often arrives last, and overspend is reconciled.
|
|
10386
|
+
* Progress rows publish once a second; awaiting-result rows always publish. */
|
|
10399
10387
|
async function foldExecutorStream({ live, childAbort, now }, stream, journal, journalRoot, nextProgressSeq, priorSpent) {
|
|
10400
10388
|
let publishedAt = Number.NEGATIVE_INFINITY;
|
|
10401
10389
|
return await foldStream(stream, async (running, event) => {
|
|
@@ -10448,6 +10436,10 @@ async function settleThrown(attempt, cause, executionEvidence, closeRetainedWrit
|
|
|
10448
10436
|
if (acceptedAccountingError === void 0) return await settleAccepted(attempt, accepted);
|
|
10449
10437
|
err = acceptedAccountingError;
|
|
10450
10438
|
}
|
|
10439
|
+
if (retainedSessionReplaced(err)) {
|
|
10440
|
+
live.recoveryPending = false;
|
|
10441
|
+
return await settleCrashed(attempt, err, executionEvidence);
|
|
10442
|
+
}
|
|
10451
10443
|
if (err instanceof RetainedExecutionPendingError || live.recoveryPending) return await settleRetainedPending(attempt, err);
|
|
10452
10444
|
return await settleCrashed(attempt, err, executionEvidence);
|
|
10453
10445
|
}
|
|
@@ -10541,11 +10533,7 @@ function failureRecord(failure, trace, metered, providerModel, harnessTranscript
|
|
|
10541
10533
|
failure: reportedFailureCause(failure)
|
|
10542
10534
|
};
|
|
10543
10535
|
}
|
|
10544
|
-
/**
|
|
10545
|
-
* Fold a streaming executor's usage into the conserved `Spend`, publishing the running total
|
|
10546
|
-
* after EVERY event: a RUNNING worker is observable (it used to report zero until the stream
|
|
10547
|
-
* drained) and one that throws mid-stream reconciles its real partial spend.
|
|
10548
|
-
*/
|
|
10536
|
+
/** Fold usage after every event, so a running worker is observable and a mid-stream throw keeps its partial spend. */
|
|
10549
10537
|
async function foldStream(stream, onProgress, signal, priorSpent, onEvent) {
|
|
10550
10538
|
const totals = newUsageTotals(priorSpent);
|
|
10551
10539
|
const iterator = stream[Symbol.asyncIterator]();
|
|
@@ -10594,14 +10582,13 @@ function preserveUnknownTelemetry(streamed, terminal) {
|
|
|
10594
10582
|
...terminal.boxMinutes !== void 0 ? { boxMinutes: terminal.boxMinutes } : {},
|
|
10595
10583
|
...terminal.boxMinutesKnown !== void 0 ? { boxMinutesKnown: terminal.boxMinutesKnown } : {},
|
|
10596
10584
|
...terminal.boxMinutesProvenance !== void 0 ? { boxMinutesProvenance: terminal.boxMinutesProvenance } : {},
|
|
10597
|
-
...(streamed.tokensProvenance ?? terminal.tokensProvenance) === void 0 ? {} : { tokensProvenance: streamed.tokensProvenance ?? terminal.tokensProvenance }
|
|
10585
|
+
...(streamed.tokensProvenance ?? terminal.tokensProvenance) === void 0 ? {} : { tokensProvenance: streamed.tokensProvenance ?? terminal.tokensProvenance },
|
|
10586
|
+
...terminal.streamedTokens === void 0 ? {} : { streamedTokens: cloneTokenUsage(terminal.streamedTokens) },
|
|
10587
|
+
...terminal.unobservedTokens === void 0 ? {} : { unobservedTokens: cloneTokenUsage(terminal.unobservedTokens) },
|
|
10588
|
+
...terminal.iterationsKnown === false ? { iterationsKnown: false } : {}
|
|
10598
10589
|
};
|
|
10599
10590
|
}
|
|
10600
|
-
/**
|
|
10601
|
-
* Await a one-shot executor's terminal result unless an abort wins first, except for an execution
|
|
10602
|
-
* whose result was accepted before the abort arrived: it is ending the turn that submitted that
|
|
10603
|
-
* result, so it is awaited rather than discarded, and the executor bounds that wait itself.
|
|
10604
|
-
*/
|
|
10591
|
+
/** An abort wins unless the result was already accepted, in which case the executor bounds the wait. */
|
|
10605
10592
|
async function awaitUnlessAccepted(work, signal, executor) {
|
|
10606
10593
|
try {
|
|
10607
10594
|
return await awaitAbortable(work, signal);
|
|
@@ -11640,52 +11627,48 @@ function makeNestedScopeSeam(args, workerSlots, childSlot, childNodeId, childBud
|
|
|
11640
11627
|
}
|
|
11641
11628
|
// src/runtime/supervise/run-layout.ts
|
|
11642
11629
|
/**
|
|
11643
|
-
*
|
|
11644
|
-
*
|
|
11645
|
-
*
|
|
11646
|
-
* `
|
|
11647
|
-
* can find it after the fact: `@tangle-network/traces` reads exactly this layout via
|
|
11648
|
-
* `traces analyze --supervisor-run-dir`, a restarted host can rehydrate a run it no longer holds
|
|
11649
|
-
* handles to, and a human can steer a live worker by appending one NDJSON line. Until now the
|
|
11650
|
-
* layout was defined only in the unpublished `loops` repo (`src/supervisor-control.ts`) — a
|
|
11651
|
-
* published reader depending on an unpublished writer's convention — so the contract is promoted
|
|
11652
|
-
* here, names preserved.
|
|
11653
|
-
*
|
|
11654
|
-
* `.agent` is the one dot-dir for ALL agent-owned state (skills already write
|
|
11655
|
-
* `.agent/hypotheses/`, `.agent/skill-runs.jsonl`); supervisor runs live beside them rather than
|
|
11656
|
-
* under a product-branded dir. Runs written by older writers used `.loops/supervisor/<id>` —
|
|
11657
|
-
* readers that must see those keep a legacy fallback; this writer never creates `.loops` again.
|
|
11658
|
-
*
|
|
11659
|
-
* Layout, relative to `supervisorRunDir(root, id)`:
|
|
11630
|
+
* On-disk supervisor run layout: `<root>/.agent/supervisor/<id>`. `@tangle-network/traces` reads it
|
|
11631
|
+
* via `traces analyze --supervisor-run-dir`. Older writers used `.loops/supervisor/<id>`; readers
|
|
11632
|
+
* keep that fallback and this writer never creates `.loops`. Layout, relative to
|
|
11633
|
+
* `supervisorRunDir(root, id)`:
|
|
11660
11634
|
*
|
|
11661
11635
|
* steers/requests/<hash>.json one exact, caller-idempotent {@link WorkerSteerRequest}
|
|
11662
11636
|
* steers/acks/<hash>.json runtime acknowledgement for that steer operation
|
|
11663
11637
|
* workers/<id>.inbox.ndjson best-effort readable projection of admitted steer requests
|
|
11664
11638
|
* workers/<label>.ndjson best-effort per-worker control-event log (delivery bookkeeping)
|
|
11639
|
+
* questions/<id>.json an `ask_parent` question {@link fileEscalateQuestion} hands to an
|
|
11640
|
+
* outside observer, who answers with a root `writeWorkerSteer`
|
|
11665
11641
|
* cancellations/requests.ndjson worker-scoped cancel requests (durable inbox); each line is a
|
|
11666
11642
|
* {@link WorkerCancelRequest}
|
|
11667
|
-
* cancellations/<opId>.json
|
|
11668
|
-
*
|
|
11669
|
-
* acknowledger in the OWNING manager's turn loop: exact node
|
|
11670
|
-
* ids route to the manager that parents them (any depth);
|
|
11671
|
-
* label/profile-name references to the root manager alone
|
|
11643
|
+
* cancellations/<opId>.json a {@link WorkerCancellation}, written ONLY by the owning
|
|
11644
|
+
* manager's acknowledger (labels resolve at the root alone)
|
|
11672
11645
|
* cancellations/run.request.json the run-scoped cancel request — one {@link RunCancelRequest},
|
|
11673
11646
|
* the whole RUN rather than one worker
|
|
11674
|
-
* cancellations/run.json
|
|
11675
|
-
* {@link RunCancellation} written ONLY by the runtime (the root
|
|
11676
|
-
* manager issues the abort; the `supervise()` settle path records
|
|
11677
|
-
* what the run actually did)
|
|
11647
|
+
* cancellations/run.json its {@link RunCancellation}, written ONLY by the runtime
|
|
11678
11648
|
*
|
|
11679
11649
|
* Reads are tolerant by contract: a partial trailing line (a writer mid-append) or a corrupt line
|
|
11680
11650
|
* never poisons the rest of the file — later valid lines still matter. Duplicate operation ids
|
|
11681
11651
|
* collapse to the first admitted request, while a payload conflict fails closed.
|
|
11682
11652
|
*
|
|
11683
|
-
*
|
|
11684
|
-
*
|
|
11685
|
-
* a readable projection only and never controls idempotency.
|
|
11653
|
+
* Steer admission requires the exact worker id and a caller-owned operation id. One atomic request
|
|
11654
|
+
* file is the durable inbox; the NDJSON file is a readable projection that never controls idempotency.
|
|
11686
11655
|
*
|
|
11687
11656
|
* @experimental
|
|
11688
11657
|
*/
|
|
11658
|
+
/** An `escalateQuestion` that writes each `ask_parent` question to `questions/<id>.json` for an
|
|
11659
|
+
* out-of-process observer, who answers through `writeWorkerSteer` on the root (#1165). */
|
|
11660
|
+
function fileEscalateQuestion(rootDir, runId) {
|
|
11661
|
+
return (question) => {
|
|
11662
|
+
const dir = join(supervisorRunDir(rootDir, runId), "questions");
|
|
11663
|
+
mkdirSync(dir, { recursive: true });
|
|
11664
|
+
const file = join(dir, `${question.id.replace(/[^\w.-]/g, "_")}.json`);
|
|
11665
|
+
writeAtomicDurableFile(file, `${JSON.stringify(question, null, 2)}\n`, { mode: 384 });
|
|
11666
|
+
return {
|
|
11667
|
+
delivered: true,
|
|
11668
|
+
to: file
|
|
11669
|
+
};
|
|
11670
|
+
};
|
|
11671
|
+
}
|
|
11689
11672
|
/** The root every supervisor run of one workspace lives under. */
|
|
11690
11673
|
function supervisorRunsRoot(rootDir) {
|
|
11691
11674
|
return join(resolve(rootDir), ".agent", "supervisor");
|
|
@@ -12309,6 +12292,20 @@ function writeLine(fd, line) {
|
|
|
12309
12292
|
offset += written;
|
|
12310
12293
|
}
|
|
12311
12294
|
}
|
|
12295
|
+
/** A `task` tool result names the session the harness itself spawned (`<task id="ses_...">` or
|
|
12296
|
+
* `task_id: ses_...`), the same text `shared-box-worker` reads from a live part. #1264: the id
|
|
12297
|
+
* reached no record, so a subagent the harness completed stayed invisible to settle and
|
|
12298
|
+
* coverage alike. */
|
|
12299
|
+
const TASK_SESSION_ID = /(?:<task id="|task_id: )(ses_[A-Za-z0-9]+)/gu;
|
|
12300
|
+
function subagentSessionIdsIn(event) {
|
|
12301
|
+
if (event.kind !== "tool_result" || event.toolName !== "task") return [];
|
|
12302
|
+
const ids = [];
|
|
12303
|
+
for (const text of [event.result, event.error?.message]) {
|
|
12304
|
+
if (typeof text !== "string") continue;
|
|
12305
|
+
for (const match of text.matchAll(TASK_SESSION_ID)) ids.push(match[1]);
|
|
12306
|
+
}
|
|
12307
|
+
return ids;
|
|
12308
|
+
}
|
|
12312
12309
|
/**
|
|
12313
12310
|
* The receipt for the root stream a run directory holds, recomputed from the file's bytes, or
|
|
12314
12311
|
* `undefined` when the directory holds none. This is what a run that never settled — a root that
|
|
@@ -12319,15 +12316,20 @@ async function readRootStreamReceipt(runDir, file = ROOT_STREAM_FILE) {
|
|
|
12319
12316
|
const path = resolve(runDir, file);
|
|
12320
12317
|
const hash = createHash("sha256");
|
|
12321
12318
|
let events = 0;
|
|
12319
|
+
const subagentSessionIds = /* @__PURE__ */ new Set();
|
|
12322
12320
|
try {
|
|
12323
|
-
for await (const
|
|
12321
|
+
for await (const record of readCommittedJsonLines(path, { onBytes: (bytes) => void hash.update(bytes) })) {
|
|
12322
|
+
events += 1;
|
|
12323
|
+
if ("event" in record) for (const id of subagentSessionIdsIn(record.event)) subagentSessionIds.add(id);
|
|
12324
|
+
}
|
|
12324
12325
|
} catch (error) {
|
|
12325
12326
|
if (isNoEntError(error)) return void 0;
|
|
12326
12327
|
throw error;
|
|
12327
12328
|
}
|
|
12328
12329
|
return Object.freeze({
|
|
12329
12330
|
ref: `sha256:${hash.digest("hex")}`,
|
|
12330
|
-
events
|
|
12331
|
+
events,
|
|
12332
|
+
...subagentSessionIds.size > 0 ? { subagentSessionIds: [...subagentSessionIds] } : {}
|
|
12331
12333
|
});
|
|
12332
12334
|
}
|
|
12333
12335
|
/** Every committed line of the root stream, in order, or `undefined` when there is no file. A
|
|
@@ -13937,15 +13939,14 @@ function settledUsage(forest, spent, providerModel, profile) {
|
|
|
13937
13939
|
/** Terminal usage comes from receipts, never admission's conservative reservation charges.
|
|
13938
13940
|
* Missing receipts stay named gaps; ms is wall time because parallel executor times overlap. */
|
|
13939
13941
|
async function terminalAccounting(journal, root, elapsedMs, profile) {
|
|
13940
|
-
const
|
|
13941
|
-
|
|
13942
|
-
for (const event of events) {
|
|
13942
|
+
const forest = await loadSpawnForest(journal, root);
|
|
13943
|
+
const rootTree = forest.trees[0];
|
|
13944
|
+
for (const event of rootTree.events) {
|
|
13943
13945
|
const spend = event.kind === "metered" ? event.spend : event.kind === "settled" || event.kind === "cancelled" || event.kind === "reconciled" ? event.spent : void 0;
|
|
13944
13946
|
if (spend !== void 0) assertValidSpend(spend, `settlement ${event.id}/${event.kind}`);
|
|
13945
13947
|
}
|
|
13946
|
-
const { childWork, driverInference } = sumMeasuredSpendFromEvents(events);
|
|
13947
|
-
const gaps = spendGapsFromEvents(events);
|
|
13948
|
-
const forest = await loadSpawnForest(journal, root);
|
|
13948
|
+
const { childWork, driverInference } = sumMeasuredSpendFromEvents([...rootTree.events]);
|
|
13949
|
+
const gaps = spendGapsFromEvents(forest.events.map((entry) => entry.event));
|
|
13949
13950
|
const providerModel = aggregateProviderModelEvidence(forest);
|
|
13950
13951
|
const fleetYield = fleetYieldFromForest(forest);
|
|
13951
13952
|
const summed = addSpend(childWork, driverInference);
|
|
@@ -13956,7 +13957,17 @@ async function terminalAccounting(journal, root, elapsedMs, profile) {
|
|
|
13956
13957
|
usdKnown: summed.usdKnown !== false && !gaps.some((gap) => gap.channels.includes("usd"))
|
|
13957
13958
|
};
|
|
13958
13959
|
assertValidSpend(spentTotal, "settlement total");
|
|
13960
|
+
const forestNodes = forest.nodes.filter((node) => node.id !== forest.root);
|
|
13959
13961
|
return {
|
|
13962
|
+
tree: {
|
|
13963
|
+
root: forest.root,
|
|
13964
|
+
nodes: forestNodes.map(({ treeRoot: _treeRoot, ...node }) => node),
|
|
13965
|
+
inFlight: forestNodes.filter((node) => [
|
|
13966
|
+
"queued",
|
|
13967
|
+
"acquiring",
|
|
13968
|
+
"running"
|
|
13969
|
+
].includes(node.status)).length
|
|
13970
|
+
},
|
|
13960
13971
|
spentTotal,
|
|
13961
13972
|
usage: settledUsage(forest, spentTotal, providerModel, profile),
|
|
13962
13973
|
childWork,
|
|
@@ -14053,6 +14064,6 @@ function unknownChannels(spend) {
|
|
|
14053
14064
|
function isNonEmptySpend(s) {
|
|
14054
14065
|
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);
|
|
14055
14066
|
}
|
|
14056
|
-
export {
|
|
14067
|
+
export { queueOf as $, harnessTranscriptFromLines as $n, awaitAbortable$1 as $r, ownedTreeRootSpawn as $t, openSlotGroup as A, environmentGone as An, providerAttemptEvidence as Ar, noteScopeOwnerPlannedExecution as At, readWorkerInteractiveBinding as B, createOpenInferenceFileExporter as Bn, runtimeOwnedPendingExecutorMaterialization as Br, scopeRetainedOwnerSettled as Bt, workerCancellationsDir as C, offerInteractiveControl as Cn, authoredAgentIdentity as Cr, abandonScopeRetainedOwnerInvocation as Ct, writeWorkerSteer as D, observerRecordDigest as Dn, knownExecutionBindingReceipt as Dr, scopeRetainedOwnerResult as Dt, writeWorkerCancellation as E, createFileObserverHooks as En, inheritRuntimeOwnedExecutorAttestation as Er, scopeRetainedOwnerPriorSpend as Et, parseWorkerToolTraceArtifact as F, mergeTraceEnv as Fn, recordRuntimeOwnedProviderModel as Fr, coordinationMcpAlias as Ft, appendSpawnEvents as G, createRuntimeEventCollector as Gn, executableAgentProfileSnapshot as Gr, noteScopeRetainedOwnerCoordination as Gt, recoverRetainedInteractiveRun as H, flatOtelSpan as Hn, runtimeOwnedScopeOwnerRuntime as Hr, assertRetainedOwnerCoordinationRekey as Ht, workerTraceAnalysisStore as I, readTraceContextFromEnv as In, runtimeOwnedDriveHarnessProviderEvidence as Ir, coordinationProfileToolPrefix as It, reenteredWorkers as J, sanitizeRuntimeStreamEvent as Jn, runtimeCheckFactory as Jr, bindScopeRetainedOwnerProvider as Jt, spawnJournalRecordEvents as K, createRuntimeStreamEventCollector as Kn, executableAgentSpecSnapshot as Kr, scopeRetainedOwnerCheckpointing as Kt, nestedScopeSeamKey as L, buildLoopOtelSpans as Ln, runtimeOwnedExecutorExecutionBinding as Lr, providerVisibleProfile as Lt, prepareScopeResume as M, workerTraceEnv as Mn, recordRuntimeOwnedProviderAttemptStart as Mr, restoreScopeOwnerAcceptedExecution as Mt, replaySpawnTree as N, workerTraceHeaders as Nn, recordRuntimeOwnedProviderDispatchNotStarted as Nr, scopeOwnerExecutionObservation as Nt, writeWorkerSteerAcknowledgement as O, verifyObserverRecords as On, knownMaterializationReceipt as Or, beginScopeOwnerAttempt as Ot, isTraceAnalysisStore as P, createPropagatingTraceEmitter as Pn, recordRuntimeOwnedProviderIdentityConflict as Pr, scopeOwnerExecutorNodeContext as Pt, freeSlots as Q, harnessTranscriptArtifact as Qn, assertEventBinding as Qr, loadSpawnForest as Qt, interactiveAdmissionSeamKey as R, buildLoopSpanNodes as Rn, runtimeOwnedExecutorMaterialization as Rr, runtimeGraphMetadataKey as Rt, workerCancelRequestsFile as S, acceptedInteractiveControl as Sn, attestRuntimeOwnedScopeOwner as Sr, resolveSpawnResourcePaths as St, workerSteerRequestsDir as T, FileObserverJournal as Tn, finalizeRuntimeOwnedPendingExecutor as Tr, prepareScopeRetainedOwnerTask as Tt, startRetainedInteractiveRun as U, generateSpanId as Un, unknownExecutionBindingReceipt as Ur, reconcileScopeRetainedOwnerEnvironment as Ut, reconnectRetainedInteractiveRun as V, createOtelExporter as Vn, runtimeOwnedScopeOwnerControl as Vr, scopeRetainedOwnerTurnEnded as Vt, FileSpawnJournal as W, loopEventToOtelSpan as Wn, unknownMaterializationReceipt as Wr, scopeRetainedOwnerRestorePoint as Wt, assertRecursiveReservationPolicy as X, captureBoundHarnessTranscript as Xn, runtimeFlowBrain as Xr, scopeRetainedOwnerContext as Xt, settledToIteration as Y, HARNESS_TRANSCRIPT_SETTLE_TIMEOUT_MS as Yn, runtimeFlowAgent as Yr, owners as Yt, deriveNodeExecutionIdentity as Z, captureHarnessTranscript as Zn, RetainedRunProviderContractError as Zr, scopeRetainedOwnerJournalRoot as Zt, readWorkerSteerAcknowledgement as _, reconnectRetainedRun as _n, seatExecutionProfile as _r, waitForScopeChildren as _t, createRootStreamSink as a, abortReason as ai, encodeResultBlob as an, resolveHarnessTranscriptCapture as ar, meterRuntimeOwnedProviderAttempt as at, supervisorRunDir as b, startRetainedRunInEnvironment as bn, attestRuntimeOwnedExecutor as br, scopeRetainedOwnerResourceReader as bt, readRootStreamReceipt as c, signalAbortError as ci, executorObservation as cn, createActivityLog as cr, recordScopeOwnerDriverAttempt as ct, claimWorkerSteerDelivery as d, providerMessageText as di, describeRetainedNativeStops as dn, isLiveNodeStatus as dr, releaseHeldEnvironments as dt, exactSession as ei, materializeTreeView as en, harnessTranscriptUnavailable as er, rollingDispatch as et, fileEscalateQuestion as f, stopRetainedNativeExecution as fn, isTerminalNodeStatus as fr, releaseRetainedEnvironments as ft, readWorkerCancellation as g, mintRetainedIdentity as gn, authoredSeatStages as gr, unconfirmedTeardowns as gt, readWorkerCancelRequests as h, assertRetainedRunReplayMaterial as hn, SeatPauseError as hr, startScopeRecoveries as ht, ROOT_STREAM_FILE as i, RunCancellationReason as ii, FileResultBlobStore as in, readBoundHarnessSessions as ir, meterRuntimeOwnedAccounting as it, resolveWorkerSlots as j, WORKER_TRACE_PROPAGATION as jn, recordRuntimeOwnedDriveHarnessProviderEvidence as jr, recordScopeOwnerMaterialization as jt, createWorkerSlots as k, destroyInteractiveEnvironment as kn, newExecutionAttemptId as kr, finalizeScopeOwnerMaterialization as kt, appendWorkerControlEvent as l, promptFromAgentTurnInput as li, executorObservationSeamKey as ln, describeToolArgs as lr, recordScopeOwnerPause as lt, legacySupervisorRunsRoot as m, RetainedMaterializationTimeoutError as mn, SeatChain as mr, settleManagerWork as mt, terminalAccounting as n, retainedSessionReplaced as ni, driverRuntime as nn, readHarnessTranscript as nr, closeScopeAdmission as nt, nodeStreamFile as o, linkAbort as oi, SpawnEventIndex as on, retainHarnessTranscript as or, pauseScopeSeatAdmission as ot, legacySupervisorRunDir as p, verifyWorkspaceMarker as pn, SEAT_RECHECK_MS as pr, retryUnconfirmedTeardowns as pt, appendTeardownConfirmation as q, sanitizeKnowledgeReadinessReport as qn, isRuntimeFlowBrain as qr, bindScopeRetainedOwnerEnvironmentId as qt, createScope as r, sameControlCoordinates as ri, nestedDriverTreeRoot as rn, HARNESS_TRANSCRIPT_READ_LIMITS as rr, hasRetriableTeardowns as rt, readRootStream as s, runAbortable as si, insideCursorNamespace as sn, DEFAULT_STALL_AFTER_MS as sr, recordScopeOwnerBarrenPause as st, isNonEmptySpend as t, freezeControlRef as ti, attestNestedDriverTreeOwner as tn, persistHarnessTranscript as tr, closeRetainedSlots as tt, cancelWorker as u, promptOptionsFromAgentTurnInput as ui, observeModelRequest as un, readWorkerProgress as ur, registerNestedTeardownScope as ut, readWorkerSteerRequests as v, recoverRetainedRun as vn, subscriptionUsageLimitSignal as vr, prepareScopeRetainedOwnerSeatSwitch as vt, workerInboxFileFromEventDir as w, prepareInteractiveDelegation as wn, authoredProfileDigest as wr, consumeScopeRetainedOwnerResult as wt, supervisorRunsRoot as x, retainedCreateMaterial as xn, attestRuntimeOwnedPendingExecutor as xr, environmentReader as xt, safeWorkerFile as y, startRetainedRun as yn, attestRuntimeOwnedDeferredExecutor as yr, releaseScopeRetainedOwnerForSeatPause as yt, attachWorker as z, buildRuntimeEventOtelSpans as zn, runtimeOwnedExecutorProviderEvidence as zr, beginScopeRetainedOwnerDrive as zt };
|
|
14057
14068
|
|
|
14058
|
-
//# sourceMappingURL=supervisor-accounting-
|
|
14069
|
+
//# sourceMappingURL=supervisor-accounting-Ow6CChpB.js.map
|