@tangle-network/agent-runtime 6.0.8 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +23 -12
  3. package/dist/{activation-Cwy3_Qn_.js → activation-Ce4zUcu8.js} +4 -4
  4. package/dist/{activation-Cwy3_Qn_.js.map → activation-Ce4zUcu8.js.map} +1 -1
  5. package/dist/{activation-CWx7I1PU.d.ts → activation-xxvLF1-1.d.ts} +2 -2
  6. package/dist/agent.d.ts +1 -1
  7. package/dist/agent.js +3 -3
  8. package/dist/{bundle-Cy5igJdv.js → bundle-CcpL2lfr.js} +2 -2
  9. package/dist/{bundle-Cy5igJdv.js.map → bundle-CcpL2lfr.js.map} +1 -1
  10. package/dist/candidate-execution/index.js +4 -4
  11. package/dist/continuation-CIyKnS1Q.js +451 -0
  12. package/dist/continuation-CIyKnS1Q.js.map +1 -0
  13. package/dist/{coordination-manager-D9z7To7p.js → coordination-manager-sa_PkISw.js} +247 -149
  14. package/dist/coordination-manager-sa_PkISw.js.map +1 -0
  15. package/dist/{coordination-map-routing-CKRNZSaV.js → coordination-map-routing-XZXTlCmn.js} +2 -4
  16. package/dist/coordination-map-routing-XZXTlCmn.js.map +1 -0
  17. package/dist/{delegate-ws-KBA2H.js → delegate-DhTkJIta.js} +3 -3
  18. package/dist/{delegate-ws-KBA2H.js.map → delegate-DhTkJIta.js.map} +1 -1
  19. package/dist/durable.d.ts +71 -4
  20. package/dist/durable.js +162 -13
  21. package/dist/durable.js.map +1 -1
  22. package/dist/{errors-AzSuGI23.js → errors-DG8yufqX.js} +8 -1
  23. package/dist/errors-DG8yufqX.js.map +1 -0
  24. package/dist/{failure-cause-BzjUUsRz.js → failure-cause-CVB-YE88.js} +17 -16
  25. package/dist/failure-cause-CVB-YE88.js.map +1 -0
  26. package/dist/{graph-registry-7X2nrreR.js → graph-registry-czAwYwNj.js} +133 -9
  27. package/dist/graph-registry-czAwYwNj.js.map +1 -0
  28. package/dist/{graph-registry-gYbq4L5Z.js → graph-registry-uTIyyy7A.js} +1 -1
  29. package/dist/graph-tSIT_eWK.js +1793 -0
  30. package/dist/graph-tSIT_eWK.js.map +1 -0
  31. package/dist/{improve-BxfqTsZy.js → improve-H08hiIw9.js} +19 -48
  32. package/dist/improve-H08hiIw9.js.map +1 -0
  33. package/dist/index.d.ts +3 -3
  34. package/dist/index.js +16 -16
  35. package/dist/intelligence.d.ts +2 -2
  36. package/dist/intelligence.js +8 -8
  37. package/dist/intelligence.js.map +1 -1
  38. package/dist/kernel.d.ts +2 -2
  39. package/dist/kernel.js +16 -15
  40. package/dist/{loop-runner-CUMypgRw.d.ts → loop-runner-BGwhcn1p.d.ts} +2 -2
  41. package/dist/{loop-runner-BmGZ4y1W.js → loop-runner-ChqK8kSz.js} +3 -3
  42. package/dist/{loop-runner-BmGZ4y1W.js.map → loop-runner-ChqK8kSz.js.map} +1 -1
  43. package/dist/loop-runner-bin.d.ts +1 -1
  44. package/dist/loop-runner-bin.js +1 -1
  45. package/dist/mcp/bin.js +7 -5
  46. package/dist/mcp/bin.js.map +1 -1
  47. package/dist/mcp/index.d.ts +1 -1
  48. package/dist/mcp/index.js +9 -9
  49. package/dist/mcp/memory-bin.js +2 -2
  50. package/dist/{model-policy-JOcg3NRs.js → model-policy-yxOrDTTj.js} +9 -9
  51. package/dist/model-policy-yxOrDTTj.js.map +1 -0
  52. package/dist/{prepare-Dtfk_Wi_.js → prepare-BnF8nGIg.js} +4 -4
  53. package/dist/{prepare-Dtfk_Wi_.js.map → prepare-BnF8nGIg.js.map} +1 -1
  54. package/dist/{profile-CNSQjOes.js → profile-CD25rTJF.js} +4 -4
  55. package/dist/{profile-CNSQjOes.js.map → profile-CD25rTJF.js.map} +1 -1
  56. package/dist/profiles.d.ts +1 -1
  57. package/dist/profiles.js +2 -2
  58. package/dist/{provision-supervisor-CeQTPHRr.js → provision-supervisor-Dx4TXTMM.js} +8 -8
  59. package/dist/{provision-supervisor-CeQTPHRr.js.map → provision-supervisor-Dx4TXTMM.js.map} +1 -1
  60. package/dist/{run-cancellation-D_4vJ522.js → run-cancellation-DYszFJtQ.js} +4 -4
  61. package/dist/{run-cancellation-D_4vJ522.js.map → run-cancellation-DYszFJtQ.js.map} +1 -1
  62. package/dist/{run-state-CTZVngI_.js → run-state-DHBoJjaF.js} +6 -6
  63. package/dist/{run-state-CTZVngI_.js.map → run-state-DHBoJjaF.js.map} +1 -1
  64. package/dist/{runtime-COm9uBBx.js → runtime-CaM5Gbbv.js} +76 -987
  65. package/dist/runtime-CaM5Gbbv.js.map +1 -0
  66. package/dist/{sandbox-profile-compat-oFGWESLn.js → sandbox-profile-compat-l-SLQnGb.js} +2 -2
  67. package/dist/{sandbox-profile-compat-oFGWESLn.js.map → sandbox-profile-compat-l-SLQnGb.js.map} +1 -1
  68. package/dist/{search-executor-DTEIl58w.js → search-executor-B9L8W_1O.js} +17 -46
  69. package/dist/search-executor-B9L8W_1O.js.map +1 -0
  70. package/dist/{server-zO9Nz9Nk.js → server-BlDV8sj_.js} +32 -8
  71. package/dist/{server-zO9Nz9Nk.js.map → server-BlDV8sj_.js.map} +1 -1
  72. package/dist/{settle-record-U7fkQEZa.js → settle-record-WWQasnDq.js} +12 -4
  73. package/dist/settle-record-WWQasnDq.js.map +1 -0
  74. package/dist/{snapshot-Cgj2Wzi6.js → snapshot-BKI_w3JD.js} +19 -5
  75. package/dist/snapshot-BKI_w3JD.js.map +1 -0
  76. package/dist/{structural-rollout-BwTwTUXF.js → structural-rollout-C1xxoWcN.js} +39 -57
  77. package/dist/structural-rollout-C1xxoWcN.js.map +1 -0
  78. package/dist/{supervise-tmqDU6YT.js → supervise-DWiSgPLB.js} +103 -111
  79. package/dist/supervise-DWiSgPLB.js.map +1 -0
  80. package/dist/{supervision-discovery-CGbXayGv.js → supervision-discovery-C9kfBLKy.js} +4 -4
  81. package/dist/{supervision-discovery-CGbXayGv.js.map → supervision-discovery-C9kfBLKy.js.map} +1 -1
  82. package/dist/{supervisor-1A27fE6k.js → supervisor-Dpeu7rnZ.js} +209 -999
  83. package/dist/supervisor-Dpeu7rnZ.js.map +1 -0
  84. package/dist/{scope-gIYQ5ZiL.js → supervisor-accounting-Bw4MJk-b.js} +755 -458
  85. package/dist/supervisor-accounting-Bw4MJk-b.js.map +1 -0
  86. package/dist/testing.d.ts +1 -1
  87. package/dist/testing.js +13 -13
  88. package/dist/{tool-server-B9q2dtnW.js → tool-server-DYwRbi0I.js} +3 -3
  89. package/dist/{tool-server-B9q2dtnW.js.map → tool-server-DYwRbi0I.js.map} +1 -1
  90. package/dist/{top-web-CS4PD4ck.js → top-web-B3HOAtrZ.js} +9 -7
  91. package/dist/{top-web-CS4PD4ck.js.map → top-web-B3HOAtrZ.js.map} +1 -1
  92. package/dist/tui/bin.js +6 -6
  93. package/dist/tui/bin.js.map +1 -1
  94. package/dist/tui/index.d.ts +1 -1
  95. package/dist/tui/index.js +2 -2
  96. package/dist/{types-w3Oo1uRJ.d.ts → types-C_qbnw9N.d.ts} +1263 -1497
  97. package/dist/{util-C_cdPNC4.js → util-fcckrkKp.js} +39 -113
  98. package/dist/util-fcckrkKp.js.map +1 -0
  99. package/dist/{workspace-streams-B7yNMahl.js → workspace-streams-DA9R3j-p.js} +2 -2
  100. package/dist/{workspace-streams-B7yNMahl.js.map → workspace-streams-DA9R3j-p.js.map} +1 -1
  101. package/package.json +1 -1
  102. package/dist/coordination-manager-D9z7To7p.js.map +0 -1
  103. package/dist/coordination-map-routing-CKRNZSaV.js.map +0 -1
  104. package/dist/errors-AzSuGI23.js.map +0 -1
  105. package/dist/failure-cause-BzjUUsRz.js.map +0 -1
  106. package/dist/graph-2_nX4ib4.js +0 -579
  107. package/dist/graph-2_nX4ib4.js.map +0 -1
  108. package/dist/graph-registry-7X2nrreR.js.map +0 -1
  109. package/dist/improve-BxfqTsZy.js.map +0 -1
  110. package/dist/model-policy-JOcg3NRs.js.map +0 -1
  111. package/dist/runtime-COm9uBBx.js.map +0 -1
  112. package/dist/scope-gIYQ5ZiL.js.map +0 -1
  113. package/dist/search-executor-DTEIl58w.js.map +0 -1
  114. package/dist/settle-record-U7fkQEZa.js.map +0 -1
  115. package/dist/snapshot-Cgj2Wzi6.js.map +0 -1
  116. package/dist/structural-rollout-BwTwTUXF.js.map +0 -1
  117. package/dist/supervise-tmqDU6YT.js.map +0 -1
  118. package/dist/supervisor-1A27fE6k.js.map +0 -1
  119. package/dist/util-C_cdPNC4.js.map +0 -1
@@ -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-AzSuGI23.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-C_cdPNC4.js";
3
- import { $ as assertValidBudget, F as executorFailureInfra, G as budgetRecoverySeeds, H as BudgetReconcileFault, I as executorFailureReason, J as refreshBudgetLease, K as completedLeafBacking, M as contentAddress, P as executorFailure, Q as spendFromUsageTotals, U as budgetLeaseUpdates, W as budgetLeases, X as newUsageTotals, Y as meterUsageEvent, et as assertValidSpend, f as executorEvidenceWriter, g as prepareRetainedExecutor, h as leafContinuationExecutionId, j as traceRootWriter, lt as extractTransportEventIdentity, n as runtimeFailureCause, o as settlementFailureCause, q as createBudgetPool, s as BUDGET_CONTINUATION_TASK, t as reportedFailureCause, u as RetainedExecutionPendingError, ut as parseCanonicalTransportEvent, wt as recordValue, y as retainedExecutorSeamKey } from "./failure-cause-BzjUUsRz.js";
4
- import { a as errMessage, c as errorText, d as resolveRedactor, l as defaultRedactor, n as detachedSnapshot, r as shareParsedStrings } from "./snapshot-Cgj2Wzi6.js";
5
- import { t as assertExecutableAgentProfile } from "./model-policy-JOcg3NRs.js";
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-yxOrDTTj.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-B7yNMahl.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-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 DOWN to a spawned worker, so one supervised tree spans machines.
3096
- *
3097
- * WHY. `otel-spans.ts` makes the supervisor's own tree readable by any trace viewer, but it stops at
3098
- * the process boundary: a worker running on a remote sandbox or in a child process opens its OWN
3099
- * trace root, so the viewer shows two unrelated trees for one run. A worker that inherits the
3100
- * parent's trace id and the spawning node's span id emits spans that JOIN the parent's trace, and
3101
- * the viewer assembles the whole cross-machine tree with no viewer change.
3102
- *
3103
- * THE WIRE IS W3C `TRACEPARENT`, dual-written with the legacy pair. `traceContextToEnv`
3104
- * (`src/mcp/trace-propagation.ts`) writes `TRACEPARENT` (the convention the ecosystem — OTel SDKs,
3105
- * collectors, the Claude Code harness binary — reads at spawn) ALONGSIDE the bespoke
3106
- * `TRACE_ID` / `PARENT_SPAN_ID` pair this package shipped first, and `readTraceContextFromEnv()`
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
- * Dispatch one detached, replayable run and return only after exact durable
4254
- * coordinates are confirmed by the provider and persisted by the caller.
4255
- *
4256
- * The required `onAdmission` hook first records a digest-only intent before
4257
- * creation or receipt-authorized attachment, then records recovery coordinates and the verified exact control
4258
- * reference. The returned promise resolves only after the dispatched admission
4259
- * is durable, so a crash cannot lose a successful start's exact reference.
4260
- *
4261
- * @stable
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.provider.create({
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, all of them together, forks included.
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 alive for a later lookup, with one exception: a
5562
- * provider that answers `not_found` for a request older than {@link WORKSPACE_CHECKPOINT_ABSENT_AFTER_MS}
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
- const signal = AbortSignal.timeout(3e4);
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 only historically captured checkpoints, unless capacity retirement permits uncaptured
5631
- * deletion. Source-scoped handles attest deletion; every provider operation remains bounded. */
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 (branching === void 0) throw new Error("source-scoped checkpoint handle unavailable");
5668
- const signal = AbortSignal.timeout(3e4);
5669
- const acknowledgement = await runAbortable(() => branching.deleteCheckpoint(request, { signal }), signal, "retained owner checkpoint cleanup timed out");
5670
- confirmed = workspaceCleanupAcknowledgementMatches(request, acknowledgement) && (acknowledgement.status === "deleted" || acknowledgement.status === "already_absent");
5671
- } catch {}
5672
- if (!confirmed) unconfirmed.push(event);
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 = AbortSignal.timeout(3e4);
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
- * Destroy every capture fork the journal does not show destroyed: one a crash left between its
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
- const signal = AbortSignal.timeout(3e4);
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
- * Group owner admissions by input and require a verified workspace receipt before deletion.
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 Set();
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.add(environmentId);
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
- if (!await ownerAttemptHasCapture(attempt, environmentId, owner.blobs, owner)) failures.add(environmentId);
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
- if (hasDurableWorkspaceCapture(output, output !== null && typeof output === "object" ? output.workspaceSnapshot : void 0, environmentId, attempt.executionId, controlRef, owner === void 0 ? void 0 : ownerCaptureIdentity(attempt, owner), allowPartialProcessCapture)) {
6086
- verified = true;
6087
- break;
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 verified;
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.coverageComplete !== true && !(allowPartialProcessCapture && hasOnlyRawProcessCaptureGap(receipt))) return false;
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 archive or the live environment
6186
- * that owns it. This narrow exception is for continuation only: release still requires complete
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 allowed = [
6201
- /^process_(?:io_missing|io_unavailable|source_unavailable)(?::[\w-]+)?$/,
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
- /** The environment admission an invocation starting at `before` continues in: the latest earlier
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
- /** Abandon the owner's in-flight invocation when its reconciliation keeps failing the same way
6942
- * (`DriverRetryRun.resolvePending`), only once the provider confirms the dispatched execution
6943
- * stopped or is not live; the journal then refuses its later admissions and results. Answers
6944
- * `false`, journaling nothing, without an unfinished invocation or a confirmed native stop. */
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 backend prompt; rebuilt coordination observations cannot replace it.
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: at the run's final
7123
- * settlement for the root, and when a nested manager's own scope has finished. A second call while
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 requestFailures = await reconcileCheckpointRequests(state, events);
7150
- const pendingSources = new Set(requestFailures.map(({ event }) => event.environmentId));
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
- if (retentionFailures.has(environmentId) || pendingSources.has(environmentId) || uncapturedSources.has(environmentId)) {
7181
- detail = pendingSources.has(environmentId) ? "checkpoint cleanup unresolved: source preserved for exact lookup and cleanup" : uncapturedSources.has(environmentId) ? "checkpoint kept uncaptured: source preserved so its checkpoint can still be forked and captured" : "provider workspace retention: source preserved because the owner execution has no verified workspace receipt";
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 PATH instead of by content.
7307
- *
7308
- * The canonical profile schema knows two resource kinds, `inline` (a content string) and `github`.
7309
- * A manager that wants a child to receive a data file therefore had one way to do it: emit the
7310
- * bytes inside its own `spawn` tool call. That is a transcription by a language model, and it does
7311
- * not survive size. Measured 2026-09-12 (discovery-lab `mech-interp-foundations-glm2-b-20260912b`):
7312
- * a 29,144-character base64 payload on the manager's disk reached three children as 15,928
7313
- * characters with 11 substitutions, and a fourth child received the literal placeholder the
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. The provider owns containment — it will not serve a path outside the box —
7358
- * but the relative-path and `..` rules are enforced here too, so the refusal names the rule the
7359
- * manager broke rather than whatever the provider says about an escape it declined.
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
- * `rollingDispatch` — the REFILLING dispatch policy over a `Scope`. It keeps `width` children in
8028
- * flight and admits the next queued unit of work the instant one settles, instead of waiting for
8029
- * a whole round to drain (`fanout`) or for a driver to decide again (`spawn → await → spawn`).
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
- * Fold a streaming executor's running total onto the live child, so `scope.progress(id)` sees it.
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);
@@ -11093,47 +11080,6 @@ function maxSeqOf(events, pred) {
11093
11080
  function reservedCursorFloor(events) {
11094
11081
  return Math.max(maxSeqOf(events, closesCursorSlot), events.reduce((max, event) => event.kind === "reconciled" && event.settledSeq !== void 0 ? Math.max(max, event.settledSeq) : max, -1));
11095
11082
  }
11096
- /** Durable measured costs plus conservative unmeasured ceilings and remaining backing.
11097
- * Unknown flags distinguish those charges from observed spend. */
11098
- function sumSpendFromEvents(events) {
11099
- const totals = sumMeasuredSpendFromEvents(events);
11100
- const rootBudget = events.find((event) => event.kind === "spawned" && event.parent === void 0)?.budget;
11101
- let remainingRootTokens = Math.max(0, (rootBudget?.maxTokens ?? 0) - chargedTokens(totals.childWork.tokens) - chargedTokens(totals.driverInference.tokens));
11102
- let remainingRootIterations = Math.max(0, (rootBudget?.maxIterations ?? 0) - totals.childWork.iterations - totals.driverInference.iterations);
11103
- let remainingRootUsd = Math.max(0, (rootBudget?.maxUsd ?? 0) - totals.childWork.usd - totals.driverInference.usd);
11104
- for (const budget of uncertainSpawnBudgets(events)) {
11105
- const unknown = withBudgetResources(zeroSpend(), budget);
11106
- Object.assign(totals.childWork, addResourceSpend(totals.childWork.resources, unknown.resources));
11107
- const iterationCharge = budget.maxIterations ?? remainingRootIterations;
11108
- totals.childWork.iterations += iterationCharge;
11109
- remainingRootIterations = Math.max(0, remainingRootIterations - iterationCharge);
11110
- const tokenCharge = budget.maxTokens ?? remainingRootTokens;
11111
- totals.childWork.tokens.input += tokenCharge;
11112
- remainingRootTokens = Math.max(0, remainingRootTokens - tokenCharge);
11113
- totals.childWork.tokensKnown = false;
11114
- const usdCharge = budget.maxUsd ?? (rootBudget?.maxUsd !== void 0 ? remainingRootUsd : 0);
11115
- totals.childWork.usd += usdCharge;
11116
- totals.childWork.usdKnown = false;
11117
- remainingRootUsd = Math.max(0, remainingRootUsd - usdCharge);
11118
- }
11119
- for (const { budget: held } of heldSpawnBudgets(events)) totals.childWork = addSpend(totals.childWork, {
11120
- ...zeroSpend(),
11121
- iterations: held.maxIterations ?? 0,
11122
- tokens: {
11123
- input: held.maxTokens ?? 0,
11124
- output: 0
11125
- },
11126
- tokensKnown: (held.maxTokens ?? 0) === 0,
11127
- usd: held.maxUsd ?? 0,
11128
- usdKnown: (held.maxUsd ?? 0) === 0,
11129
- ...held.resources === void 0 ? {} : { resources: Object.fromEntries(Object.entries(held.resources).map(([name, value]) => [name, {
11130
- unit: value.unit,
11131
- amount: value.limit,
11132
- known: value.limit === 0
11133
- }])) }
11134
- });
11135
- return totals;
11136
- }
11137
11083
  function sumMeasuredSpendFromEvents(events, ownerId) {
11138
11084
  const budgets = new Map(events.flatMap((event) => event.kind === "spawned" ? [[event.id, event.budget]] : []));
11139
11085
  let childWork = zeroSpend();
@@ -11681,52 +11627,48 @@ function makeNestedScopeSeam(args, workerSlots, childSlot, childNodeId, childBud
11681
11627
  }
11682
11628
  // src/runtime/supervise/run-layout.ts
11683
11629
  /**
11684
- * The on-disk supervisor-run layout: `<root>/.agent/supervisor/<id>`.
11685
- *
11686
- * This is the durable, cross-process face of a supervisor run — the counterpart to the in-process
11687
- * `Inbox` seam in `./inbox`. A run persists its state under one directory so that any OTHER process
11688
- * can find it after the fact: `@tangle-network/traces` reads exactly this layout via
11689
- * `traces analyze --supervisor-run-dir`, a restarted host can rehydrate a run it no longer holds
11690
- * handles to, and a human can steer a live worker by appending one NDJSON line. Until now the
11691
- * layout was defined only in the unpublished `loops` repo (`src/supervisor-control.ts`) — a
11692
- * published reader depending on an unpublished writer's convention — so the contract is promoted
11693
- * here, names preserved.
11694
- *
11695
- * `.agent` is the one dot-dir for ALL agent-owned state (skills already write
11696
- * `.agent/hypotheses/`, `.agent/skill-runs.jsonl`); supervisor runs live beside them rather than
11697
- * under a product-branded dir. Runs written by older writers used `.loops/supervisor/<id>` —
11698
- * readers that must see those keep a legacy fallback; this writer never creates `.loops` again.
11699
- *
11700
- * 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)`:
11701
11634
  *
11702
11635
  * steers/requests/<hash>.json one exact, caller-idempotent {@link WorkerSteerRequest}
11703
11636
  * steers/acks/<hash>.json runtime acknowledgement for that steer operation
11704
11637
  * workers/<id>.inbox.ndjson best-effort readable projection of admitted steer requests
11705
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`
11706
11641
  * cancellations/requests.ndjson worker-scoped cancel requests (durable inbox); each line is a
11707
11642
  * {@link WorkerCancelRequest}
11708
- * cancellations/<opId>.json the acknowledgement for one cancel operation — a
11709
- * {@link WorkerCancellation} written ONLY by the runtime
11710
- * acknowledger in the OWNING manager's turn loop: exact node
11711
- * ids route to the manager that parents them (any depth);
11712
- * 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)
11713
11645
  * cancellations/run.request.json the run-scoped cancel request — one {@link RunCancelRequest},
11714
11646
  * the whole RUN rather than one worker
11715
- * cancellations/run.json the acknowledgement for the run-scoped request — a
11716
- * {@link RunCancellation} written ONLY by the runtime (the root
11717
- * manager issues the abort; the `supervise()` settle path records
11718
- * what the run actually did)
11647
+ * cancellations/run.json its {@link RunCancellation}, written ONLY by the runtime
11719
11648
  *
11720
11649
  * Reads are tolerant by contract: a partial trailing line (a writer mid-append) or a corrupt line
11721
11650
  * never poisons the rest of the file — later valid lines still matter. Duplicate operation ids
11722
11651
  * collapse to the first admitted request, while a payload conflict fails closed.
11723
11652
  *
11724
- * Promoted from `loops/src/supervisor-control.ts`. Steer admission now requires the exact worker id
11725
- * and a caller-owned operation id. One atomic request file is the durable inbox; the NDJSON file is
11726
- * 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.
11727
11655
  *
11728
11656
  * @experimental
11729
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
+ }
11730
11672
  /** The root every supervisor run of one workspace lives under. */
11731
11673
  function supervisorRunsRoot(rootDir) {
11732
11674
  return join(resolve(rootDir), ".agent", "supervisor");
@@ -12350,6 +12292,20 @@ function writeLine(fd, line) {
12350
12292
  offset += written;
12351
12293
  }
12352
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
+ }
12353
12309
  /**
12354
12310
  * The receipt for the root stream a run directory holds, recomputed from the file's bytes, or
12355
12311
  * `undefined` when the directory holds none. This is what a run that never settled — a root that
@@ -12360,15 +12316,20 @@ async function readRootStreamReceipt(runDir, file = ROOT_STREAM_FILE) {
12360
12316
  const path = resolve(runDir, file);
12361
12317
  const hash = createHash("sha256");
12362
12318
  let events = 0;
12319
+ const subagentSessionIds = /* @__PURE__ */ new Set();
12363
12320
  try {
12364
- for await (const _record of readCommittedJsonLines(path, { onBytes: (bytes) => void hash.update(bytes) })) events += 1;
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
+ }
12365
12325
  } catch (error) {
12366
12326
  if (isNoEntError(error)) return void 0;
12367
12327
  throw error;
12368
12328
  }
12369
12329
  return Object.freeze({
12370
12330
  ref: `sha256:${hash.digest("hex")}`,
12371
- events
12331
+ events,
12332
+ ...subagentSessionIds.size > 0 ? { subagentSessionIds: [...subagentSessionIds] } : {}
12372
12333
  });
12373
12334
  }
12374
12335
  /** Every committed line of the root stream, in order, or `undefined` when there is no file. A
@@ -13767,6 +13728,342 @@ async function recoverRetainedChildren(args, children, spawn, next) {
13767
13728
  throw error;
13768
13729
  }
13769
13730
  }
13770
- export { hasRetriableTeardowns as $, readBoundHarnessSessions as $n, linkAbort as $r, FileResultBlobStore as $t, isTraceAnalysisStore as A, mergeTraceEnv as An, recordRuntimeOwnedProviderModel as Ar, scopeOwnerExecutorNodeContext as At, FileSpawnJournal as B, createRuntimeEventCollector as Bn, executableAgentProfileSnapshot as Br, scopeRetainedOwnerRestorePoint as Bt, writeWorkerSteerAcknowledgement as C, verifyObserverRecords as Cn, knownMaterializationReceipt as Cr, scopeRetainedOwnerResult as Ct, prepareScopeResume as D, workerTraceEnv as Dn, recordRuntimeOwnedProviderAttemptStart as Dr, recordScopeOwnerMaterialization as Dt, resolveWorkerSlots as E, WORKER_TRACE_PROPAGATION as En, recordRuntimeOwnedDriveHarnessProviderEvidence as Er, noteScopeOwnerPlannedExecution as Et, attachWorker as F, createOpenInferenceFileExporter as Fn, runtimeOwnedPendingExecutorMaterialization as Fr, beginScopeRetainedOwnerDrive as Ft, settledToIteration as G, captureBoundHarnessTranscript as Gn, RetainedRunProviderContractError as Gr, scopeRetainedOwnerContext as Gt, spawnJournalRecordEvents as H, sanitizeKnowledgeReadinessReport as Hn, isRuntimeFlowBrain as Hr, scopeRetainedOwnerCheckpointing as Ht, readWorkerInteractiveBinding as I, createOtelExporter as In, runtimeOwnedScopeOwnerControl as Ir, scopeRetainedOwnerSettled as It, freeSlots as J, harnessTranscriptFromLines as Jn, exactSession as Jr, ownedTreeRootSpawn as Jt, assertRecursiveReservationPolicy as K, captureHarnessTranscript as Kn, assertEventBinding as Kr, scopeRetainedOwnerJournalRoot as Kt, reconnectRetainedInteractiveRun as L, flatOtelSpan as Ln, runtimeOwnedScopeOwnerRuntime as Lr, scopeRetainedOwnerTurnEnded as Lt, workerTraceAnalysisStore as M, buildLoopOtelSpans as Mn, runtimeOwnedExecutorExecutionBinding as Mr, coordinationProfileToolPrefix as Mt, nestedScopeSeamKey as N, buildLoopSpanNodes as Nn, runtimeOwnedExecutorMaterialization as Nr, providerVisibleProfile as Nt, sumSpendFromEvents as O, workerTraceHeaders as On, recordRuntimeOwnedProviderDispatchNotStarted as Or, restoreScopeOwnerAcceptedExecution as Ot, interactiveAdmissionSeamKey as P, buildRuntimeEventOtelSpans as Pn, runtimeOwnedExecutorProviderEvidence as Pr, runtimeGraphMetadataKey as Pt, closeScopeAdmission as Q, HARNESS_TRANSCRIPT_READ_LIMITS as Qn, abortReason as Qr, nestedDriverTreeRoot as Qt, recoverRetainedInteractiveRun as R, generateSpanId as Rn, unknownExecutionBindingReceipt as Rr, assertRetainedOwnerCoordinationRekey as Rt, writeWorkerSteer as S, observerRecordDigest as Sn, knownExecutionBindingReceipt as Sr, scopeRetainedOwnerPriorSpend as St, openSlotGroup as T, environmentGone as Tn, providerAttemptEvidence as Tr, finalizeScopeOwnerMaterialization as Tt, appendTeardownConfirmation as U, sanitizeRuntimeStreamEvent as Un, runtimeFlowAgent as Ur, bindScopeRetainedOwnerEnvironmentId as Ut, appendSpawnEvents as V, createRuntimeStreamEventCollector as Vn, executableAgentSpecSnapshot as Vr, noteScopeRetainedOwnerCoordination as Vt, reenteredWorkers as W, HARNESS_TRANSCRIPT_SETTLE_TIMEOUT_MS as Wn, runtimeFlowBrain as Wr, bindScopeRetainedOwnerProvider as Wt, rollingDispatch as X, persistHarnessTranscript as Xn, sameControlCoordinates as Xr, attestNestedDriverTreeOwner as Xt, queueOf as Y, harnessTranscriptUnavailable as Yn, freezeControlRef as Yr, materializeTreeView as Yt, closeRetainedSlots as Z, readHarnessTranscript as Zn, RunCancellationReason as Zr, driverRuntime as Zt, supervisorRunDir as _, acceptedInteractiveControl as _n, attestRuntimeOwnedScopeOwner as _r, environmentReader as _t, readRootStream as a, executorObservationSeamKey as an, readWorkerProgress as ar, recordScopeOwnerPause as at, workerInboxFileFromEventDir as b, FileObserverJournal as bn, finalizeRuntimeOwnedPendingExecutor as br, consumeScopeRetainedOwnerResult as bt, cancelWorker as c, stopRetainedNativeExecution as cn, SEAT_RECHECK_MS as cr, releaseRetainedEnvironments as ct, legacySupervisorRunsRoot as d, mintRetainedIdentity as dn, authoredSeatStages as dr, startScopeRecoveries as dt, runAbortable as ei, encodeResultBlob as en, resolveHarnessTranscriptCapture as er, meterRuntimeOwnedAccounting as et, readWorkerCancelRequests as f, reconnectRetainedRun as fn, seatExecutionProfile as fr, unconfirmedTeardowns as ft, safeWorkerFile as g, retainedCreateMaterial as gn, attestRuntimeOwnedPendingExecutor as gr, scopeRetainedOwnerResourceReader as gt, readWorkerSteerRequests as h, startRetainedRunInEnvironment as hn, attestRuntimeOwnedExecutor as hr, releaseScopeRetainedOwnerForSeatPause as ht, nodeStreamFile as i, providerMessageText as ii, executorObservation as in, describeToolArgs as ir, recordScopeOwnerDriverAttempt as it, parseWorkerToolTraceArtifact as j, readTraceContextFromEnv as jn, runtimeOwnedDriveHarnessProviderEvidence as jr, coordinationMcpAlias as jt, replaySpawnTree as k, createPropagatingTraceEmitter as kn, recordRuntimeOwnedProviderIdentityConflict as kr, scopeOwnerExecutionObservation as kt, claimWorkerSteerDelivery as l, verifyWorkspaceMarker as ln, SeatChain as lr, retryUnconfirmedTeardowns as lt, readWorkerSteerAcknowledgement as m, startRetainedRun as mn, attestRuntimeOwnedDeferredExecutor as mr, prepareScopeRetainedOwnerSeatSwitch as mt, ROOT_STREAM_FILE as n, promptFromAgentTurnInput as ni, closesCursorSlot as nn, DEFAULT_STALL_AFTER_MS as nr, pauseScopeSeatAdmission as nt, readRootStreamReceipt as o, observeModelRequest as on, isLiveNodeStatus as or, registerNestedTeardownScope as ot, readWorkerCancellation as p, recoverRetainedRun as pn, subscriptionUsageLimitSignal as pr, waitForScopeChildren as pt, deriveNodeExecutionIdentity as q, harnessTranscriptArtifact as qn, awaitAbortable$1 as qr, loadSpawnForest as qt, createRootStreamSink as r, promptOptionsFromAgentTurnInput as ri, insideCursorNamespace as rn, createActivityLog as rr, recordScopeOwnerBarrenPause as rt, appendWorkerControlEvent as s, describeRetainedNativeStops as sn, isTerminalNodeStatus as sr, releaseHeldEnvironments as st, createScope as t, signalAbortError as ti, SpawnEventIndex as tn, retainHarnessTranscript as tr, meterRuntimeOwnedProviderAttempt as tt, legacySupervisorRunDir as u, assertRetainedRunReplayMaterial as un, SeatPauseError as ur, settleManagerWork as ut, supervisorRunsRoot as v, offerInteractiveControl as vn, authoredAgentIdentity as vr, resolveSpawnResourcePaths as vt, createWorkerSlots as w, destroyInteractiveEnvironment as wn, newExecutionAttemptId as wr, beginScopeOwnerAttempt as wt, writeWorkerCancellation as x, createFileObserverHooks as xn, inheritRuntimeOwnedExecutorAttestation as xr, prepareScopeRetainedOwnerTask as xt, workerCancellationsDir as y, prepareInteractiveDelegation as yn, authoredProfileDigest as yr, abandonScopeRetainedOwnerInvocation as yt, startRetainedInteractiveRun as z, loopEventToOtelSpan as zn, unknownMaterializationReceipt as zr, reconcileScopeRetainedOwnerEnvironment as zt };
13731
+ // src/durable/spawn-journal/provider-model-evidence.ts
13732
+ /**
13733
+ * Aggregate provider-served model evidence across one recursively owned journal forest.
13734
+ *
13735
+ * The parent tree records an owned driver's terminal summary, while that driver's own inference
13736
+ * and descendants live in its owned tree. This reducer follows the forest reader's explicit tree
13737
+ * edges and consumes each tree's metered attempts plus terminal leaf settlements exactly once.
13738
+ * A summary node is skipped because its owned tree supplies the source records. Plan materialization
13739
+ * is intentionally not read: it proves only what Runtime requested, not what a provider served.
13740
+ *
13741
+ * Missing legacy evidence, an unfinished node, an unopened owned tree, an identity conflict, and a
13742
+ * paid attempt without a qualified served snapshot all return unknown. An explicit
13743
+ * `providerDispatch: "not_started"` arm and Runtime accounting-only metered records are ignored
13744
+ * for served-model identity, while absent or malformed legacy data remains unknown here.
13745
+ */
13746
+ function aggregateProviderModelEvidence(forest) {
13747
+ const attempts = [];
13748
+ const observedModels = /* @__PURE__ */ new Set();
13749
+ const canonicalModels = /* @__PURE__ */ new Set();
13750
+ const nodes = /* @__PURE__ */ new Map();
13751
+ let reason;
13752
+ let sawProviderEvidence = false;
13753
+ for (const node of forest.nodes) nodes.set(forestNodeKey(node.treeRoot, node.id), node);
13754
+ if (forest.missingTrees.length > 0 || forest.inDoubt.length > 0) reason = "provider-model-missing";
13755
+ const markMissing = () => {
13756
+ reason ??= "provider-model-missing";
13757
+ };
13758
+ const markConflict = () => {
13759
+ reason = "provider-model-conflict";
13760
+ };
13761
+ const append = (raw) => {
13762
+ sawProviderEvidence = true;
13763
+ if (!isProviderEvidenceObject(raw)) {
13764
+ markMissing();
13765
+ return;
13766
+ }
13767
+ if (raw.status === "unknown") {
13768
+ if (raw.reason === "provider-model-conflict") markConflict();
13769
+ else if (!hasOnlyNotStartedAttempts(raw)) markMissing();
13770
+ } else if (raw.status !== "known") {
13771
+ markMissing();
13772
+ return;
13773
+ }
13774
+ if (!Array.isArray(raw.attempts) || !Array.isArray(raw.models)) {
13775
+ markMissing();
13776
+ return;
13777
+ }
13778
+ const declared = /* @__PURE__ */ new Set();
13779
+ for (const model of raw.models) if (typeof model !== "string" || model.length === 0) markMissing();
13780
+ else declared.add(model);
13781
+ const observedInRecord = /* @__PURE__ */ new Set();
13782
+ for (const rawAttempt of raw.attempts) {
13783
+ if (!isProviderAttemptEvidence(rawAttempt)) {
13784
+ markMissing();
13785
+ continue;
13786
+ }
13787
+ if (rawAttempt.identityConflict === true) markConflict();
13788
+ const observations = [...rawAttempt.observations];
13789
+ if (rawAttempt.providerDispatch === "not_started") {
13790
+ if (observations.length > 0) markMissing();
13791
+ attempts.push(Object.freeze({
13792
+ observations: Object.freeze([]),
13793
+ providerDispatch: "not_started",
13794
+ ...rawAttempt.identityConflict === true ? { identityConflict: true } : {}
13795
+ }));
13796
+ continue;
13797
+ }
13798
+ if (observations.length === 0) {
13799
+ markMissing();
13800
+ attempts.push(Object.freeze({
13801
+ observations: Object.freeze([]),
13802
+ ...rawAttempt.identityConflict === true ? { identityConflict: true } : {}
13803
+ }));
13804
+ continue;
13805
+ }
13806
+ let base;
13807
+ let snapshot;
13808
+ for (const model of observations) {
13809
+ if (typeof model !== "string" || model.length === 0) {
13810
+ markMissing();
13811
+ continue;
13812
+ }
13813
+ observedInRecord.add(model);
13814
+ observedModels.add(model);
13815
+ const identity = servedIdentityParts(model);
13816
+ if (identity === void 0) {
13817
+ markMissing();
13818
+ continue;
13819
+ }
13820
+ if (base !== void 0 && base !== identity.base) markConflict();
13821
+ if (snapshot !== void 0 && identity.snapshot !== void 0 && snapshot !== identity.snapshot) markConflict();
13822
+ base = identity.base;
13823
+ snapshot ??= identity.snapshot;
13824
+ if (identity.snapshot !== void 0) {
13825
+ canonicalModels.add(`${identity.base}@${identity.snapshot}`);
13826
+ if (canonicalModels.size > 1) markConflict();
13827
+ }
13828
+ }
13829
+ if (base === void 0 || snapshot === void 0) markMissing();
13830
+ attempts.push(Object.freeze({
13831
+ observations: Object.freeze(observations),
13832
+ ...rawAttempt.identityConflict === true ? { identityConflict: true } : {}
13833
+ }));
13834
+ }
13835
+ if (declared.size !== observedInRecord.size || [...declared].some((model) => !observedInRecord.has(model))) markMissing();
13836
+ };
13837
+ const terminal = (treeRoot, event) => {
13838
+ const node = nodes.get(forestNodeKey(treeRoot, event.id));
13839
+ if (node === void 0) {
13840
+ markMissing();
13841
+ return;
13842
+ }
13843
+ if (node.ownedTreeRoot !== void 0) return;
13844
+ if (event.kind === "cancelled" && event.providerModel === void 0) {
13845
+ markMissing();
13846
+ return;
13847
+ }
13848
+ append(event.providerModel ?? node.providerModel);
13849
+ };
13850
+ for (const tree of forest.trees) for (const event of tree.events) if (event.kind === "metered") {
13851
+ if (event.accountingOnly === true) {
13852
+ if (event.providerModel !== void 0) markMissing();
13853
+ continue;
13854
+ }
13855
+ if (nodes.get(forestNodeKey(tree.root, event.id))?.ownedTreeRoot !== void 0) continue;
13856
+ const evidence = event.providerModel;
13857
+ if (evidence === void 0) markMissing();
13858
+ else append(evidence);
13859
+ } else if (event.kind === "settled" || event.kind === "cancelled") terminal(tree.root, event);
13860
+ if (!sawProviderEvidence || attempts.length === 0 || observedModels.size === 0) reason ??= "provider-model-missing";
13861
+ const models = Object.freeze([...observedModels]);
13862
+ const frozenAttempts = Object.freeze(attempts);
13863
+ return Object.freeze(reason === void 0 ? {
13864
+ status: "known",
13865
+ attempts: frozenAttempts,
13866
+ models
13867
+ } : {
13868
+ status: "unknown",
13869
+ attempts: frozenAttempts,
13870
+ models,
13871
+ reason
13872
+ });
13873
+ }
13874
+ function forestNodeKey(treeRoot, nodeId) {
13875
+ return `${treeRoot}\u0000${nodeId}`;
13876
+ }
13877
+ function isProviderEvidenceObject(value) {
13878
+ return typeof value === "object" && value !== null && !Array.isArray(value) && "status" in value;
13879
+ }
13880
+ function hasOnlyNotStartedAttempts(evidence) {
13881
+ return Array.isArray(evidence.attempts) && evidence.attempts.length > 0 && evidence.attempts.every((attempt) => isProviderAttemptEvidence(attempt) && attempt.providerDispatch === "not_started" && attempt.observations.length === 0 && attempt.identityConflict !== true);
13882
+ }
13883
+ function isProviderAttemptEvidence(value) {
13884
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Array.isArray(value.observations) && (value.providerDispatch === void 0 || value.providerDispatch === "not_started");
13885
+ }
13886
+ function servedIdentityParts(model) {
13887
+ const at = model.lastIndexOf("@");
13888
+ const rawBase = at < 0 ? model : model.slice(0, at);
13889
+ const base = rawBase.slice(rawBase.lastIndexOf("/") + 1);
13890
+ if (base.length === 0) return void 0;
13891
+ const snapshot = at < 0 ? void 0 : model.slice(at + 1);
13892
+ if (snapshot !== void 0 && snapshot.length === 0) return void 0;
13893
+ return snapshot === void 0 ? { base } : {
13894
+ base,
13895
+ snapshot
13896
+ };
13897
+ }
13898
+ // src/runtime/supervise/settled-usage.ts
13899
+ /** Use ownership edges to avoid counting parent summaries as additional inference attempts. */
13900
+ function settledUsage(forest, spent, providerModel, profile) {
13901
+ let unknownAttempts = 0;
13902
+ const nodes = new Map(forest.nodes.map((node) => [`${node.treeRoot}\0${node.id}`, node]));
13903
+ for (const tree of forest.trees) {
13904
+ const metered = new Set(tree.events.filter((event) => event.kind === "metered" && !event.accountingOnly).map((event) => event.id));
13905
+ for (const event of tree.events) {
13906
+ if (nodes.get(`${tree.root}\0${event.id}`)?.ownedTreeRoot !== void 0) continue;
13907
+ if (event.kind === "metered" && event.accountingOnly) {
13908
+ if (event.spend.tokensKnown === false || event.spend.tokens.tokensKnown === false) unknownAttempts = null;
13909
+ continue;
13910
+ }
13911
+ let spend;
13912
+ if (event.kind === "metered" && !event.accountingOnly) spend = event.spend;
13913
+ else if ((event.kind === "settled" || event.kind === "cancelled") && !metered.has(event.id)) spend = event.spent;
13914
+ else continue;
13915
+ if (spend && spend.tokensKnown !== false && spend.tokens.tokensKnown !== false) continue;
13916
+ const evidence = event.providerModel;
13917
+ if (evidence?.attempts?.length !== 1) unknownAttempts = null;
13918
+ else if (unknownAttempts !== null && evidence.attempts[0]?.providerDispatch !== "not_started") unknownAttempts++;
13919
+ }
13920
+ }
13921
+ if (forest.inDoubt.length || forest.missingTrees.length) unknownAttempts = null;
13922
+ const status = spent.tokensKnown === true ? "known" : spent.tokens.input > 0 || spent.tokens.output > 0 ? "lower-bound" : "unknown";
13923
+ return {
13924
+ tokens: {
13925
+ status,
13926
+ input: status === "unknown" ? null : spent.tokens.input,
13927
+ output: status === "unknown" ? null : spent.tokens.output,
13928
+ unknownAttempts: status === "known" ? 0 : unknownAttempts === 0 ? null : unknownAttempts
13929
+ },
13930
+ requestedModel: {
13931
+ id: profile?.model?.default ?? null,
13932
+ harness: profile?.harness ?? null,
13933
+ provider: profile?.model?.provider ?? null
13934
+ },
13935
+ observedModels: providerModel.models
13936
+ };
13937
+ }
13938
+ // src/runtime/supervise/supervisor-accounting.ts
13939
+ /** Terminal usage comes from receipts, never admission's conservative reservation charges.
13940
+ * Missing receipts stay named gaps; ms is wall time because parallel executor times overlap. */
13941
+ async function terminalAccounting(journal, root, elapsedMs, profile) {
13942
+ const forest = await loadSpawnForest(journal, root);
13943
+ const rootTree = forest.trees[0];
13944
+ for (const event of rootTree.events) {
13945
+ const spend = event.kind === "metered" ? event.spend : event.kind === "settled" || event.kind === "cancelled" || event.kind === "reconciled" ? event.spent : void 0;
13946
+ if (spend !== void 0) assertValidSpend(spend, `settlement ${event.id}/${event.kind}`);
13947
+ }
13948
+ const { childWork, driverInference } = sumMeasuredSpendFromEvents([...rootTree.events]);
13949
+ const gaps = spendGapsFromEvents(forest.events.map((entry) => entry.event));
13950
+ const providerModel = aggregateProviderModelEvidence(forest);
13951
+ const fleetYield = fleetYieldFromForest(forest);
13952
+ const summed = addSpend(childWork, driverInference);
13953
+ const spentTotal = {
13954
+ ...summed,
13955
+ ms: Math.max(0, elapsedMs),
13956
+ tokensKnown: summed.tokensKnown !== false && summed.tokens.tokensKnown !== false && !gaps.some((gap) => gap.channels.includes("tokens")),
13957
+ usdKnown: summed.usdKnown !== false && !gaps.some((gap) => gap.channels.includes("usd"))
13958
+ };
13959
+ assertValidSpend(spentTotal, "settlement total");
13960
+ const forestNodes = forest.nodes.filter((node) => node.id !== forest.root);
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
+ },
13971
+ spentTotal,
13972
+ usage: settledUsage(forest, spentTotal, providerModel, profile),
13973
+ childWork,
13974
+ driverInference,
13975
+ gaps,
13976
+ providerModel,
13977
+ fleetYield
13978
+ };
13979
+ }
13980
+ /** Count each child by its last terminal record across the owned forest; roots are exempt. */
13981
+ function fleetYieldFromForest(forest) {
13982
+ const spawned = /* @__PURE__ */ new Set();
13983
+ const terminal = /* @__PURE__ */ new Map();
13984
+ const released = /* @__PURE__ */ new Set();
13985
+ const unconfirmed = /* @__PURE__ */ new Set();
13986
+ for (const { event } of forest.events) if (event.kind === "spawned") {
13987
+ if (event.parent !== void 0) spawned.add(event.id);
13988
+ } else if (event.kind === "settled" || event.kind === "cancelled") {
13989
+ const bucket = event.kind === "cancelled" ? "cancelled" : event.status;
13990
+ terminal.set(event.id, bucket);
13991
+ released.delete(event.id);
13992
+ unconfirmed.delete(event.id);
13993
+ if (bucket !== "done" && event.retainedExecution === "released") released.add(event.id);
13994
+ if (bucket !== "done" && event.retainedExecution === "release-unconfirmed") unconfirmed.add(event.id);
13995
+ }
13996
+ const count = (bucket) => [...terminal.values()].filter((value) => value === bucket).length;
13997
+ const fleetYield = {
13998
+ spawned: spawned.size,
13999
+ done: count("done"),
14000
+ down: count("down"),
14001
+ cancelled: count("cancelled"),
14002
+ neverSettled: forest.inDoubt.length,
14003
+ releasedUnrecovered: released.size,
14004
+ releaseUnconfirmed: unconfirmed.size
14005
+ };
14006
+ if (fleetYield.done + fleetYield.down + fleetYield.cancelled + fleetYield.neverSettled !== fleetYield.spawned) throw new RuntimeRunStateError(`supervisor: fleet yield does not partition the spawned children of '${forest.root}' (${JSON.stringify(fleetYield)})`);
14007
+ if (fleetYield.releasedUnrecovered + fleetYield.releaseUnconfirmed > fleetYield.down + fleetYield.cancelled) throw new RuntimeRunStateError(`supervisor: released children exceed down + cancelled for '${forest.root}' (${JSON.stringify(fleetYield)})`);
14008
+ return fleetYield;
14009
+ }
14010
+ /** Merge missing-receipt channels by node and cause; the root settles in result.json. */
14011
+ function spendGapsFromEvents(events) {
14012
+ const labels = /* @__PURE__ */ new Map();
14013
+ const budgets = /* @__PURE__ */ new Map();
14014
+ const terminal = /* @__PURE__ */ new Set();
14015
+ for (const ev of events) if (ev.kind === "spawned") {
14016
+ labels.set(ev.id, ev.label);
14017
+ budgets.set(ev.id, ev.budget);
14018
+ } else if (closesCursorSlot(ev)) terminal.add(ev.id);
14019
+ const gaps = /* @__PURE__ */ new Map();
14020
+ const record = (id, kind, channels) => {
14021
+ if (channels.length === 0) return;
14022
+ const key = `${kind}:${id}`;
14023
+ const existing = gaps.get(key);
14024
+ if (existing !== void 0) for (const channel of channels) existing.channels.add(channel);
14025
+ else gaps.set(key, {
14026
+ id,
14027
+ kind,
14028
+ channels: new Set(channels)
14029
+ });
14030
+ };
14031
+ for (const ev of events) if (ev.kind === "spawned" && ev.parent !== void 0 && !terminal.has(ev.id)) record(ev.id, "never-settled", [
14032
+ "tokens",
14033
+ "usd",
14034
+ ...Object.keys(ev.budget.resources ?? {}).map((name) => `resource:${name}`)
14035
+ ]);
14036
+ else if (ev.kind === "cancelled" && ev.spent === void 0) record(ev.id, "unreported", [
14037
+ "tokens",
14038
+ "usd",
14039
+ ...Object.keys(budgets.get(ev.id)?.resources ?? {}).map((name) => `resource:${name}`)
14040
+ ]);
14041
+ else if (ev.kind === "settled" || ev.kind === "cancelled" && ev.spent !== void 0) record(ev.id, "unreported", unknownChannels(withBudgetResources(ev.spent, budgets.get(ev.id) ?? {})));
14042
+ else if (ev.kind === "metered") record(ev.id, "unreported", unknownChannels(ev.spend));
14043
+ return [...gaps.values()].map((gap) => {
14044
+ const label = labels.get(gap.id);
14045
+ return {
14046
+ id: gap.id,
14047
+ ...label !== void 0 ? { label } : {},
14048
+ kind: gap.kind,
14049
+ channels: [...gap.channels]
14050
+ };
14051
+ });
14052
+ }
14053
+ /** The channels a single spend record explicitly marks incomplete. */
14054
+ function unknownChannels(spend) {
14055
+ const channels = [];
14056
+ if (spend.tokensKnown === false || spend.tokens.tokensKnown === false) channels.push("tokens");
14057
+ if (spend.usdKnown === false) channels.push("usd");
14058
+ for (const [name, resource] of Object.entries(spend.resources ?? {})) if (!resource.known) channels.push(`resource:${name}`);
14059
+ return channels;
14060
+ }
14061
+ /** True when any driver metered inference this run (so the winner carries a `spentBreakdown`).
14062
+ * Checks every channel `addSpend` sums — including `ms` — so the gate stays consistent with the
14063
+ * total even though the coordination driver currently stamps `ms: 0`. */
14064
+ function isNonEmptySpend(s) {
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);
14066
+ }
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 };
13771
14068
 
13772
- //# sourceMappingURL=scope-gIYQ5ZiL.js.map
14069
+ //# sourceMappingURL=supervisor-accounting-Bw4MJk-b.js.map