@tangle-network/agent-runtime 2.3.8 → 2.3.11

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 (94) hide show
  1. package/dist/{activation-Cks1h3Nw.d.ts → activation-B8dLwJj6.d.ts} +2 -2
  2. package/dist/{activation-CDPbi8JA.js → activation-BzNeFJDB.js} +4 -4
  3. package/dist/{activation-CDPbi8JA.js.map → activation-BzNeFJDB.js.map} +1 -1
  4. package/dist/agent.d.ts +1 -1
  5. package/dist/agent.js +3 -3
  6. package/dist/{bundle-PDYNXIEf.js → bundle-DhV73H6U.js} +2 -2
  7. package/dist/{bundle-PDYNXIEf.js.map → bundle-DhV73H6U.js.map} +1 -1
  8. package/dist/candidate-execution/index.js +4 -4
  9. package/dist/{coordination-manager-DNrb7zMg.js → coordination-manager-BluavEgY.js} +42 -152
  10. package/dist/coordination-manager-BluavEgY.js.map +1 -0
  11. package/dist/{coordination-map-routing-CUvwHAtv.js → coordination-map-routing-BllzcZov.js} +5 -2
  12. package/dist/coordination-map-routing-BllzcZov.js.map +1 -0
  13. package/dist/{delegate-CHdWyLz9.js → delegate-CYWL0MrO.js} +2 -2
  14. package/dist/{delegate-CHdWyLz9.js.map → delegate-CYWL0MrO.js.map} +1 -1
  15. package/dist/durable-file-DstVeHcj.js.map +1 -1
  16. package/dist/durable.d.ts +1 -1
  17. package/dist/durable.js +42 -202
  18. package/dist/durable.js.map +1 -1
  19. package/dist/{graph-oOdDpXFH.js → graph-ByNMeS0j.js} +8 -13
  20. package/dist/graph-ByNMeS0j.js.map +1 -0
  21. package/dist/{graph-registry-72DlMnUl.js → graph-registry-D6y6zffC.js} +2 -2
  22. package/dist/{graph-registry-72DlMnUl.js.map → graph-registry-D6y6zffC.js.map} +1 -1
  23. package/dist/{graph-registry-CAX15LO6.js → graph-registry-rV9mwxQO.js} +1 -1
  24. package/dist/{improve-BCjhWbVk.js → improve-D9A79N5a.js} +7 -7
  25. package/dist/{improve-BCjhWbVk.js.map → improve-D9A79N5a.js.map} +1 -1
  26. package/dist/index.d.ts +3 -3
  27. package/dist/index.js +10 -11
  28. package/dist/index.js.map +1 -1
  29. package/dist/intelligence.d.ts +2 -2
  30. package/dist/intelligence.js +7 -6
  31. package/dist/intelligence.js.map +1 -1
  32. package/dist/kernel.d.ts +1 -1
  33. package/dist/kernel.js +12 -12
  34. package/dist/{loop-runner-Bkvx4Saw.d.ts → loop-runner-D9fuOb69.d.ts} +2 -2
  35. package/dist/{loop-runner-Cy1K7f-A.js → loop-runner-DiMfhI_z.js} +2 -2
  36. package/dist/{loop-runner-Cy1K7f-A.js.map → loop-runner-DiMfhI_z.js.map} +1 -1
  37. package/dist/loop-runner-bin.d.ts +1 -1
  38. package/dist/loop-runner-bin.js +1 -1
  39. package/dist/mcp/bin.js +3 -3
  40. package/dist/mcp/index.d.ts +1 -1
  41. package/dist/mcp/index.js +6 -5
  42. package/dist/mcp/index.js.map +1 -1
  43. package/dist/{prepare-Bv6X2Wnx.js → prepare-DrHOZFJy.js} +3 -4
  44. package/dist/{prepare-Bv6X2Wnx.js.map → prepare-DrHOZFJy.js.map} +1 -1
  45. package/dist/{profile-Dir2A1_2.js → profile-BzzmKuIX.js} +3 -2
  46. package/dist/{profile-Dir2A1_2.js.map → profile-BzzmKuIX.js.map} +1 -1
  47. package/dist/profiles.d.ts +1 -1
  48. package/dist/{provision-supervisor-Pd95wA1X.js → provision-supervisor-DP5U58V4.js} +13 -5
  49. package/dist/provision-supervisor-DP5U58V4.js.map +1 -0
  50. package/dist/{run-cancellation-CzQZUlHr.js → run-cancellation-BxoFCpJ_.js} +208 -5
  51. package/dist/run-cancellation-BxoFCpJ_.js.map +1 -0
  52. package/dist/{runtime-CWZ9q7lU.js → runtime-DS5AgACg.js} +15 -16
  53. package/dist/{runtime-CWZ9q7lU.js.map → runtime-DS5AgACg.js.map} +1 -1
  54. package/dist/sandbox-profile-compat-DxegoIK5.js +65 -0
  55. package/dist/sandbox-profile-compat-DxegoIK5.js.map +1 -0
  56. package/dist/scope-DRaBilEX.js +16734 -0
  57. package/dist/scope-DRaBilEX.js.map +1 -0
  58. package/dist/{search-executor-CMPbMvpr.js → search-executor-DLo_9jUv.js} +4 -5
  59. package/dist/{search-executor-CMPbMvpr.js.map → search-executor-DLo_9jUv.js.map} +1 -1
  60. package/dist/{server-DyTbsg7n.js → server-DfHV4ajw.js} +4 -4
  61. package/dist/{server-DyTbsg7n.js.map → server-DfHV4ajw.js.map} +1 -1
  62. package/dist/{structural-rollout-BUqZsIhX.js → structural-rollout-BIjjZ_XX.js} +4 -5
  63. package/dist/{structural-rollout-BUqZsIhX.js.map → structural-rollout-BIjjZ_XX.js.map} +1 -1
  64. package/dist/{supervise-D1eYde7p.js → supervise-6BCQ73Xa.js} +34 -136
  65. package/dist/supervise-6BCQ73Xa.js.map +1 -0
  66. package/dist/{supervision-discovery-DmDWHwLu.js → supervision-discovery-Da5zebY2.js} +130 -5
  67. package/dist/supervision-discovery-Da5zebY2.js.map +1 -0
  68. package/dist/{supervisor-B8beIEqH.js → supervisor-3JHXy3tz.js} +7336 -19680
  69. package/dist/supervisor-3JHXy3tz.js.map +1 -0
  70. package/dist/testing.d.ts +1 -1
  71. package/dist/testing.js +13 -13
  72. package/dist/{top-web-CUDlOKu0.js → top-web-DK6lTMHG.js} +89 -64
  73. package/dist/top-web-DK6lTMHG.js.map +1 -0
  74. package/dist/tui/bin.js +3 -3
  75. package/dist/tui/index.d.ts +14 -8
  76. package/dist/tui/index.js +2 -2
  77. package/dist/{types-DzS0kBeK.d.ts → types-S3RbyJ4x.d.ts} +51 -65
  78. package/dist/{sandbox-profile-compat-DRiz9kME.js → workspace-streams-Dep2L6XD.js} +617 -57
  79. package/dist/workspace-streams-Dep2L6XD.js.map +1 -0
  80. package/package.json +2 -2
  81. package/dist/coordination-manager-DNrb7zMg.js.map +0 -1
  82. package/dist/coordination-map-routing-CUvwHAtv.js.map +0 -1
  83. package/dist/graph-oOdDpXFH.js.map +0 -1
  84. package/dist/provision-supervisor-Pd95wA1X.js.map +0 -1
  85. package/dist/root-stream-Bsh5WFcg.js +0 -4252
  86. package/dist/root-stream-Bsh5WFcg.js.map +0 -1
  87. package/dist/run-cancellation-CzQZUlHr.js.map +0 -1
  88. package/dist/sandbox-profile-compat-DRiz9kME.js.map +0 -1
  89. package/dist/supervise-D1eYde7p.js.map +0 -1
  90. package/dist/supervision-discovery-DmDWHwLu.js.map +0 -1
  91. package/dist/supervisor-B8beIEqH.js.map +0 -1
  92. package/dist/top-web-CUDlOKu0.js.map +0 -1
  93. package/dist/util-BX20UJ9X.js +0 -624
  94. package/dist/util-BX20UJ9X.js.map +0 -1
@@ -1,4252 +0,0 @@
1
- import { m as ValidationError, r as BackendTransportError } from "./errors-AzSuGI23.js";
2
- import { D as assertResources, E as addResourceSpend, T as zeroTokenUsage, c as errorMessage, i as chargedTokens, k as withBudgetResources, l as fnv1a32Hex, m as promptCacheTokenClasses, o as cloneTokenUsage, r as addTokenUsage, u as hasCompleteCacheBreakdown } from "./util-BX20UJ9X.js";
3
- import { d as detachedSnapshot, i as prepareJsonlAppend, l as sha256, m as errMessage, n as isNoEntError, o as readCommittedJsonLines, p as causeChain, t as appendCommittedJsonLine } from "./jsonl-file-BBP9Dz9w.js";
4
- import { a as writeAtomicDurableFile, n as assertNoSymlinkDescendant, o as captureRunWriteContext, r as publishExclusiveDurableFile, s as fenceRunWrite, t as appendDurableFile } from "./durable-file-DstVeHcj.js";
5
- import { a as withPursuitContext } from "./runtime-hooks-CsW2pfFk.js";
6
- import { AgentTurnResultSchema, CanonicalStreamEventSchema, RuntimeEventEnvelopeSchema, canonicalCandidateDigest } from "@tangle-network/agent-interface";
7
- import { isRecord } from "@tangle-network/agent-core";
8
- import { collectAgentFinalMessageText, collectAgentResponseText } from "@tangle-network/sandbox";
9
- import { z } from "zod";
10
- import { createHash } from "node:crypto";
11
- import { LedgerCanonicalizationError, canonicalString, jsonDocument, merkleLeafHash, merkleRoot, merkleSalt } from "@tangle-network/agent-eval/ledger-core";
12
- import { appendFileSync, closeSync, constants, existsSync, fstatSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, statSync, writeSync } from "node:fs";
13
- import { dirname, join, resolve } from "node:path";
14
- import { mkdir } from "node:fs/promises";
15
- // src/record-value.ts
16
- function recordValue(value) {
17
- return isRecord(value) ? value : void 0;
18
- }
19
- // src/runtime/harness-usage.ts
20
- /**
21
- * Read and validate one codex `turn.completed` usage record.
22
- *
23
- * Every counter must be a non-negative safe integer. Two cross-field invariants hold, and they are
24
- * what states that the two named counters are SUBSETS of their totals rather than additions to
25
- * them: `cached_input_tokens <= input_tokens` and `reasoning_output_tokens <= output_tokens`.
26
- * Measured against the codex CLI: a turn reporting `output_tokens 1523` with
27
- * `reasoning_output_tokens 1516` answered with about seven tokens of text.
28
- *
29
- * `label` names the surface the record came from, so one message serves both readers.
30
- * Throws `ValidationError` on any violation.
31
- */
32
- function parseCodexUsageRecord(usage, label) {
33
- const record = recordValue(usage);
34
- if (record === void 0) throw new ValidationError(`${label}: usage must be an object, received ${describe(usage)}`);
35
- const inputTokens = naturalNumber(record.input_tokens, "input_tokens", label);
36
- const cachedInputTokens = naturalNumber(record.cached_input_tokens, "cached_input_tokens", label);
37
- const outputTokens = naturalNumber(record.output_tokens, "output_tokens", label);
38
- const reasoningOutputTokens = naturalNumber(record.reasoning_output_tokens, "reasoning_output_tokens", label);
39
- const cacheWriteInputTokens = record.cache_write_input_tokens === void 0 ? void 0 : naturalNumber(record.cache_write_input_tokens, "cache_write_input_tokens", label);
40
- if (cachedInputTokens > inputTokens) throw new ValidationError(`${label}: cached_input_tokens exceeds input_tokens`);
41
- if (reasoningOutputTokens > outputTokens) throw new ValidationError(`${label}: reasoning_output_tokens exceeds output_tokens`);
42
- return {
43
- inputTokens,
44
- cachedInputTokens,
45
- outputTokens,
46
- reasoningOutputTokens,
47
- ...cacheWriteInputTokens === void 0 ? {} : { cacheWriteInputTokens }
48
- };
49
- }
50
- /** Names the wire record every codex decode error is reported against. */
51
- const codexUsageContext = "codex turn.completed";
52
- /**
53
- * codex reports the tokens of a turn inside its own `turn.completed` event, which rides the
54
- * transport either as `{ type: 'raw', data: { type: 'turn.completed', usage: { … } } }` or as
55
- * the provider-normalized Interface envelope `{ type: 'raw', data: { type: 'raw', event: {
56
- * type: 'turn.completed', usage: { … } } } }`. It emits no canonical usage event, so this adapter
57
- * is a codex worker's only usage source.
58
- *
59
- * The record is read by `parseCodexUsageRecord`, the same reader `runLocalHarness` uses on the
60
- * codex CLI's own stdout, so both surfaces hold one field policy and both cross-field invariants.
61
- *
62
- * A `turn.completed` that carries a `usage` member the reader cannot read is a `ValidationError`
63
- * naming the field. A `turn.completed` with no `usage` member reports no usage at all, which
64
- * leaves the turn's tokens unknown rather than zero.
65
- */
66
- const decodeCodexTurnUsage = (event) => {
67
- if (String(event.type ?? "") !== "raw") return void 0;
68
- const envelope = recordValue(event.data);
69
- const data = envelope?.type === "raw" && recordValue(envelope.event) !== void 0 ? recordValue(envelope.event) : envelope;
70
- if (data === void 0 || data.type !== "turn.completed" || data.usage === void 0) return;
71
- const record = parseCodexUsageRecord(data.usage, codexUsageContext);
72
- return {
73
- harness: "codex",
74
- input: record.inputTokens,
75
- output: record.outputTokens,
76
- cachedInput: record.cachedInputTokens,
77
- reasoningOutput: record.reasoningOutputTokens,
78
- ...record.cacheWriteInputTokens === void 0 ? {} : { cacheWriteInput: record.cacheWriteInputTokens }
79
- };
80
- };
81
- /** Claude's adapter forwards one delta per response snapshot, keyed and deduplicated there. */
82
- const decodeClaudeAssistantUsage = (event) => {
83
- if (String(event.type ?? "") !== "raw") return void 0;
84
- const envelope = recordValue(event.data);
85
- const data = envelope?.type === "raw" && recordValue(envelope.event) !== void 0 ? recordValue(envelope.event) : envelope;
86
- if (data?.type !== "assistant.usage") return void 0;
87
- if (typeof data.message_id !== "string" || data.message_id.length === 0) throw new ValidationError("claude assistant.usage: missing response identity");
88
- const usage = recordValue(data.usage);
89
- if (!usage) throw new ValidationError("claude assistant.usage: usage must be an object");
90
- const fresh = naturalNumber(usage.input_tokens, "input_tokens", "claude assistant.usage");
91
- const cachedInput = naturalNumber(usage.cache_read_input_tokens, "cache_read_input_tokens", "claude assistant.usage");
92
- const cacheWriteInput = naturalNumber(usage.cache_creation_input_tokens, "cache_creation_input_tokens", "claude assistant.usage");
93
- const output = naturalNumber(usage.output_tokens, "output_tokens", "claude assistant.usage");
94
- const input = fresh + cachedInput + cacheWriteInput;
95
- if (!Number.isSafeInteger(input)) throw new ValidationError("claude assistant.usage: total input tokens exceed a safe integer");
96
- return {
97
- harness: "claude-code",
98
- input,
99
- output,
100
- cachedInput,
101
- cacheWriteInput
102
- };
103
- };
104
- /**
105
- * The harness → usage-decoder registry. Add a harness by adding one entry.
106
- *
107
- * Keyed by `HarnessType`, the vocabulary the `harness` argument is drawn from, so a key no caller
108
- * can produce does not compile. A harness absent from this registry reports usage through the
109
- * canonical events or not at all.
110
- */
111
- const harnessUsageDecoders = {
112
- codex: decodeCodexTurnUsage,
113
- "claude-code": decodeClaudeAssistantUsage
114
- };
115
- /**
116
- * Decode a sandbox event with one harness's adapter, or `undefined` when the event carries no
117
- * harness-native usage.
118
- *
119
- * A NAMED harness reads with that harness's adapter only, and a named harness with no adapter
120
- * reports nothing. It never falls through to another harness's adapter: a different harness's
121
- * `turn.completed` decoded as codex would either drop the counters codex does not name or fail on
122
- * a field codex requires, and both answers would be about the wrong harness. The composite over
123
- * every registered adapter runs only when the caller cannot name the harness.
124
- *
125
- * Throws `ValidationError` when an adapter recognizes the event as its harness's usage carrier and
126
- * cannot read the numbers.
127
- */
128
- function decodeHarnessUsage(event, harness) {
129
- if (!event || typeof event !== "object") return void 0;
130
- if (harness !== void 0) return harnessUsageDecoders[harness]?.(event);
131
- for (const decode of new Set(Object.values(harnessUsageDecoders))) {
132
- const usage = decode(event);
133
- if (usage !== void 0) return usage;
134
- }
135
- }
136
- function naturalNumber(value, field, label) {
137
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new ValidationError(`${label}: usage.${field} must be a non-negative safe integer, received ${describe(value)}`);
138
- return value;
139
- }
140
- function describe(value) {
141
- if (value === null) return "null";
142
- if (typeof value === "number" || typeof value === "boolean") return String(value);
143
- if (typeof value === "string") return JSON.stringify(value);
144
- if (Array.isArray(value)) return "an array";
145
- return typeof value;
146
- }
147
- // src/runtime/sandbox-events.ts
148
- /**
149
- * Forward a sandbox event to an optional observer without letting observer
150
- * behavior affect the run. The observer receives a defensive copy, synchronous
151
- * throws are swallowed, and returned promises are deliberately not awaited.
152
- */
153
- function notifySandboxEventObserver(event, observer, meta) {
154
- if (!observer) return;
155
- try {
156
- const result = observer(cloneEventForObserver(event), meta);
157
- if (result && typeof result.then === "function") result.then(void 0, () => {});
158
- } catch {}
159
- }
160
- function cloneEventForObserver(event) {
161
- try {
162
- return structuredClone(event);
163
- } catch {
164
- return copyPlainSpine(event, /* @__PURE__ */ new WeakMap());
165
- }
166
- }
167
- function copyPlainSpine(value, seen) {
168
- if (value === null || typeof value !== "object") return value;
169
- const existing = seen.get(value);
170
- if (existing !== void 0) return existing;
171
- if (Array.isArray(value)) {
172
- const copy = [];
173
- seen.set(value, copy);
174
- for (const item of value) copy.push(copyPlainSpine(item, seen));
175
- return copy;
176
- }
177
- const proto = Object.getPrototypeOf(value);
178
- if (proto !== Object.prototype && proto !== null) return {};
179
- const copy = {};
180
- seen.set(value, copy);
181
- for (const [key, child] of Object.entries(value)) copy[key] = copyPlainSpine(child, seen);
182
- return copy;
183
- }
184
- /**
185
- * Read the served execution identity off one Sandbox event.
186
- *
187
- * The platform reports `effectiveBackend` on `execution.started` and again on the terminal
188
- * event (`@tangle-network/sandbox`, `EffectiveBackend`). Absence returns `undefined`
189
- * and must stay unknown — a request is not a receipt, so nothing here may be inferred from
190
- * what was asked for.
191
- */
192
- function sandboxEventServedBackend(event) {
193
- if (!event || typeof event !== "object") return void 0;
194
- const served = recordValue(recordValue(event.data)?.effectiveBackend);
195
- if (!served) return void 0;
196
- const provider = firstString(served.provider);
197
- const model = firstString(served.model);
198
- const source = firstString(served.source);
199
- if (provider === void 0 && model === void 0) return void 0;
200
- return {
201
- ...provider !== void 0 ? { provider } : {},
202
- ...model !== void 0 ? { model } : {},
203
- ...source !== void 0 ? { source } : {}
204
- };
205
- }
206
- /**
207
- * Fail the execution when the platform reports serving a model other than the exact one asked for.
208
- *
209
- * Measured motive (agent-runtime#892, live infrastructure): 6 of 6 boxes whose profile
210
- * declared `zai-coding-plan/glm-5.2` reported
211
- * `{"provider":"openai-compat","model":"deepseek/deepseek-v4-flash","source":"environment"}`,
212
- * while the materialization receipt recorded the declared model as `status: "known"`. Sending
213
- * `backend.model` makes that substitution unlikely; only reading the report back makes it
214
- * detectable. A run that cannot say which model produced its evidence must not settle as one
215
- * that can.
216
- *
217
- * Silent when the platform reports no served model: unobserved stays unobserved.
218
- */
219
- function assertSandboxServedModel(event, expected) {
220
- const wanted = expected?.model?.trim();
221
- if (!wanted) return;
222
- const served = sandboxEventServedBackend(event);
223
- if (served?.model === void 0) return;
224
- if (sameModelId(served.model, wanted, [served.provider, expected?.provider])) return;
225
- const attribution = [served.provider !== void 0 ? `provider ${JSON.stringify(served.provider)}` : void 0, served.source !== void 0 ? `source ${JSON.stringify(served.source)}` : void 0].filter((part) => part !== void 0);
226
- const detail = attribution.length > 0 ? ` (${attribution.join(", ")})` : "";
227
- throw new Error(`sandbox served model ${JSON.stringify(served.model)}${detail} instead of the exact profile model ${JSON.stringify(wanted)}`);
228
- }
229
- /**
230
- * Do two model ids name the same model, allowing for routing prefixes?
231
- *
232
- * Either side may spell the model bare (`glm-5.2`), provider-qualified
233
- * (`zai-coding-plan/glm-5.2`), or route-qualified, and the platform reports the provider in its
234
- * own field rather than always in the id. So the comparison drops any known provider prefix and
235
- * then accepts a `/`-boundary suffix match: a longer route to the SAME leaf model is a routing
236
- * difference, not a substitution.
237
- *
238
- * What it deliberately does NOT absorb is a different leaf: `glm-5.2` against `glm-5.3` stays a
239
- * mismatch, which is the substitution measured directly against the provider API (a request for
240
- * `glm-5.2` was served `glm-5.3`). A version suffix is the whole difference between two
241
- * instruments, so nothing here may treat it as noise.
242
- */
243
- function sameModelId(served, wanted, providers) {
244
- const a = stripProviderPrefix(served, providers);
245
- const b = stripProviderPrefix(wanted, providers);
246
- return a === b || a.endsWith(`/${b}`) || b.endsWith(`/${a}`);
247
- }
248
- function stripProviderPrefix(id, providers) {
249
- let out = id.trim().toLowerCase();
250
- for (const provider of providers) {
251
- const prefix = provider?.trim().toLowerCase();
252
- if (prefix && out.startsWith(`${prefix}/`)) out = out.slice(prefix.length + 1);
253
- }
254
- return out;
255
- }
256
- function firstString(...values) {
257
- return values.find((value) => typeof value === "string" && value.length > 0);
258
- }
259
- const terminalTypes = /* @__PURE__ */ new Set([
260
- "message.completed",
261
- "result",
262
- "final",
263
- "done"
264
- ]);
265
- /** True for an event type that ends a sandbox turn. */
266
- function isSandboxTerminalEvent(type) {
267
- return terminalTypes.has(type);
268
- }
269
- /**
270
- * Which member of a terminal event's `data` carries its usage receipt.
271
- *
272
- * The one place the terminal types legitimately differ, stated rather than implied by two lists:
273
- * sandbox 0.4.0's `done` reports under `tokenUsage` with the cost at the top level, every other
274
- * terminal type reports under `usage`.
275
- */
276
- function sandboxTerminalUsageField(type) {
277
- return type === "done" ? "tokenUsage" : "usage";
278
- }
279
- /**
280
- * Extract a `RuntimeStreamEvent`-shaped `llm_call` from a sandbox event when
281
- * the event carries usage/cost data. Returns `undefined` for non-cost events
282
- * so the kernel can iterate the full stream without branching.
283
- *
284
- * Pure by contract: it never throws on a failed run. The terminal truth
285
- * boundary is the public Sandbox outcome tracker, applied after the complete
286
- * stream. Post-hoc readers — {@link sumSandboxUsage}, the
287
- * analyst trace store, the chat projection — must stay able to read a failed
288
- * turn's events, which is when reading them matters most.
289
- *
290
- * Canonical cost-carrying types observed in the wild:
291
- * - `llm_call` — `data: { model, tokensIn, tokensOut, costUsd, ... }`
292
- * - `message.completed` / `result` — `data: { usage: { inputTokens,
293
- * outputTokens, totalCostUsd? } }`
294
- * - `cost.usage` / `usage` — same shape under a dedicated type
295
- *
296
- * Numeric coercion is strict: `Number.isFinite` gates every accumulator write
297
- * so a sentinel `NaN` from a misbehaving backend cannot poison the ledger.
298
- */
299
- function extractLlmCallEvent(event, agentRunName) {
300
- const data = canonicalUsageData(event);
301
- return data === void 0 ? void 0 : buildLlmCall(data, agentRunName);
302
- }
303
- function canonicalUsageData(event) {
304
- if (!event || typeof event !== "object") return void 0;
305
- const type = String(event.type ?? "");
306
- const data = event.data && typeof event.data === "object" ? event.data : {};
307
- if (type === "llm_call" || type === "cost.usage" || type === "usage") return usageReceiptData(data, recordValue(data.usage));
308
- if (type === "error" || isSandboxTerminalEvent(type)) {
309
- const field = type === "error" ? "tokenUsage" : sandboxTerminalUsageField(type);
310
- return usageReceiptData(data, recordValue(data[field]) ?? recordValue(data[field === "usage" ? "tokenUsage" : "usage"]));
311
- }
312
- }
313
- /** A {@link SandboxUsageLedger} for one worker. Pass the worker's harness to decode with that
314
- * harness's adapter; omit it to try every registered adapter. */
315
- function createSandboxUsageLedger(harness) {
316
- let held = [];
317
- let sawCanonical = false;
318
- const credited = {};
319
- const receipts = /* @__PURE__ */ new Map();
320
- const settledTokens = zeroTokenUsage();
321
- let turnTokens = zeroTokenUsage();
322
- let turnUsageUnknown = false;
323
- let settledUsage;
324
- return {
325
- settledUsage: () => settledUsage === void 0 ? void 0 : { ...settledUsage },
326
- tokenUsage() {
327
- const total = cloneTokenUsage(settledTokens);
328
- addTokenUsage(total, turnTokens);
329
- return total;
330
- },
331
- observe(event, agentRunName) {
332
- const call = extractLlmCallEvent(event, agentRunName);
333
- if (call) {
334
- sawCanonical = true;
335
- const receipt = creditCanonicalUsage(event, call, credited, receipts, (receipt, mode) => {
336
- refineCanonicalTokens(turnTokens, call, receipt, mode);
337
- });
338
- if (receipt?.tokensKnown === false) {
339
- turnTokens.tokensKnown = false;
340
- turnUsageUnknown = true;
341
- }
342
- return receipt;
343
- }
344
- let usage;
345
- try {
346
- usage = decodeHarnessUsage(event, harness);
347
- } catch (err) {
348
- turnUsageUnknown = true;
349
- return {
350
- type: "llm_call",
351
- model: agentRunName,
352
- tokensKnown: false,
353
- usdKnown: false,
354
- tokensUnknownReason: err instanceof Error ? err.message : String(err)
355
- };
356
- }
357
- if (usage) held.push(usage);
358
- },
359
- settleTurn(agentRunName) {
360
- const reports = held;
361
- const canonical = sawCanonical;
362
- held = [];
363
- sawCanonical = false;
364
- const receipt = !canonical && reports.length > 0 ? harnessUsageLlmCall(reports, agentRunName) : void 0;
365
- if (receipt) creditCanonicalUsage({
366
- type: "llm_call",
367
- data: {}
368
- }, receipt, credited, receipts, (credit, mode) => refineCanonicalTokens(turnTokens, receipt, credit, mode));
369
- settledUsage = !turnUsageUnknown && credited.tokensIn !== void 0 && credited.tokensOut !== void 0 ? {
370
- inputTokens: credited.tokensIn,
371
- outputTokens: credited.tokensOut,
372
- ...credited.totalTokens === void 0 ? {} : { totalTokens: credited.totalTokens },
373
- ...credited.reasoningTokens === void 0 ? {} : { reasoningTokens: credited.reasoningTokens },
374
- ...credited["promptCache.readTokens"] === void 0 ? {} : { cacheReadInputTokens: credited["promptCache.readTokens"] },
375
- ...credited["promptCache.writeTokens"] === void 0 ? {} : { cacheCreationInputTokens: credited["promptCache.writeTokens"] },
376
- ...credited.costUsd === void 0 ? {} : { cost: credited.costUsd }
377
- } : void 0;
378
- for (const key of Object.keys(credited)) delete credited[key];
379
- receipts.clear();
380
- addTokenUsage(settledTokens, turnTokens);
381
- turnTokens = zeroTokenUsage();
382
- turnUsageUnknown = false;
383
- return receipt;
384
- }
385
- };
386
- }
387
- /** Shared receipt semantics; an undeclared nonterminal observation is not a cumulative total. */
388
- function canonicalSandboxUsageMode(event) {
389
- const declaredMode = recordValue(event.data)?.usageMode;
390
- return declaredMode === "delta" || declaredMode === "cumulative" ? declaredMode : declaredMode === void 0 && event.type === "llm_call" ? "delta" : declaredMode === void 0 && (event.type === "error" || isSandboxTerminalEvent(String(event.type))) ? "cumulative" : void 0;
391
- }
392
- /** Terminal totals replace already credited usage; only per-call receipts add to it. */
393
- function creditCanonicalUsage(event, call, credited, receipts, onCredit) {
394
- const mode = canonicalSandboxUsageMode(event);
395
- const result = { ...call };
396
- const data = recordValue(event.data) ?? {};
397
- const usageData = canonicalUsageData(event) ?? {};
398
- const supplemental = Object.fromEntries(["totalTokens", "reasoningTokens"].flatMap((key) => {
399
- const value = usageData[key];
400
- return value === void 0 ? [] : [[key, typeof value === "number" && Number.isFinite(value) ? value : null]];
401
- }));
402
- const explicitTokensUnknown = [
403
- data,
404
- recordValue(data.usage),
405
- recordValue(data.tokenUsage)
406
- ].some((receipt) => ["tokensKnown", "tokens_known"].some((key) => receipt?.[key] !== void 0 && receipt[key] !== true));
407
- if (call.tokensIn === void 0 && call.tokensOut === void 0 && !call.tokensUnknownReason && !explicitTokensUnknown) delete result.tokensKnown;
408
- let reason = mode === void 0 ? "usage receipt has no declared delta or cumulative semantics" : void 0;
409
- if (event.id) {
410
- const signature = JSON.stringify({
411
- mode,
412
- call,
413
- supplemental
414
- });
415
- const previous = receipts.get(event.id);
416
- if (previous === signature) return void 0;
417
- if (previous !== void 0) return {
418
- type: "llm_call",
419
- model: call.model,
420
- tokensKnown: false,
421
- usdKnown: false,
422
- tokensUnknownReason: `usage receipt ${event.id} changed after it was credited`
423
- };
424
- receipts.set(event.id, signature);
425
- }
426
- const credit = (key, value) => {
427
- const previous = credited[key] ?? 0;
428
- if (!Number.isFinite(value) || value < 0) {
429
- reason = `usage receipt has an invalid ${key}`;
430
- return 0;
431
- }
432
- if (mode === "delta") {
433
- credited[key] = previous + value;
434
- return value;
435
- }
436
- if (value < previous) reason = `cumulative usage ${key} is below already credited usage`;
437
- credited[key] = Math.max(previous, value);
438
- return Math.max(0, value - previous);
439
- };
440
- for (const key of [
441
- "tokensIn",
442
- "tokensOut",
443
- "costUsd",
444
- "estimatedCostUsd"
445
- ]) if (call[key] !== void 0) result[key] = credit(key, call[key]);
446
- for (const [key, value] of Object.entries(supplemental)) if (typeof value !== "number") reason = `usage receipt has an invalid ${key}`;
447
- else credit(key, value);
448
- if (call.promptCache) result.promptCache = Object.fromEntries(Object.entries(call.promptCache).map(([key, value]) => [key, typeof value === "number" ? credit(`promptCache.${key}`, value) : value]));
449
- if (reason !== void 0) {
450
- result.usdKnown = false;
451
- if (call.tokensIn !== void 0 || call.tokensOut !== void 0) {
452
- result.tokensKnown = false;
453
- result.tokensUnknownReason = reason;
454
- }
455
- }
456
- onCredit(result, mode);
457
- return result;
458
- }
459
- /** Cache partitions belong to the original receipt, not the smaller incremental charge. */
460
- function refineCanonicalTokens(tokens, call, receipt, mode) {
461
- const input = receipt.tokensIn ?? 0;
462
- const output = receipt.tokensOut ?? 0;
463
- const cache = promptCacheTokenClasses(call.tokensIn, call.promptCache);
464
- if (mode !== "cumulative") {
465
- addTokenUsage(tokens, {
466
- input,
467
- output,
468
- ...cache
469
- });
470
- return;
471
- }
472
- tokens.input += input;
473
- tokens.output += output;
474
- if (cache.freshInput !== void 0 && call.tokensIn === tokens.input && receipt.tokensKnown !== false) {
475
- tokens.freshInput = cache.freshInput;
476
- tokens.cacheRead = cache.cacheRead;
477
- tokens.cacheWrite = cache.cacheWrite;
478
- delete tokens.cacheBreakdownKnown;
479
- return;
480
- }
481
- if (cache.cacheRead !== void 0) tokens.cacheRead = Math.max(tokens.cacheRead ?? 0, cache.cacheRead);
482
- if (cache.cacheWrite !== void 0) tokens.cacheWrite = Math.max(tokens.cacheWrite ?? 0, cache.cacheWrite);
483
- if ((tokens.freshInput ?? 0) + (tokens.cacheRead ?? 0) + (tokens.cacheWrite ?? 0) > tokens.input) {
484
- delete tokens.freshInput;
485
- delete tokens.cacheRead;
486
- delete tokens.cacheWrite;
487
- }
488
- if (input > 0 || cache.cacheBreakdownKnown === false) tokens.cacheBreakdownKnown = false;
489
- }
490
- /**
491
- * Fold harness-native usage reports into the canonical `llm_call` the accounting paths read.
492
- *
493
- * Every counter a `HarnessUsage` carries beside `input` and `output` CLASSIFIES one of those two
494
- * totals, so none of them is added to the total it describes:
495
- *
496
- * - the prompt-cache counters classify `input` and ride `promptCache`, the convention the
497
- * terminal `tokenUsage` branch above already follows and `promptCacheTokenClasses` folds,
498
- * where `freshInput = input - cacheRead - cacheWrite`;
499
- * - `reasoningOutput` classifies `output`. Codex counts its reasoning tokens INSIDE
500
- * `output_tokens` — a turn reporting `output_tokens 1523` with `reasoning_output_tokens 1516`
501
- * answered with about seven tokens of text — and `parseCodexUsageRecord` holds that as an
502
- * invariant. The canonical `llm_call` carries no reasoning class, so the count is not
503
- * forwarded to {@link buildLlmCall}. Canonical output totals already include reasoning.
504
- *
505
- * A counter no report carried stays absent, because a zero would claim the provider measured none.
506
- */
507
- function harnessUsageLlmCall(reports, agentRunName) {
508
- let inputTokens = 0;
509
- let outputTokens = 0;
510
- let readTokens;
511
- let writeTokens;
512
- for (const report of reports) {
513
- inputTokens += report.input;
514
- outputTokens += report.output;
515
- if (report.cachedInput !== void 0) readTokens = (readTokens ?? 0) + report.cachedInput;
516
- if (report.cacheWriteInput !== void 0) writeTokens = (writeTokens ?? 0) + report.cacheWriteInput;
517
- }
518
- const promptCache = {};
519
- if (readTokens !== void 0) promptCache.readTokens = readTokens;
520
- if (writeTokens !== void 0) promptCache.writeTokens = writeTokens;
521
- return buildLlmCall({
522
- inputTokens,
523
- outputTokens,
524
- ...Object.keys(promptCache).length > 0 ? { promptCache } : {}
525
- }, agentRunName);
526
- }
527
- /**
528
- * Sum the token usage + USD cost of a sandbox turn's events — the one honest way to meter an
529
- * `openSandboxRun` cell. Folds a {@link SandboxUsageLedger} over the stream, so it reads usage off
530
- * EVERY backend event shape — the canonical events plus a harness that reports usage only in its
531
- * own event — and a `runProfileMatrix` dispatch can report it to `ctx.cost`:
532
- *
533
- * receipt: (turn) => {
534
- * const u = sumSandboxUsage(turn.events)
535
- * return { model, inputTokens: u.input, outputTokens: u.output,
536
- * ...(u.tokensKnown === false ? { usageUnknown: true } : {}),
537
- * ...(u.usdKnown !== false && u.costUsd > 0 ? { actualCostUsd: u.costUsd } : {}),
538
- * ...(u.usdKnown === false ? { costUnknown: true } : {}),
539
- * ...(u.estimatedCostUsd !== undefined ? { estimatedCostUsd: u.estimatedCostUsd } : {}) }
540
- * }
541
- *
542
- * Without this a cell reads `{tokens:0, cost:0}` and the backend-integrity guard correctly aborts the
543
- * matrix as a stub. `agentRunName` is the fallback model label for cost-only events (default `'agent'`).
544
- *
545
- * Pure by contract, like the ledger it folds: it never throws. A harness receipt the ledger cannot
546
- * read leaves the result at `tokensKnown: false` with `tokensUnknownReason` carrying the decode
547
- * message — an unreadable receipt is a different fact from a turn that reported no usage, and a
548
- * post-hoc reader that threw would lose the whole failed turn it exists to report.
549
- */
550
- function sumSandboxUsage(events, agentRunName = "agent") {
551
- let input = 0;
552
- let output = 0;
553
- let costUsd = 0;
554
- let estimatedCostUsd = 0;
555
- let sawEstimate = false;
556
- let sawCall = false;
557
- let sawTokenReceipt = false;
558
- let tokensKnown = true;
559
- let usdKnown = true;
560
- let unreadable;
561
- const ledger = createSandboxUsageLedger();
562
- const credit = (call) => {
563
- sawCall = true;
564
- sawTokenReceipt ||= call.tokensIn !== void 0 || call.tokensOut !== void 0;
565
- input += call.tokensIn ?? 0;
566
- output += call.tokensOut ?? 0;
567
- costUsd += call.costUsd ?? 0;
568
- if (call.tokensKnown === false) tokensKnown = false;
569
- if (call.usdKnown === false) usdKnown = false;
570
- unreadable ??= call.tokensUnknownReason;
571
- if (call.estimatedCostUsd !== void 0) {
572
- estimatedCostUsd += call.estimatedCostUsd;
573
- sawEstimate = true;
574
- }
575
- };
576
- for (const ev of events) {
577
- const call = ledger.observe(ev, agentRunName);
578
- if (call) credit(call);
579
- }
580
- const harnessCall = ledger.settleTurn(agentRunName);
581
- if (harnessCall) credit(harnessCall);
582
- return {
583
- input,
584
- output,
585
- costUsd,
586
- ...sawTokenReceipt && tokensKnown ? {} : { tokensKnown: false },
587
- ...sawCall && usdKnown ? {} : { usdKnown: false },
588
- ...sawEstimate ? { estimatedCostUsd } : {},
589
- ...unreadable === void 0 ? {} : { tokensUnknownReason: unreadable }
590
- };
591
- }
592
- function usageReceiptData(envelope, usage = {}) {
593
- const merged = {
594
- ...usage,
595
- ...envelope
596
- };
597
- for (const aliases of [
598
- [
599
- "tokensIn",
600
- "inputTokens",
601
- "prompt_tokens"
602
- ],
603
- [
604
- "tokensOut",
605
- "outputTokens",
606
- "completion_tokens"
607
- ],
608
- [
609
- "costUsd",
610
- "totalCostUsd",
611
- "cost_usd",
612
- "cost"
613
- ],
614
- ["costProvenance", "cost_provenance"]
615
- ]) {
616
- const key = aliases.find((alias) => envelope[alias] !== void 0);
617
- if (key === void 0) continue;
618
- for (const alias of aliases) delete merged[alias];
619
- merged[key] = envelope[key];
620
- }
621
- for (const aliases of [["tokensKnown", "tokens_known"], [
622
- "costKnown",
623
- "cost_known",
624
- "usdKnown",
625
- "usd_known"
626
- ]]) {
627
- const values = [usage, envelope].flatMap((receipt) => aliases.map((key) => receipt[key]).filter((value) => value !== void 0));
628
- if (values.length > 0) merged[aliases[0]] = values.every((value) => value === true);
629
- }
630
- return merged;
631
- }
632
- function buildLlmCall(data, agentRunName) {
633
- const tokensIn = pickFiniteNumber(data, [
634
- "tokensIn",
635
- "inputTokens",
636
- "prompt_tokens"
637
- ]);
638
- const outputTokens = pickFiniteNumber(data, [
639
- "tokensOut",
640
- "outputTokens",
641
- "completion_tokens"
642
- ]);
643
- const reasoningTokens = pickFiniteNumber(data, ["reasoningTokens"]);
644
- const tokensOut = outputTokens ?? reasoningTokens;
645
- const reportedCostUsd = pickFiniteNumber(data, [
646
- "costUsd",
647
- "totalCostUsd",
648
- "cost_usd",
649
- "cost"
650
- ]);
651
- const explicitTokensKnown = data.tokensKnown ?? data.tokens_known;
652
- const explicitCostKnown = data.costKnown ?? data.cost_known ?? data.usdKnown ?? data.usd_known;
653
- const costProvenance = data.costProvenance ?? data.cost_provenance;
654
- const explicitEstimate = pickFiniteNumber(data, ["estimatedCostUsd", "estimated_cost_usd"]);
655
- const catalogEstimate = costProvenance === "catalog-estimate" ? explicitEstimate ?? reportedCostUsd : explicitEstimate;
656
- const costUsd = costProvenance === "catalog-estimate" ? void 0 : reportedCostUsd;
657
- const promptCache = readPromptCacheUsage(data);
658
- const tokensKnown = explicitTokensKnown !== false && tokensIn !== void 0 && outputTokens !== void 0;
659
- const usdKnown = explicitCostKnown !== false && costUsd !== void 0 && (costProvenance === void 0 || costProvenance === "provider-receipt" || costProvenance === "billing-receipt");
660
- const tools = readOfferedTools(data.tools);
661
- if (tokensIn === void 0 && tokensOut === void 0 && costUsd === void 0 && catalogEstimate === void 0 && promptCache === void 0 && tools === void 0 && explicitTokensKnown !== false && explicitCostKnown !== false) return;
662
- const event = {
663
- type: "llm_call",
664
- model: typeof data.model === "string" && data.model.length > 0 ? data.model : agentRunName
665
- };
666
- if (tokensIn !== void 0) event.tokensIn = tokensIn;
667
- if (tokensOut !== void 0) event.tokensOut = tokensOut;
668
- if (!tokensKnown) event.tokensKnown = false;
669
- if (costUsd !== void 0) event.costUsd = costUsd;
670
- if (!usdKnown) event.usdKnown = false;
671
- if (catalogEstimate !== void 0) event.estimatedCostUsd = catalogEstimate;
672
- if (promptCache !== void 0) event.promptCache = promptCache;
673
- if (tools !== void 0) event.tools = tools;
674
- return event;
675
- }
676
- /** `data.tools`, when it is a non-empty array of non-empty tool-name strings. Anything else
677
- * (absent, empty, wrong element type) is treated as "the producer did not report its offered
678
- * set" — never a placeholder empty list, which would read as "offered zero tools" downstream. */
679
- function readOfferedTools(value) {
680
- if (!Array.isArray(value) || value.length === 0) return void 0;
681
- const names = value.filter((v) => typeof v === "string" && v.length > 0);
682
- return names.length === value.length ? names : void 0;
683
- }
684
- /**
685
- * Every wire spelling of a prompt-cache READ counter, in precedence order: the canonical name
686
- * first, then the spellings observed on the paths that reach this extractor — the sandbox terminal
687
- * `tokenUsage` record, the cli-bridge OpenAI-compatible usage object (which normalizes every
688
- * backend to Anthropic's `cache_read_input_tokens`), Anthropic verbatim, OpenAI's
689
- * `prompt_tokens_details.cached_tokens`, and DeepSeek's `prompt_cache_hit_tokens`.
690
- */
691
- const PROMPT_CACHE_READ_KEYS = [
692
- "readTokens",
693
- "read_tokens",
694
- "cacheReadInputTokens",
695
- "cache_read_input_tokens",
696
- "cacheRead",
697
- "cache_read",
698
- "cachedInputTokens",
699
- "cachedTokens",
700
- "cached_tokens",
701
- "prompt_cache_hit_tokens"
702
- ];
703
- /**
704
- * Every wire spelling of a prompt-cache WRITE counter. OpenAI reports no write counter at all, so
705
- * an OpenAI-shaped usage record matches nothing here and the write stays absent — which is the
706
- * point: absent is not zero.
707
- */
708
- const PROMPT_CACHE_WRITE_KEYS = [
709
- "writeTokens",
710
- "write_tokens",
711
- "cacheWriteInputTokens",
712
- "cache_write_input_tokens",
713
- "cacheCreationInputTokens",
714
- "cache_creation_input_tokens",
715
- "cacheWrite",
716
- "cache_write"
717
- ];
718
- const PROMPT_CACHE_MISS_KEYS = [
719
- "missTokens",
720
- "miss_tokens",
721
- "prompt_cache_miss_tokens"
722
- ];
723
- const PROMPT_CACHE_SAVINGS_KEYS = ["readSavingsUsd", "read_savings_usd"];
724
- /**
725
- * Read the provider's own prompt-cache accounting off one usage-bearing event payload, in the
726
- * `PromptCacheUsage` vocabulary the router, driver, and chat-client paths already speak.
727
- *
728
- * Two sources, one record. A payload that already carries a `promptCache` / `prompt_cache` object
729
- * is forwarded VERBATIM — those fields are the provider's own report, and an unrecognized one must
730
- * stay visible rather than be bucketed away. On top of that, flat provider counters are translated
731
- * to the canonical names, filling only the names the verbatim object did not already define.
732
- *
733
- * A counter the payload does not carry is left out. Defaulting it to zero would assert the provider
734
- * measured no cache, which is a different fact from a provider that reported nothing — and it is
735
- * the fact the budget reads to decide whether its charge is a measurement or a bound.
736
- */
737
- function readPromptCacheUsage(data) {
738
- const declared = finiteMetadata(data.promptCache ?? data.prompt_cache);
739
- const nested = recordValue(data.promptCache) ?? recordValue(data.prompt_cache);
740
- const sources = [
741
- nested,
742
- data,
743
- recordValue(data.prompt_tokens_details)
744
- ].filter((s) => s !== void 0);
745
- const canonical = {};
746
- const readTokens = pickTokenCount(sources, PROMPT_CACHE_READ_KEYS);
747
- const writeTokens = pickTokenCount(sources, PROMPT_CACHE_WRITE_KEYS);
748
- const missTokens = pickTokenCount(sources, PROMPT_CACHE_MISS_KEYS);
749
- const readSavingsUsd = pickNonNegativeNumber(sources, PROMPT_CACHE_SAVINGS_KEYS);
750
- const status = nested !== void 0 && typeof nested.status === "string" ? nested.status : void 0;
751
- if (readTokens !== void 0) canonical.readTokens = readTokens;
752
- if (writeTokens !== void 0) canonical.writeTokens = writeTokens;
753
- if (missTokens !== void 0) canonical.missTokens = missTokens;
754
- if (readSavingsUsd !== void 0) canonical.readSavingsUsd = readSavingsUsd;
755
- if (status !== void 0) canonical.status = status;
756
- const merged = {
757
- ...canonical,
758
- ...declared
759
- };
760
- return Object.keys(merged).length > 0 ? merged : void 0;
761
- }
762
- function pickTokenCount(sources, keys) {
763
- for (const key of keys) for (const source of sources) {
764
- const value = source[key];
765
- if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) return value;
766
- }
767
- }
768
- function pickNonNegativeNumber(sources, keys) {
769
- for (const key of keys) for (const source of sources) {
770
- const value = source[key];
771
- if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
772
- }
773
- }
774
- function finiteMetadata(value) {
775
- if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
776
- const result = {};
777
- for (const [key, entry] of Object.entries(value)) if (typeof entry === "number" && Number.isFinite(entry) || typeof entry === "string") result[key] = entry;
778
- return Object.keys(result).length > 0 ? result : void 0;
779
- }
780
- function pickFiniteNumber(data, keys) {
781
- for (const key of keys) {
782
- const value = data[key];
783
- if (typeof value === "number" && Number.isFinite(value)) return value;
784
- }
785
- }
786
- /**
787
- * Collect one turn's events, keeping only the newest snapshot of each streamed text or
788
- * reasoning part.
789
- *
790
- * opencode sends `message.part.updated` once per token, and every frame carries the part's
791
- * whole cumulative text, so keeping every frame holds O(L²) bytes per turn: two concurrent
792
- * audit workflows took the platform API from 459 MB to an OOM at 1.95 GB in 11 minutes.
793
- * A snapshot (a frame whose part carries `text`) whose text extends the previous snapshot
794
- * of the same part replaces it and moves to the newest position, which is the rule the
795
- * Sandbox SDK's text collectors already apply, so the collected text is unchanged. The kept
796
- * frame drops its `delta`, because its text already holds every earlier increment and a
797
- * reader that joins `delta ?? part.text` across frames would otherwise read only the last
798
- * one. Delta-only frames and every other event are kept as they arrived.
799
- */
800
- function createSandboxTurnEventCollector() {
801
- const slots = [];
802
- const snapshots = /* @__PURE__ */ new Map();
803
- return {
804
- push(event) {
805
- const snapshot = partSnapshot(event);
806
- if (!snapshot) {
807
- slots.push(event);
808
- return;
809
- }
810
- const previous = snapshots.get(snapshot.key);
811
- const extends_ = previous !== void 0 && snapshot.text.startsWith(previous.text);
812
- if (extends_) slots[previous.at] = void 0;
813
- snapshots.set(snapshot.key, {
814
- at: slots.length,
815
- text: snapshot.text
816
- });
817
- slots.push(extends_ ? withoutDelta(event) : event);
818
- },
819
- events() {
820
- return slots.filter((event) => event !== void 0);
821
- }
822
- };
823
- }
824
- function partSnapshot(event) {
825
- if (event?.type !== "message.part.updated") return void 0;
826
- const data = asRecord(event.data);
827
- const part = asRecord(data?.part) ?? asRecord(asRecord(data?.properties)?.part);
828
- if (!part || typeof part.id !== "string") return void 0;
829
- if (part.type !== "text" && part.type !== "reasoning" && part.type !== "thinking") return void 0;
830
- const text = typeof part.text === "string" ? part.text : typeof part.content === "string" ? part.content : void 0;
831
- return text === void 0 ? void 0 : {
832
- key: `${part.type}:${part.id}`,
833
- text
834
- };
835
- }
836
- function withoutDelta(event) {
837
- const data = asRecord(event.data);
838
- if (!data || !("delta" in data)) return event;
839
- const { delta: _delta, ...rest } = data;
840
- return {
841
- ...event,
842
- data: rest
843
- };
844
- }
845
- function asRecord(value) {
846
- return value !== null && typeof value === "object" ? value : void 0;
847
- }
848
- // src/runtime/timestamps.ts
849
- const validTimestamp = "2026-01-01T00:00:00.000Z";
850
- /** Validate a timestamp with the public runtime envelope contract. */
851
- function assertRuntimeTimestamp(value, label) {
852
- try {
853
- RuntimeEventEnvelopeSchema.parse({
854
- runId: "runtime",
855
- eventId: "event",
856
- sequence: 0,
857
- occurredAt: value,
858
- receivedAt: validTimestamp,
859
- event: {
860
- type: "status",
861
- status: "processing"
862
- }
863
- });
864
- } catch (error) {
865
- throw new Error(`${label} must be a valid ISO timestamp`, { cause: error });
866
- }
867
- }
868
- // src/runtime/sandbox-transport-events.ts
869
- /** Canonical Sandbox/provider wire-envelope decoding and transport identity. */
870
- const CANONICAL_STREAM_EVENT_TYPES = /* @__PURE__ */ new Set([
871
- "child-task",
872
- "message.part.updated",
873
- "tool-heartbeat",
874
- "tool-slow",
875
- "model-processing",
876
- "status",
877
- "warning",
878
- "raw",
879
- "session.updated",
880
- "interaction",
881
- "interaction.cancel",
882
- "plan.submitted"
883
- ]);
884
- /** Decode one known Agent Interface event from a Sandbox event. */
885
- function canonicalStreamEventFromSandboxEvent(event) {
886
- if (!event || typeof event !== "object") return void 0;
887
- const type = String(event.type ?? "");
888
- const data = event.data && typeof event.data === "object" ? event.data : {};
889
- const normalized = data.normalized;
890
- if (normalized === void 0 && !CANONICAL_STREAM_EVENT_TYPES.has(type)) return void 0;
891
- return parseCanonicalTransportEvent(type, data, normalized, "sandbox");
892
- }
893
- /**
894
- * Transport type whose payload is the harness's OWN event, passed through verbatim.
895
- *
896
- * A `raw` payload names a harness-native type from a vocabulary the canonical schema does not
897
- * define (codex emits `thread.started` / `turn.started` / `turn.completed`). The transport type is
898
- * therefore the only canonical statement such an event makes, and its payload must never be read
899
- * as a canonical type: the mismatch guard below exists to catch a producer that MISLABELS a
900
- * canonical event, which a harness-native payload cannot do. A payload that does satisfy the
901
- * canonical `raw` shape (`backend` + `event`) still parses; anything else is not a canonical
902
- * event, and the caller reads it off the transport event instead.
903
- */
904
- const harnessNativePayloadType = "raw";
905
- /** Parse canonical payload fields without treating transport identity as event data. */
906
- function parseCanonicalTransportEvent(type, data, normalized, source) {
907
- if (!isRecord(data)) throw new Error(`${source} emitted a canonical event without an object payload`);
908
- const outerType = String(type ?? "");
909
- const harnessNative = outerType === harnessNativePayloadType;
910
- if (normalized !== void 0) {
911
- if (!harnessNative && typeof normalized === "object" && normalized !== null && "type" in normalized && typeof normalized.type === "string" && normalized.type !== outerType) throw new Error(`${source} canonical event type "${normalized.type}" does not match transport type "${outerType}"`);
912
- const candidate = CanonicalStreamEventSchema.safeParse(normalized);
913
- if (!candidate.success) {
914
- if (harnessNative) return void 0;
915
- throw new Error(`${source} emitted an invalid normalized canonical event`, { cause: candidate.error });
916
- }
917
- return candidate.data;
918
- }
919
- const { type: embeddedType, eventId: _eventId, cursor: _cursor, sequence: _sequence, occurredAt: _occurredAt, normalized: _normalized, ...payload } = data;
920
- if (!harnessNative && embeddedType !== void 0 && embeddedType !== outerType) throw new Error(`${source} canonical event type "${String(embeddedType)}" does not match transport type "${outerType}"`);
921
- const candidate = CanonicalStreamEventSchema.safeParse({
922
- ...payload,
923
- type: outerType
924
- });
925
- return candidate.success ? candidate.data : void 0;
926
- }
927
- /** Extract shared event identity without privileging one provider wire shape. */
928
- function extractTransportEventIdentity(event) {
929
- const record = isRecord(event) ? event : {};
930
- const data = isRecord(record.data) ? record.data : {};
931
- const providerEvent = isRecord(record.providerEvent) ? record.providerEvent : {};
932
- const providerData = isRecord(providerEvent.data) ? providerEvent.data : {};
933
- const eventId = canonicalEventId(record, data, providerEvent, providerData);
934
- const cursor = stableString(record.cursor ?? data.cursor ?? providerEvent.cursor ?? providerData.cursor);
935
- const sequence = optionalSequence(record.sequence ?? data.sequence ?? providerEvent.sequence ?? providerData.sequence);
936
- const occurredAt = optionalTimestamp(record.occurredAt ?? data.occurredAt ?? providerEvent.occurredAt ?? providerData.occurredAt);
937
- return {
938
- ...eventId === void 0 ? {} : { eventId },
939
- ...cursor === void 0 ? {} : { cursor },
940
- ...sequence === void 0 ? {} : { sequence },
941
- ...occurredAt === void 0 ? {} : { occurredAt }
942
- };
943
- }
944
- function canonicalEventId(record, data, providerEvent, providerData) {
945
- const present = [
946
- ["data.eventId", data.eventId],
947
- ["providerEvent.eventId", providerEvent.eventId],
948
- ["providerData.eventId", providerData.eventId],
949
- ["record.eventId", record.eventId]
950
- ].filter(([, value]) => value !== void 0);
951
- if (present.length === 0) return stableString(record.id ?? providerEvent.id);
952
- const normalized = present.map(([source, value]) => {
953
- const eventId = stableString(value);
954
- if (eventId === void 0) throw new Error(`transport event ${source} must be a stable string`);
955
- return {
956
- source,
957
- eventId
958
- };
959
- });
960
- const first = normalized[0];
961
- if (normalized.some((candidate) => candidate.eventId !== first.eventId)) throw new Error("transport event canonical identities disagree");
962
- return first.eventId;
963
- }
964
- function stableString(value) {
965
- return typeof value === "string" && value.length > 0 && value.trim() === value ? value : void 0;
966
- }
967
- function finiteNonNegativeInteger(value) {
968
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
969
- }
970
- function optionalSequence(value) {
971
- if (value === void 0) return void 0;
972
- if (finiteNonNegativeInteger(value) === void 0) throw new Error("transport event sequence must be a non-negative safe integer");
973
- return value;
974
- }
975
- function optionalTimestamp(value) {
976
- if (value === void 0) return void 0;
977
- assertRuntimeTimestamp(value, "occurredAt");
978
- return value;
979
- }
980
- // src/runtime/sandbox-executor-output.ts
981
- /** Streamed and terminal Sandbox executor output, using one tool-part projector. */
982
- /**
983
- * Fresh per-turn {@link SandboxToolPartState} for {@link mapSandboxToolEvent} — an
984
- * empty call-status map so each turn projects tool frames independently.
985
- *
986
- * @experimental
987
- */
988
- function createSandboxToolPartState() {
989
- return {
990
- statusByCall: /* @__PURE__ */ new Map(),
991
- seq: 0
992
- };
993
- }
994
- /** Statuses that settle one tool call as a failure. A failed tool call stays a tool result:
995
- * the run's terminal state comes from the public Sandbox outcome tracker, never from here. */
996
- const TERMINAL_FAILURE = /^(error|errored|failed|failure|cancelled|canceled|timeout|timed_out)$/i;
997
- /**
998
- * Project one `SandboxEvent` onto the `tool_call` / `tool_result` variants of
999
- * `RuntimeStreamEvent` — the tool-part projection `mapSandboxEvent`
1000
- * deliberately does NOT perform. Opt-in and additive: `mapSandboxEvent`'s
1001
- * default vocabulary (text/reasoning deltas + `llm_call`) is unchanged;
1002
- * consumers that need the tool surface (chat UIs rendering tool activity)
1003
- * compose this projector alongside it — `streamAgentTurn` does exactly that
1004
- * under its `preserveToolParts` option.
1005
- *
1006
- * Handled shapes (observed on the opencode / claude-code sandbox backends):
1007
- * - `message.part.updated` with `part.type === 'tool'` — stateful: a
1008
- * `tool_call` on the call id's first frame (args from `state.input` or
1009
- * `state.metadata.input`), a `tool_result` when the status transitions to
1010
- * `completed` (result from `state.output` / `metadata.output`) or to a
1011
- * terminal failure (result is `{ error, status, output? }` — the error
1012
- * surfaced in-band, never dropped).
1013
- * - bare `tool*` event types (`tool.call`, `tool_result`, …) — stateless:
1014
- * `*result*` types project to `tool_result`, the rest to `tool_call`.
1015
- * - codex `raw` events whose inner `item.started` / `item.updated` /
1016
- * `item.completed` carries a command, MCP, collaboration, web search or
1017
- * file change item — stateful on the item id, like a tool part. Other raw
1018
- * events, including opencode's raw `tool_use` echoes, project nothing.
1019
- *
1020
- * Returns `[]` for every non-tool event.
1021
- *
1022
- * @experimental
1023
- */
1024
- function mapSandboxToolEvent(event, state) {
1025
- if (!event || typeof event !== "object") return [];
1026
- const type = String(event.type ?? "");
1027
- const data = event.data && typeof event.data === "object" ? event.data : {};
1028
- if (type === "message.part.updated") {
1029
- const part = data.part && typeof data.part === "object" ? data.part : {};
1030
- if (String(part.type ?? "") !== "tool") return [];
1031
- return projectToolPart(part, state, typeof event.id === "string" ? event.id : void 0);
1032
- }
1033
- if (type === "raw") return projectCodexItem(data, state);
1034
- if (CANONICAL_STREAM_EVENT_TYPES.has(type)) return [];
1035
- if (type.includes("tool")) {
1036
- const callId = pickString(data, [
1037
- "toolCallId",
1038
- "tool_use_id",
1039
- "id"
1040
- ]) ?? (typeof event.id === "string" ? event.id : void 0) ?? `sandbox-tool-${++state.seq}`;
1041
- const toolName = pickString(data, [
1042
- "name",
1043
- "toolName",
1044
- "tool"
1045
- ]) ?? "sandbox_tool";
1046
- if (type.includes("result")) return [{
1047
- type: "tool_result",
1048
- toolName,
1049
- toolCallId: callId,
1050
- result: data.output ?? data.result ?? data.content ?? data
1051
- }];
1052
- return [{
1053
- type: "tool_call",
1054
- toolName,
1055
- toolCallId: callId,
1056
- args: data.input ?? data.args ?? {}
1057
- }];
1058
- }
1059
- return [];
1060
- }
1061
- function projectToolPart(part, state, eventId) {
1062
- const callId = pickString(part, [
1063
- "callID",
1064
- "callId",
1065
- "toolCallId",
1066
- "id"
1067
- ]) ?? eventId ?? `sandbox-tool-${++state.seq}`;
1068
- const toolName = pickString(part, [
1069
- "tool",
1070
- "toolName",
1071
- "name"
1072
- ]) ?? "sandbox_tool";
1073
- const toolState = part.state && typeof part.state === "object" ? part.state : {};
1074
- const metadata = toolState.metadata && typeof toolState.metadata === "object" ? toolState.metadata : {};
1075
- const status = pickString(toolState, ["status"]) ?? "updated";
1076
- const previous = state.statusByCall.get(callId);
1077
- if (previous === "completed" || previous !== void 0 && TERMINAL_FAILURE.test(previous)) return [];
1078
- const out = [];
1079
- if (previous === void 0) out.push({
1080
- type: "tool_call",
1081
- toolName,
1082
- toolCallId: callId,
1083
- args: toolState.input ?? metadata.input ?? {}
1084
- });
1085
- state.statusByCall.set(callId, status);
1086
- if (status === "completed") out.push({
1087
- type: "tool_result",
1088
- toolName,
1089
- toolCallId: callId,
1090
- result: toolState.output ?? metadata.output ?? ""
1091
- });
1092
- else if (TERMINAL_FAILURE.test(status)) {
1093
- const message = pickString(toolState, ["error", "message"]) ?? pickString(metadata, ["error", "message"]) ?? `sandbox tool ended with status ${status}`;
1094
- const output = toolState.output ?? metadata.output;
1095
- out.push({
1096
- type: "tool_result",
1097
- toolName,
1098
- toolCallId: callId,
1099
- result: {
1100
- error: message,
1101
- status,
1102
- ...output !== void 0 ? { output } : {}
1103
- }
1104
- });
1105
- }
1106
- return out;
1107
- }
1108
- /** Codex item types that are tool calls. Reasoning, messages and todo lists are not. */
1109
- const CODEX_TOOL_ITEMS = /* @__PURE__ */ new Set([
1110
- "command_execution",
1111
- "mcp_tool_call",
1112
- "collab_tool_call",
1113
- "web_search",
1114
- "file_change"
1115
- ]);
1116
- /** Item fields that describe the call's progress or outcome rather than what it was asked to do. */
1117
- const CODEX_ITEM_OUTCOME_FIELDS = /* @__PURE__ */ new Set([
1118
- "id",
1119
- "type",
1120
- "status",
1121
- "result",
1122
- "error",
1123
- "aggregated_output",
1124
- "exit_code"
1125
- ]);
1126
- /**
1127
- * Project a codex `item.started` / `item.updated` / `item.completed` tool item through the same
1128
- * per-call state as a tool part, keyed on the item id: one `tool_call` on its first frame and one
1129
- * `tool_result` when it completes. Tool names follow Eval's codex intake: `server/tool` for MCP.
1130
- */
1131
- function projectCodexItem(data, state) {
1132
- if (data.backend !== "codex" || !data.event || typeof data.event !== "object") return [];
1133
- const inner = data.event;
1134
- const phase = inner.type;
1135
- if (phase !== "item.started" && phase !== "item.updated" && phase !== "item.completed") return [];
1136
- if (!inner.item || typeof inner.item !== "object") return [];
1137
- const item = inner.item;
1138
- const itemType = pickString(item, ["type"]);
1139
- if (itemType === void 0 || !CODEX_TOOL_ITEMS.has(itemType)) return [];
1140
- const error = item.error && typeof item.error === "object" ? pickString(item.error, ["message"]) ?? `${itemType} failed` : void 0;
1141
- const failed = item.status === "failed" || error !== void 0 || itemType === "command_execution" && typeof item.exit_code === "number" && item.exit_code !== 0;
1142
- const status = phase !== "item.completed" ? "running" : failed ? "failed" : "completed";
1143
- const input = Object.fromEntries(Object.entries(item).filter(([key]) => !CODEX_ITEM_OUTCOME_FIELDS.has(key)));
1144
- const output = item.result ?? item.aggregated_output ?? item.changes;
1145
- return projectToolPart({
1146
- ...typeof item.id === "string" ? { callID: item.id } : {},
1147
- tool: codexToolName(itemType, item),
1148
- state: {
1149
- status,
1150
- input,
1151
- ...output === void 0 || output === null ? {} : { output },
1152
- ...status === "failed" ? { error: error ?? `${itemType} failed` } : {}
1153
- }
1154
- }, state, void 0);
1155
- }
1156
- function codexToolName(itemType, item) {
1157
- if (itemType === "mcp_tool_call") {
1158
- const name = [pickString(item, ["server"]), pickString(item, ["tool"])].filter((part) => part !== void 0).join("/");
1159
- return name === "" ? "mcp" : name;
1160
- }
1161
- if (itemType === "collab_tool_call") return pickString(item, ["tool"]) ?? "subagent";
1162
- if (itemType === "web_search" || itemType === "file_change") return itemType;
1163
- return pickString(item, ["name"]) ?? itemType;
1164
- }
1165
- function pickString(data, keys) {
1166
- for (const key of keys) {
1167
- const value = data[key];
1168
- if (typeof value === "string" && value.length > 0) return value;
1169
- }
1170
- }
1171
- /**
1172
- * Project one `SandboxEvent` onto the `RuntimeStreamEvent` chat-UX vocabulary,
1173
- * for runtimes that bridge a sandbox `streamPrompt` into the
1174
- * `AgentRuntime.act` streaming contract. Returns `undefined` for events that
1175
- * have no faithful projection — the raw stream is preserved separately for the
1176
- * `OutputAdapter`, so an unmapped event never loses data.
1177
- *
1178
- * Mapped (the task-optional incremental variants — no synthesized task
1179
- * lifecycle, no guessed tool-part shapes):
1180
- * - `message.part.updated` text part → `text_delta`
1181
- * - `message.part.updated` reasoning/thinking part → `reasoning_delta`
1182
- * - cost-bearing events → `llm_call` (shared with the ledger extractor)
1183
- *
1184
- * Tool parts are deliberately NOT mapped here (unchanged default) — compose
1185
- * {@link mapSandboxToolEvent} alongside when a consumer needs them.
1186
- *
1187
- * The opencode backend emits incremental text as
1188
- * `{ type: 'message.part.updated', data: { part: { type, text }, delta } }`;
1189
- * `delta` is the increment, `part.text` the running accumulation.
1190
- */
1191
- function mapSandboxEvent(event, opts = {}) {
1192
- if (!event || typeof event !== "object") return void 0;
1193
- const type = String(event.type ?? "");
1194
- const data = event.data && typeof event.data === "object" ? event.data : {};
1195
- if (type === "message.part.updated") {
1196
- const part = data.part && typeof data.part === "object" ? data.part : {};
1197
- const partType = String(part.type ?? "");
1198
- const text = (typeof data.delta === "string" ? data.delta : void 0) ?? (typeof part.text === "string" ? part.text : void 0);
1199
- if (text === void 0) return void 0;
1200
- if (partType === "text") return {
1201
- type: "text_delta",
1202
- text
1203
- };
1204
- if (partType === "reasoning" || partType === "thinking") return {
1205
- type: "reasoning_delta",
1206
- text
1207
- };
1208
- return;
1209
- }
1210
- return extractLlmCallEvent(event, opts.agentRunName ?? "agent");
1211
- }
1212
- /**
1213
- * Project one `SandboxEvent` onto Runtime's executor progress vocabulary: incremental text and
1214
- * reasoning, tool calls and results, and an interaction request. It composes the existing
1215
- * projections ({@link mapSandboxEvent}, {@link mapSandboxToolEvent}, and the canonical Agent
1216
- * Interface decode) so every sandbox-shaped executor publishes live output through one reader.
1217
- * Usage-bearing events project to nothing here — accounting stays on the `tokens`/`cost`
1218
- * channels.
1219
- *
1220
- * Pass one {@link SandboxToolPartState} per turn so a multi-frame tool call yields one call and
1221
- * at most one result.
1222
- *
1223
- * @experimental
1224
- */
1225
- function sandboxProgressEvents(event, state) {
1226
- const canonical = canonicalStreamEventFromSandboxEvent(event);
1227
- if (canonical?.type === "interaction") return [{
1228
- kind: "interaction",
1229
- request: canonical.request
1230
- }];
1231
- if (canonical?.type === "child-task") return [{
1232
- kind: "child_task",
1233
- event: canonical
1234
- }];
1235
- const tools = mapSandboxToolEvent(event, state).map((projected) => projected.type === "tool_call" ? {
1236
- kind: "tool_call",
1237
- toolName: projected.toolName,
1238
- ...projected.toolCallId === void 0 ? {} : { toolCallId: projected.toolCallId },
1239
- ...projected.args === void 0 ? {} : { args: projected.args }
1240
- } : {
1241
- kind: "tool_result",
1242
- toolName: projected.toolName,
1243
- ...projected.toolCallId === void 0 ? {} : { toolCallId: projected.toolCallId },
1244
- ...projected.result === void 0 ? {} : { result: projected.result }
1245
- });
1246
- if (tools.length > 0) return tools;
1247
- const mapped = mapSandboxEvent(event);
1248
- if (mapped?.type === "text_delta") return [{
1249
- kind: "text_delta",
1250
- text: mapped.text
1251
- }];
1252
- if (mapped?.type === "reasoning_delta") return [{
1253
- kind: "reasoning_delta",
1254
- text: mapped.text
1255
- }];
1256
- return [];
1257
- }
1258
- /** Project the complete Sandbox event stream into the standard executor result shape. */
1259
- function sandboxLeafOutputFromEvents(events) {
1260
- const content = collectAgentFinalMessageText(events) ?? collectAgentResponseText(events);
1261
- const toolCalls = sandboxToolCalls(events);
1262
- const servedBackend = lastServedBackend(events);
1263
- return {
1264
- events,
1265
- content,
1266
- output: sandboxOutputMarker(content, events.some(carriesText)),
1267
- ...servedBackend === void 0 ? {} : { servedBackend },
1268
- ...toolCalls.length === 0 ? {} : { toolCalls }
1269
- };
1270
- }
1271
- /**
1272
- * Classify observed text into the explicit output marker. `sawTextEvent` says whether the stream
1273
- * carried a text-bearing event at all, which is what separates a box that answered nothing from a
1274
- * box whose answer was never observed.
1275
- */
1276
- function sandboxOutputMarker(content, sawTextEvent = content !== void 0) {
1277
- if (content !== void 0 && content.length > 0) return {
1278
- kind: "text",
1279
- bytes: Buffer.byteLength(content, "utf8")
1280
- };
1281
- return sawTextEvent ? { kind: "empty" } : { kind: "absent" };
1282
- }
1283
- /** True when the event carries a text field a turn's answer could come from, empty or not. */
1284
- function carriesText(event) {
1285
- if (!event || typeof event !== "object") return false;
1286
- const data = event.data && typeof event.data === "object" ? event.data : {};
1287
- if (typeof data.finalText === "string" || typeof data.text === "string") return true;
1288
- if (typeof data.delta === "string") return true;
1289
- const part = data.part && typeof data.part === "object" ? data.part : void 0;
1290
- return part !== void 0 && part.type === "text" && typeof part.text === "string";
1291
- }
1292
- /** The last served identity the platform reported across a turn's events, or `undefined`. */
1293
- function lastServedBackend(events) {
1294
- let served;
1295
- for (const event of events) {
1296
- const next = sandboxEventServedBackend(event);
1297
- if (next !== void 0) served = next;
1298
- }
1299
- return served;
1300
- }
1301
- function sandboxToolCalls(events) {
1302
- const state = createSandboxToolPartState();
1303
- return events.flatMap((event) => mapSandboxToolEvent(event, state).flatMap((projected) => projected.type === "tool_call" ? [{
1304
- ...projected.toolCallId === void 0 ? {} : { id: projected.toolCallId },
1305
- name: projected.toolName,
1306
- arguments: projected.args ?? {}
1307
- }] : []));
1308
- }
1309
- /** Read the final text off a terminal sandbox event, when present. */
1310
- function terminalTextFromSandboxEvent(event) {
1311
- if (!event || typeof event !== "object") return void 0;
1312
- if (!isSandboxTerminalEvent(String(event.type ?? ""))) return void 0;
1313
- const data = event.data && typeof event.data === "object" ? event.data : {};
1314
- for (const key of [
1315
- "finalText",
1316
- "text",
1317
- "response",
1318
- "content"
1319
- ]) {
1320
- const value = data[key];
1321
- if (typeof value === "string") return value;
1322
- }
1323
- }
1324
- // src/runtime/supervise/spend-validation.ts
1325
- /** Reject malformed ceilings before they can mint capacity through negative reservations. */
1326
- function assertValidBudget(budget, label = "budget") {
1327
- const safeInteger = (value, field) => {
1328
- if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${label}.${field} must be a non-negative safe integer`);
1329
- };
1330
- const finiteNonNegative = (value, field) => {
1331
- if (!Number.isFinite(value) || value < 0) throw new Error(`${label}.${field} must be a non-negative finite number`);
1332
- };
1333
- assertResources(budget.resources, "limit", `${label}.resources`);
1334
- safeInteger(budget.maxIterations, "maxIterations");
1335
- safeInteger(budget.maxTokens, "maxTokens");
1336
- if (budget.maxUsd !== void 0) finiteNonNegative(budget.maxUsd, "maxUsd");
1337
- if (budget.deadlineMs !== void 0) finiteNonNegative(budget.deadlineMs, "deadlineMs");
1338
- }
1339
- function assertValidSpend(spend, label) {
1340
- assertResources(spend.resources, "amount", `${label}.resources`);
1341
- if (!Number.isSafeInteger(spend.iterations) || spend.iterations < 0) throw new Error(`${label}.iterations must be a non-negative safe integer`);
1342
- for (const [field, value] of [
1343
- ["input", spend.tokens.input],
1344
- ["output", spend.tokens.output],
1345
- ["freshInput", spend.tokens.freshInput],
1346
- ["cacheRead", spend.tokens.cacheRead],
1347
- ["cacheWrite", spend.tokens.cacheWrite]
1348
- ]) {
1349
- if (value === void 0) continue;
1350
- if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${label}.tokens.${field} must be a non-negative safe integer`);
1351
- }
1352
- for (const [field, value] of [["tokensKnown", spend.tokens.tokensKnown], ["cacheBreakdownKnown", spend.tokens.cacheBreakdownKnown]]) if (value !== void 0 && value !== false) throw new Error(`${label}.tokens.${field} must be false when present`);
1353
- const { freshInput, cacheRead, cacheWrite } = spend.tokens;
1354
- if (freshInput !== void 0 && cacheRead !== void 0 && cacheWrite !== void 0) {
1355
- const classified = freshInput + cacheRead + cacheWrite;
1356
- if (spend.tokens.cacheBreakdownKnown !== false) {
1357
- if (classified !== spend.tokens.input) throw new Error(`${label}.tokens cache classes must sum to input`);
1358
- } else if (classified > spend.tokens.input) throw new Error(`${label}.tokens cache classes must not exceed input`);
1359
- }
1360
- for (const [field, value] of [["usd", spend.usd], ["ms", spend.ms]]) if (!Number.isFinite(value) || value < 0) throw new Error(`${label}.${field} must be a non-negative finite number`);
1361
- if (spend.usdEstimated !== void 0) {
1362
- if (!Number.isFinite(spend.usdEstimated) || spend.usdEstimated < 0) throw new Error(`${label}.usdEstimated must be a non-negative finite number`);
1363
- if (spend.usdEstimated > spend.usd) throw new Error(`${label}.usdEstimated must not exceed ${label}.usd`);
1364
- if (spend.usdKnown !== false) throw new Error(`${label}.usdEstimated requires ${label}.usdKnown false`);
1365
- }
1366
- }
1367
- // src/runtime/supervise/budget-ledger.ts
1368
- /**
1369
- * The balances of one conserved budget pool, and the arithmetic that moves them. `createBudgetPool`
1370
- * (`./budget`) owns tickets, the wait queue and the public contract; this module owns the numbers:
1371
- * `total ≡ free + reserved + committed` per channel, the taint flags that say a balance is a
1372
- * ceiling rather than a measurement, and the named-resource states.
1373
- */
1374
- function openBudgetLedger(root, runStartedAtMs) {
1375
- return {
1376
- usdCapped: root.maxUsd !== void 0,
1377
- absoluteDeadlineMs: root.deadlineMs === void 0 ? 0 : runStartedAtMs + root.deadlineMs,
1378
- resources: new Map(Object.entries(root.resources ?? {}).map(([name, value]) => [name, {
1379
- ...value,
1380
- remaining: value.limit,
1381
- reserved: 0,
1382
- committed: 0,
1383
- known: true
1384
- }])),
1385
- freeTokens: root.maxTokens,
1386
- reservedTokens: 0,
1387
- committedTokens: 0,
1388
- tokensTainted: false,
1389
- cacheBreakdownTainted: false,
1390
- freeUsd: root.maxUsd ?? 0,
1391
- reservedUsd: 0,
1392
- committedUsd: 0,
1393
- usdTainted: false,
1394
- usdMeasured: true,
1395
- freeIterations: root.maxIterations,
1396
- reservedIterations: 0,
1397
- committedIterations: 0
1398
- };
1399
- }
1400
- function ledgerValidateUnits(l, spend) {
1401
- for (const [name, value] of Object.entries(spend.resources ?? {})) {
1402
- const limit = l.resources.get(name);
1403
- if (limit && limit.unit !== value.unit) throw new Error(`resource ${name}: unit mismatch`);
1404
- }
1405
- }
1406
- /** Record that `spend` carried an unreported or unclassified channel. */
1407
- function noteUnknowns(l, spend) {
1408
- if (spend.tokensKnown === false) l.tokensTainted = true;
1409
- if (!hasCompleteCacheBreakdown(spend.tokens)) l.cacheBreakdownTainted = true;
1410
- if (spend.usdKnown === false) l.usdMeasured = false;
1411
- }
1412
- /**
1413
- * Commit named-resource spend and classify it. `allocations` is the settling reservation's own
1414
- * allocations, or `undefined` for spend holding no reservation (`observe`, restore). Unverifiable
1415
- * usage is a `fault` (the remaining balance is consumed so admission closes); measured usage above
1416
- * an allocation is `overspent`; without allocations, usage leaving a root limit overdrawn is
1417
- * `rootExceeded`. A reservation within its own allocation is never charged with a root overdraw:
1418
- * a negative balance at its settlement was caused, and reported, by earlier spend.
1419
- */
1420
- function ledgerCommitResources(l, spend, allocations, requireAll = true) {
1421
- let fault;
1422
- let rootExceeded;
1423
- const overspent = [];
1424
- for (const [name, state] of l.resources) {
1425
- const value = spend.resources?.[name];
1426
- if (!requireAll && value === void 0) continue;
1427
- const allocation = allocations?.[name]?.limit ?? 0;
1428
- const amount = value?.amount ?? 0;
1429
- state.reserved -= allocation;
1430
- const committed = state.committed + amount;
1431
- const overflow = !Number.isSafeInteger(committed);
1432
- state.committed = Math.min(committed, Number.MAX_SAFE_INTEGER);
1433
- state.remaining = state.limit - state.reserved - state.committed;
1434
- if (value?.known !== true || overflow) {
1435
- state.known = false;
1436
- const retained = Math.max(0, state.remaining);
1437
- state.committed += retained;
1438
- state.remaining -= retained;
1439
- fault ??= overflow ? `resource ${name}: amount overflow under an enforced limit` : `resource ${name}: unknown usage under an enforced limit`;
1440
- } else if (allocations !== void 0) {
1441
- if (amount > allocation) overspent.push({
1442
- channel: `resource:${name}`,
1443
- reserved: allocation,
1444
- spent: amount
1445
- });
1446
- } else if (state.remaining < 0) rootExceeded ??= `resource ${name}: exceeded root limit ${state.limit}`;
1447
- }
1448
- return {
1449
- ...fault ? { fault } : {},
1450
- overspent,
1451
- ...rootExceeded ? { rootExceeded } : {}
1452
- };
1453
- }
1454
- /** Every channel `b` does not fit, measured against the free balance less `keep`. */
1455
- function ledgerShortfalls(l, b, keep) {
1456
- const wantTokens = b.maxTokens;
1457
- const wantUsd = b.maxUsd ?? 0;
1458
- const wantIterations = b.maxIterations;
1459
- const shortfalls = [];
1460
- const short = (channel, requested, free, held, closedByUnknownSpend = false) => {
1461
- shortfalls.push({
1462
- channel,
1463
- requested,
1464
- free: closedByUnknownSpend ? 0 : Math.max(0, free),
1465
- ...closedByUnknownSpend || held <= 0 ? {} : { held },
1466
- ...closedByUnknownSpend ? { closedByUnknownSpend: true } : {}
1467
- });
1468
- };
1469
- for (const [name, state] of l.resources) {
1470
- const limit = b.resources[name].limit;
1471
- if (!state.known) short(`resource:${name}`, limit, 0, 0, true);
1472
- else if (limit > state.remaining) short(`resource:${name}`, limit, state.remaining, state.reserved);
1473
- }
1474
- if (l.usdCapped && l.usdTainted) short("usd", wantUsd, 0, 0, true);
1475
- const tokensFree = l.freeTokens - (keep?.tokens ?? 0);
1476
- const iterationsFree = l.freeIterations - (keep?.iterations ?? 0);
1477
- if (wantTokens > tokensFree) short("tokens", wantTokens, tokensFree, l.reservedTokens);
1478
- if (wantIterations > iterationsFree) short("iterations", wantIterations, iterationsFree, l.reservedIterations);
1479
- const usdFree = l.freeUsd - (keep?.usd ?? 0);
1480
- if (l.usdCapped && !l.usdTainted && wantUsd > usdFree) short("usd", wantUsd, usdFree, l.reservedUsd);
1481
- return shortfalls;
1482
- }
1483
- /** Move `b` from free to reserved. The caller has checked that it fits. */
1484
- function ledgerTake(l, b) {
1485
- for (const [name, state] of l.resources) {
1486
- const amount = b.resources[name].limit;
1487
- state.remaining -= amount;
1488
- state.reserved += amount;
1489
- }
1490
- l.freeTokens -= b.maxTokens;
1491
- l.reservedTokens += b.maxTokens;
1492
- l.freeIterations -= b.maxIterations;
1493
- l.reservedIterations += b.maxIterations;
1494
- const wantUsd = b.maxUsd ?? 0;
1495
- if (wantUsd > 0) {
1496
- l.freeUsd -= wantUsd;
1497
- l.reservedUsd += wantUsd;
1498
- }
1499
- }
1500
- /**
1501
- * The overspends a settlement of `spent` against `held` decides, before any balance moves. An
1502
- * OVERSPEND is measured spend above the reservation; unknown dollars under a dollar cap (a fault
1503
- * the caller reports) are flagged `unknownUnderCap`. `usdBudgeted` is whether the child declared a
1504
- * dollar ceiling: without `maxUsd` it reserved NO dollars, so under a capped root its dollars are
1505
- * OBSERVED spend, committed and debited from `free` like `observe`d inference.
1506
- */
1507
- function ledgerClassifyOverspend(l, held, usdBudgeted, spent) {
1508
- const unknownUnderCap = l.usdCapped && spent.usdKnown === false;
1509
- const spentTokens = chargedTokens(spent.tokens);
1510
- const overspent = [];
1511
- if (spentTokens > held.tokens) overspent.push({
1512
- channel: "tokens",
1513
- reserved: held.tokens,
1514
- spent: spentTokens
1515
- });
1516
- if (spent.iterations > held.iterations) overspent.push({
1517
- channel: "iterations",
1518
- reserved: held.iterations,
1519
- spent: spent.iterations
1520
- });
1521
- if (!unknownUnderCap && l.usdCapped && usdBudgeted && spent.usd > held.usd) overspent.push({
1522
- channel: "usd",
1523
- reserved: held.usd,
1524
- spent: spent.usd
1525
- });
1526
- return {
1527
- overspent,
1528
- unknownUnderCap
1529
- };
1530
- }
1531
- /**
1532
- * Release the whole reservation `held`, then commit actual spend; the difference is the refund
1533
- * that flows back to `free`. Unconditional arithmetic: nothing here throws on spend it can read.
1534
- */
1535
- function ledgerSettle(l, held, spent, unknownUnderCap) {
1536
- noteUnknowns(l, spent);
1537
- const spentTokens = chargedTokens(spent.tokens);
1538
- l.reservedTokens -= held.tokens;
1539
- l.committedTokens += spentTokens;
1540
- l.freeTokens += held.tokens - spentTokens;
1541
- l.reservedIterations -= held.iterations;
1542
- l.committedIterations += spent.iterations;
1543
- l.freeIterations += held.iterations - spent.iterations;
1544
- if (l.usdCapped) {
1545
- l.reservedUsd -= held.usd;
1546
- l.committedUsd += spent.usd;
1547
- if (unknownUnderCap) {
1548
- l.usdTainted = true;
1549
- l.freeUsd = 0;
1550
- } else l.freeUsd += held.usd - spent.usd;
1551
- } else l.committedUsd += spent.usd;
1552
- return ledgerCommitResources(l, spent, held.resources ?? {});
1553
- }
1554
- /**
1555
- * Direct free → committed debit (no ticket). `free` may go negative on overspend: the readout then
1556
- * reports exhaustion and the in-loop guard halts the driver. Work with an unreported token count
1557
- * still debits what IS known and marks the balance incomplete; refusing to record it is the "free
1558
- * turn" the pool must never see.
1559
- */
1560
- function ledgerObserve(l, spend, unknownUnderCap, requireAllResources) {
1561
- noteUnknowns(l, spend);
1562
- const tokens = chargedTokens(spend.tokens);
1563
- l.freeTokens -= tokens;
1564
- l.committedTokens += tokens;
1565
- l.freeIterations -= spend.iterations;
1566
- l.committedIterations += spend.iterations;
1567
- l.committedUsd += spend.usd;
1568
- if (l.usdCapped) l.freeUsd -= spend.usd;
1569
- if (unknownUnderCap) l.usdTainted = true;
1570
- return ledgerCommitResources(l, spend, void 0, requireAllResources);
1571
- }
1572
- /**
1573
- * Reconstruct the balances from a prior process's durable record. Measured prior spend is debited
1574
- * exactly once; a started-without-terminal child is charged at its entire reservation, so restart
1575
- * never mints capacity and the false known flags stay visible. Direct arithmetic, not `observe`:
1576
- * the prior spend ALREADY happened, and refusing to record it would be a zero-cost restart.
1577
- */
1578
- function ledgerRestore(l, root, restore) {
1579
- if (restore.committed !== void 0) {
1580
- const committed = restore.committed;
1581
- assertValidSpend(committed, "budget restore committed");
1582
- ledgerValidateUnits(l, committed);
1583
- ledgerCommitResources(l, committed);
1584
- noteUnknowns(l, committed);
1585
- const tokens = chargedTokens(committed.tokens);
1586
- l.freeTokens -= tokens;
1587
- l.committedTokens += tokens;
1588
- l.freeIterations -= committed.iterations;
1589
- l.committedIterations += committed.iterations;
1590
- l.committedUsd += committed.usd;
1591
- if (l.usdCapped) {
1592
- l.freeUsd -= committed.usd;
1593
- if (committed.usdKnown === false) {
1594
- l.usdTainted = true;
1595
- if (l.freeUsd > 0) l.committedUsd += l.freeUsd;
1596
- l.freeUsd = 0;
1597
- }
1598
- }
1599
- }
1600
- for (const [index, uncertain] of (restore.uncertainReservations ?? []).entries()) {
1601
- assertValidBudget(uncertain, `budget restore uncertainReservations[${index}]`);
1602
- ledgerCommitResources(l, withBudgetResources({
1603
- iterations: 0,
1604
- tokens: zeroTokenUsage(),
1605
- usd: 0,
1606
- ms: 0
1607
- }, root));
1608
- l.tokensTainted = true;
1609
- l.usdMeasured = false;
1610
- l.freeTokens -= uncertain.maxTokens;
1611
- l.committedTokens += uncertain.maxTokens;
1612
- l.freeIterations -= uncertain.maxIterations;
1613
- l.committedIterations += uncertain.maxIterations;
1614
- if (!l.usdCapped) continue;
1615
- if (uncertain.maxUsd === void 0) {
1616
- l.usdTainted = true;
1617
- l.committedUsd += l.freeUsd > 0 ? l.freeUsd : 0;
1618
- l.freeUsd = 0;
1619
- } else {
1620
- l.freeUsd -= uncertain.maxUsd;
1621
- l.committedUsd += uncertain.maxUsd;
1622
- }
1623
- }
1624
- }
1625
- // src/runtime/supervise/usage-metering.ts
1626
- /** Fold a normalized `UsageEvent` array into a `Spend`. Tokens and usd are separate
1627
- * channels; iterations come from `'iteration'` events. Pure; `ms` stays zero (the
1628
- * pool does not read wall-clock). */
1629
- function spendFromUsageEvents(events) {
1630
- const totals = newUsageTotals();
1631
- for (const ev of events) meterUsageEvent(totals, ev);
1632
- return spendFromUsageTotals(totals);
1633
- }
1634
- /** Fresh accounting totals, optionally restored from verified committed spend. */
1635
- function newUsageTotals(priorSpent) {
1636
- if (priorSpent !== void 0) assertValidSpend(priorSpent, "prior committed usage");
1637
- return {
1638
- ...addResourceSpend(priorSpent?.resources),
1639
- tokens: priorSpent === void 0 ? zeroTokenUsage() : cloneTokenUsage(priorSpent.tokens),
1640
- tokensKnown: priorSpent?.tokensKnown !== false,
1641
- ...priorSpent?.tokensProvenance === void 0 ? {} : { tokensProvenance: priorSpent.tokensProvenance },
1642
- usd: priorSpent?.usd ?? 0,
1643
- usdEstimated: priorSpent?.usdEstimated ?? 0,
1644
- usdKnown: priorSpent?.usdKnown !== false,
1645
- iterations: priorSpent?.iterations ?? 0
1646
- };
1647
- }
1648
- /**
1649
- * Fold ONE usage event into running totals — the single place that decides which event kinds
1650
- * meter. `progress` carries observed output rather than accounting, so it meters nothing; only
1651
- * `iteration` advances the iteration count.
1652
- */
1653
- function meterUsageEvent(totals, ev) {
1654
- if (ev.kind === "resource") {
1655
- totals.resources = addResourceSpend(totals.resources, Object.fromEntries([[ev.name, {
1656
- unit: ev.unit,
1657
- amount: ev.amount,
1658
- known: ev.known
1659
- }]])).resources;
1660
- return;
1661
- }
1662
- if (ev.kind === "tokens") {
1663
- if (ev.mode === "cumulative") {
1664
- if (ev.input < totals.tokens.input || ev.output < totals.tokens.output) throw new ValidationError("cumulative executor tokens must not decrease");
1665
- const snapshot = zeroTokenUsage();
1666
- addTokenUsage(snapshot, {
1667
- ...ev,
1668
- ...ev.freshInput === void 0 && totals.tokens.freshInput !== void 0 ? { freshInput: totals.tokens.freshInput } : {},
1669
- ...ev.cacheRead === void 0 && totals.tokens.cacheRead !== void 0 ? { cacheRead: totals.tokens.cacheRead } : {},
1670
- ...ev.cacheWrite === void 0 && totals.tokens.cacheWrite !== void 0 ? { cacheWrite: totals.tokens.cacheWrite } : {},
1671
- ...totals.tokens.tokensKnown === false ? { tokensKnown: false } : {}
1672
- });
1673
- totals.tokens = snapshot;
1674
- } else addTokenUsage(totals.tokens, ev);
1675
- if (ev.tokensKnown === false) totals.tokensKnown = false;
1676
- const source = ev.provenance ?? "stream-receipt";
1677
- totals.tokensProvenance = totals.tokensProvenance === void 0 || totals.tokensProvenance === source ? source : "mixed";
1678
- return;
1679
- }
1680
- if (ev.kind === "cost") {
1681
- totals.usd += ev.usd;
1682
- if (ev.usdKnown === false) {
1683
- totals.usdEstimated += ev.usdEstimated ?? 0;
1684
- totals.usdKnown = false;
1685
- }
1686
- return;
1687
- }
1688
- if (ev.kind === "iteration") totals.iterations += 1;
1689
- }
1690
- /** Project folded totals onto the conserved-pool `Spend`. `ms` stays zero — the pool does not
1691
- * read wall-clock. */
1692
- function spendFromUsageTotals(totals) {
1693
- return {
1694
- ...addResourceSpend(totals.resources),
1695
- iterations: totals.iterations,
1696
- tokens: totals.tokens,
1697
- ...totals.tokensKnown ? {} : { tokensKnown: false },
1698
- usd: totals.usd,
1699
- ...totals.usdKnown ? {} : { usdKnown: false },
1700
- ...totals.usdEstimated > 0 ? { usdEstimated: totals.usdEstimated } : {},
1701
- ...totals.tokensProvenance === void 0 || totals.tokensProvenance === "stream-receipt" ? {} : { tokensProvenance: totals.tokensProvenance },
1702
- ms: 0
1703
- };
1704
- }
1705
- async function foldUsage(events) {
1706
- if (Array.isArray(events)) return spendFromUsageEvents(events);
1707
- const totals = newUsageTotals();
1708
- for await (const ev of events) meterUsageEvent(totals, ev);
1709
- return spendFromUsageTotals(totals);
1710
- }
1711
- // src/runtime/supervise/budget.ts
1712
- /**
1713
- * The conserved budget reservation pool — the invariant the whole instrument rests on (critique
1714
- * M5/B3). One root `Budget` becomes a pool of standard quantities (tokens, usd, iterations) and
1715
- * caller-named resources plus an absolute deadline. Children reserve atomically at spawn and
1716
- * reconcile at settle:
1717
- *
1718
- * total ≡ free + reserved + committed (for every known quantity)
1719
- *
1720
- * `reserve` moves a child's whole ceiling `free` → `reserved` and fails closed when `free` can't
1721
- * cover it (never read-then-spawn overcommit, so `Σk(treatment) ≡ Σk(blind)` by construction).
1722
- * `reconcile` releases the reservation, commits ACTUAL spend, and refunds the remainder to `free`.
1723
- * Tokens and usd are separate channels; iterations are conserved alongside them.
1724
- *
1725
- * The token unit is `chargedTokens`: every token counted ONCE, when it first enters the context
1726
- * (`input − cacheRead + output`, which is `freshInput + cacheWrite + output` under a complete
1727
- * split). A cached prefix was already charged when written; charging a cache read again made a
1728
- * declared cap bite about 56x early on a real fleet (cache is 98% of the rolled-up prompt total).
1729
- * No price weight enters the token channel; money is budgeted on `maxUsd`. The unit is additive,
1730
- * so a rolled-up report agrees with the pool. Prompt tokens the provider never classified are
1731
- * charged in full and `readout().cacheBreakdownKnown` reads false (an upper bound on newly-
1732
- * presented work). The pool trusts reported usage: it is an accounting unit, not a trust boundary.
1733
- *
1734
- * Named resources require explicit units, ceilings, and complete executor measurements; missing
1735
- * or unknown enforced measurements close admission for that resource. The pool trusts those
1736
- * receipts and meters no external system. `Spend.boxMinutes` is evidence only, with no implicit
1737
- * resource ceiling; never present estimated box lifetime as a measured resource receipt.
1738
- *
1739
- * Pure and deterministic: the run's start instant is supplied; no I/O, wall-clock or RNG read. A
1740
- * ticket is single-use (fail-loud on double or unknown reconcile). Reconciling an OPEN ticket
1741
- * always closes it: a fail-loud condition settles the reservation first and throws afterwards, so
1742
- * no error path strands a reservation past `assertNoOpenTickets`. Measured spend above a
1743
- * reservation is not fail-loud: the pool commits it, returns a `BudgetViolation`, and refuses
1744
- * later reservations from the reduced free balance. A child that declared no `maxUsd` reserved no
1745
- * dollars, so its real dollars are committed as observed spend against the root, not as an
1746
- * overspend of a $0 ceiling. Unknowable dollar cost under a dollar limit closes the known
1747
- * token/iteration work, marks the dollar channel unusable and refuses later reservations. An
1748
- * unknowable TOKEN count records the work, debits what is known, and marks
1749
- * `readout().tokensKnown` false (a ceiling, not a measurement); tokens are always capped, so one
1750
- * unreported turn must not end the run.
1751
- *
1752
- * A reservation the free balance cannot cover now can WAIT (`reserve(b, holder, { wait: true })`)
1753
- * when it would fit once open reservations return. It holds nothing while waiting, so
1754
- * conservation is untouched; waiters are granted in asking order and refused once no open
1755
- * reservation is left to return any.
1756
- *
1757
- * A pool can be RESTORED from a prior process's durable record: measured committed spend is
1758
- * debited once, and each child journaled as started but never settled is charged at its full
1759
- * declared ceiling. Restart never mints capacity, unknown flags stay visible, and the original
1760
- * absolute deadline never slides.
1761
- *
1762
- * @experimental
1763
- */
1764
- /** A reconciliation whose spend the pool cannot verify, thrown after the reservation settled.
1765
- * `budgetViolation` carries any measured overspend on the same settlement. */
1766
- var BudgetReconcileFault = class extends Error {
1767
- budgetViolation;
1768
- constructor(message, budgetViolation) {
1769
- super(message);
1770
- this.name = "BudgetReconcileFault";
1771
- if (budgetViolation !== void 0) this.budgetViolation = budgetViolation;
1772
- }
1773
- };
1774
- /**
1775
- * A waiting reservation that can never be granted: every open reservation settled and the free
1776
- * balance still cannot cover it. `shortfalls` are the channels that did not fit at that moment.
1777
- */
1778
- var ReservationWaitRefused = class extends Error {
1779
- reason = "budget-exhausted";
1780
- shortfalls;
1781
- constructor(shortfalls) {
1782
- super(`budget-exhausted: nothing left running could return enough budget for this worker (${shortfalls.map((shortfall) => `${shortfall.channel} requested ${shortfall.requested}, free ${shortfall.free}`).join("; ")})`);
1783
- this.name = "ReservationWaitRefused";
1784
- this.shortfalls = shortfalls;
1785
- }
1786
- };
1787
- /**
1788
- * Create a conserved reservation pool from a root `Budget`. `runStartedAtMs` is the WALL-CLOCK
1789
- * instant the root was recorded; the deadline is `runStartedAtMs + root.deadlineMs` (`0` when
1790
- * none), an absolute instant. A resumed pool passes the ORIGINAL instant.
1791
- */
1792
- function createBudgetPool(root, runStartedAtMs, restore = {}) {
1793
- assertValidBudget(root, "root budget");
1794
- if (!Number.isFinite(runStartedAtMs) || runStartedAtMs < 0) throw new Error("budget runStartedAtMs must be a non-negative finite number");
1795
- const ledger = openBudgetLedger(root, runStartedAtMs);
1796
- let nextTicketId = 0;
1797
- const open = /* @__PURE__ */ new Map();
1798
- /** Tickets admitted to wait for budget, in the order they asked. Each is also in `open`. */
1799
- const waiting = [];
1800
- /** Open tickets that hold nothing: waiting, or refused and not yet reconciled. */
1801
- const holdingNothing = /* @__PURE__ */ new Set();
1802
- /** Grant waiting tickets in the order they asked while the head fits. When the head does not
1803
- * fit and no reservation is held that could return budget, nothing can ever cover it: refuse
1804
- * it, and go on with the next. */
1805
- function grantWaiting() {
1806
- while (waiting.length > 0) {
1807
- const head = waiting[0];
1808
- const shortfalls = ledgerShortfalls(ledger, head.budget, head.keep);
1809
- if (shortfalls.length === 0) {
1810
- waiting.shift();
1811
- ledgerTake(ledger, head.budget);
1812
- holdingNothing.delete(head.ticket.id);
1813
- head.grant();
1814
- continue;
1815
- }
1816
- if (open.size > holdingNothing.size) return;
1817
- waiting.shift();
1818
- head.refuse(new ReservationWaitRefused(shortfalls));
1819
- }
1820
- }
1821
- /** Admit `b` to wait: it holds nothing until settlements return enough, in the order asked. */
1822
- function admitToWait(b, holder, keep) {
1823
- const id = nextTicketId++;
1824
- open.set(id, holder);
1825
- const ticket = ticketFor(id, b);
1826
- let grant;
1827
- let refuse;
1828
- const granted = new Promise((resolve, reject) => {
1829
- grant = resolve;
1830
- refuse = reject;
1831
- });
1832
- granted.catch(() => void 0);
1833
- holdingNothing.add(id);
1834
- waiting.push({
1835
- ticket,
1836
- budget: b,
1837
- keep,
1838
- grant,
1839
- refuse
1840
- });
1841
- return {
1842
- ok: true,
1843
- ticket,
1844
- granted
1845
- };
1846
- }
1847
- function reserve(b, holder = { stage: "admitted" }, options = {}) {
1848
- assertValidBudget(b, "reservation budget");
1849
- assertResourceDeclarations(ledger, b);
1850
- const wantUsd = b.maxUsd ?? 0;
1851
- const keep = options.keep;
1852
- const shortfalls = ledgerShortfalls(ledger, b, keep);
1853
- if (shortfalls.length === 0 && wantUsd > 0 && !ledger.usdCapped) return {
1854
- ok: false,
1855
- reason: "usd-unbudgeted"
1856
- };
1857
- const queueAhead = options.wait === true && waiting.length > 0;
1858
- if (shortfalls.length === 0 && !queueAhead) {
1859
- ledgerTake(ledger, b);
1860
- const id = nextTicketId++;
1861
- open.set(id, holder);
1862
- return {
1863
- ok: true,
1864
- ticket: ticketFor(id, b)
1865
- };
1866
- }
1867
- if (options.wait !== true || !fitsAfterRefunds(shortfalls)) return {
1868
- ok: false,
1869
- reason: "budget-exhausted",
1870
- shortfalls
1871
- };
1872
- return admitToWait(b, holder, keep);
1873
- }
1874
- function attribute(ticket, holder) {
1875
- const current = open.get(ticket.id);
1876
- if (current === void 0) return;
1877
- open.set(ticket.id, {
1878
- ...current,
1879
- ...holder
1880
- });
1881
- }
1882
- function reconcile(ticket, spent) {
1883
- if (!open.has(ticket.id)) throw new Error(`budget pool: reconcile of unknown or already-settled ticket ${ticket.id}`);
1884
- assertValidSpend(spent, `budget pool ticket ${ticket.id} spend`);
1885
- ledgerValidateUnits(ledger, spent);
1886
- const heldNothing = holdingNothing.delete(ticket.id);
1887
- if (heldNothing) {
1888
- const index = waiting.findIndex((entry) => entry.ticket.id === ticket.id);
1889
- if (index >= 0) waiting.splice(index, 1);
1890
- }
1891
- const held = heldNothing ? {
1892
- tokens: 0,
1893
- usd: 0,
1894
- iterations: 0,
1895
- resources: void 0
1896
- } : ticket.reserved;
1897
- const usdBudgeted = ticket.reserved.usdBudgeted !== false;
1898
- const { overspent, unknownUnderCap } = ledgerClassifyOverspend(ledger, held, usdBudgeted, spent);
1899
- let fault = unknownUnderCap ? `ticket ${ticket.id} reported unknown dollar cost under a dollar-capped budget` : void 0;
1900
- open.delete(ticket.id);
1901
- const resourceSettlement = ledgerSettle(ledger, held, spent, unknownUnderCap);
1902
- fault ??= resourceSettlement.fault;
1903
- overspent.push(...resourceSettlement.overspent);
1904
- grantWaiting();
1905
- const violation = overspent.length === 0 ? void 0 : Object.freeze({ overspent: Object.freeze(overspent.map((entry) => Object.freeze(entry))) });
1906
- if (fault !== void 0) throw new BudgetReconcileFault(`budget pool: ${fault}`, violation);
1907
- return violation;
1908
- }
1909
- function observe(spend, options = {}) {
1910
- assertValidSpend(spend, "observed spend");
1911
- ledgerValidateUnits(ledger, spend);
1912
- const unknownUnderCap = ledger.usdCapped && spend.usdKnown === false;
1913
- if (unknownUnderCap && options.alreadySpent !== true) throw new ValidationError("budget pool: cannot observe unknown dollar cost under a dollar-capped budget");
1914
- const settlement = ledgerObserve(ledger, spend, unknownUnderCap, !options.partial);
1915
- const violation = settlement.fault ?? settlement.rootExceeded;
1916
- if (violation) throw new ValidationError(`budget pool: ${violation}`);
1917
- }
1918
- function readout() {
1919
- return {
1920
- ...ledger.resources.size ? { resources: Object.fromEntries([...ledger.resources].map(([name, value]) => [name, { ...value }])) } : {},
1921
- tokensLeft: ledger.freeTokens,
1922
- tokensKnown: !ledger.tokensTainted,
1923
- cacheBreakdownKnown: !ledger.cacheBreakdownTainted,
1924
- usdLeft: ledger.usdCapped ? ledger.usdTainted ? 0 : ledger.freeUsd : 0,
1925
- usdCapped: ledger.usdCapped,
1926
- usdKnown: ledger.usdMeasured && !ledger.usdTainted,
1927
- iterationsLeft: ledger.freeIterations,
1928
- deadlineMs: ledger.absoluteDeadlineMs,
1929
- reservedTokens: ledger.reservedTokens
1930
- };
1931
- }
1932
- function openReservations() {
1933
- return [...open].map(([ticketId, holder]) => Object.freeze({
1934
- ticketId,
1935
- ...holder
1936
- }));
1937
- }
1938
- function assertNoOpenTickets() {
1939
- if (open.size > 0) throw new Error(`budget pool: ${open.size} reservation(s) still open at join barrier (${openReservations().map(describeLeakedReservation).join("; ")}) — conserved-pool invariant violated`);
1940
- }
1941
- ledgerRestore(ledger, root, restore);
1942
- return {
1943
- reserve,
1944
- attribute,
1945
- reconcile,
1946
- spendFrom: foldUsage,
1947
- readout,
1948
- observe,
1949
- assertNoOpenTickets,
1950
- openReservations
1951
- };
1952
- }
1953
- /** A reservation must name a limit for exactly the resources the root declared, in its units. */
1954
- function assertResourceDeclarations(ledger, b) {
1955
- for (const [name, state] of ledger.resources) {
1956
- const wanted = b.resources?.[name];
1957
- if (!wanted) throw new ValidationError(`resource ${name}: child must declare its limit`);
1958
- if (wanted.unit !== state.unit) throw new ValidationError(`resource ${name}: unit mismatch`);
1959
- }
1960
- for (const name of Object.keys(b.resources ?? {})) if (!ledger.resources.has(name)) throw new ValidationError(`resource ${name}: root must declare its limit`);
1961
- }
1962
- /** Whether every short channel would fit once the open reservations return what they hold. */
1963
- function fitsAfterRefunds(shortfalls) {
1964
- return shortfalls.every((shortfall) => shortfall.closedByUnknownSpend !== true && shortfall.requested <= shortfall.free + (shortfall.held ?? 0));
1965
- }
1966
- function ticketFor(id, b) {
1967
- return {
1968
- id,
1969
- reserved: {
1970
- ...b.resources === void 0 ? {} : { resources: Object.fromEntries(Object.entries(b.resources).map(([name, value]) => [name, { ...value }])) },
1971
- tokens: b.maxTokens,
1972
- usd: b.maxUsd ?? 0,
1973
- iterations: b.maxIterations,
1974
- usdBudgeted: b.maxUsd !== void 0
1975
- }
1976
- };
1977
- }
1978
- /** Name one leaked reservation by its holder. */
1979
- function describeLeakedReservation(leak) {
1980
- const parts = [`ticket ${leak.ticketId}`];
1981
- if (leak.childId !== void 0) parts.push(`child ${leak.childId}`);
1982
- if (leak.label !== void 0) parts.push(`label ${leak.label}`);
1983
- if (leak.assignment !== void 0) parts.push(`assignment ${leak.assignment}`);
1984
- parts.push(`stage ${leak.stage}`);
1985
- return parts.join(", ");
1986
- }
1987
- // src/runtime/supervise/credential-refusal.ts
1988
- /**
1989
- * The account owner refused an unsupported credential route before dispatch. This is the
1990
- * provider SDK's machine contract, not an HTTP status, class-name guess, or prose match.
1991
- */
1992
- function isCredentialRefusal(error) {
1993
- if (!(error instanceof Error)) return false;
1994
- try {
1995
- return Object.getOwnPropertyDescriptor(error, "code")?.value === "model_credential_refused";
1996
- } catch {
1997
- return false;
1998
- }
1999
- }
2000
- /**
2001
- * The message prefix of agent-provider-tangle's generic credential command failure. The provider
2002
- * exports no type or constant for it, so the kernel credential test raises it through the
2003
- * provider's own command resolver and fails when the message drifts.
2004
- */
2005
- const CREDENTIAL_COMMAND_FAILURE = "Tangle credential command failed (";
2006
- /**
2007
- * The exit code of agent-provider-tangle's credential command (`acct sandbox resolve`) in the
2008
- * error or its causes, or undefined.
2009
- *
2010
- * The provider reports the command's generic failure as `Tangle credential command failed
2011
- * (exit N)` with the process's `exitCode` and `terminationSignal` as own fields; a refusal it can
2012
- * parse (exit 75 or 78) arrives typed instead and is classified on its code. Before any dispatch
2013
- * this failure ran nothing, and counting it as an unresolved pending invocation stopped 10 nodes
2014
- * and lost 7 runs between 2026-10-12 and 2026-10-19 (test-acceptance-canary-20261015c met 26 in
2015
- * 2.2 minutes). It pauses like a capacity refusal, bounded by the deadline, the budget and
2016
- * cancellation as every pause is.
2017
- */
2018
- function credentialCommandExit(error) {
2019
- for (const link of causeChain(error)) try {
2020
- if (!(link instanceof Error) || !link.message.startsWith(CREDENTIAL_COMMAND_FAILURE) || !Object.hasOwn(link, "terminationSignal")) continue;
2021
- const exitCode = Reflect.get(link, "exitCode");
2022
- if (typeof exitCode === "number" && Number.isInteger(exitCode) && exitCode > 0) return exitCode;
2023
- } catch {}
2024
- }
2025
- // src/runtime/json-content-hash.ts
2026
- /**
2027
- * Stable hash for the trace payload. Not cryptographic — only used so
2028
- * downstream eval pipelines can group iterations whose task / output is the
2029
- * same. Bare structural hash; non-JSON values stringify via their `toString`.
2030
- */
2031
- function hashJson(value) {
2032
- const str = jsonContentText(value);
2033
- return fnv1a32Hex(str);
2034
- }
2035
- /** JSON text with String fallback, including quoted JSON strings and cyclic values. */
2036
- function jsonContentText(value) {
2037
- let str;
2038
- try {
2039
- str = JSON.stringify(value) ?? String(value);
2040
- } catch {
2041
- str = String(value);
2042
- }
2043
- return str;
2044
- }
2045
- // src/runtime/supervise/executor-outcome.ts
2046
- const outcomeSchema = AgentTurnResultSchema.pick({
2047
- success: true,
2048
- error: true
2049
- }).extend({
2050
- errorCode: AgentTurnResultSchema.shape.error,
2051
- statusCode: z.number().int().min(400).max(599).optional()
2052
- }).strict();
2053
- /** Read only the executor envelope; application output and grades are not execution status. */
2054
- function executorFailure(result) {
2055
- if (result.outcome === void 0) return void 0;
2056
- const outcome = outcomeSchema.parse(result.outcome);
2057
- if (outcome.success) return void 0;
2058
- return {
2059
- error: outcome.error ?? "Agent execution failed",
2060
- ...outcome.errorCode === void 0 ? {} : { errorCode: outcome.errorCode },
2061
- ...outcome.statusCode === void 0 ? {} : { statusCode: outcome.statusCode }
2062
- };
2063
- }
2064
- /** The failure text alone, for callers that record a settlement reason. */
2065
- function executorFailureReason(result) {
2066
- return executorFailure(result)?.error;
2067
- }
2068
- /**
2069
- * The platform's own failure vocabulary: machine codes and lifecycle messages that the sandbox
2070
- * runtime, the router, or the egress path put in an executor envelope when the platform, not
2071
- * the agent's work, ended the execution. Each entry names its evidence: the count is how many
2072
- * down children carried it on the 2026-09-20 fleet corpus (277 down settlements across 691
2073
- * runs), every one of them stamped `infra: false` by the hard-coded call site this table
2074
- * replaced. The table is deliberately the platform's fixed strings, not the agent's prose.
2075
- */
2076
- const PLATFORM_FAILURE_CODES = new Set([
2077
- "QUOTA_EXCEEDED",
2078
- "provider_quota_exceeded",
2079
- "provider_quota_exhausted",
2080
- "buffer.overflow",
2081
- "TIMEOUT",
2082
- "ECONNRESET",
2083
- "ECONNREFUSED",
2084
- "ETIMEDOUT",
2085
- "EPIPE",
2086
- "EADDRINUSE"
2087
- ].map((code) => code.toLowerCase()));
2088
- const PLATFORM_FAILURE_MESSAGES = [
2089
- /^terminated$/u,
2090
- /Execution exceeded its time limit/u,
2091
- /Execution interrupted: the agent runtime restarted/u,
2092
- /Platform key[- ]verification/u,
2093
- /Platform key delegation failed/u,
2094
- /Bad Gateway/iu,
2095
- /Cannot connect to API/u,
2096
- /Failed to connect to Sandbox API/u,
2097
- /fetch failed/u,
2098
- /A sandbox lifecycle operation is already in progress/u,
2099
- /exceeded its string bound/u,
2100
- /exceeds its JSON bound/u,
2101
- /No provider served model/u,
2102
- /Invalid API key/u,
2103
- /provider_quota_exceeded|provider_quota_exhausted|QUOTA_EXCEEDED|rate limit/iu,
2104
- /is at its box cap/u,
2105
- /could not materialize the seat credential/u,
2106
- /OpenCode exited while a model step was still in progress/u,
2107
- /the agent runtime restarted/u
2108
- ];
2109
- /**
2110
- * Whether an executor envelope's failure was the platform's, as far as the envelope can say.
2111
- *
2112
- * `true` when the code or the message is in the platform's own vocabulary above. `undefined`
2113
- * otherwise: the harness reported a failure and nothing in the envelope attributes it, so the
2114
- * settlement carries no `infra` claim at all rather than a false one. This path never answers
2115
- * `false`, because an executor envelope cannot prove the agent's work was at fault; the throw
2116
- * path in `scope.ts` still answers `false` for an ordinary thrown result, where the runtime does
2117
- * know. Until this function existed both envelope sites stamped `infra: false` unconditionally,
2118
- * and the flag caught 1 of the 78 platform losses it was checked against on the fleet corpus.
2119
- */
2120
- function executorFailureInfra(failure) {
2121
- const code = failure.errorCode?.toLowerCase();
2122
- if (code !== void 0 && PLATFORM_FAILURE_CODES.has(code)) return true;
2123
- return PLATFORM_FAILURE_MESSAGES.some((pattern) => pattern.test(failure.error)) ? true : void 0;
2124
- }
2125
- /** Preserve deterministic result pointers across built-in executors and replay.
2126
- * FNV-1a is an identity convention here, not a cryptographic integrity check. */
2127
- function contentRef(prefix, value) {
2128
- const str = jsonContentText(value);
2129
- return `${prefix}:${fnv1a32Hex(str)}`;
2130
- }
2131
- // src/durable/content-address.ts
2132
- /** Stable content address shared by result and trace artifacts. */
2133
- function contentAddress(artifact) {
2134
- return `sha256:${createHash("sha256").update(stableStringify(artifact), "utf-8").digest("hex")}`;
2135
- }
2136
- function stableStringify(value) {
2137
- if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
2138
- if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
2139
- return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
2140
- }
2141
- // src/runtime/supervise/trace-root.ts
2142
- /**
2143
- * Trace roots: one Merkle root over the events a turn archived, so a single event can be proven
2144
- * against the hash its journal record carries without reading the rest of the archive.
2145
- *
2146
- * The leaves are the archived events in stored order, each hashed with its own random salt, then
2147
- * one summary leaf that counts the events and the tool calls among them. The artifact keeps every
2148
- * leaf and salt and is stored under its content address in the run's result blob store, beside
2149
- * the archive it commits to. A verifier recomputes leaf `i` from `events[i]` of the `outRef` blob
2150
- * and `salts[i]`, checks it against `leaves[i]`, and checks `merkleRoot(leaves)` against the root
2151
- * the journal recorded. Leaf identity is `(outRef, index)`: an event's own `id` is a session
2152
- * buffer offset and is absent on terminal events.
2153
- *
2154
- * One event array has one root per node. A result always gets its own. Evidence whose events the
2155
- * node already rooted gets none: a retained owner's deferred workspace capture stores the turn it
2156
- * just committed again, with the workspace fields added, under another `outRef`. A second root
2157
- * over the same events would give one turn two count candidates and two run-root leaves.
2158
- *
2159
- * Salts are random, so a trace root can never be recomputed from the archive alone. Recovery and
2160
- * replay copy the recorded receipt; they never derive a new one.
2161
- */
2162
- /** Schema identifier of a stored {@link TraceRootArtifact}. */
2163
- const TRACE_ROOT_SCHEMA = "agent-runtime.trace-root.v1";
2164
- /** Schema identifier of the {@link TraceSummary} carried by a trace root's last leaf. */
2165
- const TRACE_SUMMARY_SCHEMA = "agent-runtime.trace-summary.v1";
2166
- /** @internal */
2167
- function traceRootWriter(blobs, nodeId) {
2168
- const roots = /* @__PURE__ */ new Map();
2169
- /** Digests of the event arrays this node holds a stored root for. */
2170
- const rooted = /* @__PURE__ */ new Set();
2171
- const persist = async (outRef, out, evidence) => {
2172
- const events = archivedEvents(out);
2173
- if (events === void 0) return void 0;
2174
- if (evidence) {
2175
- await Promise.allSettled([...roots.values()]);
2176
- if (rooted.has(await eventsDigest(events))) return void 0;
2177
- }
2178
- const stored = await persistTraceRoot(blobs, nodeId, outRef, events);
2179
- if (stored === void 0) return void 0;
2180
- rooted.add(stored.digest);
2181
- return stored.receipt;
2182
- };
2183
- const memoized = (outRef, out, evidence) => {
2184
- const known = roots.get(outRef);
2185
- if (known !== void 0) return known;
2186
- const pending = persist(outRef, out, evidence);
2187
- roots.set(outRef, pending);
2188
- pending.catch(() => roots.delete(outRef));
2189
- return pending;
2190
- };
2191
- return {
2192
- write: (outRef, out) => memoized(outRef, out, false),
2193
- writeEvidence: (outRef, out) => memoized(outRef, out, true),
2194
- async recorded(outRef) {
2195
- if (outRef === void 0) return void 0;
2196
- return roots.get(outRef)?.catch(() => void 0);
2197
- }
2198
- };
2199
- }
2200
- async function persistTraceRoot(blobs, nodeId, outRef, events) {
2201
- let built;
2202
- try {
2203
- built = await traceRootArtifact(nodeId, outRef, events);
2204
- } catch (error) {
2205
- if (error instanceof LedgerCanonicalizationError) return void 0;
2206
- throw error;
2207
- }
2208
- const { artifact, digest } = built;
2209
- await nextTurn();
2210
- const ref = contentAddress(artifact);
2211
- await nextTurn();
2212
- await blobs.put(ref, artifact);
2213
- return {
2214
- receipt: {
2215
- root: artifact.root,
2216
- ref,
2217
- leafCount: artifact.leafCount
2218
- },
2219
- digest
2220
- };
2221
- }
2222
- function archivedEvents(out) {
2223
- if (out === null || typeof out !== "object" || Array.isArray(out)) return void 0;
2224
- const events = out.events;
2225
- return Array.isArray(events) ? events : void 0;
2226
- }
2227
- /** The longest stretch of hashing between yields to the event loop. Hashing runs about 40 µs per
2228
- * event, and the largest archived turn holds 65,276 events: unbroken, that blocked the process
2229
- * for 2.7 s while sibling turns, coordination calls and heartbeats waited. A slice is measured
2230
- * in time, not events, because one event can be megabytes. */
2231
- const SLICE_MS = 10;
2232
- /** Yields to the event loop once a slice has run `SLICE_MS`; `undefined` while it has not. */
2233
- function slicer() {
2234
- let started = performance.now();
2235
- return () => {
2236
- if (performance.now() - started < SLICE_MS) return void 0;
2237
- return nextTurn().then(() => {
2238
- started = performance.now();
2239
- });
2240
- };
2241
- }
2242
- function nextTurn() {
2243
- return new Promise((resolve) => setImmediate(resolve));
2244
- }
2245
- /** The identity of an event array as stored: the digest of each event's stored JSON, in order.
2246
- * Two arrays with one digest have the same leaves' content. */
2247
- async function eventsDigest(events) {
2248
- const hash = createHash("sha256");
2249
- const slice = slicer();
2250
- for (const event of events) {
2251
- const yielded = slice();
2252
- if (yielded !== void 0) await yielded;
2253
- hash.update(storedText(event)).update("\n");
2254
- }
2255
- return hash.digest("hex");
2256
- }
2257
- async function traceRootArtifact(nodeId, outRef, archived) {
2258
- const salts = [];
2259
- const leaves = [];
2260
- const tree = merkleTree();
2261
- const identity = createHash("sha256");
2262
- const tools = createSandboxToolPartState();
2263
- const toolCalls = /* @__PURE__ */ new Map();
2264
- const slice = slicer();
2265
- for (const event of archived) {
2266
- const yielded = slice();
2267
- if (yielded !== void 0) await yielded;
2268
- const text = storedText(event);
2269
- identity.update(text).update("\n");
2270
- const stored = JSON.parse(text);
2271
- const salt = merkleSalt();
2272
- const leaf = merkleLeafHash(canonicalString(jsonDocument(stored)), salt);
2273
- salts.push(salt);
2274
- leaves.push(leaf);
2275
- tree.push(leaf);
2276
- for (const projected of mapSandboxToolEvent(stored, tools)) {
2277
- if (projected.type !== "tool_call") continue;
2278
- toolCalls.set(projected.toolName, (toolCalls.get(projected.toolName) ?? 0) + 1);
2279
- }
2280
- }
2281
- const summary = {
2282
- schema: TRACE_SUMMARY_SCHEMA,
2283
- events: archived.length,
2284
- toolCalls: Object.fromEntries(toolCalls)
2285
- };
2286
- const salt = merkleSalt();
2287
- const leaf = merkleLeafHash(canonicalString(summary), salt);
2288
- salts.push(salt);
2289
- leaves.push(leaf);
2290
- tree.push(leaf);
2291
- return {
2292
- artifact: {
2293
- schema: TRACE_ROOT_SCHEMA,
2294
- nodeId,
2295
- outRef,
2296
- root: tree.root(),
2297
- leafCount: leaves.length,
2298
- salts,
2299
- leaves,
2300
- summary
2301
- },
2302
- digest: identity.digest("hex")
2303
- };
2304
- }
2305
- /**
2306
- * `merkleRoot(leaves)` built as the leaves arrive, so its interior hashes run inside the sliced
2307
- * leaf loop instead of in one block after it (0.4 s for 65,277 leaves). It keeps the perfect
2308
- * subtrees of RFC 9162, largest first, and joins two with ledger-core's own interior hash,
2309
- * `merkleRoot([left, right])`; the root folds them from the right, as RFC 9162 splits at the
2310
- * largest power of two.
2311
- */
2312
- function merkleTree() {
2313
- const peaks = [];
2314
- return {
2315
- push(leaf) {
2316
- let hash = leaf;
2317
- let size = 1;
2318
- while (peaks.at(-1)?.size === size) {
2319
- hash = merkleRoot([peaks.pop().hash, hash]);
2320
- size *= 2;
2321
- }
2322
- peaks.push({
2323
- hash,
2324
- size
2325
- });
2326
- },
2327
- root() {
2328
- let root = peaks.at(-1).hash;
2329
- for (let index = peaks.length - 2; index >= 0; index -= 1) root = merkleRoot([peaks[index].hash, root]);
2330
- return root;
2331
- }
2332
- };
2333
- }
2334
- /** One array item as `JSON.stringify` writes it: `JSON.parse` of it is what `events[i]` holds. */
2335
- function storedText(event) {
2336
- return JSON.stringify(event) ?? "null";
2337
- }
2338
- // src/runtime/supervise/upstream-unavailable.ts
2339
- /**
2340
- * An upstream that cannot serve NOW: how Runtime recognizes one, and how long an agent waits for it.
2341
- *
2342
- * A model provider's exhausted quota, a rate limit, an overload, or the router's own refused
2343
- * credential is not a fault in the agent: the same request succeeds when capacity returns. A
2344
- * driver that meets one re-enters after a pause (`runDriverWithRetry`), and a leaf continues in
2345
- * its environment after the same pause (`providerAsExecutor`). Both read the refusal and the pause
2346
- * from here, so an outage costs a director and its workers the same number of turns.
2347
- *
2348
- * This module imports nothing, so the provider executor can read it without the error classes of
2349
- * `../../errors`.
2350
- */
2351
- /**
2352
- * Machine codes that say the upstream cannot serve now rather than that the request is wrong.
2353
- * Each is a code an upstream publishes, not prose: the router's
2354
- * (`lib/model-substitution.ts`, `lib/upstream-error-triage.ts`) and the OpenAI- and Anthropic-shaped
2355
- * error types a relayed body carries.
2356
- */
2357
- const UNAVAILABLE_CODES = /* @__PURE__ */ new Set([
2358
- "provider_quota_exhausted",
2359
- "provider_quota_exceeded",
2360
- "provider_rate_limit",
2361
- "upstream_unavailable",
2362
- "provider_key_invalid",
2363
- "rate_limit_exceeded",
2364
- "rate_limit_error",
2365
- "overloaded_error"
2366
- ]);
2367
- /** 429 Too Many Requests, 503 Service Unavailable, and 529, which Anthropic uses for overload. */
2368
- const UNAVAILABLE_STATUSES = /* @__PURE__ */ new Set([
2369
- 429,
2370
- 503,
2371
- 529
2372
- ]);
2373
- /**
2374
- * A harness that flattens the router's JSON body into its own failure text leaves the router's
2375
- * code in that text: `opencode execution failed: No provider served model "..."
2376
- * (provider_quota_exhausted)`. The code is matched as a whole token, and a status only in the
2377
- * `status code <n>` framing a harness prints, so prose that merely mentions a quota does not match.
2378
- */
2379
- function unavailableSignalInText(text) {
2380
- for (const token of text.toLowerCase().matchAll(/[a-z_]+/gu)) if (UNAVAILABLE_CODES.has(token[0])) return token[0];
2381
- const status = text.match(/\bstatus code (\d{3})\b/u)?.[1];
2382
- return status !== void 0 && UNAVAILABLE_STATUSES.has(Number(status)) ? `http-${status}` : void 0;
2383
- }
2384
- /**
2385
- * The code or status that marks a failed turn outcome as an upstream capacity refusal, or
2386
- * `undefined`. The outcome's own `errorCode` decides first; the failure text is read only when no
2387
- * code or numeric status decided, because a harness CLI can report the router's refusal as text.
2388
- */
2389
- function unavailableSignalOfFailure(failure) {
2390
- const code = failure.errorCode?.toLowerCase();
2391
- if (code !== void 0 && UNAVAILABLE_CODES.has(code)) return code;
2392
- if (failure.statusCode !== void 0) return UNAVAILABLE_STATUSES.has(failure.statusCode) ? `http-${failure.statusCode}` : void 0;
2393
- return unavailableSignalInText(failure.error);
2394
- }
2395
- const DEFAULT_UNAVAILABLE_PAUSE_MS = 15e3;
2396
- const DEFAULT_MAX_UNAVAILABLE_PAUSE_MS = 3e5;
2397
- /**
2398
- * The pause after the `consecutive`-th refusal in a row (1-based): the first pause, doubled per
2399
- * earlier refusal, up to the ceiling. The one rule for a driver that re-enters and a leaf that
2400
- * continues in its environment, so an outage costs both the same number of turns.
2401
- */
2402
- function unavailablePauseMs(consecutive, policy = {}) {
2403
- return cappedDoublingMs(consecutive, policy.unavailablePauseMs ?? DEFAULT_UNAVAILABLE_PAUSE_MS, policy.maxUnavailablePauseMs ?? DEFAULT_MAX_UNAVAILABLE_PAUSE_MS);
2404
- }
2405
- /** `first` doubled per earlier consecutive wait (`consecutive` is 1-based), up to `ceiling`. */
2406
- function cappedDoublingMs(consecutive, first, ceiling) {
2407
- const start = Math.max(0, first);
2408
- return Math.min(Math.max(start, ceiling), start * 2 ** Math.max(0, consecutive - 1));
2409
- }
2410
- /** The longest hold a refusal's own reset time may ask for: a 5-hour plan window plus a margin. */
2411
- const MAX_RESET_HOLD_MS = 216e5;
2412
- /** How often a refusal whose reset is beyond {@link MAX_RESET_HOLD_MS} is probed again. */
2413
- const FAR_RESET_PROBE_MS = 36e5;
2414
- /**
2415
- * When a refusal says its quota resets, as epoch ms, or `undefined` when it names no usable time.
2416
- * The Z.ai Coding Plan's opencode refusal names one in Beijing time with no zone:
2417
- * `Usage limit reached for 5 hour. Your limit will reset at 2026-10-18 15:17:33`. On Discovery run
2418
- * magnetar-rust-20261018b, 219 of 324 driver attempts ended on it between 06:04Z and 22:18Z, and
2419
- * the last refusal came at 22:18Z against `reset at 2026-10-19 06:18:14`, which is 22:18:14Z at
2420
- * +08:00. A time with its own zone is read as written. A time in the past is no reset time: the
2421
- * caller keeps its doubling pause. A time more than MAX_RESET_HOLD_MS ahead is held for only
2422
- * FAR_RESET_PROBE_MS at a time, so a misread zone cannot hold a seat for a day, yet the spent seat
2423
- * is not asked again every 5 minutes: on magnetar-rust-20261019c a reset beyond 6 h drew about 12
2424
- * attempts an hour against the refusing seat.
2425
- */
2426
- function unavailableResumeAt(text, nowMs) {
2427
- const match = text.match(/\breset at (\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}:\d{2})(?:\.\d+)?(Z|[+-]\d{2}:?\d{2})?/u);
2428
- if (match === null) return void 0;
2429
- const zone = match[3] ?? (/Usage limit reached for \d+ hour/u.test(text) ? "+08:00" : void 0);
2430
- if (zone === void 0) return void 0;
2431
- const at = Date.parse(`${match[1]}T${match[2]}${zone.length === 5 ? `${zone.slice(0, 3)}:${zone.slice(3)}` : zone}`);
2432
- if (!Number.isFinite(at) || at <= nowMs) return void 0;
2433
- return at - nowMs > MAX_RESET_HOLD_MS ? nowMs + FAR_RESET_PROBE_MS : at;
2434
- }
2435
- /**
2436
- * Spread a wait over `[ms / 2, ms]`. Every driver a Platform restart cut off fails within the same
2437
- * seconds; without jitter their doubling schedules stay aligned and they all re-enter together
2438
- * when the service returns. `random` returns a value in `[0, 1)`; 0 keeps the full wait.
2439
- */
2440
- function jitteredMs(ms, random) {
2441
- return ms - Math.floor(ms / 2 * Math.min(1, Math.max(0, random())));
2442
- }
2443
- // src/runtime/supervise/retained-executor.ts
2444
- /** Scope owns the durable writer; provider executors only publish sanitized admissions. */
2445
- const retainedExecutorSeamKey = "runtime.retainedExecutor";
2446
- /** @internal A capture may finish after cancellation; its durable citation remains appendable. */
2447
- function executorEvidenceWriter(args) {
2448
- const traceRoots = args.traceRoots ?? traceRootWriter(args.blobs, args.nodeId);
2449
- return async (out) => {
2450
- const snapshot = detachedSnapshot(out, "executor evidence");
2451
- const outRef = contentAddress(snapshot);
2452
- await args.blobs.put(outRef, snapshot);
2453
- const traceRoot = await traceRoots.writeEvidence(outRef, snapshot);
2454
- await args.journal.appendEvent(args.rootId, {
2455
- kind: "execution-evidence",
2456
- id: args.nodeId,
2457
- outRef,
2458
- ...traceRoot === void 0 ? {} : { traceRoot },
2459
- seq: args.nextSequence(),
2460
- at: new Date(args.now()).toISOString()
2461
- });
2462
- };
2463
- }
2464
- /** @internal The journal writers for `onWorkspaceCapture` and `onWorkspaceCaptureStarted`;
2465
- * `nextSequence` is the node's own. */
2466
- function workspaceCaptureWriter(args) {
2467
- return {
2468
- onWorkspaceCapture: async (capture) => {
2469
- await args.journal.appendEvent(args.rootId, {
2470
- kind: "workspace-capture",
2471
- id: args.nodeId,
2472
- ...detachedSnapshot(capture, "workspace capture timing"),
2473
- seq: args.nextSequence(),
2474
- at: new Date(args.now()).toISOString()
2475
- });
2476
- },
2477
- onWorkspaceCaptureStarted: async (capture) => {
2478
- await args.journal.appendEvent(args.rootId, {
2479
- kind: "workspace-capture-started",
2480
- id: args.nodeId,
2481
- ...detachedSnapshot(capture, "workspace capture start"),
2482
- seq: args.nextSequence(),
2483
- at: new Date(args.now()).toISOString()
2484
- });
2485
- }
2486
- };
2487
- }
2488
- function retainedExecutorContext(ctx) {
2489
- return ctx.seams[retainedExecutorSeamKey];
2490
- }
2491
- const causeMessages = {
2492
- unobservable: "retained provider execution requires reconciliation before replacement",
2493
- "provider-contract": "retained provider response violated its contract; original execution remains unresolved",
2494
- "request-rejected": "retained provider execution was refused before it ran; nothing to reconcile",
2495
- transport: "retained provider execution lost its transport; status in doubt",
2496
- "nested-recovery": "retained nested execution requires recovery before replacement"
2497
- };
2498
- /**
2499
- * agent-provider-tangle's `TangleDispatchNotAdmittedError` (since 3.6.11): Sandbox answered a
2500
- * dispatch with `dispatched: false` and named another execution that owns the session or turn.
2501
- * Nothing ran for the request.
2502
- */
2503
- const DISPATCH_NOT_ADMITTED_CODE = "DISPATCH_NOT_ADMITTED";
2504
- /**
2505
- * The Sandbox SDK's codes for a box that cannot be provisioned for this profile, sent with HTTP
2506
- * 422 when the failure is deterministic: a profile file mount outside the box's writable roots,
2507
- * refused before any box exists, and a profile-file materialization that would fail the same way
2508
- * again. On Discovery run queens21-h22-capture-root-1-20261019c every child profile mounted
2509
- * `/work/queens21_count.c` on a read-only root filesystem, and 154 attempts across 4 workers were
2510
- * retried as a lost transport. The same codes at another status may be transient and are not
2511
- * read here.
2512
- */
2513
- const PROVISIONING_REFUSAL_CODES = /* @__PURE__ */ new Set(["PROFILE_FILE_MOUNT_OUTSIDE_ROOTS", "PROFILE_FILES_MATERIALIZE_FAILED"]);
2514
- /** True for an error that is itself a deterministic provisioning refusal (see above). */
2515
- function isProvisioningRefusal(value) {
2516
- try {
2517
- const code = readString(value, "code");
2518
- const status = readNumber(value, "status") ?? readNumber(value, "statusCode");
2519
- return code !== void 0 && status === 422 && PROVISIONING_REFUSAL_CODES.has(code);
2520
- } catch {
2521
- return false;
2522
- }
2523
- }
2524
- /**
2525
- * Platform refusals the orchestrator answers BEFORE it forwards a dispatch, by machine code and
2526
- * the HTTP status it is sent with. Run 002f69d5: `HUB_CREDENTIAL_REFRESH_FAILED` at 503 left no
2527
- * effect, yet read as a lost transport. Any other 5xx stays a transport failure, in doubt.
2528
- */
2529
- const PLATFORM_PREDISPATCH_REFUSALS = /* @__PURE__ */ new Map([["HUB_CREDENTIAL_REFRESH_FAILED", 503]]);
2530
- /** True for an error that is itself a named platform refusal of a dispatch (see above). */
2531
- function isPlatformPredispatchRefusal(value) {
2532
- try {
2533
- const code = readString(value, "code");
2534
- const status = readNumber(value, "status") ?? readNumber(value, "statusCode");
2535
- const expected = code === void 0 ? void 0 : PLATFORM_PREDISPATCH_REFUSALS.get(code);
2536
- return expected !== void 0 && expected === status;
2537
- } catch {
2538
- return false;
2539
- }
2540
- }
2541
- /** The contract-error codes that wrap a failed READ rather than name a broken answer. */
2542
- const readFailureCodes = /* @__PURE__ */ new Set(["RETAINED_RESULT_READ_FAILED", "RETAINED_CONTROL_REF_READ_FAILED"]);
2543
- /** Socket-level failures, by the codes Node and undici assign, plus the Sandbox SDK's own. */
2544
- const TRANSPORT_ERROR_CODES = /* @__PURE__ */ new Set([
2545
- "ECONNRESET",
2546
- "ECONNREFUSED",
2547
- "ETIMEDOUT",
2548
- "ENOTFOUND",
2549
- "EAI_AGAIN",
2550
- "EPIPE",
2551
- "UND_ERR_SOCKET",
2552
- "UND_ERR_CONNECT_TIMEOUT",
2553
- "UND_ERR_HEADERS_TIMEOUT",
2554
- "UND_ERR_BODY_TIMEOUT",
2555
- "NETWORK_ERROR"
2556
- ]);
2557
- function readNumber(value, key) {
2558
- const read = Reflect.get(value, key);
2559
- return typeof read === "number" && Number.isFinite(read) ? read : void 0;
2560
- }
2561
- function readString(value, key) {
2562
- const read = Reflect.get(value, key);
2563
- return typeof read === "string" ? read : void 0;
2564
- }
2565
- const credentialAdmissionRefusals = /* @__PURE__ */ new WeakSet();
2566
- /**
2567
- * Walk the cause chain (bounded, cycle-safe) and name the first cause whose structure decides.
2568
- *
2569
- * `phase` says whether a 4xx or a client deadline can mean "refused before it ran": only at
2570
- * admission. After admission the same status means the provider cannot resolve what it
2571
- * admitted, and the honest name is the refusal.
2572
- */
2573
- function classifyRetainedPendingCause(cause, phase = "execution") {
2574
- return classifyPendingCause(cause, phase);
2575
- }
2576
- function classifyPendingCause(cause, phase, onCredentialRefusal) {
2577
- const seen = /* @__PURE__ */ new Set();
2578
- const queue = [{
2579
- value: cause,
2580
- phase
2581
- }];
2582
- let steps = 0;
2583
- while (queue.length > 0 && steps < 12) {
2584
- steps += 1;
2585
- const { value, phase: at } = queue.shift();
2586
- if (typeof value !== "object" || value === null || seen.has(value)) continue;
2587
- seen.add(value);
2588
- if (value instanceof RetainedExecutionPendingError) {
2589
- if (credentialAdmissionRefusals.has(value)) {
2590
- if (at === "execution") return "unobservable";
2591
- onCredentialRefusal?.();
2592
- }
2593
- return value.pendingCause;
2594
- }
2595
- const name = readString(value, "name");
2596
- const code = readString(value, "code");
2597
- const status = readNumber(value, "status") ?? readNumber(value, "statusCode");
2598
- if (name === "RetainedRunProviderContractError") {
2599
- if (code === void 0 || !readFailureCodes.has(code)) return "provider-contract";
2600
- queue.unshift({
2601
- value: Reflect.get(value, "cause"),
2602
- phase: "execution"
2603
- });
2604
- continue;
2605
- }
2606
- if (name === "ZodError" || Array.isArray(Reflect.get(value, "issues")) || code === "JSON_BOUND_VIOLATION") return at === "admission" ? "request-rejected" : "provider-contract";
2607
- if (code === "DISPATCH_NOT_ADMITTED") return at === "admission" ? "request-rejected" : "provider-contract";
2608
- if (isProvisioningRefusal(value)) return "request-rejected";
2609
- if (isCredentialRefusal(value)) {
2610
- if (at === "execution") return "unobservable";
2611
- onCredentialRefusal?.();
2612
- return "request-rejected";
2613
- }
2614
- if (code !== void 0 && TRANSPORT_ERROR_CODES.has(code)) return "transport";
2615
- if (at === "admission" && isPlatformPredispatchRefusal(value)) return "request-rejected";
2616
- if (name === "NetworkError" || name === "ServerError") return "transport";
2617
- if (status === void 0 && at === "admission" && code !== void 0 && UNAVAILABLE_CODES.has(code.toLowerCase())) return "request-rejected";
2618
- if (status !== void 0) {
2619
- if (status >= 500) return "transport";
2620
- if (at === "admission" && status !== 408) return "request-rejected";
2621
- return "unobservable";
2622
- }
2623
- const errors = Reflect.get(value, "errors");
2624
- if (Array.isArray(errors)) for (const member of errors) queue.push({
2625
- value: member,
2626
- phase: at
2627
- });
2628
- queue.push({
2629
- value: Reflect.get(value, "cause"),
2630
- phase: at
2631
- });
2632
- }
2633
- return "unobservable";
2634
- }
2635
- /**
2636
- * Local observation stopped; the retained provider execution has no accepted terminal result.
2637
- *
2638
- * The message names WHICH of the situations in {@link RetainedPendingCause} this is, and the
2639
- * `pendingCause` field carries it as a value the settlement records, so a reader never splits
2640
- * the population on the message's wording. The cause chain is kept for the `caused by` receipt.
2641
- */
2642
- var RetainedExecutionPendingError = class extends Error {
2643
- pendingCause;
2644
- phase;
2645
- constructor(cause, pendingCause = "execution") {
2646
- let credentialRefused = false;
2647
- const resolved = pendingCause === "admission" || pendingCause === "execution" ? classifyPendingCause(cause, pendingCause, () => {
2648
- credentialRefused = true;
2649
- }) : pendingCause;
2650
- super(causeMessages[resolved], { cause });
2651
- this.name = "RetainedExecutionPendingError";
2652
- this.pendingCause = resolved;
2653
- this.phase = pendingCause === "admission" ? "admission" : "execution";
2654
- if (credentialRefused) credentialAdmissionRefusals.add(this);
2655
- }
2656
- };
2657
- /** The instruction a keyed continuation runs in the held session: the node's task is unchanged,
2658
- * and its conversation and workspace are where the budget stop left them. */
2659
- const BUDGET_CONTINUATION_TASK = "Your previous turn reached its budget and stopped. Your manager gave you more budget to continue the same task in the same session: your conversation so far and the files in your workspace are where you left them. Continue from where you stopped. Do not start over.";
2660
- /** The execution id of a leaf's later invocation, derived from its `execution-input` sequence so
2661
- * a recovery derives the same one. A first invocation keeps the node id. */
2662
- function leafContinuationExecutionId(nodeId, inputSeq) {
2663
- return `${nodeId}:input:${inputSeq}`;
2664
- }
2665
- const retainedPreparations = /* @__PURE__ */ new WeakMap();
2666
- /** Reconstruct an admitted worker through its original composition boundary. @internal */
2667
- function registerRetainedExecutorPreparation(factory, prepare) {
2668
- retainedPreparations.set(factory, prepare);
2669
- return factory;
2670
- }
2671
- function prepareRetainedExecutor(factory, input) {
2672
- return retainedPreparations.get(factory)?.(input);
2673
- }
2674
- // src/runtime/failure-cause.ts
2675
- const MAX_CAUSE_CHARS = 2048;
2676
- const MAX_CHAIN_STEPS = 12;
2677
- const MACHINE_CODE_PATTERN = /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/u;
2678
- /** @internal Canonical producer-supplied remediation, shared by transport and read models. */
2679
- const runtimeFailureRemediationSchema = z.object({
2680
- cause: z.enum([
2681
- "api_key_selected",
2682
- "provider_unresolved",
2683
- "subscription_unverified",
2684
- "not_authenticated"
2685
- ]),
2686
- fix: z.string().min(1).max(256),
2687
- harness: z.string().min(1).max(64),
2688
- provider: z.string().min(1).max(128).nullable()
2689
- });
2690
- /** @internal Validate Runtime-owned failure values on observation transports. */
2691
- const runtimeFailureCauseSchema = z.object({
2692
- code: z.enum([
2693
- "aborted",
2694
- "deadline-exceeded",
2695
- "budget-exhausted",
2696
- "credential-refused",
2697
- "request-rejected",
2698
- "upstream-unavailable",
2699
- "transport",
2700
- "provider-contract",
2701
- "retained-unobservable",
2702
- "nested-recovery",
2703
- "platform",
2704
- "work-failed",
2705
- "unknown"
2706
- ]),
2707
- owner: z.enum([
2708
- "provider",
2709
- "platform",
2710
- "runtime",
2711
- "work",
2712
- "caller",
2713
- "unknown"
2714
- ]),
2715
- cause: z.string().max(MAX_CAUSE_CHARS),
2716
- /** The upstream or provider machine code the failure carried, when it carried one. */
2717
- providerCode: z.string().regex(MACHINE_CODE_PATTERN).optional(),
2718
- /** The HTTP status the provider's typed error carried with that code. */
2719
- providerStatus: z.number().int().min(100).max(599).optional(),
2720
- /** The provider's typed retry-after hint, in milliseconds. A hint, never an automatic retry. */
2721
- retryAfterMs: z.number().int().nonnegative().optional(),
2722
- remediation: runtimeFailureRemediationSchema.readonly().optional()
2723
- }).readonly();
2724
- const retainedFailures = {
2725
- unobservable: {
2726
- code: "retained-unobservable",
2727
- owner: "provider"
2728
- },
2729
- "provider-contract": {
2730
- code: "provider-contract",
2731
- owner: "provider"
2732
- },
2733
- "request-rejected": {
2734
- code: "request-rejected",
2735
- owner: "caller"
2736
- },
2737
- transport: {
2738
- code: "transport",
2739
- owner: "platform"
2740
- },
2741
- "nested-recovery": {
2742
- code: "nested-recovery",
2743
- owner: "runtime"
2744
- }
2745
- };
2746
- function read(value, key) {
2747
- try {
2748
- return Reflect.get(value, key);
2749
- } catch {
2750
- return;
2751
- }
2752
- }
2753
- /** Every object in a failure's cause chain and aggregate members, bounded and cycle-safe. */
2754
- function chain(error) {
2755
- const out = [];
2756
- const queue = [error];
2757
- while (queue.length > 0 && out.length < MAX_CHAIN_STEPS) {
2758
- const value = queue.shift();
2759
- if (typeof value !== "object" || value === null || out.includes(value)) continue;
2760
- out.push(value);
2761
- const members = read(value, "errors");
2762
- if (Array.isArray(members)) {
2763
- const limit = Math.min(members.length, MAX_CHAIN_STEPS);
2764
- for (let index = 0; index < limit; index++) queue.push(read(members, String(index)));
2765
- }
2766
- queue.push(read(value, "cause"));
2767
- }
2768
- return out;
2769
- }
2770
- const UNATTRIBUTED = {
2771
- code: "unknown",
2772
- owner: "unknown"
2773
- };
2774
- /** One link's own structure: budget refusal, credential refusal, machine code, HTTP status. */
2775
- function structuralFailure(link) {
2776
- if (link instanceof ReservationWaitRefused || read(link, "reason") === "budget-exhausted") return {
2777
- code: "budget-exhausted",
2778
- owner: "caller"
2779
- };
2780
- const code = upstreamMachineCode([link]);
2781
- if (isCredentialRefusal(link) || code === "SEAT_NOT_LOGGED_IN") return {
2782
- code: "credential-refused",
2783
- owner: "caller"
2784
- };
2785
- const status = read(link, "status") ?? read(link, "statusCode");
2786
- if (code === "MODEL_NOT_FOUND") return {
2787
- code: "request-rejected",
2788
- owner: "caller"
2789
- };
2790
- if (typeof code === "string" && UNAVAILABLE_CODES.has(code.toLowerCase())) return {
2791
- code: "upstream-unavailable",
2792
- owner: "platform"
2793
- };
2794
- if (typeof status === "number" && Number.isInteger(status)) {
2795
- if (UNAVAILABLE_STATUSES.has(status)) return {
2796
- code: "upstream-unavailable",
2797
- owner: "platform"
2798
- };
2799
- if (status >= 500 || status === 0 || status === 408) return {
2800
- code: "transport",
2801
- owner: "platform"
2802
- };
2803
- if (status === 401 || status === 403) return {
2804
- code: "credential-refused",
2805
- owner: "caller"
2806
- };
2807
- if (status >= 400) return {
2808
- code: "request-rejected",
2809
- owner: "caller"
2810
- };
2811
- }
2812
- if (typeof code === "string" && TRANSPORT_ERROR_CODES.has(code)) return {
2813
- code: "transport",
2814
- owner: "platform"
2815
- };
2816
- if (typeof code === "string" && PLATFORM_FAILURE_CODES.has(code.toLowerCase())) return {
2817
- code: "platform",
2818
- owner: "platform"
2819
- };
2820
- const name = read(link, "name");
2821
- if (name === "NetworkError" || name === "ServerError") return {
2822
- code: "transport",
2823
- owner: "platform"
2824
- };
2825
- }
2826
- /** @internal The same machine-code validation used by public failure values. */
2827
- function runtimeProviderCode(value) {
2828
- return typeof value === "string" && MACHINE_CODE_PATTERN.test(value) ? value : void 0;
2829
- }
2830
- function upstreamMachineCode(links) {
2831
- return [
2832
- "upstreamCode",
2833
- "errorCode",
2834
- "code"
2835
- ].flatMap((field) => links.map((link) => runtimeProviderCode(read(link, field)))).find((code) => code !== void 0);
2836
- }
2837
- /** The typed HTTP status and retry-after of the link carrying the provider's code. */
2838
- function upstreamResponse(links, providerCode) {
2839
- const link = links.find((each) => [
2840
- "upstreamCode",
2841
- "errorCode",
2842
- "code"
2843
- ].some((field) => read(each, field) === providerCode));
2844
- if (providerCode === void 0 || link === void 0) return {};
2845
- const status = read(link, "status") ?? read(link, "statusCode");
2846
- const retryAfterMs = read(link, "retryAfterMs");
2847
- return {
2848
- ...Number.isInteger(status) && status >= 100 && status <= 599 ? { providerStatus: status } : {},
2849
- ...Number.isInteger(retryAfterMs) && retryAfterMs >= 0 ? { retryAfterMs } : {}
2850
- };
2851
- }
2852
- /**
2853
- * Classify a thrown failure. Pass the caller's signal so a cancellation the caller asked for is
2854
- * attributed to the caller even when the provider surfaced it as an ordinary error, and say when
2855
- * the operation's own deadline expired so a blown deadline is never reported as a cancellation.
2856
- */
2857
- function runtimeFailureCause(error, context = {}) {
2858
- const cause = errMessage(error).slice(0, MAX_CAUSE_CHARS);
2859
- const caller = context.signal?.aborted ? "aborted" : context.deadlineExpired ? "deadline-exceeded" : void 0;
2860
- try {
2861
- const links = chain(error);
2862
- const providerCode = upstreamMachineCode(links);
2863
- const upstream = links.find((link) => link instanceof BackendTransportError && link.backend === "bridge" && link.status === 403 && link.upstreamCode === "subscription_required" && link.providerDispatch === "not_started");
2864
- const remedy = runtimeFailureRemediationSchema.readonly().safeParse(upstream?.remediation);
2865
- const failure = ({ code, owner }) => ({
2866
- code,
2867
- owner,
2868
- cause,
2869
- ...providerCode === void 0 ? {} : { providerCode },
2870
- ...upstreamResponse(links, providerCode),
2871
- ...code === "credential-refused" && providerCode === "subscription_required" && remedy.success ? { remediation: remedy.data } : {}
2872
- });
2873
- if (caller !== void 0) return failure({
2874
- code: caller,
2875
- owner: "caller"
2876
- });
2877
- if (context.signal === void 0 && links.some((link) => read(link, "name") === "AbortError")) return failure({
2878
- code: "aborted",
2879
- owner: "caller"
2880
- });
2881
- for (const link of links) {
2882
- if (link instanceof RetainedExecutionPendingError) {
2883
- if (link.pendingCause === "request-rejected" && links.some(isPlatformPredispatchRefusal)) return failure({
2884
- code: "request-rejected",
2885
- owner: "platform"
2886
- });
2887
- return failure(retainedFailures[link.pendingCause]);
2888
- }
2889
- if (read(link, "name") === "RetainedRunProviderContractError") return failure(retainedFailures[classifyRetainedPendingCause(link)]);
2890
- }
2891
- for (const link of links) {
2892
- const classified = structuralFailure(link);
2893
- if (classified !== void 0) return failure(classified);
2894
- }
2895
- return failure(UNATTRIBUTED);
2896
- } catch {
2897
- return {
2898
- ...caller === void 0 ? UNATTRIBUTED : {
2899
- code: caller,
2900
- owner: "caller"
2901
- },
2902
- cause
2903
- };
2904
- }
2905
- }
2906
- /**
2907
- * Classify a failure the execution reported without throwing — a Sandbox outcome or executor
2908
- * envelope with `success: false` — from its machine code and HTTP status. A harness that reports
2909
- * a failure with neither does not say who must act, so it is `unknown`, never the work's fault.
2910
- */
2911
- function reportedFailureCause(outcome) {
2912
- return runtimeFailureCause(Object.assign(new Error(outcome.error ?? "execution reported a failure"), {
2913
- errorCode: outcome.errorCode,
2914
- statusCode: outcome.statusCode
2915
- }));
2916
- }
2917
- /**
2918
- * Classify a down settlement from the typed facts Runtime recorded while the cause was still a
2919
- * value: the retained cause, the failure classified where the executor threw, then `infra: false`,
2920
- * the throw path's statement that the work itself failed. `infra: true` is not an owner: Runtime
2921
- * also sets it for cancellations, budget reconcile faults and validation errors, and from the
2922
- * platform's failure messages, so a settlement with nothing else typed is `unknown`.
2923
- */
2924
- function settlementFailureCause(down) {
2925
- const cause = errMessage(down.reason).slice(0, MAX_CAUSE_CHARS);
2926
- if (down.retainedPendingCause !== void 0) {
2927
- const retained = retainedFailures[down.retainedPendingCause];
2928
- if (down.failure !== void 0 && down.failure.code === retained.code) return {
2929
- ...down.failure,
2930
- cause
2931
- };
2932
- return {
2933
- ...retained,
2934
- cause,
2935
- ...down.failure?.providerCode === void 0 ? {} : { providerCode: down.failure.providerCode }
2936
- };
2937
- }
2938
- if (down.failure !== void 0 && down.failure.code !== "unknown") return {
2939
- ...down.failure,
2940
- cause
2941
- };
2942
- return {
2943
- ...down.infra === false ? {
2944
- code: "work-failed",
2945
- owner: "work"
2946
- } : UNATTRIBUTED,
2947
- cause,
2948
- ...down.failure?.providerCode === void 0 ? {} : { providerCode: down.failure.providerCode }
2949
- };
2950
- }
2951
- // src/durable/observer-journal.ts
2952
- /**
2953
- * Durable, append-only third-person history for one concrete Runtime execution.
2954
- * It consumes Runtime's existing hook stream and does not participate in execution
2955
- * decisions. A broken observer therefore cannot change what an agent is allowed to do.
2956
- *
2957
- * The write discipline deliberately matches `FileSpawnJournal`: serialized appends,
2958
- * torn-tail recovery, short-write handling, and fsync before acknowledgement. One
2959
- * execution owns one journal file; higher-level pursuit aggregation joins isolated
2960
- * journals by `pursuitId` instead of making independent processes share a write head.
2961
- */
2962
- var FileObserverJournal = class FileObserverJournal {
2963
- path;
2964
- pursuitId;
2965
- writeContext = captureRunWriteContext();
2966
- tail = Promise.resolve();
2967
- initialized = false;
2968
- sequence = 0;
2969
- previousDigest;
2970
- appendFailure;
2971
- constructor(path, pursuitId) {
2972
- const stableId = pursuitId.trim();
2973
- if (stableId.length === 0) throw new TypeError("FileObserverJournal: pursuitId must be non-empty");
2974
- this.path = resolve(path);
2975
- this.pursuitId = stableId;
2976
- }
2977
- /** Open existing committed history by its recorded identity; never creates a file. */
2978
- static async open(path) {
2979
- for await (const record of readCommittedJsonLines(path, { allowMissing: true })) {
2980
- if (typeof record?.pursuitId !== "string") throw new TypeError("observer journal: missing pursuit identity");
2981
- const journal = new FileObserverJournal(path, record.pursuitId);
2982
- verifyObserverRecords([record], journal.pursuitId);
2983
- return journal;
2984
- }
2985
- }
2986
- hooks() {
2987
- return withPursuitContext(this.pursuitId, {
2988
- onEvent: (event) => this.appendEvent(event).then(() => void 0),
2989
- onDecisionPoint: (point) => this.appendDecision(point).then(() => void 0)
2990
- });
2991
- }
2992
- appendEvent(event) {
2993
- return this.enqueue("event", event);
2994
- }
2995
- appendDecision(point) {
2996
- return this.enqueue("decision", point);
2997
- }
2998
- async read() {
2999
- await this.tail;
3000
- this.assertComplete();
3001
- const records = [];
3002
- const verify = observerVerifier(this.pursuitId);
3003
- for await (const record of this.readExistingUnsafe()) {
3004
- verify(record);
3005
- records.push(record);
3006
- }
3007
- return Object.freeze(records);
3008
- }
3009
- async enqueue(kind, value) {
3010
- if (value.pursuitId !== this.pursuitId) throw new Error(`FileObserverJournal: ${kind} pursuitId ${String(value.pursuitId)} does not match ${this.pursuitId}`);
3011
- const snapshot = detachedSnapshot(value, "observer record");
3012
- const operation = this.tail.then(() => this.writeContext(async () => {
3013
- this.assertComplete();
3014
- await this.initialize();
3015
- const unsigned = {
3016
- schemaVersion: 1,
3017
- pursuitId: this.pursuitId,
3018
- sequence: this.sequence + 1,
3019
- kind,
3020
- observedAt: Date.now(),
3021
- ...this.previousDigest ? { previousDigest: this.previousDigest } : {},
3022
- ...kind === "event" ? { event: snapshot } : { decision: snapshot }
3023
- };
3024
- const record = Object.freeze({
3025
- ...unsigned,
3026
- digest: observerRecordDigest(unsigned)
3027
- });
3028
- const fs = await import("node:fs/promises");
3029
- const { dirname } = await import("node:path");
3030
- await fs.mkdir(dirname(this.path), { recursive: true });
3031
- await appendCommittedJsonLine(this.path, record);
3032
- this.sequence = record.sequence;
3033
- this.previousDigest = record.digest;
3034
- return record;
3035
- })).catch((error) => {
3036
- this.appendFailure ??= toError(error);
3037
- throw error;
3038
- });
3039
- this.tail = operation.then(() => void 0, () => void 0);
3040
- return operation;
3041
- }
3042
- async initialize() {
3043
- if (this.initialized) return;
3044
- const verify = observerVerifier(this.pursuitId);
3045
- let tail;
3046
- for await (const record of this.readExistingUnsafe()) {
3047
- verify(record);
3048
- tail = record;
3049
- }
3050
- this.sequence = tail?.sequence ?? 0;
3051
- this.previousDigest = tail?.digest;
3052
- this.initialized = true;
3053
- }
3054
- readExistingUnsafe() {
3055
- return readCommittedJsonLines(this.path, { allowMissing: true });
3056
- }
3057
- assertComplete() {
3058
- if (!this.appendFailure) return;
3059
- throw new Error("FileObserverJournal: a prior durable append failed; observer completeness is unknown", { cause: this.appendFailure });
3060
- }
3061
- };
3062
- /** Verify identity, monotonic sequence, payload shape, and the complete digest chain. */
3063
- function verifyObserverRecords(records, pursuitId) {
3064
- const verify = observerVerifier(pursuitId);
3065
- for (const record of records) verify(record);
3066
- return Object.freeze([...records]);
3067
- }
3068
- /** The same full chain validation is used for replay, startup and in-memory evidence. */
3069
- function observerVerifier(pursuitId) {
3070
- let previousDigest;
3071
- let expectedSequence = 1;
3072
- return (record) => {
3073
- if (record.schemaVersion !== 1) throw new Error("observer journal: unsupported schemaVersion");
3074
- if (pursuitId !== void 0 && record.pursuitId !== pursuitId) throw new Error(`observer journal: pursuit identity mismatch at sequence ${record.sequence}`);
3075
- if (record.sequence !== expectedSequence) throw new Error(`observer journal: non-contiguous sequence ${record.sequence}; expected ${expectedSequence}`);
3076
- if (record.previousDigest !== previousDigest) throw new Error(`observer journal: digest-chain break at sequence ${record.sequence}`);
3077
- for (const kind of ["event", "decision"]) {
3078
- const payload = record[kind];
3079
- if (record.kind === kind === (payload === void 0)) throw new Error(`observer journal: invalid ${kind} payload at sequence ${record.sequence}`);
3080
- if (payload !== void 0 && payload.pursuitId !== record.pursuitId) throw new Error(`observer journal: nested ${kind} pursuit mismatch at sequence ${record.sequence}`);
3081
- }
3082
- const { digest, ...unsigned } = record;
3083
- if (digest !== observerRecordDigest(unsigned)) throw new Error(`observer journal: digest mismatch at sequence ${record.sequence}`);
3084
- previousDigest = digest;
3085
- expectedSequence += 1;
3086
- };
3087
- }
3088
- /** Compute the canonical SHA-256 digest for an unsigned observer record. */
3089
- function observerRecordDigest(record) {
3090
- return createHash("sha256").update(JSON.stringify(record)).digest("hex");
3091
- }
3092
- /** Build the canonical durable observer hook in one call. */
3093
- function createFileObserverHooks(path, pursuitId) {
3094
- const journal = new FileObserverJournal(path, pursuitId);
3095
- return {
3096
- journal,
3097
- hooks: journal.hooks()
3098
- };
3099
- }
3100
- function toError(error) {
3101
- return error instanceof Error ? error : new Error(String(error));
3102
- }
3103
- // src/durable/spawn-journal/event-index.ts
3104
- /**
3105
- * A trace root is valid only beside the output it commits to, with a well-formed root, artifact
3106
- * reference and leaf count (at least the summary leaf). Records written before trace roots carry
3107
- * none and stay valid.
3108
- */
3109
- function assertTraceRoot(event, root) {
3110
- if (!("traceRoot" in event) || event.traceRoot === void 0) return;
3111
- const receipt = event.traceRoot;
3112
- const fields = receipt !== null && typeof receipt === "object" ? receipt : {};
3113
- if (typeof event.outRef !== "string" || typeof fields.root !== "string" || !/^sha256:[0-9a-f]{64}$/.test(fields.root) || typeof fields.ref !== "string" || !/^sha256:[0-9a-f]{64}$/.test(fields.ref) || typeof fields.leafCount !== "number" || !Number.isSafeInteger(fields.leafCount) || fields.leafCount < 1) throw new Error(`spawn journal corrupted: ${event.kind} for node '${event.id}' carries a malformed trace root in tree '${root}'`);
3114
- }
3115
- /** One validation implementation for memory, durable append, and cold replay. */
3116
- /** @internal shared with the SQL-backed store. */
3117
- var SpawnEventIndex = class {
3118
- root;
3119
- nodes = /* @__PURE__ */ new Map();
3120
- cursors = /* @__PURE__ */ new Set();
3121
- constructor(root) {
3122
- this.root = root;
3123
- }
3124
- assert(event) {
3125
- const node = this.nodes.get(event.id);
3126
- this.assertRetained(node, event);
3127
- assertTraceRoot(event, this.root);
3128
- if (event.kind === "execution-evidence") {
3129
- if (!node?.spawned || !/^sha256:[0-9a-f]{64}$/.test(event.outRef)) throw new Error("spawn journal corrupted: executor evidence has no spawned node or valid content reference");
3130
- }
3131
- if (event.kind === "materialized") {
3132
- if (node?.materialized) throw new Error(`spawn journal corrupted: duplicate materialization receipt for node '${event.id}' in tree '${this.root}'`);
3133
- if (!node?.spawned) throw new Error(`spawn journal corrupted: materialization for node '${event.id}' precedes its spawn in tree '${this.root}'`);
3134
- }
3135
- if (event.kind === "seat-materialized") {
3136
- if (!node?.materialized || node.authoredProfileDigest === void 0 || event.receipt.status !== "known" || event.receipt.authoredProfileDigest !== node.authoredProfileDigest || !Number.isSafeInteger(event.segmentIndex) || event.segmentIndex <= node.latestSeatMaterialization) throw new Error(`spawn journal corrupted: invalid seat materialization for node '${event.id}' in tree '${this.root}'`);
3137
- }
3138
- if (event.kind === "execution-bound") {
3139
- if (node?.bindings.has(event.binding.attemptId)) throw new Error(`spawn journal corrupted: duplicate execution binding for node '${event.id}' attempt '${event.binding.attemptId}' in tree '${this.root}'`);
3140
- if (!node?.materialized) throw new Error(`spawn journal corrupted: execution binding for node '${event.id}' precedes materialization in tree '${this.root}'`);
3141
- }
3142
- if (!outsideCursorNamespace(event) && this.cursors.has(event.seq)) throw new Error(`spawn journal corrupted: duplicate cursor seq ${event.seq} in tree '${this.root}'; the cursor order replay relies on is not unique`);
3143
- }
3144
- /** Called after validation; a file writer discards staged state if grouped publication fails. */
3145
- add(event) {
3146
- if (!outsideCursorNamespace(event)) this.cursors.add(event.seq);
3147
- if (event.kind !== "spawned" && event.kind !== "materialized" && event.kind !== "seat-materialized" && event.kind !== "execution-bound" && event.kind !== "execution-input" && event.kind !== "execution-admitted" && event.kind !== "execution-result" && event.kind !== "execution-abandoned" && event.kind !== "environment-teardown" && event.kind !== "environment-terminal" && !closesCursorSlot(event)) return;
3148
- let node = this.nodes.get(event.id);
3149
- if (!node) {
3150
- node = {
3151
- spawned: false,
3152
- closed: false,
3153
- materialized: false,
3154
- latestSeatMaterialization: 0,
3155
- bindings: /* @__PURE__ */ new Set(),
3156
- inputs: /* @__PURE__ */ new Set(),
3157
- input: false,
3158
- result: false,
3159
- abandoned: false,
3160
- admissions: /* @__PURE__ */ new Map(),
3161
- unusableEnvironments: /* @__PURE__ */ new Set()
3162
- };
3163
- this.nodes.set(event.id, node);
3164
- }
3165
- if (event.kind === "environment-teardown") {
3166
- if (event.destroyed) node.unusableEnvironments.add(event.environmentId);
3167
- return;
3168
- }
3169
- if (event.kind === "environment-terminal") {
3170
- node.unusableEnvironments.add(event.environmentId);
3171
- return;
3172
- }
3173
- if (event.kind === "execution-admitted" && event.admission.phase === "environment") node.lastEnvironmentId = event.admission.environmentId;
3174
- if (event.kind === "spawned") node.spawned = true;
3175
- else if (event.kind === "materialized") {
3176
- node.materialized = true;
3177
- node.authoredProfileDigest = event.receipt.authoredProfileDigest;
3178
- } else if (event.kind === "seat-materialized") node.latestSeatMaterialization = event.segmentIndex;
3179
- else if (event.kind === "execution-bound") node.bindings.add(event.binding.attemptId);
3180
- else if (event.kind === "execution-input") {
3181
- node.inputs.add(event.seq);
3182
- node.input = true;
3183
- node.latestInput = event.seq;
3184
- node.result = false;
3185
- node.abandoned = false;
3186
- node.admissions.clear();
3187
- } else if (event.kind === "execution-admitted") node.admissions.set(event.admission.phase, event.admission);
3188
- else if (event.kind === "execution-result") node.result = true;
3189
- else if (event.kind === "execution-abandoned") node.abandoned = true;
3190
- if (closesCursorSlot(event)) node.closed = true;
3191
- }
3192
- assertRetained(node, event) {
3193
- if (event.kind !== "execution-input" && event.kind !== "execution-admitted" && event.kind !== "execution-result" && event.kind !== "execution-abandoned") return;
3194
- function fail(reason) {
3195
- throw new Error(`spawn journal corrupted: retained execution '${event.id}' ${reason}`);
3196
- }
3197
- if (!node?.spawned) fail("precedes its spawn");
3198
- if (node.closed) fail("follows its terminal settlement");
3199
- if (event.kind === "execution-input") {
3200
- if (!/^sha256:[0-9a-f]{64}$/.test(event.taskRef)) fail("has an invalid task reference");
3201
- if (node.inputs.has(event.seq)) fail("has duplicate input sequence");
3202
- const environment = node.admissions.get("environment");
3203
- const ranIn = environment?.phase === "environment" ? environment.environmentId : node.lastEnvironmentId;
3204
- const closed = node.abandoned || ranIn !== void 0 && node.unusableEnvironments.has(ranIn);
3205
- if (node.input && !node.result && !closed) fail("input replaces an unfinished invocation");
3206
- return;
3207
- }
3208
- if (event.kind === "execution-abandoned") {
3209
- if (!node.input || node.latestInput !== event.inputSeq) fail("abandons an invocation that is not its latest");
3210
- if (node.result) fail("abandons an invocation that has a result");
3211
- if (node.abandoned) fail("abandons an invocation twice");
3212
- if (!Number.isSafeInteger(event.failures) || event.failures < 1) fail("abandons without a failure count");
3213
- return;
3214
- }
3215
- if (node.abandoned) fail(`${event.kind} follows its abandonment`);
3216
- if (event.kind === "execution-result") {
3217
- if (node.result) fail("has duplicate result");
3218
- if (!node.admissions.has("dispatched")) fail("result precedes dispatch");
3219
- if (!/^sha256:[0-9a-f]{64}$/.test(event.outRef)) fail("has an invalid result reference");
3220
- assertValidSpend(event.spent, "retained execution result");
3221
- executorFailureReason(event);
3222
- return;
3223
- }
3224
- const admission = event.admission;
3225
- if (!admission || ![
3226
- "intent",
3227
- "environment",
3228
- "dispatched"
3229
- ].includes(admission.phase)) fail("has an invalid admission phase");
3230
- if (node.admissions.has(admission.phase)) fail("has duplicate admission phase");
3231
- if (node.result) fail("admission follows result");
3232
- if (!node.input) fail("admission precedes input");
3233
- if (admission.phase === "intent") {
3234
- if (node.admissions.size !== 0) fail("intent follows another admission");
3235
- return;
3236
- }
3237
- const intent = node.admissions.get("intent");
3238
- if (intent?.phase !== "intent") fail("admission precedes intent");
3239
- if (admission.idempotencyKey !== intent.idempotencyKey || admission.turnId !== intent.turnId) fail("changes its admitted request identity");
3240
- if (admission.phase === "environment") {
3241
- if (admission.provider !== intent.provider || admission.sessionId !== intent.sessionId || admission.executionId !== intent.executionId) fail("changes its admitted execution identity");
3242
- return;
3243
- }
3244
- const environment = node.admissions.get("environment");
3245
- if (environment?.phase !== "environment") fail("dispatch precedes environment");
3246
- const control = admission.controlRef;
3247
- if (!control || control.provider !== intent.provider || control.environmentId !== environment.environmentId || control.sessionId !== intent.sessionId || control.executionId !== intent.executionId) fail("dispatch changes its admitted execution identity");
3248
- }
3249
- };
3250
- /**
3251
- * The records that CLOSE a node's cursor slot: a settlement or a cancellation. The complement question to {@link outsideCursorNamespace}, and the one a resume needs —
3252
- * a kind missing here leaves a finished node looking live, so its budget is never reclaimed and
3253
- * its reservation is replayed as still in doubt.
3254
- */
3255
- function closesCursorSlot(ev) {
3256
- return ev.kind === "settled" || ev.kind === "cancelled";
3257
- }
3258
- const outsideKinds = /* @__PURE__ */ new Set([
3259
- "spawned",
3260
- "metered",
3261
- "materialized",
3262
- "execution-bound",
3263
- "execution-input",
3264
- "execution-admitted",
3265
- "execution-result",
3266
- "execution-abandoned",
3267
- "execution-evidence",
3268
- "evidence-pending",
3269
- "progress",
3270
- "reconciled",
3271
- "edge",
3272
- "teardown-unconfirmed",
3273
- "teardown-pending",
3274
- "teardown-confirmed",
3275
- "environment-teardown",
3276
- "environment-terminal",
3277
- "workspace-checkpoint-requested",
3278
- "workspace-checkpoint",
3279
- "workspace-checkpoint-fork-requested",
3280
- "workspace-checkpoint-fork-ready",
3281
- "workspace-checkpoint-fork-teardown",
3282
- "workspace-checkpoint-capture",
3283
- "workspace-checkpoint-cleanup",
3284
- "workspace-restored",
3285
- "workspace-capture-started",
3286
- "workspace-capture",
3287
- "trace-unpropagated",
3288
- "seat-segment",
3289
- "seat-materialized",
3290
- "seat-paused",
3291
- "run-parked",
3292
- "root-segment",
3293
- "root-reentered",
3294
- "reopened",
3295
- "paused",
3296
- "barren-pause",
3297
- "driver-attempt",
3298
- "node-inputs-resolved",
3299
- "edge-verdict",
3300
- "join-state"
3301
- ]);
3302
- /** True for a node-CREATION or informational record. Narrows, so a caller keeps the union's arms. */
3303
- function outsideCursorNamespace(ev) {
3304
- return outsideKinds.has(ev.kind);
3305
- }
3306
- /** True for a record inside the cursor namespace. Narrows to {@link CursorNamespaceEvent}. */
3307
- function insideCursorNamespace(ev) {
3308
- return !outsideKinds.has(ev.kind);
3309
- }
3310
- // src/durable/spawn-journal/result-blob-store.ts
3311
- /**
3312
- * FS `ResultBlobStore`. One JSON file per artifact under `dir`, named by a
3313
- * filesystem-safe encoding of the `outRef` (`sha256:<hex>` → `sha256-<hex>.json`).
3314
- * `put` fsyncs so a crash between writes never loses an acknowledged blob.
3315
- *
3316
- * @stable
3317
- */
3318
- var FileResultBlobStore = class {
3319
- dir;
3320
- writeContext = captureRunWriteContext();
3321
- constructor(dir) {
3322
- this.dir = dir;
3323
- }
3324
- async put(outRef, artifact) {
3325
- return this.writeContext(async () => {
3326
- const { text } = encodeResultBlob(outRef, artifact);
3327
- const filePath = this.blobPath(outRef);
3328
- assertNoSymlinkDescendant(this.dir, filePath, "result blob");
3329
- await (await import("node:fs/promises")).mkdir(this.dir, { recursive: true });
3330
- assertNoSymlinkDescendant(this.dir, filePath, "result blob");
3331
- if (!publishExclusiveDurableFile(filePath, text, { mode: 384 })) assertContentAddress(outRef, await this.get(outRef));
3332
- });
3333
- }
3334
- async get(outRef) {
3335
- const filePath = this.blobPath(outRef);
3336
- assertNoSymlinkDescendant(this.dir, filePath, "result blob");
3337
- const fs = await import("node:fs/promises");
3338
- const { constants } = await import("node:fs");
3339
- let text;
3340
- try {
3341
- const handle = await fs.open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
3342
- try {
3343
- if (!(await handle.stat()).isFile()) throw new Error("result blob must be a regular file");
3344
- text = await handle.readFile("utf8");
3345
- } finally {
3346
- await handle.close();
3347
- }
3348
- } catch (err) {
3349
- if (isNoEntError(err)) return void 0;
3350
- throw err;
3351
- }
3352
- const artifact = JSON.parse(text);
3353
- assertContentAddress(outRef, artifact);
3354
- return artifact;
3355
- }
3356
- blobPath(outRef) {
3357
- if (!/^sha256:[0-9a-f]{64}$/.test(outRef)) throw new Error("invalid result blob content address");
3358
- return `${this.dir}/${outRef.replace(/:/g, "-")}.json`;
3359
- }
3360
- };
3361
- /** @internal shared with the SQL-backed store. */
3362
- function encodeResultBlob(outRef, artifact) {
3363
- const detached = detachedSnapshot(artifact, "result blob");
3364
- assertContentAddress(outRef, detached);
3365
- const text = JSON.stringify(detached);
3366
- if (text === void 0) throw new Error("result blob must contain JSON");
3367
- const snapshot = JSON.parse(text);
3368
- assertContentAddress(outRef, snapshot);
3369
- return {
3370
- snapshot,
3371
- text
3372
- };
3373
- }
3374
- /** @internal shared with the SQL-backed store. */
3375
- function assertContentAddress(outRef, artifact) {
3376
- const expected = contentAddress(artifact);
3377
- if (outRef !== expected) throw new Error(`blob outRef '${outRef}' does not match the artifact content hash '${expected}'; a content-addressed store refuses a mismatched ref (breaks the replay invariant)`);
3378
- }
3379
- // src/durable/spawn-journal/record-events.ts
3380
- /** Decode the file journal's historical single-event and atomic-group envelopes. @internal */
3381
- function spawnJournalRecordEvents(record) {
3382
- if (!isRecord(record)) throw new Error("spawn journal corrupted: record must be an object");
3383
- if (record.kind === "begin") return [];
3384
- if (record.kind === "event") return [record.event];
3385
- if (record.kind !== "events") throw new Error(`spawn journal corrupted: unknown record kind '${String(record.kind)}'`);
3386
- if (!Array.isArray(record.events)) throw new Error("spawn journal group has no events array");
3387
- return record.events;
3388
- }
3389
- // src/durable/spawn-journal/journal-store.ts
3390
- /**
3391
- * Append every event as one grouped publication where the journal supports it (`appendEvents`),
3392
- * preserving legacy per-event appends otherwise — the SQL contexts' fenced head advances once.
3393
- * @internal
3394
- */
3395
- async function appendSpawnEvents(journal, root, events) {
3396
- if (journal.appendEvents !== void 0) return journal.appendEvents(root, events);
3397
- for (const event of events) await journal.appendEvent(root, event);
3398
- }
3399
- /**
3400
- * JSONL on disk. One line per record: the first record is `begin`, subsequent records
3401
- * are `event` or atomic `events` envelopes. `loadTree` replays the whole file,
3402
- * filtering by `root`, and applies the same begin-precedes-events + unique-seq
3403
- * corruption guards as the in-memory impl. Each append fsyncs so a crash between
3404
- * writes never loses an acknowledged event.
3405
- *
3406
- * @stable
3407
- */
3408
- var FileSpawnJournal = class {
3409
- path;
3410
- writeContext = captureRunWriteContext();
3411
- appendTail = Promise.resolve();
3412
- appendIndex;
3413
- constructor(path) {
3414
- this.path = path;
3415
- }
3416
- async loadTree(root) {
3417
- let begun = false;
3418
- const events = [];
3419
- const index = new SpawnEventIndex(root);
3420
- for await (const record of readCommittedJsonLines(this.path, { allowMissing: true })) {
3421
- if (record.root !== root) continue;
3422
- if (record.kind === "begin") begun = true;
3423
- else {
3424
- if (!begun) throw new Error(`spawn journal corrupted: event for tree '${root}' precedes its begin record`);
3425
- for (const event of spawnJournalRecordEvents(record)) {
3426
- index.assert(event);
3427
- index.add(event);
3428
- events.push(event);
3429
- }
3430
- }
3431
- }
3432
- return begun ? events : void 0;
3433
- }
3434
- async beginTree(root, at) {
3435
- return this.serializeAppend(async () => {
3436
- const state = await this.validationIndex();
3437
- const existing = state.trees.get(root);
3438
- if (existing) {
3439
- if (existing.begunAt !== at) throw new Error(`spawn tree '${root}' already begun in ${this.path} at ${existing.begunAt}; refusing to overwrite with ${at}`);
3440
- return;
3441
- }
3442
- state.stamp = await this.writeRecord({
3443
- kind: "begin",
3444
- root,
3445
- at
3446
- });
3447
- state.trees.set(root, {
3448
- begunAt: at,
3449
- index: new SpawnEventIndex(root)
3450
- });
3451
- });
3452
- }
3453
- async appendEvent(root, ev) {
3454
- const event = detachedSnapshot(ev, "spawn event");
3455
- return this.serializeAppend(async () => {
3456
- const state = await this.validationIndex();
3457
- const tree = state.trees.get(root);
3458
- if (tree === void 0) throw new Error(`appendEvent called for unknown spawn tree '${root}'; call beginTree first`);
3459
- tree.index.assert(event);
3460
- state.stamp = await this.writeRecord({
3461
- kind: "event",
3462
- root,
3463
- event
3464
- });
3465
- tree.index.add(event);
3466
- });
3467
- }
3468
- async appendEvents(root, events) {
3469
- const captured = detachedSnapshot(events, "spawn events");
3470
- return this.serializeAppend(async () => {
3471
- const state = await this.validationIndex();
3472
- const tree = state.trees.get(root);
3473
- if (tree === void 0) throw new Error(`appendEvents called for unknown spawn tree '${root}'; call beginTree first`);
3474
- try {
3475
- for (const event of captured) {
3476
- tree.index.assert(event);
3477
- tree.index.add(event);
3478
- }
3479
- state.stamp = await this.writeRecord({
3480
- kind: "events",
3481
- root,
3482
- events: captured
3483
- });
3484
- } catch (error) {
3485
- this.appendIndex = void 0;
3486
- throw error;
3487
- }
3488
- });
3489
- }
3490
- async validationIndex() {
3491
- const stamp = await this.fileStamp();
3492
- if (this.appendIndex && this.appendIndex.stamp === stamp) return this.appendIndex;
3493
- this.appendIndex = void 0;
3494
- const trees = /* @__PURE__ */ new Map();
3495
- if (stamp !== void 0) for await (const record of readCommittedJsonLines(this.path)) if (record.kind === "begin") {
3496
- if (!trees.has(record.root)) trees.set(record.root, {
3497
- begunAt: record.at,
3498
- index: new SpawnEventIndex(record.root)
3499
- });
3500
- } else {
3501
- const tree = trees.get(record.root);
3502
- if (!tree) throw new Error(`spawn journal corrupted: event for tree '${record.root}' precedes its begin record`);
3503
- for (const event of spawnJournalRecordEvents(record)) {
3504
- tree.index.assert(event);
3505
- tree.index.add(event);
3506
- }
3507
- }
3508
- if (await this.fileStamp() !== stamp) throw new Error("spawn journal changed while rebuilding its append index");
3509
- this.appendIndex = {
3510
- stamp,
3511
- trees
3512
- };
3513
- return this.appendIndex;
3514
- }
3515
- async fileStamp() {
3516
- const fs = await import("node:fs/promises");
3517
- try {
3518
- return journalFileStamp(await fs.stat(this.path, { bigint: true }));
3519
- } catch (error) {
3520
- if (isNoEntError(error)) return void 0;
3521
- throw error;
3522
- }
3523
- }
3524
- async serializeAppend(operation) {
3525
- const append = this.appendTail.then(() => this.writeContext(operation));
3526
- this.appendTail = append.catch(() => void 0);
3527
- return append;
3528
- }
3529
- async writeRecord(record) {
3530
- const fs = await import("node:fs/promises");
3531
- const { dirname } = await import("node:path");
3532
- try {
3533
- await fs.mkdir(dirname(this.path), { recursive: true });
3534
- return await appendCommittedJsonLine(this.path, record, (fd) => {
3535
- const stamp = journalFileStamp(fstatSync(fd, { bigint: true }));
3536
- if (journalFileStamp(statSync(this.path, { bigint: true })) !== stamp) throw new Error("spawn journal changed while acknowledging its append");
3537
- return stamp;
3538
- });
3539
- } catch (error) {
3540
- this.appendIndex = void 0;
3541
- throw error;
3542
- }
3543
- }
3544
- };
3545
- /** Detect replacement, truncation, and same-size edits; this is not a cross-process write lock. */
3546
- function journalFileStamp(stat) {
3547
- return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
3548
- }
3549
- // src/runtime/supervise/run-layout.ts
3550
- /**
3551
- * The on-disk supervisor-run layout: `<root>/.agent/supervisor/<id>`.
3552
- *
3553
- * This is the durable, cross-process face of a supervisor run — the counterpart to the in-process
3554
- * `Inbox` seam in `./inbox`. A run persists its state under one directory so that any OTHER process
3555
- * can find it after the fact: `@tangle-network/traces` reads exactly this layout via
3556
- * `traces analyze --supervisor-run-dir`, a restarted host can rehydrate a run it no longer holds
3557
- * handles to, and a human can steer a live worker by appending one NDJSON line. Until now the
3558
- * layout was defined only in the unpublished `loops` repo (`src/supervisor-control.ts`) — a
3559
- * published reader depending on an unpublished writer's convention — so the contract is promoted
3560
- * here, names preserved.
3561
- *
3562
- * `.agent` is the one dot-dir for ALL agent-owned state (skills already write
3563
- * `.agent/hypotheses/`, `.agent/skill-runs.jsonl`); supervisor runs live beside them rather than
3564
- * under a product-branded dir. Runs written by older writers used `.loops/supervisor/<id>` —
3565
- * readers that must see those keep a legacy fallback; this writer never creates `.loops` again.
3566
- *
3567
- * Layout, relative to `supervisorRunDir(root, id)`:
3568
- *
3569
- * steers/requests/<hash>.json one exact, caller-idempotent {@link WorkerSteerRequest}
3570
- * steers/acks/<hash>.json runtime acknowledgement for that steer operation
3571
- * workers/<id>.inbox.ndjson best-effort readable projection of admitted steer requests
3572
- * workers/<label>.ndjson best-effort per-worker control-event log (delivery bookkeeping)
3573
- * cancellations/requests.ndjson worker-scoped cancel requests (durable inbox); each line is a
3574
- * {@link WorkerCancelRequest}
3575
- * cancellations/<opId>.json the acknowledgement for one cancel operation — a
3576
- * {@link WorkerCancellation} written ONLY by the runtime
3577
- * acknowledger in the OWNING manager's turn loop: exact node
3578
- * ids route to the manager that parents them (any depth);
3579
- * label/profile-name references to the root manager alone
3580
- * cancellations/run.request.json the run-scoped cancel request — one {@link RunCancelRequest},
3581
- * the whole RUN rather than one worker
3582
- * cancellations/run.json the acknowledgement for the run-scoped request — a
3583
- * {@link RunCancellation} written ONLY by the runtime (the root
3584
- * manager issues the abort; the `supervise()` settle path records
3585
- * what the run actually did)
3586
- *
3587
- * Reads are tolerant by contract: a partial trailing line (a writer mid-append) or a corrupt line
3588
- * never poisons the rest of the file — later valid lines still matter. Duplicate operation ids
3589
- * collapse to the first admitted request, while a payload conflict fails closed.
3590
- *
3591
- * Promoted from `loops/src/supervisor-control.ts`. Steer admission now requires the exact worker id
3592
- * and a caller-owned operation id. One atomic request file is the durable inbox; the NDJSON file is
3593
- * a readable projection only and never controls idempotency.
3594
- *
3595
- * @experimental
3596
- */
3597
- /** The root every supervisor run of one workspace lives under. */
3598
- function supervisorRunsRoot(rootDir) {
3599
- return join(resolve(rootDir), ".agent", "supervisor");
3600
- }
3601
- /** The run directory every artifact of one supervisor run lives under. */
3602
- function supervisorRunDir(rootDir, id) {
3603
- return join(supervisorRunsRoot(rootDir), id);
3604
- }
3605
- /**
3606
- * Where a pre-rename writer put the same run (`<root>/.loops/supervisor/<id>`). Readers that must
3607
- * see historical runs check {@link supervisorRunDir} first and fall back to this; nothing writes
3608
- * here anymore.
3609
- */
3610
- function legacySupervisorRunDir(rootDir, id) {
3611
- return join(legacySupervisorRunsRoot(rootDir), id);
3612
- }
3613
- /**
3614
- * The pre-rename runs root (`<root>/.loops/supervisor`). Only readers that ENUMERATE historical
3615
- * runs need this — the per-id form is {@link legacySupervisorRunDir}. Nothing writes here.
3616
- */
3617
- function legacySupervisorRunsRoot(rootDir) {
3618
- return join(resolve(rootDir), ".loops", "supervisor");
3619
- }
3620
- /** A worker label reduced to a safe filename stem. Empty labels get a stable fallback. */
3621
- function safeWorkerFile(label) {
3622
- const safe = label.replace(/[^A-Za-z0-9._-]/g, "_");
3623
- return safe.length > 0 ? safe : "worker";
3624
- }
3625
- /** The directory holding every per-worker file of one run (inboxes and control-event logs). */
3626
- function supervisorWorkersDir(eventDir) {
3627
- return join(eventDir, "workers");
3628
- }
3629
- /** Same, addressed from an already-known run directory (the reader's usual entry point). */
3630
- function workerInboxFileFromEventDir(eventDir, worker) {
3631
- return join(supervisorWorkersDir(eventDir), `${safeWorkerFile(worker)}.inbox.ndjson`);
3632
- }
3633
- /**
3634
- * The best-effort control-event log for one worker (`workers/<label>.ndjson`) — delivery
3635
- * bookkeeping for steers, plus whatever lifecycle events a writer chooses to append. Distinct from
3636
- * the inbox: the inbox is the durable down-leg queue, this is the record of what happened to it.
3637
- */
3638
- function workerControlLogFile(eventDir, worker) {
3639
- return join(supervisorWorkersDir(eventDir), `${safeWorkerFile(worker)}.ndjson`);
3640
- }
3641
- /** Directory containing atomically admitted steer requests and runtime acknowledgements. */
3642
- function workerSteersDir(eventDir) {
3643
- return join(eventDir, "steers");
3644
- }
3645
- /** Directory containing one canonical request file per steer operation. */
3646
- function workerSteerRequestsDir(eventDir) {
3647
- return join(workerSteersDir(eventDir), "requests");
3648
- }
3649
- /** Directory containing one runtime acknowledgement per steer operation. */
3650
- function workerSteerAcknowledgementsDir(eventDir) {
3651
- return join(workerSteersDir(eventDir), "acks");
3652
- }
3653
- /** Canonical request file for one caller-owned steer operation id. */
3654
- function workerSteerRequestFile(eventDir, operationId) {
3655
- return join(workerSteerRequestsDir(eventDir), `${operationFileHash(operationId)}.json`);
3656
- }
3657
- /** Runtime acknowledgement file for one caller-owned steer operation id. */
3658
- function workerSteerAcknowledgementFile(eventDir, operationId) {
3659
- return join(workerSteerAcknowledgementsDir(eventDir), `${operationFileHash(operationId)}.json`);
3660
- }
3661
- /**
3662
- * Admit one steer exactly once under a caller-owned operation id.
3663
- *
3664
- * Pass `supervisorId` as `worker` to address the root. Router roots consume requests between
3665
- * turns; native roots use their existing accepting inbox during execution, or acknowledge
3666
- * `unsupported` when none is available. Delivery does not prove model consumption.
3667
- * This filesystem API requires trusted write access to the run directory; it adds no MCP grant.
3668
- *
3669
- * The per-operation request file is linked into place atomically after its bytes reach disk. A
3670
- * same-body retry returns the winner's request. A changed-body retry fails loud. The NDJSON inbox
3671
- * and control log are readable projections written only by the admission winner.
3672
- * @stable
3673
- */
3674
- function writeWorkerSteer(rootDir, supervisorId, worker, options) {
3675
- const workerId = worker.trim();
3676
- if (!workerId) throw new Error("writeWorkerSteer: worker id is empty");
3677
- const operationId = options.operationId.trim();
3678
- if (!operationId) throw new Error("writeWorkerSteer: operationId is empty");
3679
- const trimmed = options.message.trim();
3680
- if (!trimmed) throw new Error("steer message is empty");
3681
- const source = options.source?.trim() || "human";
3682
- const interrupt = options.interrupt === true;
3683
- if (options.eventDir !== void 0 && !options.eventDir.trim()) throw new Error("writeWorkerSteer: eventDir is empty");
3684
- const dir = options.eventDir === void 0 ? supervisorRunDir(rootDir, supervisorId) : resolve(options.eventDir);
3685
- const requestDigest = workerSteerRequestDigest({
3686
- operationId,
3687
- worker: workerId,
3688
- message: trimmed,
3689
- source,
3690
- interrupt
3691
- });
3692
- const request = {
3693
- schemaVersion: 1,
3694
- operationId,
3695
- requestDigest,
3696
- at: (/* @__PURE__ */ new Date()).toISOString(),
3697
- source,
3698
- worker: workerId,
3699
- message: trimmed,
3700
- interrupt
3701
- };
3702
- const file = workerSteerRequestFile(dir, operationId);
3703
- assertNoSymlinkDescendant(dir, file, "steer request");
3704
- mkdirSync(workerSteerRequestsDir(dir), { recursive: true });
3705
- assertNoSymlinkDescendant(dir, file, "steer request");
3706
- const admitted = admitSteerRequest(file, request);
3707
- if (admitted.replayed) return {
3708
- worker: admitted.request.worker,
3709
- file,
3710
- request: admitted.request,
3711
- ...readWorkerSteerAcknowledgement(dir, operationId) === void 0 ? {} : { acknowledgement: readWorkerSteerAcknowledgement(dir, operationId) },
3712
- replayed: true
3713
- };
3714
- mkdirSync(supervisorWorkersDir(dir), { recursive: true });
3715
- const projection = workerInboxFileFromEventDir(dir, workerId);
3716
- try {
3717
- fenceRunWrite(() => appendFileSync(projection, `${JSON.stringify(request)}\n`, "utf8"));
3718
- } catch {}
3719
- appendWorkerControlEvent(dir, workerId, {
3720
- kind: "message",
3721
- direction: "down",
3722
- source,
3723
- operationId,
3724
- requestDigest,
3725
- message: trimmed,
3726
- interrupt,
3727
- queued: true,
3728
- delivered: false
3729
- });
3730
- return {
3731
- worker: workerId,
3732
- file,
3733
- request,
3734
- replayed: false
3735
- };
3736
- }
3737
- /** Read every atomically admitted request for one exact worker id, in admission order. @stable */
3738
- function readWorkerSteerRequests(eventDir, worker) {
3739
- const workerId = worker?.trim();
3740
- if (worker !== void 0 && !workerId) return [];
3741
- let names;
3742
- assertNoSymlinkDescendant(eventDir, workerSteerRequestsDir(eventDir), "steer request");
3743
- try {
3744
- names = readdirSync(workerSteerRequestsDir(eventDir));
3745
- } catch {
3746
- return [];
3747
- }
3748
- const out = [];
3749
- for (const name of names) {
3750
- if (!name.endsWith(".json")) continue;
3751
- try {
3752
- const file = join(workerSteerRequestsDir(eventDir), name);
3753
- assertNoSymlinkDescendant(eventDir, file, "steer request");
3754
- const parsed = parseWorkerSteerRequest(JSON.parse(readFileSync(file, "utf8")));
3755
- if (name !== `${operationFileHash(parsed.operationId)}.json`) continue;
3756
- if (workerId === void 0 || parsed.worker === workerId) out.push(parsed);
3757
- } catch {}
3758
- }
3759
- return out.sort((left, right) => left.at === right.at ? left.operationId.localeCompare(right.operationId) : left.at.localeCompare(right.at));
3760
- }
3761
- /** Read one runtime steer acknowledgement, or `undefined` while no manager has answered. @stable */
3762
- function readWorkerSteerAcknowledgement(eventDir, operationId) {
3763
- const id = operationId.trim();
3764
- if (!id) throw new Error("readWorkerSteerAcknowledgement: operationId is empty");
3765
- const file = workerSteerAcknowledgementFile(eventDir, id);
3766
- assertNoSymlinkDescendant(eventDir, file, "steer acknowledgement");
3767
- if (!existsSync(file)) return void 0;
3768
- const record = parseWorkerSteerAcknowledgement(JSON.parse(readFileSync(file, "utf8")));
3769
- if (record.operationId !== id) throw new Error(`steer acknowledgement '${file}' belongs to another operation`);
3770
- return record;
3771
- }
3772
- /**
3773
- * Claim one steer for delivery with an atomic no-clobber acknowledgement.
3774
- * Returns `true` only to the process that created the pre-delivery record.
3775
- * @internal
3776
- */
3777
- function claimWorkerSteerDelivery(eventDir, record) {
3778
- const exact = parseWorkerSteerAcknowledgement(record);
3779
- if (exact.effect !== "unknown") throw new Error("steer delivery claim must use effect unknown");
3780
- const request = readWorkerSteerRequest(eventDir, exact.operationId);
3781
- if (request === void 0 || request.requestDigest !== exact.requestDigest) throw new Error("steer delivery claim does not match an admitted request");
3782
- const dir = workerSteerAcknowledgementsDir(eventDir);
3783
- assertNoSymlinkDescendant(eventDir, dir, "steer acknowledgement");
3784
- mkdirSync(dir, { recursive: true });
3785
- assertNoSymlinkDescendant(eventDir, dir, "steer acknowledgement");
3786
- const file = workerSteerAcknowledgementFile(eventDir, exact.operationId);
3787
- if (publishExclusiveDurableFile(file, `${JSON.stringify(exact, null, 2)}\n`)) return true;
3788
- const winner = readWorkerSteerAcknowledgement(eventDir, exact.operationId);
3789
- if (winner === void 0 || winner.operationId !== exact.operationId || winner.requestDigest !== exact.requestDigest) throw new Error("steer delivery claim conflicts with another operation");
3790
- return false;
3791
- }
3792
- /** Atomically replace the runtime acknowledgement for one steer operation. @internal */
3793
- function writeWorkerSteerAcknowledgement(eventDir, record) {
3794
- const exact = parseWorkerSteerAcknowledgement(record);
3795
- const request = readWorkerSteerRequest(eventDir, exact.operationId);
3796
- if (request === void 0 || request.requestDigest !== exact.requestDigest) throw new Error("steer acknowledgement does not match an admitted request");
3797
- const dir = workerSteerAcknowledgementsDir(eventDir);
3798
- assertNoSymlinkDescendant(eventDir, dir, "steer acknowledgement");
3799
- mkdirSync(dir, { recursive: true });
3800
- assertNoSymlinkDescendant(eventDir, dir, "steer acknowledgement");
3801
- const file = workerSteerAcknowledgementFile(eventDir, exact.operationId);
3802
- const prior = readWorkerSteerAcknowledgement(eventDir, exact.operationId);
3803
- if (prior !== void 0 && prior.effect !== "unknown" && canonicalCandidateDigest(prior) !== canonicalCandidateDigest(exact)) throw new Error(`steer operation '${exact.operationId}' already has a terminal acknowledgement`);
3804
- writeAtomicDurableFile(file, `${JSON.stringify(exact, null, 2)}\n`);
3805
- }
3806
- /** The directory holding every cancellation artifact of one run (request inbox + acknowledgements). */
3807
- function workerCancellationsDir(eventDir) {
3808
- return join(eventDir, "cancellations");
3809
- }
3810
- /**
3811
- * The durable cancel-request inbox of one run — one NDJSON line per {@link WorkerCancelRequest}.
3812
- * It stays append-only so independent operations retain admission order and readers can skip a
3813
- * partial trailing line. `appendDurableFile` uses O_APPEND, one write, file fsync, and directory
3814
- * fsync; single-record acknowledgements use atomic replacement instead.
3815
- */
3816
- function workerCancelRequestsFile(eventDir) {
3817
- return join(workerCancellationsDir(eventDir), "requests.ndjson");
3818
- }
3819
- /**
3820
- * The acknowledgement file for one cancel operation. The filename is a sanitized stem of the
3821
- * `operationId`; the record inside carries the exact id, and readers verify it so two distinct
3822
- * ids that sanitize to one stem fail loud instead of answering for each other.
3823
- */
3824
- function workerCancellationFile(eventDir, operationId) {
3825
- return join(workerCancellationsDir(eventDir), `${safeOperationFile(operationId)}.json`);
3826
- }
3827
- function safeOperationFile(operationId) {
3828
- const safe = operationId.replace(/[^A-Za-z0-9._-]/g, "_");
3829
- if (safe.length === 0) throw new Error("cancel operationId is empty");
3830
- return safe;
3831
- }
3832
- /** Read every valid cancel request in the run's cancellation inbox. Corrupt lines are skipped. */
3833
- function readWorkerCancelRequests(eventDir) {
3834
- const dir = workerCancellationsDir(eventDir);
3835
- const file = workerCancelRequestsFile(eventDir);
3836
- assertNoSymlinkDescendant(eventDir, dir, "cancel request");
3837
- assertNoSymlinkDescendant(eventDir, file, "cancel request");
3838
- if (!existsSync(file)) return [];
3839
- const out = [];
3840
- const byOperationId = /* @__PURE__ */ new Map();
3841
- let raw = "";
3842
- try {
3843
- raw = readFileSync(file, "utf8");
3844
- } catch {
3845
- return out;
3846
- }
3847
- for (const line of raw.split("\n")) {
3848
- const trimmed = line.trim();
3849
- if (!trimmed) continue;
3850
- let parsed;
3851
- try {
3852
- parsed = JSON.parse(trimmed);
3853
- } catch {
3854
- continue;
3855
- }
3856
- if (!isWorkerCancelRequest(parsed)) continue;
3857
- const prior = byOperationId.get(parsed.operationId);
3858
- if (prior !== void 0) assertSameWorkerCancelRequest(prior, parsed);
3859
- else {
3860
- byOperationId.set(parsed.operationId, parsed);
3861
- out.push(parsed);
3862
- }
3863
- }
3864
- return out;
3865
- }
3866
- /**
3867
- * Read the acknowledgement for one cancel operation. `undefined` when the runtime has not
3868
- * answered. A record whose stored `operationId` differs from the requested one is a filename
3869
- * collision between two sanitized ids — fail loud rather than return another operation's answer.
3870
- */
3871
- function readWorkerCancellation(eventDir, operationId) {
3872
- const dir = workerCancellationsDir(eventDir);
3873
- const file = workerCancellationFile(eventDir, operationId);
3874
- assertNoSymlinkDescendant(eventDir, dir, "cancel acknowledgement");
3875
- assertNoSymlinkDescendant(eventDir, file, "cancel acknowledgement");
3876
- if (!existsSync(file)) return void 0;
3877
- const parsed = JSON.parse(readFileSync(file, "utf8"));
3878
- if (parsed.operationId !== operationId) throw new Error(`cancel acknowledgement collision: '${file}' holds operation '${parsed.operationId}', not '${operationId}' — use operation ids that stay distinct after filename sanitization`);
3879
- return parsed;
3880
- }
3881
- /**
3882
- * Durably write one acknowledgement record. The shared writer fsyncs the file and containing
3883
- * directory, so a concurrent reader sees the prior complete record or the new complete record.
3884
- *
3885
- * @internal The runtime acknowledger is the only intended writer; clients read via
3886
- * {@link readWorkerCancellation} or {@link cancelWorker}.
3887
- */
3888
- function writeWorkerCancellation(eventDir, record) {
3889
- const dir = workerCancellationsDir(eventDir);
3890
- assertNoSymlinkDescendant(eventDir, dir, "cancel acknowledgement");
3891
- mkdirSync(dir, { recursive: true });
3892
- assertNoSymlinkDescendant(eventDir, dir, "cancel acknowledgement");
3893
- const file = workerCancellationFile(eventDir, record.operationId);
3894
- assertNoSymlinkDescendant(eventDir, file, "cancel acknowledgement");
3895
- writeAtomicDurableFile(file, `${JSON.stringify(record, null, 2)}\n`, { mode: 384 });
3896
- }
3897
- /**
3898
- * Request the cancellation of ONE worker, idempotently, and return the operation's current
3899
- * durable state.
3900
- *
3901
- * The write half of the acknowledged-cancellation contract (`writeWorkerSteer` is the steer
3902
- * analog): append the request to the run's cancellation inbox, where the OWNING manager's
3903
- * acknowledger (its turn loop — the root for label/profile references, the parent manager for an
3904
- * exact node id at any depth) applies it — aborting exactly that worker's subtree and recording
3905
- * what it proved. This function never applies the cancellation itself; writing a
3906
- * request file is not an acknowledgement.
3907
- *
3908
- * Idempotency is a lookup: when an acknowledgement for `operationId` already exists, it is
3909
- * returned AS-IS and nothing is appended — repeating one operation can never apply twice. A
3910
- * retry that changes the worker, source, or reason fails closed. A request the runtime has not
3911
- * answered yet returns `effect: 'unknown'` (never a success); call again with the same
3912
- * `operationId` — or `readWorkerCancellation` — to read the acknowledged result after a
3913
- * reconnect.
3914
- */
3915
- function cancelWorker(eventDir, worker, operationId, options = {}) {
3916
- const ref = worker.trim();
3917
- if (!ref) throw new Error("cancelWorker: worker reference is empty");
3918
- const opId = operationId.trim();
3919
- if (!opId) throw new Error("cancelWorker: operationId is empty");
3920
- const pending = readWorkerCancelRequests(eventDir).find((r) => r.operationId === opId);
3921
- const candidate = {
3922
- operationId: opId,
3923
- worker: ref,
3924
- source: options.source ?? "human",
3925
- ...options.reason === void 0 ? {} : { reason: options.reason }
3926
- };
3927
- if (pending !== void 0) assertSameWorkerCancelRequest(pending, candidate);
3928
- const acknowledged = readWorkerCancellation(eventDir, opId);
3929
- if (acknowledged !== void 0) {
3930
- if (pending === void 0 && options.source !== void 0) throw new Error(`cancelWorker: operation '${opId}' cannot verify source without its admitted request`);
3931
- assertWorkerCancellationMatchesCandidate(acknowledged, candidate);
3932
- return acknowledged;
3933
- }
3934
- const source = options.source ?? "human";
3935
- const request = pending ?? {
3936
- operationId: opId,
3937
- at: (/* @__PURE__ */ new Date()).toISOString(),
3938
- source,
3939
- worker: ref,
3940
- ...options.reason === void 0 ? {} : { reason: options.reason }
3941
- };
3942
- if (!pending) {
3943
- const dir = workerCancellationsDir(eventDir);
3944
- assertNoSymlinkDescendant(eventDir, dir, "cancel request");
3945
- mkdirSync(dir, { recursive: true });
3946
- assertNoSymlinkDescendant(eventDir, dir, "cancel request");
3947
- const file = workerCancelRequestsFile(eventDir);
3948
- assertNoSymlinkDescendant(eventDir, file, "cancel request");
3949
- appendDurableFile(file, `${JSON.stringify(request)}\n`, { mode: 384 });
3950
- appendWorkerControlEvent(eventDir, ref, {
3951
- kind: "cancel-request",
3952
- operationId: opId,
3953
- source,
3954
- queued: true,
3955
- ...options.reason === void 0 ? {} : { reason: options.reason }
3956
- });
3957
- }
3958
- return {
3959
- operationId: opId,
3960
- worker: request.worker,
3961
- effect: "unknown",
3962
- requestedAt: request.at,
3963
- observedAt: request.at,
3964
- ...request.reason === void 0 ? {} : { reason: request.reason },
3965
- detail: "request queued; no runtime acknowledger has answered yet",
3966
- terminated: []
3967
- };
3968
- }
3969
- function assertSameWorkerCancelRequest(existing, candidate) {
3970
- if (existing.operationId !== candidate.operationId) throw new Error(`cancelWorker: operation '${candidate.operationId}' has an id collision`);
3971
- if (existing.worker !== candidate.worker) throw new Error(`cancelWorker: operation '${candidate.operationId}' conflicts with its admitted request (worker '${existing.worker}' != '${candidate.worker}')`);
3972
- if (existing.source !== candidate.source) throw new Error(`cancelWorker: operation '${candidate.operationId}' conflicts with its admitted request (source '${existing.source}' != '${candidate.source}')`);
3973
- if (existing.reason !== candidate.reason) throw new Error(`cancelWorker: operation '${candidate.operationId}' conflicts with its admitted request (reason differs)`);
3974
- }
3975
- function assertWorkerCancellationMatchesCandidate(existing, candidate) {
3976
- if (existing.worker !== candidate.worker) throw new Error(`cancelWorker: operation '${candidate.operationId}' conflicts with its acknowledgement (worker '${existing.worker}' != '${candidate.worker}')`);
3977
- if (existing.reason !== candidate.reason) throw new Error(`cancelWorker: operation '${candidate.operationId}' conflicts with its acknowledgement (reason differs)`);
3978
- }
3979
- function isWorkerCancelRequest(value) {
3980
- return typeof value.operationId === "string" && value.operationId.length > 0 && typeof value.at === "string" && typeof value.source === "string" && typeof value.worker === "string" && value.worker.length > 0 && (value.reason === void 0 || typeof value.reason === "string");
3981
- }
3982
- function appendWorkerControlEvent(eventDir, label, event) {
3983
- try {
3984
- fenceRunWrite(() => {
3985
- mkdirSync(supervisorWorkersDir(eventDir), { recursive: true });
3986
- appendFileSync(workerControlLogFile(eventDir, label), `${JSON.stringify({
3987
- at: (/* @__PURE__ */ new Date()).toISOString(),
3988
- label,
3989
- ...event
3990
- })}\n`, "utf8");
3991
- });
3992
- } catch {}
3993
- }
3994
- function workerSteerRequestDigest(value) {
3995
- return canonicalCandidateDigest({
3996
- kind: "worker-steer-request.v1",
3997
- ...value
3998
- });
3999
- }
4000
- function operationFileHash(operationId) {
4001
- const id = operationId.trim();
4002
- if (!id) throw new Error("steer operationId is empty");
4003
- return sha256(id);
4004
- }
4005
- function admitSteerRequest(file, request) {
4006
- const existing = readWorkerSteerRequestFile(file);
4007
- if (existing !== void 0) {
4008
- assertSameSteerRequest(existing, request);
4009
- return {
4010
- request: existing,
4011
- replayed: true
4012
- };
4013
- }
4014
- if (publishExclusiveDurableFile(file, `${JSON.stringify(request, null, 2)}\n`)) return {
4015
- request,
4016
- replayed: false
4017
- };
4018
- const winner = readWorkerSteerRequestFile(file);
4019
- if (winner === void 0) throw new Error("steer request publication lost its winner");
4020
- assertSameSteerRequest(winner, request);
4021
- return {
4022
- request: winner,
4023
- replayed: true
4024
- };
4025
- }
4026
- function readWorkerSteerRequest(eventDir, operationId) {
4027
- const file = workerSteerRequestFile(eventDir, operationId);
4028
- assertNoSymlinkDescendant(eventDir, file, "steer request");
4029
- const request = readWorkerSteerRequestFile(file);
4030
- if (request !== void 0 && request.operationId !== operationId) throw new Error("steer request file belongs to another operation");
4031
- return request;
4032
- }
4033
- function readWorkerSteerRequestFile(file) {
4034
- if (!existsSync(file)) return void 0;
4035
- return parseWorkerSteerRequest(JSON.parse(readFileSync(file, "utf8")));
4036
- }
4037
- function parseWorkerSteerRequest(value) {
4038
- if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("steer request is malformed");
4039
- const request = value;
4040
- if (request.schemaVersion !== 1 || typeof request.operationId !== "string" || request.operationId.length === 0 || typeof request.requestDigest !== "string" || typeof request.at !== "string" || typeof request.source !== "string" || request.source.length === 0 || typeof request.worker !== "string" || request.worker.length === 0 || typeof request.message !== "string" || request.message.trim().length === 0 || typeof request.interrupt !== "boolean") throw new Error("steer request is malformed");
4041
- const exact = request;
4042
- const expected = workerSteerRequestDigest({
4043
- operationId: exact.operationId,
4044
- worker: exact.worker,
4045
- message: exact.message,
4046
- source: exact.source,
4047
- interrupt: exact.interrupt
4048
- });
4049
- if (exact.requestDigest !== expected) throw new Error("steer request digest does not match");
4050
- return exact;
4051
- }
4052
- function parseWorkerSteerAcknowledgement(value) {
4053
- if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("steer acknowledgement is malformed");
4054
- const record = value;
4055
- if (record.schemaVersion !== 1 || typeof record.operationId !== "string" || record.operationId.length === 0 || typeof record.requestDigest !== "string" || typeof record.worker !== "string" || record.worker.length === 0 || record.effect !== "unknown" && record.effect !== "delivered" && record.effect !== "not_live" && record.effect !== "unsupported" && record.effect !== "refused" || typeof record.requestedAt !== "string" || typeof record.observedAt !== "string" || typeof record.detail !== "string") throw new Error("steer acknowledgement is malformed");
4056
- return record;
4057
- }
4058
- function assertSameSteerRequest(existing, candidate) {
4059
- if (existing.operationId !== candidate.operationId) throw new Error("steer operation file collision");
4060
- if (existing.requestDigest !== candidate.requestDigest) throw new Error(`writeWorkerSteer: operation '${candidate.operationId}' conflicts with its admitted request`);
4061
- }
4062
- // src/runtime/supervise/root-stream.ts
4063
- /**
4064
- * The root manager's own provider stream, retained where Runtime owns it.
4065
- *
4066
- * Measured motive (agent-runtime#1233): a CHILD's full event stream — reasoning, text,
4067
- * and tool parts, 28k to 76k reasoning characters per child — is retained in the blob its
4068
- * `settled` record names as `outRef`. The ROOT's stream reached nobody: `driveHarnessFromBackend`
4069
- * drained the root executor for accounting and discarded every `progress` event, and on a
4070
- * `winner` run `result.outRef` is the SELECTED CHILD's artifact, not the root's. A root that died
4071
- * at its join barrier (fourier-e, 63 children) left a 607-byte `failure.json` and nothing it
4072
- * thought; the Lab's only transcript of a root was a post-hoc scrape of the harness's own
4073
- * database.
4074
- *
4075
- * `<runDir>/root-stream.jsonl` is the record: one JSONL line per `ExecutorProgressEvent`, written
4076
- * AS IT ARRIVES, so a root whose process is killed mid-turn keeps every line already written. The
4077
- * file itself is the evidence; the receipt (`ref`, the content address of the file's bytes, and
4078
- * `events`, its committed line count) is what `result.json` and `failure.json` carry. The winner's
4079
- * `outRef` keeps its meaning; this is a separate reference.
4080
- *
4081
- * Durability boundary: each line is written synchronously to an `O_APPEND` descriptor before the
4082
- * next event is drained, and the file is fsynced at every drive-attempt boundary and at close, not
4083
- * per line. An fsync per line cost 7.1 ms per event over
4084
- * 1,000 events and 8.1 ms over 5,000 (~80 s for a root emitting ten thousand text deltas, all of
4085
- * it serialized inside the drain loop that also meters the budget); an async `FileHandle.write`
4086
- * still cost 0.9 to 1.1 ms per line in the thread pool the children's journal writes share; a
4087
- * `writeSync` costs 6 µs. A process kill loses nothing written; only a kernel crash or power
4088
- * loss can drop lines written since the last fsync, and the reader's torn-tail rule keeps such a
4089
- * file readable.
4090
- *
4091
- * The bridge wire carries text and tool calls only: cli-bridge's SSE frames decode
4092
- * `delta.content` and `delta.tool_calls`, never a reasoning delta. A bridge-placed root's retained
4093
- * stream is therefore text plus tool calls; reasoning arrives only on the provider/sandbox path,
4094
- * which projects `message.part.updated` reasoning parts to `reasoning_delta`.
4095
- */
4096
- /** The root stream: one JSONL line per progress event the root's executor observed. */
4097
- const ROOT_STREAM_FILE = "root-stream.jsonl";
4098
- /** Each other node's stream, in the same record shape, keyed by its node id. A name the filename
4099
- * rules changed carries a digest of the id, so `a:b` and `a_b` never share a file. */
4100
- function nodeStreamFile(nodeId) {
4101
- const safe = safeWorkerFile(nodeId);
4102
- return `streams/${safe === nodeId ? safe : `${safe}.${createHash("sha256").update(nodeId).digest("hex").slice(0, 12)}`}.jsonl`;
4103
- }
4104
- /** Create the writer for `<runDir>/root-stream.jsonl`, or for another `file` in the run directory.
4105
- * Nothing touches disk until the first `beginAttempt`, so a run whose root never drives leaves no
4106
- * file and no receipt. The root numbers attempts per process; a node stream, whose writer closes
4107
- * after every execution attempt, sets `continueAttempts` so a later attempt never reuses a number. */
4108
- function createRootStreamSink(runDir, now, file = ROOT_STREAM_FILE, options = {}) {
4109
- const withClaim = captureRunWriteContext();
4110
- const dir = resolve(runDir);
4111
- const path = resolve(dir, file);
4112
- let fd;
4113
- let attempt = 0;
4114
- let seq = 0;
4115
- let closed = false;
4116
- const openOnce = async () => {
4117
- if (fd !== void 0) return;
4118
- await mkdir(dirname(path), { recursive: true });
4119
- assertNoSymlinkDescendant(dir, path, "root stream");
4120
- const needsSeparator = await prepareJsonlAppend(path);
4121
- seq = 0;
4122
- let priorAttempts = 0;
4123
- for await (const record of readCommittedJsonLines(path, { allowMissing: true })) {
4124
- seq += 1;
4125
- priorAttempts = Math.max(priorAttempts, record.attempt);
4126
- }
4127
- if (options.continueAttempts === true) attempt += priorAttempts;
4128
- fd = fenceRunWrite(() => {
4129
- const writer = openSync(path, constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY | (typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0), 384);
4130
- try {
4131
- if (needsSeparator) writeLine(writer, "");
4132
- return writer;
4133
- } catch (error) {
4134
- closeSync(writer);
4135
- throw error;
4136
- }
4137
- });
4138
- };
4139
- return {
4140
- async beginAttempt() {
4141
- return withClaim(async () => {
4142
- if (closed) throw new Error("root stream: beginAttempt after close");
4143
- attempt += 1;
4144
- const writer = fd;
4145
- if (writer !== void 0) fenceRunWrite(() => fsyncSync(writer));
4146
- await openOnce();
4147
- });
4148
- },
4149
- append(event) {
4150
- return withClaim(() => fenceRunWrite(() => {
4151
- if (closed) throw new Error("root stream: append after close");
4152
- if (fd === void 0) throw new Error("root stream: append before beginAttempt");
4153
- const at = new Date(now()).toISOString();
4154
- const record = {
4155
- seq: seq + 1,
4156
- at,
4157
- attempt,
4158
- event
4159
- };
4160
- let line;
4161
- try {
4162
- line = JSON.stringify(record);
4163
- } catch (error) {
4164
- line = JSON.stringify({
4165
- seq: seq + 1,
4166
- at,
4167
- attempt,
4168
- dropped: {
4169
- kind: event.kind,
4170
- reason: errorMessage(error)
4171
- }
4172
- });
4173
- }
4174
- if (line === void 0) line = JSON.stringify({
4175
- seq: seq + 1,
4176
- at,
4177
- attempt,
4178
- dropped: {
4179
- kind: event.kind,
4180
- reason: "event serialized to nothing"
4181
- }
4182
- });
4183
- writeLine(fd, line);
4184
- seq += 1;
4185
- }));
4186
- },
4187
- async close() {
4188
- return withClaim(async () => {
4189
- if (closed) throw new Error("root stream: closed twice");
4190
- closed = true;
4191
- const writer = fd;
4192
- if (writer === void 0) return void 0;
4193
- try {
4194
- fenceRunWrite(() => fsyncSync(writer));
4195
- } finally {
4196
- closeSync(writer);
4197
- fd = void 0;
4198
- }
4199
- return readRootStreamReceipt(dir, file);
4200
- });
4201
- }
4202
- };
4203
- }
4204
- /** `writeSync` may legally make a short write; loop until the whole line and its newline land. */
4205
- function writeLine(fd, line) {
4206
- const bytes = Buffer.from(`${line}\n`, "utf8");
4207
- let offset = 0;
4208
- while (offset < bytes.byteLength) {
4209
- const written = writeSync(fd, bytes, offset, bytes.byteLength - offset);
4210
- if (written <= 0) throw new Error(`root stream: append made no progress at byte ${offset}`);
4211
- offset += written;
4212
- }
4213
- }
4214
- /**
4215
- * The receipt for the root stream a run directory holds, recomputed from the file's bytes, or
4216
- * `undefined` when the directory holds none. This is what a run that never settled — a root that
4217
- * died mid-turn — gets on its failure record, and it equals what `close()` returned for a run
4218
- * that did.
4219
- */
4220
- async function readRootStreamReceipt(runDir, file = ROOT_STREAM_FILE) {
4221
- const path = resolve(runDir, file);
4222
- const hash = createHash("sha256");
4223
- let events = 0;
4224
- try {
4225
- for await (const _record of readCommittedJsonLines(path, { onBytes: (bytes) => {
4226
- hash.update(bytes);
4227
- } })) events += 1;
4228
- } catch (error) {
4229
- if (isNoEntError(error)) return void 0;
4230
- throw error;
4231
- }
4232
- return Object.freeze({
4233
- ref: `sha256:${hash.digest("hex")}`,
4234
- events
4235
- });
4236
- }
4237
- /** Every committed line of the root stream, in order, or `undefined` when there is no file. A
4238
- * torn final line from a process that died mid-write is not a record and is left out. */
4239
- async function readRootStream(runDir) {
4240
- const path = resolve(runDir, ROOT_STREAM_FILE);
4241
- const records = [];
4242
- try {
4243
- for await (const record of readCommittedJsonLines(path)) records.push(record);
4244
- } catch (error) {
4245
- if (isNoEntError(error)) return void 0;
4246
- throw error;
4247
- }
4248
- return records;
4249
- }
4250
- export { UNAVAILABLE_CODES as $, insideCursorNamespace as A, sandboxOutputMarker as At, runtimeProviderCode as B, extractLlmCallEvent as Bt, FileSpawnJournal as C, spendFromUsageTotals as Ct, encodeResultBlob as D, mapSandboxEvent as Dt, FileResultBlobStore as E, createSandboxToolPartState as Et, verifyObserverRecords as F, parseCanonicalTransportEvent as Ft, executorEvidenceWriter as G, sumSandboxUsage as Gt, BUDGET_CONTINUATION_TASK as H, notifySandboxEventObserver as Ht, reportedFailureCause as I, assertSandboxServedModel as It, prepareRetainedExecutor as J, recordValue as Jt, isProvisioningRefusal as K, decodeHarnessUsage as Kt, runtimeFailureCause as L, canonicalSandboxUsageMode as Lt, FileObserverJournal as M, terminalTextFromSandboxEvent as Mt, createFileObserverHooks as N, canonicalStreamEventFromSandboxEvent as Nt, SpawnEventIndex as O, mapSandboxToolEvent as Ot, observerRecordDigest as P, extractTransportEventIdentity as Pt, workspaceCaptureWriter as Q, runtimeFailureCauseSchema as R, createSandboxTurnEventCollector as Rt, writeWorkerSteerAcknowledgement as S, spendFromUsageEvents as St, spawnJournalRecordEvents as T, assertValidSpend as Tt, DISPATCH_NOT_ADMITTED_CODE as U, sandboxEventServedBackend as Ut, settlementFailureCause as V, isSandboxTerminalEvent as Vt, RetainedExecutionPendingError as W, sandboxTerminalUsageField as Wt, retainedExecutorContext as X, registerRetainedExecutorPreparation as Y, retainedExecutorSeamKey as Z, supervisorRunsRoot as _, isCredentialRefusal as _t, readRootStreamReceipt as a, unavailableSignalInText as at, writeWorkerCancellation as b, meterUsageEvent as bt, claimWorkerSteerDelivery as c, TRACE_SUMMARY_SCHEMA as ct, readWorkerCancelRequests as d, contentRef as dt, UNAVAILABLE_STATUSES as et, readWorkerCancellation as f, executorFailure as ft, supervisorRunDir as g, credentialCommandExit as gt, safeWorkerFile as h, hashJson as ht, readRootStream as i, unavailableResumeAt as it, outsideCursorNamespace as j, sandboxProgressEvents as jt, closesCursorSlot as k, sandboxLeafOutputFromEvents as kt, legacySupervisorRunDir as l, traceRootWriter as lt, readWorkerSteerRequests as m, executorFailureReason as mt, createRootStreamSink as n, jitteredMs as nt, appendWorkerControlEvent as o, unavailableSignalOfFailure as ot, readWorkerSteerAcknowledgement as p, executorFailureInfra as pt, leafContinuationExecutionId as q, parseCodexUsageRecord as qt, nodeStreamFile as r, unavailablePauseMs as rt, cancelWorker as s, TRACE_ROOT_SCHEMA as st, ROOT_STREAM_FILE as t, cappedDoublingMs as tt, legacySupervisorRunsRoot as u, contentAddress as ut, workerCancellationsDir as v, BudgetReconcileFault as vt, appendSpawnEvents as w, assertValidBudget as wt, writeWorkerSteer as x, newUsageTotals as xt, workerInboxFileFromEventDir as y, createBudgetPool as yt, runtimeFailureRemediationSchema as z, createSandboxUsageLedger as zt };
4251
-
4252
- //# sourceMappingURL=root-stream-Bsh5WFcg.js.map