@tangle-network/agent-runtime 0.90.0 → 0.91.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/agent.d.ts +1 -1
  2. package/dist/agent.js +4 -4
  3. package/dist/analyst-loop.d.ts +1 -1
  4. package/dist/{chunk-4WXGK6GV.js → chunk-7ON74BQO.js} +2 -2
  5. package/dist/{chunk-4IBAMGBE.js → chunk-AD7JW4QG.js} +420 -23
  6. package/dist/chunk-AD7JW4QG.js.map +1 -0
  7. package/dist/{chunk-7LO5GMAO.js → chunk-CMYMTRGA.js} +14 -5
  8. package/dist/chunk-CMYMTRGA.js.map +1 -0
  9. package/dist/{chunk-XQEISTK2.js → chunk-DWWII6N2.js} +2 -2
  10. package/dist/{chunk-XMOU4WEY.js → chunk-FF77IBQM.js} +15 -3
  11. package/dist/chunk-FF77IBQM.js.map +1 -0
  12. package/dist/{chunk-MHK62APK.js → chunk-IOUUITQA.js} +3 -3
  13. package/dist/{chunk-UD4BHQMI.js → chunk-IVGYLCFH.js} +2 -2
  14. package/dist/{chunk-UD4BHQMI.js.map → chunk-IVGYLCFH.js.map} +1 -1
  15. package/dist/{chunk-74SBMDTO.js → chunk-JRS3YSRZ.js} +2 -2
  16. package/dist/{chunk-QSNSMJSZ.js → chunk-NC66AM3S.js} +1487 -1481
  17. package/dist/chunk-NC66AM3S.js.map +1 -0
  18. package/dist/{completion-gate-BDaBP8dd.d.ts → completion-gate-DkAnUmpb.d.ts} +2 -2
  19. package/dist/{coordination-DxHduZg7.d.ts → coordination-rRj5hjJK.d.ts} +5 -5
  20. package/dist/environment-provider.d.ts +2 -2
  21. package/dist/index.d.ts +915 -18
  22. package/dist/index.js +5742 -33
  23. package/dist/index.js.map +1 -1
  24. package/dist/intelligence.d.ts +227 -83
  25. package/dist/intelligence.js +259 -166
  26. package/dist/intelligence.js.map +1 -1
  27. package/dist/knowledge.d.ts +6 -6
  28. package/dist/knowledge.js +4 -4
  29. package/dist/lifecycle.d.ts +2 -2
  30. package/dist/lifecycle.js +2 -2
  31. package/dist/{local-harness-sI0S_XNA.d.ts → local-harness-dcD5WTTr.d.ts} +3 -0
  32. package/dist/{loop-runner-bin-D3GmB2Ru.d.ts → loop-runner-bin-DTbZVGfM.d.ts} +2 -2
  33. package/dist/loop-runner-bin.d.ts +6 -6
  34. package/dist/loop-runner-bin.js +5 -5
  35. package/dist/loops.d.ts +16 -256
  36. package/dist/loops.js +30 -4
  37. package/dist/mcp/bin.js +4 -4
  38. package/dist/mcp/index.d.ts +9 -9
  39. package/dist/mcp/index.js +6 -6
  40. package/dist/{mcp-serve-verifier-FL7-ZEb_.d.ts → mcp-serve-verifier-XsX8rkB9.d.ts} +12 -1
  41. package/dist/{openai-tools-D0xmzo0y.d.ts → openai-tools-C4ZfUD4L.d.ts} +1 -1
  42. package/dist/profiles.d.ts +1 -1
  43. package/dist/structural-rollout-MwlpgQ-6.d.ts +446 -0
  44. package/dist/{supervise-BpCdssu0.d.ts → supervise-DPmYPk0j.d.ts} +3 -3
  45. package/dist/{types-Dnk189QA.d.ts → types-SyuwunY_.d.ts} +1 -1
  46. package/dist/{types-DAJQRIUD.d.ts → types-eMNgWgFi.d.ts} +2 -2
  47. package/dist/{worktree-fanout-CfRXYmgV.d.ts → worktree-fanout-BDFQIO-Y.d.ts} +234 -234
  48. package/package.json +6 -7
  49. package/skills/build-with-agent-runtime/SKILL.md +20 -17
  50. package/dist/chunk-4IBAMGBE.js.map +0 -1
  51. package/dist/chunk-7LO5GMAO.js.map +0 -1
  52. package/dist/chunk-QSNSMJSZ.js.map +0 -1
  53. package/dist/chunk-XMOU4WEY.js.map +0 -1
  54. /package/dist/{chunk-4WXGK6GV.js.map → chunk-7ON74BQO.js.map} +0 -0
  55. /package/dist/{chunk-XQEISTK2.js.map → chunk-DWWII6N2.js.map} +0 -0
  56. /package/dist/{chunk-MHK62APK.js.map → chunk-IOUUITQA.js.map} +0 -0
  57. /package/dist/{chunk-74SBMDTO.js.map → chunk-JRS3YSRZ.js.map} +0 -0
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-DPEUKJRO.js";
4
4
  import {
5
5
  buildLoopSpanNodes
6
- } from "./chunk-UD4BHQMI.js";
6
+ } from "./chunk-IVGYLCFH.js";
7
7
  import {
8
8
  addTokenUsage,
9
9
  deleteBoxSafe,
@@ -25,7 +25,7 @@ import {
25
25
  import {
26
26
  harnessInvocation,
27
27
  runLocalHarness
28
- } from "./chunk-7LO5GMAO.js";
28
+ } from "./chunk-CMYMTRGA.js";
29
29
  import {
30
30
  AgentEvalError,
31
31
  ConfigError,
@@ -34,58 +34,6 @@ import {
34
34
  ValidationError
35
35
  } from "./chunk-YEJR7IXO.js";
36
36
 
37
- // src/runtime/supervise/model-policy.ts
38
- function assertModelAllowed(model, allowed) {
39
- if (!allowed || model === void 0) return;
40
- if (!allowed.includes(model)) {
41
- throw new ConfigError(
42
- `model ${JSON.stringify(model)} is not in the allowed set ${JSON.stringify([...allowed])}`
43
- );
44
- }
45
- }
46
-
47
- // src/runtime/supervise/completion-gate.ts
48
- function gateOnDeliverable(inner, deliverable) {
49
- let gated;
50
- const check = async (out, baseScore) => {
51
- let delivered;
52
- try {
53
- delivered = await deliverable.check(out) === true;
54
- } catch {
55
- delivered = false;
56
- }
57
- return { valid: delivered, score: baseScore ?? (delivered ? 1 : 0) };
58
- };
59
- return {
60
- runtime: inner.runtime,
61
- ...inner.budgetExempt !== void 0 ? { budgetExempt: inner.budgetExempt } : {},
62
- ...inner.deliver ? { deliver: (m) => inner.deliver?.(m) } : {},
63
- execute(task, signal) {
64
- const r = inner.execute(task, signal);
65
- if (isAsyncIterable(r)) {
66
- return (async function* () {
67
- for await (const ev of r) yield ev;
68
- const art = inner.resultArtifact();
69
- gated = await check(art.out, art.verdict?.score);
70
- })();
71
- }
72
- return (async () => {
73
- const res = await r;
74
- gated = await check(res.out, res.verdict?.score);
75
- return { ...res, verdict: gated };
76
- })();
77
- },
78
- teardown: (grace) => inner.teardown(grace),
79
- resultArtifact() {
80
- const art = inner.resultArtifact();
81
- return { ...art, verdict: gated ?? art.verdict };
82
- }
83
- };
84
- }
85
- function isAsyncIterable(v) {
86
- return v != null && typeof v[Symbol.asyncIterator] === "function";
87
- }
88
-
89
37
  // src/durable/spawn-journal.ts
90
38
  import { createHash } from "crypto";
91
39
  function contentAddress(artifact) {
@@ -153,574 +101,1182 @@ function assertSeqUnique(root, events, ev) {
153
101
  }
154
102
  }
155
103
 
156
- // src/runtime-hooks.ts
157
- function defineRuntimeHooks(hooks) {
158
- return hooks;
159
- }
160
- function composeRuntimeHooks(...entries) {
161
- const hooks = entries.filter((entry) => !!entry);
162
- return {
163
- onEvent: hooks.some((hook) => hook.onEvent) ? (event, context) => {
164
- const pending = [];
165
- for (const hook of hooks) {
166
- const result = hook.onEvent?.(event, context);
167
- if (isThenable(result)) pending.push(Promise.resolve(result));
168
- }
169
- if (pending.length > 0) return Promise.all(pending).then(() => void 0);
170
- return void 0;
171
- } : void 0,
172
- onDecisionPoint: hooks.some((hook) => hook.onDecisionPoint) ? (point, context) => {
173
- const pending = [];
174
- for (const hook of hooks) {
175
- const result = hook.onDecisionPoint?.(point, context);
176
- if (isThenable(result)) pending.push(Promise.resolve(result));
177
- }
178
- if (pending.length > 0) return Promise.all(pending).then(() => void 0);
179
- return void 0;
180
- } : void 0,
181
- onHookError: hooks.some((hook) => hook.onHookError) ? (error, context) => {
182
- const pending = [];
183
- for (const hook of hooks) {
184
- const result = hook.onHookError?.(error, context);
185
- if (isThenable(result)) pending.push(Promise.resolve(result));
186
- }
187
- if (pending.length > 0) return Promise.all(pending).then(() => void 0);
188
- return void 0;
189
- } : void 0
190
- };
191
- }
192
- function notifyRuntimeHookEvent(hooks, event, context = {}) {
193
- const onEvent = hooks?.onEvent;
194
- if (!onEvent) return;
195
- try {
196
- const result = onEvent(event, context);
197
- if (isThenable(result)) {
198
- void result.catch((error) => {
199
- notifyRuntimeHookError(hooks, toError(error), {
200
- hook: "onEvent",
201
- eventId: event.id,
202
- target: event.target,
203
- phase: event.phase
204
- });
205
- });
206
- }
207
- } catch (error) {
208
- notifyRuntimeHookError(hooks, toError(error), {
209
- hook: "onEvent",
210
- eventId: event.id,
211
- target: event.target,
212
- phase: event.phase
213
- });
104
+ // src/runtime/supervise/model-policy.ts
105
+ function assertModelAllowed(model, allowed) {
106
+ if (!allowed || model === void 0) return;
107
+ if (!allowed.includes(model)) {
108
+ throw new ConfigError(
109
+ `model ${JSON.stringify(model)} is not in the allowed set ${JSON.stringify([...allowed])}`
110
+ );
214
111
  }
215
112
  }
216
- function notifyRuntimeDecisionPoint(hooks, point, context = {}) {
217
- const onDecisionPoint = hooks?.onDecisionPoint;
218
- if (!onDecisionPoint) return;
219
- try {
220
- const result = onDecisionPoint(point, context);
221
- if (isThenable(result)) {
222
- void result.catch((error) => {
223
- notifyRuntimeHookError(hooks, toError(error), {
224
- hook: "onDecisionPoint",
225
- decisionId: point.id,
226
- decisionKind: point.kind
227
- });
228
- });
229
- }
230
- } catch (error) {
231
- notifyRuntimeHookError(hooks, toError(error), {
232
- hook: "onDecisionPoint",
233
- decisionId: point.id,
234
- decisionKind: point.kind
235
- });
113
+
114
+ // src/runtime/router-client.ts
115
+ import { estimateCost, isModelPriced } from "@tangle-network/agent-eval";
116
+
117
+ // src/runtime/tool-loop.ts
118
+ function estimateConversationTokens(messages) {
119
+ let chars = 0;
120
+ for (const m of messages) {
121
+ const content = m.content;
122
+ chars += typeof content === "string" ? content.length : safeJsonLength(content);
123
+ const calls = m.tool_calls;
124
+ if (calls) for (const c of calls) chars += c.function?.arguments?.length ?? 0;
236
125
  }
126
+ return Math.ceil(chars / 4);
237
127
  }
238
- function notifyRuntimeHookError(hooks, error, context) {
128
+ function safeJsonLength(value) {
129
+ if (value === void 0 || value === null) return 0;
239
130
  try {
240
- const result = hooks?.onHookError?.(error, context);
241
- if (isThenable(result)) void result.catch(() => void 0);
131
+ return JSON.stringify(value)?.length ?? 0;
242
132
  } catch {
133
+ return String(value).length;
243
134
  }
244
135
  }
245
- function isThenable(value) {
246
- return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
247
- }
248
- function toError(error) {
249
- return error instanceof Error ? error : new Error(String(error));
250
- }
251
-
252
- // src/runtime/supervise/scope.ts
253
- var nestedScopeSeamKey = "nested-scope";
254
- function makeNestedScopeSeam(args, childNodeId) {
255
- return {
256
- depth: args.depth,
257
- ...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
258
- journalRoot: args.root,
259
- mount(nestedRoot, signal) {
260
- return createScope({
261
- parentId: childNodeId,
262
- root: nestedRoot,
263
- pool: args.pool,
264
- journal: args.journal,
265
- blobs: args.blobs,
266
- executors: args.executors,
267
- // Re-seed the parent's NON-recursion seams (sandbox/router for leaf grandchildren);
268
- // the nested scope adds its OWN nested-scope seam per child in `spawn`.
269
- seams: args.seams,
270
- depth: args.depth + 1,
271
- ...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
272
- signal,
273
- ...args.now ? { now: args.now } : {},
274
- ...args.hooks ? { hooks: args.hooks } : {}
275
- });
276
- }
277
- };
136
+ async function maybeCompact(messages, c, turn) {
137
+ const head = c.preserveHead !== void 0 && c.preserveHead > 0 ? c.preserveHead : 2;
138
+ if (messages.length <= head + 1) return false;
139
+ const estimate = c.estimateTokens ?? estimateConversationTokens;
140
+ const before = estimate(messages);
141
+ if (before <= c.thresholdTokens) return false;
142
+ const digest = await c.distill([...messages]);
143
+ messages.splice(head, messages.length - head, {
144
+ role: "user",
145
+ content: `[earlier work compacted to save context \u2014 progress so far]
146
+ ${digest}`
147
+ });
148
+ c.onCompact?.({ turn, beforeTokens: before, afterTokens: estimate(messages) });
149
+ return true;
278
150
  }
279
- function createScope(args) {
280
- const children = /* @__PURE__ */ new Map();
281
- let spawnOrdinal = 0;
282
- let cursorSeq = 0;
283
- let meterSeq = 0;
284
- const now = args.now ?? Date.now;
285
- function spawn4(agent, task, opts) {
286
- if (args.maxDepth !== void 0 && args.depth >= args.maxDepth) {
287
- return { ok: false, reason: "depth-exceeded" };
151
+ async function runBrainLoop(opts) {
152
+ const maxTurns = opts.maxTurns ?? 4;
153
+ const messages = [...opts.initialMessages];
154
+ let toolCalls = 0;
155
+ let lastText = "";
156
+ const usage = { input: 0, output: 0 };
157
+ const toolTrace = [];
158
+ for (let turn = 1; turn <= maxTurns; turn += 1) {
159
+ if (opts.hooks?.stopBefore?.(turn)) break;
160
+ await opts.hooks?.beforeTurn?.(turn, messages);
161
+ if (opts.compaction) await maybeCompact(messages, opts.compaction, turn);
162
+ const r = await opts.chat(messages, opts.tools);
163
+ if (r.usage) {
164
+ usage.input += r.usage.input;
165
+ usage.output += r.usage.output;
166
+ opts.hooks?.onUsage?.(r.usage);
288
167
  }
289
- const spec = agent.executorSpec;
290
- if (!isAgentSpec(spec)) {
291
- throw new ValidationError(
292
- `scope.spawn: agent "${agent.name}" exposes no \`executorSpec\` (AgentSpec) to resolve a Executor`
293
- );
168
+ if (r.content) lastText = r.content;
169
+ if (r.toolCalls.length === 0) {
170
+ if (r.content) messages.push({ role: "assistant", content: r.content });
171
+ return { final: lastText, turns: turn, toolCalls, toolTrace, usage, messages };
294
172
  }
295
- const resolved = args.executors.resolve(spec);
296
- if (!resolved.succeeded) throw new ValidationError(`scope.spawn: ${resolved.error}`);
297
- const reservation = args.pool.reserve(opts.budget);
298
- if (!reservation.ok) return { ok: false, reason: reservation.reason };
299
- try {
300
- const ordinal = spawnOrdinal++;
301
- const id = `${args.parentId}:s${ordinal}`;
302
- const childAbort = new AbortController();
303
- const cascadeAbort = () => childAbort.abort();
304
- if (args.signal.aborted) childAbort.abort();
305
- else args.signal.addEventListener("abort", cascadeAbort, { once: true });
306
- const ctx = {
307
- signal: childAbort.signal,
308
- seams: { ...args.seams, [nestedScopeSeamKey]: makeNestedScopeSeam(args, id) }
309
- };
310
- const executor = resolved.value(spec, ctx);
311
- const handle = {
312
- id,
313
- label: opts.label,
314
- get status() {
315
- return children.get(id)?.status ?? "cancelled";
316
- },
317
- abort(reason) {
318
- childAbort.abort(reason);
319
- }
320
- };
321
- const live = {
322
- id,
323
- status: "acquiring",
324
- runtime: executor.runtime,
325
- budget: opts.budget,
326
- label: opts.label,
327
- spent: zeroSpend(),
328
- settled: void 0,
329
- delivered: false,
330
- executorDone: false,
331
- ...executor.deliver ? { deliver: executor.deliver.bind(executor) } : {}
332
- };
333
- children.set(id, live);
334
- void args.journal.appendEvent(args.root, {
335
- kind: "spawned",
336
- id,
337
- parent: args.parentId,
338
- label: opts.label,
339
- budget: opts.budget,
340
- runtime: executor.runtime,
341
- seq: ordinal,
342
- at: new Date(now()).toISOString()
343
- });
344
- notifyRuntimeHookEvent(
345
- args.hooks,
346
- {
347
- id: `${id}:spawn`,
348
- runId: args.root,
349
- target: "agent.spawn",
350
- phase: "after",
351
- timestamp: now(),
352
- stepIndex: ordinal,
353
- parentId: args.parentId,
354
- payload: {
355
- childId: id,
356
- label: opts.label,
357
- runtime: executor.runtime,
358
- budget: opts.budget,
359
- depth: args.depth
360
- }
361
- },
362
- { signal: args.signal }
363
- );
364
- const settled = runChild(
365
- live,
366
- executor,
367
- childAbort,
368
- task,
369
- opts,
370
- args.pool,
371
- reservation.ticket,
372
- args.blobs
373
- ).then((s) => {
374
- live.resolved = s;
375
- return s;
376
- }).finally(() => {
377
- args.signal.removeEventListener("abort", cascadeAbort);
378
- });
379
- live.settled = settled;
380
- return { ok: true, handle };
381
- } catch (err) {
382
- args.pool.reconcile(reservation.ticket, zeroSpend());
383
- throw err;
173
+ messages.push({
174
+ role: "assistant",
175
+ content: r.content ?? "",
176
+ tool_calls: r.toolCalls.map((tc) => ({
177
+ id: tc.id,
178
+ type: "function",
179
+ function: { name: tc.name, arguments: tc.arguments }
180
+ }))
181
+ });
182
+ for (const tc of r.toolCalls) {
183
+ toolCalls += 1;
184
+ let args = {};
185
+ try {
186
+ args = JSON.parse(tc.arguments);
187
+ } catch {
188
+ messages.push({
189
+ role: "tool",
190
+ tool_call_id: tc.id,
191
+ content: `error: arguments were not valid JSON: ${tc.arguments.slice(0, 200)}`
192
+ });
193
+ continue;
194
+ }
195
+ const out = await opts.execute(tc.name, args);
196
+ messages.push({ role: "tool", tool_call_id: tc.id, content: out });
197
+ toolTrace.push({ name: tc.name, args: tc.arguments, result: out });
384
198
  }
385
199
  }
386
- async function next() {
387
- const undelivered = () => [...children.values()].filter((c) => !c.delivered);
388
- if (undelivered().length === 0) return null;
389
- for (; ; ) {
390
- const pending = undelivered();
391
- if (pending.length === 0) return null;
392
- const ready = pending.find((c) => c.resolved !== void 0);
393
- const chosen = ready ?? await raceFirstSettled(pending);
394
- if (chosen.delivered) continue;
395
- chosen.delivered = true;
396
- const seq = cursorSeq++;
397
- const settlement = chosen.resolved;
398
- if (!settlement) {
399
- throw new ValidationError(
400
- `scope.next: child '${chosen.id}' won the settle race without a resolved value`
401
- );
200
+ return { final: lastText, turns: maxTurns, toolCalls, toolTrace, usage, messages };
201
+ }
202
+
203
+ // src/runtime/router-client.ts
204
+ async function routerChatWithUsage(cfg, messages, opts) {
205
+ const url = `${cfg.routerBaseUrl.replace(/\/$/, "")}/chat/completions`;
206
+ const headers = { "content-type": "application/json", authorization: `Bearer ${cfg.routerKey}` };
207
+ let temperature = opts?.temperature ?? 0.2;
208
+ const body = () => ({
209
+ model: cfg.model,
210
+ messages,
211
+ temperature,
212
+ max_tokens: opts?.maxTokens ?? 8192,
213
+ ...opts?.reasoningEffort ? { reasoning_effort: opts.reasoningEffort } : {}
214
+ });
215
+ if (cfg.complete) return parseChatResult(await cfg.complete(body()), cfg.model);
216
+ let lastErr = "";
217
+ for (let attempt = 0; attempt < 5; attempt += 1) {
218
+ const res = await fetch(url, {
219
+ method: "POST",
220
+ headers,
221
+ body: JSON.stringify(body()),
222
+ ...opts?.signal ? { signal: opts.signal } : {}
223
+ });
224
+ if (res.ok) return parseChatResult(await res.json(), cfg.model);
225
+ const status = res.status;
226
+ const text = (await res.text()).slice(0, 200);
227
+ lastErr = `router ${status}: ${text}`;
228
+ if (status === 400 && /temperature/i.test(text) && temperature !== 1) {
229
+ temperature = 1;
230
+ continue;
231
+ }
232
+ if (![408, 425, 429, 500, 502, 503, 504, 520, 522, 524].includes(status))
233
+ throw new Error(lastErr);
234
+ if (attempt < 4) await new Promise((r) => setTimeout(r, 800 * 2 ** attempt));
235
+ }
236
+ throw new Error(`${lastErr} (exhausted retries)`);
237
+ }
238
+ function parseChatResult(json, model) {
239
+ const data = json;
240
+ const u = data.usage;
241
+ const usage = u && typeof u.prompt_tokens === "number" && typeof u.completion_tokens === "number" ? { input: u.prompt_tokens, output: u.completion_tokens } : void 0;
242
+ const costUsd = usage && isModelPriced(model) ? estimateCost(usage.input, usage.output, model) : void 0;
243
+ const msg = data.choices?.[0]?.message;
244
+ const { content, reasoning } = splitReasoning(
245
+ msg?.content ?? "",
246
+ msg?.reasoning ?? msg?.reasoning_content
247
+ );
248
+ return {
249
+ content,
250
+ ...reasoning ? { reasoning } : {},
251
+ ...usage ? { usage } : {},
252
+ ...costUsd !== void 0 ? { costUsd } : {}
253
+ };
254
+ }
255
+ function splitReasoning(rawContent, fieldReasoning) {
256
+ const open = rawContent.indexOf("<think>");
257
+ if (open !== -1) {
258
+ const close = rawContent.indexOf("</think>", open);
259
+ const inline = close !== -1 ? rawContent.slice(open + 7, close) : rawContent.slice(open + 7);
260
+ const rest = close !== -1 ? rawContent.slice(0, open) + rawContent.slice(close + 8) : rawContent.slice(0, open);
261
+ const reasoning = [fieldReasoning, inline.trim()].filter(Boolean).join("\n");
262
+ return { content: rest.trim(), ...reasoning ? { reasoning } : {} };
263
+ }
264
+ return { content: rawContent, ...fieldReasoning ? { reasoning: fieldReasoning } : {} };
265
+ }
266
+ async function routerChatWithTools(cfg, messages, tools, opts) {
267
+ const body = {
268
+ model: cfg.model,
269
+ messages,
270
+ tools,
271
+ tool_choice: opts?.toolChoice ?? "auto",
272
+ temperature: opts?.temperature ?? 0.3,
273
+ ...opts?.maxTokens ? { max_tokens: opts.maxTokens } : {}
274
+ };
275
+ const raw = cfg.complete ? await cfg.complete(body) : await (async () => {
276
+ const res = await fetch(`${cfg.routerBaseUrl.replace(/\/$/, "")}/chat/completions`, {
277
+ method: "POST",
278
+ headers: { "content-type": "application/json", authorization: `Bearer ${cfg.routerKey}` },
279
+ body: JSON.stringify(body),
280
+ ...opts?.signal ? { signal: opts.signal } : {}
281
+ });
282
+ if (!res.ok) throw new Error(`router ${res.status}: ${(await res.text()).slice(0, 200)}`);
283
+ return res.json();
284
+ })();
285
+ const data = raw;
286
+ const msg = data.choices?.[0]?.message;
287
+ const toolCalls = (msg?.tool_calls ?? []).map((tc, i) => ({
288
+ id: tc.id ?? `call_${i}`,
289
+ name: tc.function?.name ?? "",
290
+ arguments: tc.function?.arguments ?? "{}"
291
+ }));
292
+ const u = data.usage;
293
+ const usage = u && typeof u.prompt_tokens === "number" && typeof u.completion_tokens === "number" ? { input: u.prompt_tokens, output: u.completion_tokens } : void 0;
294
+ const costUsd = usage && isModelPriced(cfg.model) ? estimateCost(usage.input, usage.output, cfg.model) : void 0;
295
+ return {
296
+ content: msg?.content ?? null,
297
+ toolCalls,
298
+ ...usage ? { usage } : {},
299
+ ...costUsd !== void 0 ? { costUsd } : {}
300
+ };
301
+ }
302
+ async function routerToolLoop(cfg, system, user, tools, execute, opts) {
303
+ const initialMessages = opts?.initialMessages ?? [
304
+ { role: "system", content: system },
305
+ { role: "user", content: user }
306
+ ];
307
+ return runBrainLoop({
308
+ chat: (messages, toolSpecs) => routerChatWithTools(cfg, messages, toolSpecs, {
309
+ ...opts?.temperature !== void 0 ? { temperature: opts.temperature } : {},
310
+ ...opts?.maxTokens ? { maxTokens: opts.maxTokens } : {},
311
+ ...opts?.signal ? { signal: opts.signal } : {}
312
+ }),
313
+ tools,
314
+ execute,
315
+ initialMessages,
316
+ maxTurns: opts?.maxTurns ?? 4
317
+ });
318
+ }
319
+ function routerBrain(cfg, opts = {}) {
320
+ const temperature = opts.temperature ?? 0.4;
321
+ return (messages, tools) => routerChatWithTools(cfg, messages, tools, { temperature, toolChoice: "auto" });
322
+ }
323
+
324
+ // src/runtime-hooks.ts
325
+ function defineRuntimeHooks(hooks) {
326
+ return hooks;
327
+ }
328
+ function composeRuntimeHooks(...entries) {
329
+ const hooks = entries.filter((entry) => !!entry);
330
+ return {
331
+ onEvent: hooks.some((hook) => hook.onEvent) ? (event, context) => {
332
+ const pending = [];
333
+ for (const hook of hooks) {
334
+ const result = hook.onEvent?.(event, context);
335
+ if (isThenable(result)) pending.push(Promise.resolve(result));
402
336
  }
403
- return finalizeSettlement(chosen, settlement, seq, args, now);
337
+ if (pending.length > 0) return Promise.all(pending).then(() => void 0);
338
+ return void 0;
339
+ } : void 0,
340
+ onDecisionPoint: hooks.some((hook) => hook.onDecisionPoint) ? (point, context) => {
341
+ const pending = [];
342
+ for (const hook of hooks) {
343
+ const result = hook.onDecisionPoint?.(point, context);
344
+ if (isThenable(result)) pending.push(Promise.resolve(result));
345
+ }
346
+ if (pending.length > 0) return Promise.all(pending).then(() => void 0);
347
+ return void 0;
348
+ } : void 0,
349
+ onHookError: hooks.some((hook) => hook.onHookError) ? (error, context) => {
350
+ const pending = [];
351
+ for (const hook of hooks) {
352
+ const result = hook.onHookError?.(error, context);
353
+ if (isThenable(result)) pending.push(Promise.resolve(result));
354
+ }
355
+ if (pending.length > 0) return Promise.all(pending).then(() => void 0);
356
+ return void 0;
357
+ } : void 0
358
+ };
359
+ }
360
+ function notifyRuntimeHookEvent(hooks, event, context = {}) {
361
+ const onEvent = hooks?.onEvent;
362
+ if (!onEvent) return;
363
+ try {
364
+ const result = onEvent(event, context);
365
+ if (isThenable(result)) {
366
+ void result.catch((error) => {
367
+ notifyRuntimeHookError(hooks, toError(error), {
368
+ hook: "onEvent",
369
+ eventId: event.id,
370
+ target: event.target,
371
+ phase: event.phase
372
+ });
373
+ });
374
+ }
375
+ } catch (error) {
376
+ notifyRuntimeHookError(hooks, toError(error), {
377
+ hook: "onEvent",
378
+ eventId: event.id,
379
+ target: event.target,
380
+ phase: event.phase
381
+ });
382
+ }
383
+ }
384
+ function notifyRuntimeDecisionPoint(hooks, point, context = {}) {
385
+ const onDecisionPoint = hooks?.onDecisionPoint;
386
+ if (!onDecisionPoint) return;
387
+ try {
388
+ const result = onDecisionPoint(point, context);
389
+ if (isThenable(result)) {
390
+ void result.catch((error) => {
391
+ notifyRuntimeHookError(hooks, toError(error), {
392
+ hook: "onDecisionPoint",
393
+ decisionId: point.id,
394
+ decisionKind: point.kind
395
+ });
396
+ });
397
+ }
398
+ } catch (error) {
399
+ notifyRuntimeHookError(hooks, toError(error), {
400
+ hook: "onDecisionPoint",
401
+ decisionId: point.id,
402
+ decisionKind: point.kind
403
+ });
404
+ }
405
+ }
406
+ function notifyRuntimeHookError(hooks, error, context) {
407
+ try {
408
+ const result = hooks?.onHookError?.(error, context);
409
+ if (isThenable(result)) void result.catch(() => void 0);
410
+ } catch {
411
+ }
412
+ }
413
+ function isThenable(value) {
414
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
415
+ }
416
+ function toError(error) {
417
+ return error instanceof Error ? error : new Error(String(error));
418
+ }
419
+
420
+ // src/runtime/supervise/scope.ts
421
+ var nestedScopeSeamKey = "nested-scope";
422
+ function makeNestedScopeSeam(args, childNodeId) {
423
+ return {
424
+ depth: args.depth,
425
+ ...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
426
+ journalRoot: args.root,
427
+ mount(nestedRoot, signal) {
428
+ return createScope({
429
+ parentId: childNodeId,
430
+ root: nestedRoot,
431
+ pool: args.pool,
432
+ journal: args.journal,
433
+ blobs: args.blobs,
434
+ executors: args.executors,
435
+ // Re-seed the parent's NON-recursion seams (sandbox/router for leaf grandchildren);
436
+ // the nested scope adds its OWN nested-scope seam per child in `spawn`.
437
+ seams: args.seams,
438
+ depth: args.depth + 1,
439
+ ...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
440
+ signal,
441
+ ...args.now ? { now: args.now } : {},
442
+ ...args.hooks ? { hooks: args.hooks } : {}
443
+ });
444
+ }
445
+ };
446
+ }
447
+ function createScope(args) {
448
+ const children = /* @__PURE__ */ new Map();
449
+ let spawnOrdinal = 0;
450
+ let cursorSeq = 0;
451
+ let meterSeq = 0;
452
+ const now = args.now ?? Date.now;
453
+ function spawn4(agent, task, opts) {
454
+ if (args.maxDepth !== void 0 && args.depth >= args.maxDepth) {
455
+ return { ok: false, reason: "depth-exceeded" };
456
+ }
457
+ const spec = agent.executorSpec;
458
+ if (!isAgentSpec(spec)) {
459
+ throw new ValidationError(
460
+ `scope.spawn: agent "${agent.name}" exposes no \`executorSpec\` (AgentSpec) to resolve a Executor`
461
+ );
462
+ }
463
+ const resolved = args.executors.resolve(spec);
464
+ if (!resolved.succeeded) throw new ValidationError(`scope.spawn: ${resolved.error}`);
465
+ const reservation = args.pool.reserve(opts.budget);
466
+ if (!reservation.ok) return { ok: false, reason: reservation.reason };
467
+ try {
468
+ const ordinal = spawnOrdinal++;
469
+ const id = `${args.parentId}:s${ordinal}`;
470
+ const childAbort = new AbortController();
471
+ const cascadeAbort = () => childAbort.abort();
472
+ if (args.signal.aborted) childAbort.abort();
473
+ else args.signal.addEventListener("abort", cascadeAbort, { once: true });
474
+ const ctx = {
475
+ signal: childAbort.signal,
476
+ seams: { ...args.seams, [nestedScopeSeamKey]: makeNestedScopeSeam(args, id) }
477
+ };
478
+ const executor = resolved.value(spec, ctx);
479
+ const handle = {
480
+ id,
481
+ label: opts.label,
482
+ get status() {
483
+ return children.get(id)?.status ?? "cancelled";
484
+ },
485
+ abort(reason) {
486
+ childAbort.abort(reason);
487
+ }
488
+ };
489
+ const live = {
490
+ id,
491
+ status: "acquiring",
492
+ runtime: executor.runtime,
493
+ budget: opts.budget,
494
+ label: opts.label,
495
+ spent: zeroSpend(),
496
+ settled: void 0,
497
+ delivered: false,
498
+ executorDone: false,
499
+ ...executor.deliver ? { deliver: executor.deliver.bind(executor) } : {}
500
+ };
501
+ children.set(id, live);
502
+ void args.journal.appendEvent(args.root, {
503
+ kind: "spawned",
504
+ id,
505
+ parent: args.parentId,
506
+ label: opts.label,
507
+ budget: opts.budget,
508
+ runtime: executor.runtime,
509
+ seq: ordinal,
510
+ at: new Date(now()).toISOString()
511
+ });
512
+ notifyRuntimeHookEvent(
513
+ args.hooks,
514
+ {
515
+ id: `${id}:spawn`,
516
+ runId: args.root,
517
+ target: "agent.spawn",
518
+ phase: "after",
519
+ timestamp: now(),
520
+ stepIndex: ordinal,
521
+ parentId: args.parentId,
522
+ payload: {
523
+ childId: id,
524
+ label: opts.label,
525
+ runtime: executor.runtime,
526
+ budget: opts.budget,
527
+ depth: args.depth
528
+ }
529
+ },
530
+ { signal: args.signal }
531
+ );
532
+ const settled = runChild(
533
+ live,
534
+ executor,
535
+ childAbort,
536
+ task,
537
+ opts,
538
+ args.pool,
539
+ reservation.ticket,
540
+ args.blobs
541
+ ).then((s) => {
542
+ live.resolved = s;
543
+ return s;
544
+ }).finally(() => {
545
+ args.signal.removeEventListener("abort", cascadeAbort);
546
+ });
547
+ live.settled = settled;
548
+ return { ok: true, handle };
549
+ } catch (err) {
550
+ args.pool.reconcile(reservation.ticket, zeroSpend());
551
+ throw err;
552
+ }
553
+ }
554
+ async function next() {
555
+ const undelivered = () => [...children.values()].filter((c) => !c.delivered);
556
+ if (undelivered().length === 0) return null;
557
+ for (; ; ) {
558
+ const pending = undelivered();
559
+ if (pending.length === 0) return null;
560
+ const ready = pending.find((c) => c.resolved !== void 0);
561
+ const chosen = ready ?? await raceFirstSettled(pending);
562
+ if (chosen.delivered) continue;
563
+ chosen.delivered = true;
564
+ const seq = cursorSeq++;
565
+ const settlement = chosen.resolved;
566
+ if (!settlement) {
567
+ throw new ValidationError(
568
+ `scope.next: child '${chosen.id}' won the settle race without a resolved value`
569
+ );
570
+ }
571
+ return finalizeSettlement(chosen, settlement, seq, args, now);
572
+ }
573
+ }
574
+ async function nextResolved() {
575
+ for (; ; ) {
576
+ const candidates = [...children.values()].filter((c) => !c.delivered);
577
+ const pick = candidates.find((c) => c.resolved !== void 0) ?? candidates.find((c) => c.executorDone);
578
+ if (!pick) return null;
579
+ const settlement = await pick.settled;
580
+ if (pick.delivered) continue;
581
+ pick.delivered = true;
582
+ const seq = cursorSeq++;
583
+ return finalizeSettlement(pick, settlement, seq, args, now);
584
+ }
585
+ }
586
+ function send(nodeId, msg) {
587
+ const child = children.get(nodeId);
588
+ if (!child || child.delivered || !child.deliver) return false;
589
+ child.deliver(msg);
590
+ return true;
591
+ }
592
+ async function meter(spend, detail) {
593
+ const seq = meterSeq++;
594
+ args.pool.observe(spend);
595
+ await args.journal.appendEvent(args.root, {
596
+ kind: "metered",
597
+ id: args.parentId,
598
+ spend,
599
+ seq,
600
+ at: new Date(now()).toISOString()
601
+ });
602
+ notifyRuntimeHookEvent(
603
+ args.hooks,
604
+ {
605
+ id: `${args.parentId}:meter:${seq}`,
606
+ runId: args.root,
607
+ target: "agent.turn",
608
+ phase: "after",
609
+ timestamp: now(),
610
+ parentId: args.parentId,
611
+ payload: { spend, ...detail ?? {} }
612
+ },
613
+ { signal: args.signal }
614
+ );
615
+ }
616
+ return {
617
+ spawn: spawn4,
618
+ next,
619
+ nextResolved,
620
+ send,
621
+ signal: args.signal,
622
+ meter,
623
+ get view() {
624
+ return makeTreeView(args.parentId, children);
625
+ },
626
+ get budget() {
627
+ return args.pool.readout();
628
+ }
629
+ };
630
+ }
631
+ async function raceFirstSettled(pending) {
632
+ return Promise.race(pending.map((c) => c.settled.then(() => c)));
633
+ }
634
+ async function finalizeSettlement(child, settlement, seq, args, now) {
635
+ const handle = frozenHandle(child);
636
+ if (settlement.kind === "down") {
637
+ child.status = "failed";
638
+ await args.journal.appendEvent(args.root, {
639
+ kind: "settled",
640
+ id: child.id,
641
+ status: "down",
642
+ spent: child.spent,
643
+ infra: settlement.infra,
644
+ seq,
645
+ at: new Date(now()).toISOString()
646
+ });
647
+ if (settlement.metered) {
648
+ await args.journal.appendEvent(args.root, {
649
+ kind: "metered",
650
+ id: child.id,
651
+ spend: settlement.metered,
652
+ seq,
653
+ at: new Date(now()).toISOString()
654
+ });
655
+ }
656
+ notifyRuntimeHookEvent(
657
+ args.hooks,
658
+ {
659
+ id: `${child.id}:settled`,
660
+ runId: args.root,
661
+ target: "agent.child",
662
+ phase: "after",
663
+ timestamp: now(),
664
+ stepIndex: seq,
665
+ parentId: args.parentId,
666
+ payload: {
667
+ childId: child.id,
668
+ status: "down",
669
+ reason: settlement.reason,
670
+ infra: settlement.infra,
671
+ spent: child.spent
672
+ }
673
+ },
674
+ { signal: args.signal }
675
+ );
676
+ return {
677
+ kind: "down",
678
+ handle,
679
+ reason: settlement.reason,
680
+ infra: settlement.infra,
681
+ restartCount: settlement.restartCount,
682
+ seq
683
+ };
684
+ }
685
+ child.status = "done";
686
+ child.outRef = settlement.outRef;
687
+ child.spent = settlement.spent;
688
+ await args.journal.appendEvent(args.root, {
689
+ kind: "settled",
690
+ id: child.id,
691
+ status: "done",
692
+ outRef: settlement.outRef,
693
+ ...settlement.verdict ? { verdict: settlement.verdict } : {},
694
+ spent: settlement.spent,
695
+ seq,
696
+ at: new Date(now()).toISOString()
697
+ });
698
+ if (settlement.metered) {
699
+ await args.journal.appendEvent(args.root, {
700
+ kind: "metered",
701
+ id: child.id,
702
+ spend: settlement.metered,
703
+ seq,
704
+ at: new Date(now()).toISOString()
705
+ });
706
+ }
707
+ notifyRuntimeHookEvent(
708
+ args.hooks,
709
+ {
710
+ id: `${child.id}:settled`,
711
+ runId: args.root,
712
+ target: "agent.child",
713
+ phase: "after",
714
+ timestamp: now(),
715
+ stepIndex: seq,
716
+ parentId: args.parentId,
717
+ payload: {
718
+ childId: child.id,
719
+ status: "done",
720
+ outRef: settlement.outRef,
721
+ score: settlement.verdict?.score,
722
+ valid: settlement.verdict?.valid,
723
+ spent: settlement.spent
724
+ }
725
+ },
726
+ { signal: args.signal }
727
+ );
728
+ return {
729
+ kind: "done",
730
+ handle,
731
+ out: settlement.out,
732
+ outRef: settlement.outRef,
733
+ ...settlement.verdict ? { verdict: settlement.verdict } : {},
734
+ spent: settlement.spent,
735
+ seq
736
+ };
737
+ }
738
+ async function runChild(live, executor, childAbort, task, opts, pool, ticket, blobs) {
739
+ let reconciled = false;
740
+ const reconcileOnce = (spend) => {
741
+ if (reconciled) return;
742
+ reconciled = true;
743
+ pool.reconcile(ticket, clampSpend(spend, opts.budget));
744
+ };
745
+ try {
746
+ live.status = "running";
747
+ const ran = executor.execute(task, childAbort.signal);
748
+ let artifact;
749
+ if (isAsyncIterable(ran)) {
750
+ const spend = await foldStream(ran);
751
+ live.spent = spend;
752
+ artifact = executor.resultArtifact();
753
+ reconcileOnce(spend);
754
+ } else {
755
+ const terminal = await ran;
756
+ live.spent = terminal.spent;
757
+ artifact = terminal;
758
+ reconcileOnce(terminal.spent);
759
+ }
760
+ live.executorDone = true;
761
+ const ownMetered = executor.metered?.();
762
+ if (childAbort.signal.aborted) {
763
+ await teardownSafe(executor, opts.shutdown ?? "brutalKill");
764
+ return downRecord("aborted before settle", true, ownMetered);
765
+ }
766
+ const outRef = contentAddress(artifact.out);
767
+ await blobs.put(outRef, artifact.out);
768
+ await teardownSafe(executor, opts.shutdown ?? "infinity");
769
+ return {
770
+ kind: "done",
771
+ out: artifact.out,
772
+ outRef,
773
+ ...artifact.verdict ? { verdict: artifact.verdict } : {},
774
+ spent: live.spent,
775
+ ...ownMetered ? { metered: ownMetered } : {}
776
+ };
777
+ } catch (err) {
778
+ live.executorDone = true;
779
+ reconcileOnce(live.spent);
780
+ await teardownSafe(executor, "brutalKill");
781
+ const aborted = childAbort.signal.aborted || isAbortError(err);
782
+ return downRecord(errMessage(err), aborted || isInfraError(err), executor.metered?.());
783
+ }
784
+ }
785
+ function settledToIteration(settled) {
786
+ if (settled.kind === "down") {
787
+ throw new ValidationError(
788
+ `settledToIteration: cannot adapt a 'down' settlement (node '${settled.handle.id}', seq ${settled.seq}) to an Iteration`
789
+ );
790
+ }
791
+ return {
792
+ index: settled.seq,
793
+ task: void 0,
794
+ agentRunName: settled.handle.label,
795
+ output: settled.out,
796
+ ...settled.verdict ? { verdict: settled.verdict } : {},
797
+ events: [],
798
+ startedAt: 0,
799
+ endedAt: settled.spent.ms,
800
+ costUsd: settled.spent.usd,
801
+ tokenUsage: { input: settled.spent.tokens.input, output: settled.spent.tokens.output }
802
+ };
803
+ }
804
+ function makeTreeView(root, children) {
805
+ const nodes = [...children.values()].map((c) => ({
806
+ id: c.id,
807
+ parent: root,
808
+ label: c.label,
809
+ status: c.status,
810
+ runtime: c.runtime,
811
+ budget: c.budget,
812
+ spent: c.spent,
813
+ ...c.outRef ? { outRef: c.outRef } : {}
814
+ }));
815
+ return {
816
+ root,
817
+ nodes,
818
+ inFlight: nodes.filter((n) => n.status === "running" || n.status === "acquiring").length
819
+ };
820
+ }
821
+ function frozenHandle(child) {
822
+ return {
823
+ id: child.id,
824
+ label: child.label,
825
+ status: child.status,
826
+ abort() {
827
+ }
828
+ };
829
+ }
830
+ async function foldStream(stream) {
831
+ const tokens = { input: 0, output: 0 };
832
+ let usd = 0;
833
+ let iterations = 0;
834
+ for await (const ev of stream) {
835
+ if (ev.kind === "tokens") {
836
+ tokens.input += ev.input;
837
+ tokens.output += ev.output;
838
+ } else if (ev.kind === "cost") {
839
+ usd += ev.usd;
840
+ } else {
841
+ iterations += 1;
842
+ }
843
+ }
844
+ return { iterations, tokens, usd, ms: 0 };
845
+ }
846
+ function clampSpend(spend, budget) {
847
+ const totalTokens2 = spend.tokens.input + spend.tokens.output;
848
+ const tokensOk = totalTokens2 <= budget.maxTokens;
849
+ const itersOk = spend.iterations <= budget.maxIterations;
850
+ const usdOk = budget.maxUsd === void 0 || spend.usd <= budget.maxUsd;
851
+ if (tokensOk && itersOk && usdOk) return spend;
852
+ const ratio = !tokensOk && totalTokens2 > 0 ? budget.maxTokens / totalTokens2 : 1;
853
+ return {
854
+ iterations: Math.min(spend.iterations, budget.maxIterations),
855
+ tokens: ratio < 1 ? {
856
+ input: Math.floor(spend.tokens.input * ratio),
857
+ output: Math.floor(spend.tokens.output * ratio)
858
+ } : spend.tokens,
859
+ usd: budget.maxUsd === void 0 ? spend.usd : Math.min(spend.usd, budget.maxUsd),
860
+ ms: spend.ms
861
+ };
862
+ }
863
+ async function teardownSafe(executor, grace) {
864
+ try {
865
+ await executor.teardown(grace);
866
+ } catch {
867
+ }
868
+ }
869
+ function downRecord(reason, infra, metered) {
870
+ return { kind: "down", reason, infra, restartCount: 0, ...metered ? { metered } : {} };
871
+ }
872
+ function zeroSpend() {
873
+ return { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
874
+ }
875
+ function isAsyncIterable(value) {
876
+ return typeof value === "object" && value !== null && typeof value[Symbol.asyncIterator] === "function";
877
+ }
878
+ function isAgentSpec(value) {
879
+ if (typeof value !== "object" || value === null) return false;
880
+ const v = value;
881
+ return "profile" in v && "harness" in v;
882
+ }
883
+ function isAbortError(err) {
884
+ return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
885
+ }
886
+ function isInfraError(err) {
887
+ return err instanceof ValidationError;
888
+ }
889
+ function errMessage(err) {
890
+ if (err instanceof Error) return err.message;
891
+ return String(err);
892
+ }
893
+
894
+ // src/runtime/supervise/budget.ts
895
+ function spendFromUsageEvents(events) {
896
+ const tokens = zeroTokenUsage();
897
+ let usd = 0;
898
+ let iterations = 0;
899
+ for (const ev of events) {
900
+ if (ev.kind === "tokens") {
901
+ addTokenUsage(tokens, { input: ev.input, output: ev.output });
902
+ } else if (ev.kind === "cost") {
903
+ usd += ev.usd;
904
+ } else {
905
+ iterations += 1;
906
+ }
907
+ }
908
+ return { iterations, tokens, usd, ms: 0 };
909
+ }
910
+ async function foldUsage(events) {
911
+ if (Array.isArray(events)) return spendFromUsageEvents(events);
912
+ const tokens = zeroTokenUsage();
913
+ let usd = 0;
914
+ let iterations = 0;
915
+ for await (const ev of events) {
916
+ if (ev.kind === "tokens") {
917
+ addTokenUsage(tokens, { input: ev.input, output: ev.output });
918
+ } else if (ev.kind === "cost") {
919
+ usd += ev.usd;
920
+ } else {
921
+ iterations += 1;
922
+ }
923
+ }
924
+ return { iterations, tokens, usd, ms: 0 };
925
+ }
926
+ function totalTokens(usage) {
927
+ return usage.input + usage.output;
928
+ }
929
+ function createBudgetPool(root, now = Date.now) {
930
+ let freeTokens = root.maxTokens;
931
+ let reservedTokens = 0;
932
+ let committedTokens = 0;
933
+ const usdCapped = root.maxUsd !== void 0;
934
+ let freeUsd = root.maxUsd ?? 0;
935
+ let reservedUsd = 0;
936
+ let committedUsd = 0;
937
+ let freeIterations = root.maxIterations;
938
+ let reservedIterations = 0;
939
+ let committedIterations = 0;
940
+ const absoluteDeadlineMs = root.deadlineMs !== void 0 ? now() + root.deadlineMs : 0;
941
+ let nextTicketId = 0;
942
+ const open = /* @__PURE__ */ new Set();
943
+ function reserve(b) {
944
+ const wantTokens = b.maxTokens;
945
+ const wantUsd = b.maxUsd ?? 0;
946
+ const wantIterations = b.maxIterations;
947
+ if (wantTokens > freeTokens) return { ok: false, reason: "budget-exhausted" };
948
+ if (wantIterations > freeIterations) return { ok: false, reason: "budget-exhausted" };
949
+ if (wantUsd > 0 && (!usdCapped || wantUsd > freeUsd)) {
950
+ return { ok: false, reason: "budget-exhausted" };
951
+ }
952
+ freeTokens -= wantTokens;
953
+ reservedTokens += wantTokens;
954
+ freeIterations -= wantIterations;
955
+ reservedIterations += wantIterations;
956
+ if (wantUsd > 0) {
957
+ freeUsd -= wantUsd;
958
+ reservedUsd += wantUsd;
959
+ }
960
+ const id = nextTicketId++;
961
+ open.add(id);
962
+ return {
963
+ ok: true,
964
+ ticket: { id, reserved: { tokens: wantTokens, usd: wantUsd, iterations: wantIterations } }
965
+ };
966
+ }
967
+ function reconcile(ticket, spent) {
968
+ if (!open.has(ticket.id)) {
969
+ throw new Error(`budget pool: reconcile of unknown or already-settled ticket ${ticket.id}`);
970
+ }
971
+ open.delete(ticket.id);
972
+ const { tokens: rTokens, usd: rUsd, iterations: rIterations } = ticket.reserved;
973
+ const spentTokens = totalTokens(spent.tokens);
974
+ if (spentTokens > rTokens) {
975
+ throw new Error(
976
+ `budget pool: ticket ${ticket.id} spent ${spentTokens} tokens > reserved ${rTokens}`
977
+ );
978
+ }
979
+ if (spent.iterations > rIterations) {
980
+ throw new Error(
981
+ `budget pool: ticket ${ticket.id} spent ${spent.iterations} iterations > reserved ${rIterations}`
982
+ );
983
+ }
984
+ if (usdCapped && spent.usd > rUsd) {
985
+ throw new Error(`budget pool: ticket ${ticket.id} spent $${spent.usd} > reserved $${rUsd}`);
986
+ }
987
+ reservedTokens -= rTokens;
988
+ committedTokens += spentTokens;
989
+ freeTokens += rTokens - spentTokens;
990
+ reservedIterations -= rIterations;
991
+ committedIterations += spent.iterations;
992
+ freeIterations += rIterations - spent.iterations;
993
+ if (usdCapped && rUsd > 0) {
994
+ reservedUsd -= rUsd;
995
+ committedUsd += spent.usd;
996
+ freeUsd += rUsd - spent.usd;
997
+ } else {
998
+ committedUsd += spent.usd;
404
999
  }
405
1000
  }
406
- async function nextResolved() {
407
- for (; ; ) {
408
- const candidates = [...children.values()].filter((c) => !c.delivered);
409
- const pick = candidates.find((c) => c.resolved !== void 0) ?? candidates.find((c) => c.executorDone);
410
- if (!pick) return null;
411
- const settlement = await pick.settled;
412
- if (pick.delivered) continue;
413
- pick.delivered = true;
414
- const seq = cursorSeq++;
415
- return finalizeSettlement(pick, settlement, seq, args, now);
416
- }
1001
+ function observe(spend) {
1002
+ const tokens = totalTokens(spend.tokens);
1003
+ freeTokens -= tokens;
1004
+ committedTokens += tokens;
1005
+ freeIterations -= spend.iterations;
1006
+ committedIterations += spend.iterations;
1007
+ committedUsd += spend.usd;
1008
+ if (usdCapped) freeUsd -= spend.usd;
417
1009
  }
418
- function send(nodeId, msg) {
419
- const child = children.get(nodeId);
420
- if (!child || child.delivered || !child.deliver) return false;
421
- child.deliver(msg);
422
- return true;
1010
+ function readout() {
1011
+ return {
1012
+ tokensLeft: freeTokens,
1013
+ usdLeft: usdCapped ? freeUsd : 0,
1014
+ usdCapped,
1015
+ deadlineMs: absoluteDeadlineMs,
1016
+ reservedTokens
1017
+ };
423
1018
  }
424
- async function meter(spend, detail) {
425
- const seq = meterSeq++;
426
- args.pool.observe(spend);
427
- await args.journal.appendEvent(args.root, {
428
- kind: "metered",
429
- id: args.parentId,
430
- spend,
431
- seq,
432
- at: new Date(now()).toISOString()
433
- });
434
- notifyRuntimeHookEvent(
435
- args.hooks,
436
- {
437
- id: `${args.parentId}:meter:${seq}`,
438
- runId: args.root,
439
- target: "agent.turn",
440
- phase: "after",
441
- timestamp: now(),
442
- parentId: args.parentId,
443
- payload: { spend, ...detail ?? {} }
444
- },
445
- { signal: args.signal }
446
- );
1019
+ function assertNoOpenTickets() {
1020
+ if (open.size > 0) {
1021
+ throw new Error(
1022
+ `budget pool: ${open.size} reservation(s) still open at join barrier (leaked ticket ids: ${[...open].join(", ")}) \u2014 conserved-pool invariant violated`
1023
+ );
1024
+ }
447
1025
  }
448
1026
  return {
449
- spawn: spawn4,
450
- next,
451
- nextResolved,
452
- send,
453
- signal: args.signal,
454
- meter,
455
- get view() {
456
- return makeTreeView(args.parentId, children);
457
- },
458
- get budget() {
459
- return args.pool.readout();
460
- }
1027
+ reserve,
1028
+ reconcile,
1029
+ spendFrom: foldUsage,
1030
+ readout,
1031
+ observe,
1032
+ assertNoOpenTickets
461
1033
  };
462
1034
  }
463
- async function raceFirstSettled(pending) {
464
- return Promise.race(pending.map((c) => c.settled.then(() => c)));
465
- }
466
- async function finalizeSettlement(child, settlement, seq, args, now) {
467
- const handle = frozenHandle(child);
468
- if (settlement.kind === "down") {
469
- child.status = "failed";
470
- await args.journal.appendEvent(args.root, {
471
- kind: "settled",
472
- id: child.id,
473
- status: "down",
474
- spent: child.spent,
475
- infra: settlement.infra,
476
- seq,
1035
+
1036
+ // src/runtime/supervise/supervisor.ts
1037
+ var defaultMaxDepth = 4;
1038
+ function createSupervisor() {
1039
+ let attached;
1040
+ async function run(root, task, opts) {
1041
+ const now = opts.now ?? Date.now;
1042
+ const pool = createBudgetPool(opts.budget, now);
1043
+ await opts.journal.beginTree(opts.runId, new Date(now()).toISOString());
1044
+ await opts.journal.appendEvent(opts.runId, {
1045
+ kind: "spawned",
1046
+ id: opts.runId,
1047
+ label: "root",
1048
+ budget: opts.budget,
1049
+ runtime: "inline",
1050
+ seq: 0,
477
1051
  at: new Date(now()).toISOString()
478
1052
  });
479
- if (settlement.metered) {
480
- await args.journal.appendEvent(args.root, {
481
- kind: "metered",
482
- id: child.id,
483
- spend: settlement.metered,
484
- seq,
485
- at: new Date(now()).toISOString()
486
- });
487
- }
488
- notifyRuntimeHookEvent(
489
- args.hooks,
490
- {
491
- id: `${child.id}:settled`,
492
- runId: args.root,
493
- target: "agent.child",
494
- phase: "after",
495
- timestamp: now(),
496
- stepIndex: seq,
497
- parentId: args.parentId,
498
- payload: {
499
- childId: child.id,
500
- status: "down",
501
- reason: settlement.reason,
502
- infra: settlement.infra,
503
- spent: child.spent
504
- }
505
- },
506
- { signal: args.signal }
507
- );
508
- return {
509
- kind: "down",
510
- handle,
511
- reason: settlement.reason,
512
- infra: settlement.infra,
513
- restartCount: settlement.restartCount,
514
- seq
1053
+ const controller = new AbortController();
1054
+ const cascadeAbort = (reason) => {
1055
+ if (controller.signal.aborted) return;
1056
+ controller.abort(reason);
515
1057
  };
516
- }
517
- child.status = "done";
518
- child.outRef = settlement.outRef;
519
- child.spent = settlement.spent;
520
- await args.journal.appendEvent(args.root, {
521
- kind: "settled",
522
- id: child.id,
523
- status: "done",
524
- outRef: settlement.outRef,
525
- ...settlement.verdict ? { verdict: settlement.verdict } : {},
526
- spent: settlement.spent,
527
- seq,
528
- at: new Date(now()).toISOString()
529
- });
530
- if (settlement.metered) {
531
- await args.journal.appendEvent(args.root, {
532
- kind: "metered",
533
- id: child.id,
534
- spend: settlement.metered,
535
- seq,
536
- at: new Date(now()).toISOString()
1058
+ const onCallerAbort = () => cascadeAbort("caller signal aborted");
1059
+ if (opts.signal) {
1060
+ if (opts.signal.aborted) cascadeAbort("caller signal aborted");
1061
+ else opts.signal.addEventListener("abort", onCallerAbort, { once: true });
1062
+ }
1063
+ const breaker = createIntensityBreaker(opts, () => cascadeAbort("intensity breaker tripped"));
1064
+ const journal = wrapJournalForBreaker(opts.journal, breaker);
1065
+ const scope = createScope({
1066
+ parentId: opts.runId,
1067
+ root: opts.runId,
1068
+ pool,
1069
+ journal,
1070
+ blobs: opts.blobs,
1071
+ executors: opts.executors,
1072
+ seams: {},
1073
+ depth: 0,
1074
+ maxDepth: opts.maxDepth ?? defaultMaxDepth,
1075
+ signal: controller.signal,
1076
+ now,
1077
+ hooks: opts.hooks
537
1078
  });
538
- }
539
- notifyRuntimeHookEvent(
540
- args.hooks,
541
- {
542
- id: `${child.id}:settled`,
543
- runId: args.root,
544
- target: "agent.child",
545
- phase: "after",
546
- timestamp: now(),
547
- stepIndex: seq,
548
- parentId: args.parentId,
549
- payload: {
550
- childId: child.id,
551
- status: "done",
552
- outRef: settlement.outRef,
553
- score: settlement.verdict?.score,
554
- valid: settlement.verdict?.valid,
555
- spent: settlement.spent
556
- }
557
- },
558
- { signal: args.signal }
559
- );
560
- return {
561
- kind: "done",
562
- handle,
563
- out: settlement.out,
564
- outRef: settlement.outRef,
565
- ...settlement.verdict ? { verdict: settlement.verdict } : {},
566
- spent: settlement.spent,
567
- seq
568
- };
569
- }
570
- async function runChild(live, executor, childAbort, task, opts, pool, ticket, blobs) {
571
- let reconciled = false;
572
- const reconcileOnce = (spend) => {
573
- if (reconciled) return;
574
- reconciled = true;
575
- pool.reconcile(ticket, clampSpend(spend, opts.budget));
576
- };
577
- try {
578
- live.status = "running";
579
- const ran = executor.execute(task, childAbort.signal);
580
- let artifact;
581
- if (isAsyncIterable2(ran)) {
582
- const spend = await foldStream(ran);
583
- live.spent = spend;
584
- artifact = executor.resultArtifact();
585
- reconcileOnce(spend);
586
- } else {
587
- const terminal = await ran;
588
- live.spent = terminal.spent;
589
- artifact = terminal;
590
- reconcileOnce(terminal.spent);
1079
+ const openScope = scope;
1080
+ if (attached) {
1081
+ attached.bind({ scope: openScope, cascadeAbort, signal: pushRootSignal(cascadeAbort) });
1082
+ }
1083
+ let actOutcome;
1084
+ try {
1085
+ const out = await root.act(task, scope);
1086
+ actOutcome = { ok: true, out };
1087
+ } catch (error) {
1088
+ actOutcome = { ok: false, error };
1089
+ } finally {
1090
+ await drainLiveChildren(openScope, controller);
1091
+ if (opts.signal) opts.signal.removeEventListener("abort", onCallerAbort);
1092
+ if (attached) attached.unbind();
591
1093
  }
592
- live.executorDone = true;
593
- const ownMetered = executor.metered?.();
594
- if (childAbort.signal.aborted) {
595
- await teardownSafe(executor, opts.shutdown ?? "brutalKill");
596
- return downRecord("aborted before settle", true, ownMetered);
1094
+ const tree = scope.view;
1095
+ if (actOutcome.ok) {
1096
+ pool.assertNoOpenTickets();
1097
+ const out = actOutcome.out;
1098
+ if (out !== void 0) {
1099
+ const outRef = contentAddress(out);
1100
+ await opts.blobs.put(outRef, out);
1101
+ const { childWork, driverInference } = await spentFromJournal(journal, opts.runId);
1102
+ return {
1103
+ kind: "winner",
1104
+ out,
1105
+ outRef,
1106
+ tree,
1107
+ spentTotal: addSpend(childWork, driverInference),
1108
+ ...isNonEmptySpend(driverInference) ? { spentBreakdown: { driverInference, childWork } } : {}
1109
+ };
1110
+ }
1111
+ return noWinner();
1112
+ }
1113
+ return noWinner();
1114
+ async function noWinner() {
1115
+ const { childWork, driverInference } = await spentFromJournal(journal, opts.runId);
1116
+ return {
1117
+ kind: "no-winner",
1118
+ reason: classifyNoWinner(controller, pool, opts, breaker),
1119
+ tree,
1120
+ downCount: breaker.downCount(),
1121
+ spentTotal: addSpend(childWork, driverInference)
1122
+ };
597
1123
  }
598
- const outRef = contentAddress(artifact.out);
599
- await blobs.put(outRef, artifact.out);
600
- await teardownSafe(executor, opts.shutdown ?? "infinity");
601
- return {
602
- kind: "done",
603
- out: artifact.out,
604
- outRef,
605
- ...artifact.verdict ? { verdict: artifact.verdict } : {},
606
- spent: live.spent,
607
- ...ownMetered ? { metered: ownMetered } : {}
608
- };
609
- } catch (err) {
610
- live.executorDone = true;
611
- reconcileOnce(live.spent);
612
- await teardownSafe(executor, "brutalKill");
613
- const aborted = childAbort.signal.aborted || isAbortError(err);
614
- return downRecord(errMessage(err), aborted || isInfraError(err), executor.metered?.());
615
1124
  }
616
- }
617
- function settledToIteration(settled) {
618
- if (settled.kind === "down") {
619
- throw new ValidationError(
620
- `settledToIteration: cannot adapt a 'down' settlement (node '${settled.handle.id}', seq ${settled.seq}) to an Iteration`
621
- );
1125
+ function attach(h) {
1126
+ const control = rootControls.get(h);
1127
+ if (!control) {
1128
+ throw new RuntimeRunStateError(
1129
+ "supervisor.attach: handle was not minted by createRootHandle (no control channel)"
1130
+ );
1131
+ }
1132
+ attached = control;
622
1133
  }
623
- return {
624
- index: settled.seq,
625
- task: void 0,
626
- agentRunName: settled.handle.label,
627
- output: settled.out,
628
- ...settled.verdict ? { verdict: settled.verdict } : {},
629
- events: [],
630
- startedAt: 0,
631
- endedAt: settled.spent.ms,
632
- costUsd: settled.spent.usd,
633
- tokenUsage: { input: settled.spent.tokens.input, output: settled.spent.tokens.output }
1134
+ return { run, attach };
1135
+ }
1136
+ var rootControls = /* @__PURE__ */ new WeakMap();
1137
+ function pushRootSignal(cascadeAbort) {
1138
+ return (msg) => {
1139
+ if (msg.kind === "cancel") cascadeAbort(msg.reason ?? "root signal: cancel");
634
1140
  };
635
1141
  }
636
- function makeTreeView(root, children) {
637
- const nodes = [...children.values()].map((c) => ({
638
- id: c.id,
639
- parent: root,
640
- label: c.label,
641
- status: c.status,
642
- runtime: c.runtime,
643
- budget: c.budget,
644
- spent: c.spent,
645
- ...c.outRef ? { outRef: c.outRef } : {}
646
- }));
1142
+ function createIntensityBreaker(opts, trip) {
1143
+ const max = opts.maxRestarts;
1144
+ const within = opts.withinMs;
1145
+ const armed = max !== void 0 && within !== void 0;
1146
+ const recent = [];
1147
+ let total = 0;
1148
+ let isTripped = false;
647
1149
  return {
648
- root,
649
- nodes,
650
- inFlight: nodes.filter((n) => n.status === "running" || n.status === "acquiring").length
1150
+ recordDown(at) {
1151
+ total += 1;
1152
+ if (!armed || isTripped) return;
1153
+ recent.push(at);
1154
+ const cutoff = at - within;
1155
+ while (recent.length > 0 && recent[0] < cutoff) recent.shift();
1156
+ if (recent.length > max) {
1157
+ isTripped = true;
1158
+ trip();
1159
+ }
1160
+ },
1161
+ tripped() {
1162
+ return isTripped;
1163
+ },
1164
+ downCount() {
1165
+ return total;
1166
+ }
651
1167
  };
652
1168
  }
653
- function frozenHandle(child) {
1169
+ function wrapJournalForBreaker(journal, breaker) {
654
1170
  return {
655
- id: child.id,
656
- label: child.label,
657
- status: child.status,
658
- abort() {
1171
+ loadTree: (root) => journal.loadTree(root),
1172
+ beginTree: (root, at) => journal.beginTree(root, at),
1173
+ appendEvent: (root, ev) => {
1174
+ if (ev.kind === "settled" && ev.status === "down") breaker.recordDown(Date.parse(ev.at));
1175
+ return journal.appendEvent(root, ev);
659
1176
  }
660
1177
  };
661
1178
  }
662
- async function foldStream(stream) {
663
- const tokens = { input: 0, output: 0 };
664
- let usd = 0;
665
- let iterations = 0;
666
- for await (const ev of stream) {
667
- if (ev.kind === "tokens") {
668
- tokens.input += ev.input;
669
- tokens.output += ev.output;
670
- } else if (ev.kind === "cost") {
671
- usd += ev.usd;
672
- } else {
673
- iterations += 1;
674
- }
1179
+ async function drainLiveChildren(scope, controller) {
1180
+ const hasLive = scope.view.inFlight > 0;
1181
+ if (!hasLive) return;
1182
+ if (!controller.signal.aborted) controller.abort();
1183
+ await Promise.allSettled([drainCursor(scope)]);
1184
+ }
1185
+ async function drainCursor(scope) {
1186
+ for (; ; ) {
1187
+ const settled = await scope.next();
1188
+ if (settled === null) return;
675
1189
  }
676
- return { iterations, tokens, usd, ms: 0 };
677
1190
  }
678
- function clampSpend(spend, budget) {
679
- const totalTokens2 = spend.tokens.input + spend.tokens.output;
680
- const tokensOk = totalTokens2 <= budget.maxTokens;
681
- const itersOk = spend.iterations <= budget.maxIterations;
682
- const usdOk = budget.maxUsd === void 0 || spend.usd <= budget.maxUsd;
683
- if (tokensOk && itersOk && usdOk) return spend;
684
- const ratio = !tokensOk && totalTokens2 > 0 ? budget.maxTokens / totalTokens2 : 1;
1191
+ function classifyNoWinner(controller, pool, opts, breaker) {
1192
+ if (breaker.tripped()) return "all-children-down";
1193
+ if (controller.signal.aborted) return "aborted";
1194
+ if (poolExhausted(pool, opts)) return "budget-exhausted";
1195
+ return "all-children-down";
1196
+ }
1197
+ function poolExhausted(pool, opts) {
1198
+ const r = pool.readout();
1199
+ if (r.tokensLeft <= 0) return true;
1200
+ if (opts.budget.maxUsd !== void 0 && r.usdLeft <= 0) return true;
1201
+ if (opts.budget.deadlineMs !== void 0 && r.deadlineMs > 0 && (opts.now ?? Date.now)() >= r.deadlineMs) {
1202
+ return true;
1203
+ }
1204
+ return false;
1205
+ }
1206
+ async function spentFromJournal(journal, root) {
1207
+ const events = await journal.loadTree(root);
1208
+ if (events === void 0) {
1209
+ throw new RuntimeRunStateError(
1210
+ `supervisor: spawn tree '${root}' is missing from the journal after run (corrupted log)`
1211
+ );
1212
+ }
1213
+ const childWork = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
1214
+ const driverInference = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
1215
+ for (const ev of events) {
1216
+ if (ev.kind === "settled") accumulate(childWork, ev.spent);
1217
+ else if (ev.kind === "metered") accumulate(driverInference, ev.spend);
1218
+ }
1219
+ return { childWork, driverInference };
1220
+ }
1221
+ function accumulate(a, b) {
1222
+ a.iterations += b.iterations;
1223
+ a.tokens.input += b.tokens.input;
1224
+ a.tokens.output += b.tokens.output;
1225
+ a.usd += b.usd;
1226
+ a.ms += b.ms;
1227
+ }
1228
+ function addSpend(a, b) {
685
1229
  return {
686
- iterations: Math.min(spend.iterations, budget.maxIterations),
687
- tokens: ratio < 1 ? {
688
- input: Math.floor(spend.tokens.input * ratio),
689
- output: Math.floor(spend.tokens.output * ratio)
690
- } : spend.tokens,
691
- usd: budget.maxUsd === void 0 ? spend.usd : Math.min(spend.usd, budget.maxUsd),
692
- ms: spend.ms
1230
+ iterations: a.iterations + b.iterations,
1231
+ tokens: { input: a.tokens.input + b.tokens.input, output: a.tokens.output + b.tokens.output },
1232
+ usd: a.usd + b.usd,
1233
+ ms: a.ms + b.ms
1234
+ };
1235
+ }
1236
+ function isNonEmptySpend(s) {
1237
+ return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
1238
+ }
1239
+
1240
+ // src/runtime/supervise/completion-gate.ts
1241
+ function gateOnDeliverable(inner, deliverable) {
1242
+ let gated;
1243
+ const check = async (out, baseScore) => {
1244
+ let delivered;
1245
+ try {
1246
+ delivered = await deliverable.check(out) === true;
1247
+ } catch {
1248
+ delivered = false;
1249
+ }
1250
+ return { valid: delivered, score: baseScore ?? (delivered ? 1 : 0) };
1251
+ };
1252
+ return {
1253
+ runtime: inner.runtime,
1254
+ ...inner.budgetExempt !== void 0 ? { budgetExempt: inner.budgetExempt } : {},
1255
+ ...inner.deliver ? { deliver: (m) => inner.deliver?.(m) } : {},
1256
+ execute(task, signal) {
1257
+ const r = inner.execute(task, signal);
1258
+ if (isAsyncIterable2(r)) {
1259
+ return (async function* () {
1260
+ for await (const ev of r) yield ev;
1261
+ const art = inner.resultArtifact();
1262
+ gated = await check(art.out, art.verdict?.score);
1263
+ })();
1264
+ }
1265
+ return (async () => {
1266
+ const res = await r;
1267
+ gated = await check(res.out, res.verdict?.score);
1268
+ return { ...res, verdict: gated };
1269
+ })();
1270
+ },
1271
+ teardown: (grace) => inner.teardown(grace),
1272
+ resultArtifact() {
1273
+ const art = inner.resultArtifact();
1274
+ return { ...art, verdict: gated ?? art.verdict };
1275
+ }
693
1276
  };
694
1277
  }
695
- async function teardownSafe(executor, grace) {
696
- try {
697
- await executor.teardown(grace);
698
- } catch {
699
- }
700
- }
701
- function downRecord(reason, infra, metered) {
702
- return { kind: "down", reason, infra, restartCount: 0, ...metered ? { metered } : {} };
703
- }
704
- function zeroSpend() {
705
- return { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
706
- }
707
- function isAsyncIterable2(value) {
708
- return typeof value === "object" && value !== null && typeof value[Symbol.asyncIterator] === "function";
709
- }
710
- function isAgentSpec(value) {
711
- if (typeof value !== "object" || value === null) return false;
712
- const v = value;
713
- return "profile" in v && "harness" in v;
714
- }
715
- function isAbortError(err) {
716
- return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
717
- }
718
- function isInfraError(err) {
719
- return err instanceof ValidationError;
720
- }
721
- function errMessage(err) {
722
- if (err instanceof Error) return err.message;
723
- return String(err);
1278
+ function isAsyncIterable2(v) {
1279
+ return v != null && typeof v[Symbol.asyncIterator] === "function";
724
1280
  }
725
1281
 
726
1282
  // src/mcp/worktree.ts
@@ -790,231 +1346,23 @@ function parseShortstat(text) {
790
1346
  return out;
791
1347
  }
792
1348
  async function removeWorktree(options) {
793
- const force = options.force ?? true;
794
- const args = ["worktree", "remove"];
795
- if (force) args.push("--force");
796
- args.push(options.worktree.path);
797
- const result = await runGitAsync(args, options.repoRoot, options.runGit);
798
- if (result.exitCode !== 0 && !/not a working tree/.test(result.stderr)) {
799
- await runGitAsync(
800
- ["branch", "-D", options.worktree.branch],
801
- options.repoRoot,
802
- options.runGit
803
- ).catch(() => void 0);
804
- }
805
- await runGitAsync(
806
- ["branch", "-D", options.worktree.branch],
807
- options.repoRoot,
808
- options.runGit
809
- ).catch(() => void 0);
810
- }
811
-
812
- // src/runtime/router-client.ts
813
- import { estimateCost, isModelPriced } from "@tangle-network/agent-eval";
814
-
815
- // src/runtime/tool-loop.ts
816
- function estimateConversationTokens(messages) {
817
- let chars = 0;
818
- for (const m of messages) {
819
- const content = m.content;
820
- chars += typeof content === "string" ? content.length : safeJsonLength(content);
821
- const calls = m.tool_calls;
822
- if (calls) for (const c of calls) chars += c.function?.arguments?.length ?? 0;
823
- }
824
- return Math.ceil(chars / 4);
825
- }
826
- function safeJsonLength(value) {
827
- if (value === void 0 || value === null) return 0;
828
- try {
829
- return JSON.stringify(value)?.length ?? 0;
830
- } catch {
831
- return String(value).length;
832
- }
833
- }
834
- async function maybeCompact(messages, c, turn) {
835
- const head = c.preserveHead !== void 0 && c.preserveHead > 0 ? c.preserveHead : 2;
836
- if (messages.length <= head + 1) return false;
837
- const estimate = c.estimateTokens ?? estimateConversationTokens;
838
- const before = estimate(messages);
839
- if (before <= c.thresholdTokens) return false;
840
- const digest = await c.distill([...messages]);
841
- messages.splice(head, messages.length - head, {
842
- role: "user",
843
- content: `[earlier work compacted to save context \u2014 progress so far]
844
- ${digest}`
845
- });
846
- c.onCompact?.({ turn, beforeTokens: before, afterTokens: estimate(messages) });
847
- return true;
848
- }
849
- async function runBrainLoop(opts) {
850
- const maxTurns = opts.maxTurns ?? 4;
851
- const messages = [...opts.initialMessages];
852
- let toolCalls = 0;
853
- let lastText = "";
854
- const usage = { input: 0, output: 0 };
855
- const toolTrace = [];
856
- for (let turn = 1; turn <= maxTurns; turn += 1) {
857
- if (opts.hooks?.stopBefore?.(turn)) break;
858
- await opts.hooks?.beforeTurn?.(turn, messages);
859
- if (opts.compaction) await maybeCompact(messages, opts.compaction, turn);
860
- const r = await opts.chat(messages, opts.tools);
861
- if (r.usage) {
862
- usage.input += r.usage.input;
863
- usage.output += r.usage.output;
864
- opts.hooks?.onUsage?.(r.usage);
865
- }
866
- if (r.content) lastText = r.content;
867
- if (r.toolCalls.length === 0)
868
- return { final: lastText, turns: turn, toolCalls, toolTrace, usage, messages };
869
- messages.push({
870
- role: "assistant",
871
- content: r.content ?? "",
872
- tool_calls: r.toolCalls.map((tc) => ({
873
- id: tc.id,
874
- type: "function",
875
- function: { name: tc.name, arguments: tc.arguments }
876
- }))
877
- });
878
- for (const tc of r.toolCalls) {
879
- toolCalls += 1;
880
- let args = {};
881
- try {
882
- args = JSON.parse(tc.arguments);
883
- } catch {
884
- messages.push({
885
- role: "tool",
886
- tool_call_id: tc.id,
887
- content: `error: arguments were not valid JSON: ${tc.arguments.slice(0, 200)}`
888
- });
889
- continue;
890
- }
891
- const out = await opts.execute(tc.name, args);
892
- messages.push({ role: "tool", tool_call_id: tc.id, content: out });
893
- toolTrace.push({ name: tc.name, args: tc.arguments, result: out });
894
- }
895
- }
896
- return { final: lastText, turns: maxTurns, toolCalls, toolTrace, usage, messages };
897
- }
898
-
899
- // src/runtime/router-client.ts
900
- async function routerChatWithUsage(cfg, messages, opts) {
901
- const url = `${cfg.routerBaseUrl.replace(/\/$/, "")}/chat/completions`;
902
- const headers = { "content-type": "application/json", authorization: `Bearer ${cfg.routerKey}` };
903
- let temperature = opts?.temperature ?? 0.2;
904
- const body = () => ({
905
- model: cfg.model,
906
- messages,
907
- temperature,
908
- max_tokens: opts?.maxTokens ?? 8192,
909
- ...opts?.reasoningEffort ? { reasoning_effort: opts.reasoningEffort } : {}
910
- });
911
- if (cfg.complete) return parseChatResult(await cfg.complete(body()), cfg.model);
912
- let lastErr = "";
913
- for (let attempt = 0; attempt < 5; attempt += 1) {
914
- const res = await fetch(url, {
915
- method: "POST",
916
- headers,
917
- body: JSON.stringify(body()),
918
- ...opts?.signal ? { signal: opts.signal } : {}
919
- });
920
- if (res.ok) return parseChatResult(await res.json(), cfg.model);
921
- const status = res.status;
922
- const text = (await res.text()).slice(0, 200);
923
- lastErr = `router ${status}: ${text}`;
924
- if (status === 400 && /temperature/i.test(text) && temperature !== 1) {
925
- temperature = 1;
926
- continue;
927
- }
928
- if (![408, 425, 429, 500, 502, 503, 504, 520, 522, 524].includes(status))
929
- throw new Error(lastErr);
930
- if (attempt < 4) await new Promise((r) => setTimeout(r, 800 * 2 ** attempt));
931
- }
932
- throw new Error(`${lastErr} (exhausted retries)`);
933
- }
934
- function parseChatResult(json, model) {
935
- const data = json;
936
- const u = data.usage;
937
- const usage = u && typeof u.prompt_tokens === "number" && typeof u.completion_tokens === "number" ? { input: u.prompt_tokens, output: u.completion_tokens } : void 0;
938
- const costUsd = usage && isModelPriced(model) ? estimateCost(usage.input, usage.output, model) : void 0;
939
- const msg = data.choices?.[0]?.message;
940
- const { content, reasoning } = splitReasoning(
941
- msg?.content ?? "",
942
- msg?.reasoning ?? msg?.reasoning_content
943
- );
944
- return {
945
- content,
946
- ...reasoning ? { reasoning } : {},
947
- ...usage ? { usage } : {},
948
- ...costUsd !== void 0 ? { costUsd } : {}
949
- };
950
- }
951
- function splitReasoning(rawContent, fieldReasoning) {
952
- const open = rawContent.indexOf("<think>");
953
- if (open !== -1) {
954
- const close = rawContent.indexOf("</think>", open);
955
- const inline = close !== -1 ? rawContent.slice(open + 7, close) : rawContent.slice(open + 7);
956
- const rest = close !== -1 ? rawContent.slice(0, open) + rawContent.slice(close + 8) : rawContent.slice(0, open);
957
- const reasoning = [fieldReasoning, inline.trim()].filter(Boolean).join("\n");
958
- return { content: rest.trim(), ...reasoning ? { reasoning } : {} };
959
- }
960
- return { content: rawContent, ...fieldReasoning ? { reasoning: fieldReasoning } : {} };
961
- }
962
- async function routerChatWithTools(cfg, messages, tools, opts) {
963
- const body = {
964
- model: cfg.model,
965
- messages,
966
- tools,
967
- tool_choice: opts?.toolChoice ?? "auto",
968
- temperature: opts?.temperature ?? 0.3,
969
- ...opts?.maxTokens ? { max_tokens: opts.maxTokens } : {}
970
- };
971
- const raw = cfg.complete ? await cfg.complete(body) : await (async () => {
972
- const res = await fetch(`${cfg.routerBaseUrl.replace(/\/$/, "")}/chat/completions`, {
973
- method: "POST",
974
- headers: { "content-type": "application/json", authorization: `Bearer ${cfg.routerKey}` },
975
- body: JSON.stringify(body),
976
- ...opts?.signal ? { signal: opts.signal } : {}
977
- });
978
- if (!res.ok) throw new Error(`router ${res.status}: ${(await res.text()).slice(0, 200)}`);
979
- return res.json();
980
- })();
981
- const data = raw;
982
- const msg = data.choices?.[0]?.message;
983
- const toolCalls = (msg?.tool_calls ?? []).map((tc, i) => ({
984
- id: tc.id ?? `call_${i}`,
985
- name: tc.function?.name ?? "",
986
- arguments: tc.function?.arguments ?? "{}"
987
- }));
988
- const u = data.usage;
989
- const usage = u && typeof u.prompt_tokens === "number" && typeof u.completion_tokens === "number" ? { input: u.prompt_tokens, output: u.completion_tokens } : void 0;
990
- const costUsd = usage && isModelPriced(cfg.model) ? estimateCost(usage.input, usage.output, cfg.model) : void 0;
991
- return {
992
- content: msg?.content ?? null,
993
- toolCalls,
994
- ...usage ? { usage } : {},
995
- ...costUsd !== void 0 ? { costUsd } : {}
996
- };
997
- }
998
- async function routerToolLoop(cfg, system, user, tools, execute, opts) {
999
- const initialMessages = opts?.initialMessages ?? [
1000
- { role: "system", content: system },
1001
- { role: "user", content: user }
1002
- ];
1003
- return runBrainLoop({
1004
- chat: (messages, toolSpecs) => routerChatWithTools(cfg, messages, toolSpecs, {
1005
- ...opts?.temperature !== void 0 ? { temperature: opts.temperature } : {},
1006
- ...opts?.maxTokens ? { maxTokens: opts.maxTokens } : {},
1007
- ...opts?.signal ? { signal: opts.signal } : {}
1008
- }),
1009
- tools,
1010
- execute,
1011
- initialMessages,
1012
- maxTurns: opts?.maxTurns ?? 4
1013
- });
1014
- }
1015
- function routerBrain(cfg, opts = {}) {
1016
- const temperature = opts.temperature ?? 0.4;
1017
- return (messages, tools) => routerChatWithTools(cfg, messages, tools, { temperature, toolChoice: "auto" });
1349
+ const force = options.force ?? true;
1350
+ const args = ["worktree", "remove"];
1351
+ if (force) args.push("--force");
1352
+ args.push(options.worktree.path);
1353
+ const result = await runGitAsync(args, options.repoRoot, options.runGit);
1354
+ if (result.exitCode !== 0 && !/not a working tree/.test(result.stderr)) {
1355
+ await runGitAsync(
1356
+ ["branch", "-D", options.worktree.branch],
1357
+ options.repoRoot,
1358
+ options.runGit
1359
+ ).catch(() => void 0);
1360
+ }
1361
+ await runGitAsync(
1362
+ ["branch", "-D", options.worktree.branch],
1363
+ options.repoRoot,
1364
+ options.runGit
1365
+ ).catch(() => void 0);
1018
1366
  }
1019
1367
 
1020
1368
  // src/runtime/sandbox-acquire.ts
@@ -2044,7 +2392,11 @@ async function runWorktreeHarness(opts) {
2044
2392
  ...opts.runGit ? { runGit: opts.runGit } : {}
2045
2393
  }).catch(() => void 0);
2046
2394
  try {
2047
- const { command, args } = harnessInvocation(opts.harness, opts.profile, opts.taskPrompt);
2395
+ const { command, args } = harnessInvocation(opts.harness, opts.profile, opts.taskPrompt, {
2396
+ // This helper created the candidate worktree above; autonomous Claude
2397
+ // edits are permitted only inside that isolated checkout.
2398
+ dangerouslySkipPermissions: opts.harness === "claude"
2399
+ });
2048
2400
  const harnessResult = await runHarness({
2049
2401
  harness: opts.harness,
2050
2402
  cwd: worktree.path,
@@ -3099,723 +3451,377 @@ function bridgeWorktreeExecutor(spec, ctx, seam) {
3099
3451
  } finally {
3100
3452
  await cleanupWorktree();
3101
3453
  }
3102
- return { destroyed };
3103
- },
3104
- resultArtifact() {
3105
- if (!artifact) {
3106
- throw new ValidationError(
3107
- "cliWorktreeExecutor: bridge resultArtifact() read before stream drained"
3108
- );
3109
- }
3110
- return artifact;
3111
- }
3112
- };
3113
- }
3114
- function resolveBridgeWorktreeModel(spec, bridge) {
3115
- if (bridge.model) return bridge.model;
3116
- const model = spec.profile.model?.default;
3117
- if (typeof model === "string" && model.length > 0) return model;
3118
- throw new ValidationError(
3119
- "cliWorktreeExecutor: bridge.model or AgentProfile.model.default required"
3120
- );
3121
- }
3122
- function bridgeOutputText(out) {
3123
- if (typeof out === "string") return out;
3124
- if (out && typeof out === "object") {
3125
- const content = out.content;
3126
- if (typeof content === "string") return content;
3127
- }
3128
- try {
3129
- return JSON.stringify(out) ?? String(out);
3130
- } catch {
3131
- return String(out);
3132
- }
3133
- }
3134
- function isAsyncIterable3(value) {
3135
- return value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function";
3136
- }
3137
- var cliWorktreeExecutor = (spec, ctx) => {
3138
- const seam = readSeam(ctx, cliWorktreeSeamKey, "cli-worktree");
3139
- if (!seam.repoRoot || !seam.taskPrompt) {
3140
- throw new ValidationError("cliWorktreeExecutor: CliWorktreeSeam.repoRoot + taskPrompt required");
3141
- }
3142
- if (seam.bridge) return bridgeWorktreeExecutor(spec, ctx, seam);
3143
- if (!seam.harness) {
3144
- throw new ValidationError(
3145
- "cliWorktreeExecutor: CliWorktreeSeam.harness required when bridge is not set"
3146
- );
3147
- }
3148
- return createWorktreeCliExecutor({
3149
- repoRoot: seam.repoRoot,
3150
- profile: spec.profile,
3151
- harness: seam.harness,
3152
- taskPrompt: seam.taskPrompt,
3153
- ...seam.runId ? { runId: seam.runId } : {},
3154
- ...seam.baseRef ? { baseRef: seam.baseRef } : {},
3155
- ...seam.harnessTimeoutMs !== void 0 ? { harnessTimeoutMs: seam.harnessTimeoutMs } : {},
3156
- ...seam.testCmd !== void 0 ? { testCmd: seam.testCmd } : {},
3157
- ...seam.typecheckCmd !== void 0 ? { typecheckCmd: seam.typecheckCmd } : {},
3158
- ...seam.checkTimeoutMs !== void 0 ? { checkTimeoutMs: seam.checkTimeoutMs } : {},
3159
- ...seam.checkOutputCap !== void 0 ? { checkOutputCap: seam.checkOutputCap } : {},
3160
- ...seam.runGit ? { runGit: seam.runGit } : {},
3161
- ...seam.runCommand ? { runCommand: seam.runCommand } : {},
3162
- ...seam.budgetExempt !== void 0 ? { budgetExempt: seam.budgetExempt } : {}
3163
- });
3164
- };
3165
- function createExecutor(config) {
3166
- return (spec, ctx) => {
3167
- const { backend, ...seam } = config;
3168
- const seamed = { ...ctx, seams: { ...ctx.seams, [backend]: seam } };
3169
- switch (config.backend) {
3170
- case "router":
3171
- return routerInlineExecutor(spec, seamed);
3172
- case "router-tools":
3173
- return routerToolsInlineExecutor(spec, seamed);
3174
- case "bridge":
3175
- return bridgeExecutor(spec, seamed);
3176
- case "cli":
3177
- return cliExecutor(spec, seamed);
3178
- case "cli-worktree":
3179
- return cliWorktreeExecutor(spec, seamed);
3180
- case "provider": {
3181
- const providerSeam = readSeam(seamed, providerSeamKey, "provider");
3182
- const provider = resolveAgentEnvironmentProvider(
3183
- providerSeam.provider,
3184
- providerSeam.registry
3185
- );
3186
- return providerAsExecutor(provider, providerSeam)(spec, seamed);
3187
- }
3188
- case "sandbox": {
3189
- const harness = spec.harness ?? config.harness ?? null;
3190
- return sandboxExecutor({ ...spec, harness }, seamed);
3191
- }
3192
- }
3193
- };
3194
- }
3195
- function createExecutorRegistry() {
3196
- const factories = /* @__PURE__ */ new Map();
3197
- factories.set("router", routerInlineExecutor);
3198
- factories.set("inline", routerInlineExecutor);
3199
- factories.set("sandbox", sandboxExecutor);
3200
- factories.set("cli", cliExecutor);
3201
- return {
3202
- register(runtime, factory) {
3203
- if (factories.has(runtime)) {
3204
- throw new ValidationError(`executor registry: runtime "${runtime}" already registered`);
3205
- }
3206
- factories.set(runtime, factory);
3207
- },
3208
- resolve(spec) {
3209
- if (spec.executor) {
3210
- const byo = spec.executor;
3211
- return { succeeded: true, value: (() => byo) };
3212
- }
3213
- if (spec.harness === null) {
3214
- const f2 = factories.get("router");
3215
- if (!f2) return { succeeded: false, error: 'executor registry: no "router" factory' };
3216
- return { succeeded: true, value: f2 };
3217
- }
3218
- const runtimeTag = "sandbox";
3219
- const f = factories.get(runtimeTag);
3220
- if (!f) {
3221
- return {
3222
- succeeded: false,
3223
- error: `executor registry: no factory for runtime "${runtimeTag}" (harness "${spec.harness}") and no BYO executor`
3224
- };
3225
- }
3226
- return { succeeded: true, value: f };
3227
- }
3228
- };
3229
- }
3230
- function readSeam(ctx, key, who) {
3231
- const seam = ctx.seams[key];
3232
- if (seam === void 0 || seam === null) {
3233
- throw new ValidationError(`${who} executor: missing required seam "${key}" on ExecutorContext`);
3234
- }
3235
- return seam;
3236
- }
3237
- function taskToPrompt(task) {
3238
- if (typeof task === "string") return task;
3239
- if (task && typeof task === "object") {
3240
- const obj = task;
3241
- for (const k of ["prompt", "content", "task", "message"]) {
3242
- if (typeof obj[k] === "string") return obj[k];
3243
- }
3244
- }
3245
- return JSON.stringify(task);
3246
- }
3247
- function taskToMessages(task, spec) {
3248
- const messages = [];
3249
- const system = spec.profile.prompt?.systemPrompt;
3250
- if (typeof system === "string" && system.length > 0) {
3251
- messages.push({ role: "system", content: system });
3252
- }
3253
- messages.push({ role: "user", content: taskToPrompt(task) });
3254
- return messages;
3255
- }
3256
- function singleShotDriver(maxIterations) {
3257
- return {
3258
- name: "leaf",
3259
- plan(task, history) {
3260
- return Promise.resolve(history.length >= maxIterations ? [] : [task]);
3261
- },
3262
- decide(history) {
3263
- return history.length >= maxIterations ? "stop" : "continue";
3264
- }
3265
- };
3266
- }
3267
- function linkSignals2(a, b) {
3268
- if (a.aborted || b.aborted) {
3269
- const c2 = new AbortController();
3270
- c2.abort();
3271
- return c2.signal;
3272
- }
3273
- const c = new AbortController();
3274
- const onAbort = () => c.abort();
3275
- a.addEventListener("abort", onAbort, { once: true });
3276
- b.addEventListener("abort", onAbort, { once: true });
3277
- return c.signal;
3278
- }
3279
- function mergeAbortSignals(...signals) {
3280
- const c = new AbortController();
3281
- const onAbort = () => c.abort();
3282
- for (const s of signals) {
3283
- if (s.aborted) {
3284
- c.abort();
3285
- break;
3286
- }
3287
- s.addEventListener("abort", onAbort, { once: true });
3288
- }
3289
- return c.signal;
3290
- }
3291
-
3292
- // src/runtime/supervise/driver-executor.ts
3293
- var driverRuntime = "driver";
3294
- var driverRole = "driver";
3295
- function isDriverSpec(spec) {
3296
- const role = spec.profile.metadata?.role;
3297
- if (role !== driverRole) return false;
3298
- const driver = spec.driver;
3299
- if (!isAgent(driver)) {
3300
- throw new ValidationError(
3301
- "driverExecutor: a driver-role spec must carry a `driver` Agent to run inside its nested scope"
3302
- );
3303
- }
3304
- return true;
3305
- }
3306
- var driverExecutorFactory = (spec, ctx) => {
3307
- if (!isDriverSpec(spec)) {
3308
- throw new ValidationError(
3309
- 'driverExecutorFactory: spec is not a driver child (no role:"driver" marker)'
3310
- );
3311
- }
3312
- const driver = spec.driver;
3313
- const journal = spec.journal;
3314
- const seam = readNestedScopeSeam(ctx);
3315
- let artifact;
3316
- let meteredSpend;
3317
- return {
3318
- runtime: driverRuntime,
3319
- async execute(task, signal) {
3320
- const nestedRoot = nestedTreeKey(seam, journal);
3321
- await journal.beginTree(nestedRoot, (/* @__PURE__ */ new Date(0)).toISOString());
3322
- const nestedScope = seam.mount(nestedRoot, signal);
3323
- try {
3324
- const out = await driver.act(task, nestedScope);
3325
- const events = await loadTreeEvents(journal, nestedRoot);
3326
- const settled = events.filter(isSettled);
3327
- meteredSpend = nonZeroOrUndef(sumMetered(events));
3328
- const verdict = deriveDeliveryVerdict(settled);
3329
- artifact = {
3330
- outRef: `${driverRuntime}:${nestedRoot}`,
3331
- out,
3332
- spent: sumSpend(settled),
3333
- ...verdict ? { verdict } : {}
3334
- };
3335
- return artifact;
3336
- } catch (err) {
3337
- meteredSpend = await safeSumMetered(journal, nestedRoot);
3338
- throw err;
3339
- }
3340
- },
3341
- metered() {
3342
- return meteredSpend;
3343
- },
3344
- teardown() {
3345
- return Promise.resolve({ destroyed: true });
3454
+ return { destroyed };
3346
3455
  },
3347
3456
  resultArtifact() {
3348
3457
  if (!artifact) {
3349
- throw new ValidationError("driverExecutor: resultArtifact() read before execute()");
3458
+ throw new ValidationError(
3459
+ "cliWorktreeExecutor: bridge resultArtifact() read before stream drained"
3460
+ );
3350
3461
  }
3351
3462
  return artifact;
3352
3463
  }
3353
3464
  };
3354
- };
3355
- function withDriverExecutor(base) {
3356
- return {
3357
- register: base.register.bind(base),
3358
- resolve(spec) {
3359
- const role = spec.profile.metadata?.role;
3360
- if (role === driverRole && !spec.executor) {
3361
- return { succeeded: true, value: driverExecutorFactory };
3362
- }
3363
- return base.resolve(spec);
3364
- }
3365
- };
3366
- }
3367
- function nestedTreeKey(seam, journal) {
3368
- return `${seam.journalRoot}/d${nextNestOrdinal(journal)}`;
3369
- }
3370
- var nestCounters = /* @__PURE__ */ new WeakMap();
3371
- function nextNestOrdinal(journal) {
3372
- let c = nestCounters.get(journal);
3373
- if (!c) {
3374
- c = { n: 0 };
3375
- nestCounters.set(journal, c);
3376
- }
3377
- return c.n++;
3378
- }
3379
- async function loadTreeEvents(journal, nestedRoot) {
3380
- const events = await journal.loadTree(nestedRoot);
3381
- if (events === void 0) {
3382
- throw new ValidationError(
3383
- `driverExecutor: nested tree '${nestedRoot}' missing from the journal after run (corrupted log)`
3384
- );
3385
- }
3386
- return events;
3387
3465
  }
3388
- function isSettled(ev) {
3389
- return ev.kind === "settled";
3390
- }
3391
- function sumSpend(settled) {
3392
- const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
3393
- for (const ev of settled) {
3394
- total.iterations += ev.spent.iterations;
3395
- total.tokens.input += ev.spent.tokens.input;
3396
- total.tokens.output += ev.spent.tokens.output;
3397
- total.usd += ev.spent.usd;
3398
- total.ms += ev.spent.ms;
3399
- }
3400
- return total;
3466
+ function resolveBridgeWorktreeModel(spec, bridge) {
3467
+ if (bridge.model) return bridge.model;
3468
+ const model = spec.profile.model?.default;
3469
+ if (typeof model === "string" && model.length > 0) return model;
3470
+ throw new ValidationError(
3471
+ "cliWorktreeExecutor: bridge.model or AgentProfile.model.default required"
3472
+ );
3401
3473
  }
3402
- function sumMetered(events) {
3403
- const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
3404
- for (const ev of events) {
3405
- if (ev.kind !== "metered") continue;
3406
- total.iterations += ev.spend.iterations;
3407
- total.tokens.input += ev.spend.tokens.input;
3408
- total.tokens.output += ev.spend.tokens.output;
3409
- total.usd += ev.spend.usd;
3410
- total.ms += ev.spend.ms;
3474
+ function bridgeOutputText(out) {
3475
+ if (typeof out === "string") return out;
3476
+ if (out && typeof out === "object") {
3477
+ const content = out.content;
3478
+ if (typeof content === "string") return content;
3411
3479
  }
3412
- return total;
3413
- }
3414
- function isNonZeroSpend(s) {
3415
- return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
3416
- }
3417
- function nonZeroOrUndef(s) {
3418
- return isNonZeroSpend(s) ? s : void 0;
3419
- }
3420
- async function safeSumMetered(journal, nestedRoot) {
3421
3480
  try {
3422
- return nonZeroOrUndef(sumMetered(await loadTreeEvents(journal, nestedRoot)));
3481
+ return JSON.stringify(out) ?? String(out);
3423
3482
  } catch {
3424
- return void 0;
3425
- }
3426
- }
3427
- function deriveDeliveryVerdict(settled) {
3428
- let sawChild = false;
3429
- let anyValid = false;
3430
- let bestValidScore;
3431
- let bestDoneScore;
3432
- for (const ev of settled) {
3433
- sawChild = true;
3434
- if (ev.status !== "done") continue;
3435
- const score = ev.verdict?.score;
3436
- if (score !== void 0 && (bestDoneScore === void 0 || score > bestDoneScore)) {
3437
- bestDoneScore = score;
3438
- }
3439
- if (ev.verdict?.valid === true) {
3440
- anyValid = true;
3441
- if (score !== void 0 && (bestValidScore === void 0 || score > bestValidScore)) {
3442
- bestValidScore = score;
3443
- }
3444
- }
3445
- }
3446
- if (!sawChild) return void 0;
3447
- return {
3448
- valid: anyValid,
3449
- score: anyValid ? bestValidScore ?? 1 : bestDoneScore ?? 0
3450
- };
3451
- }
3452
- function readNestedScopeSeam(ctx) {
3453
- const seam = ctx.seams[nestedScopeSeamKey];
3454
- if (!seam || typeof seam.mount !== "function") {
3455
- throw new ValidationError(
3456
- `driverExecutor: missing required seam "${nestedScopeSeamKey}" \u2014 a driver child must be spawned through a Scope that seeds it (the keystone scope does)`
3457
- );
3458
- }
3459
- return seam;
3460
- }
3461
- function isAgent(value) {
3462
- return typeof value === "object" && value !== null && typeof value.act === "function" && typeof value.name === "string";
3463
- }
3464
-
3465
- // src/runtime/supervise/run-context.ts
3466
- function createInMemoryRunContext(opts = {}) {
3467
- const base = createExecutorRegistry();
3468
- return {
3469
- journal: new InMemorySpawnJournal(),
3470
- blobs: new InMemoryResultBlobStore(),
3471
- executors: opts.withDriver ? withDriverExecutor(base) : base
3472
- };
3473
- }
3474
-
3475
- // src/runtime/supervise/budget.ts
3476
- function spendFromUsageEvents(events) {
3477
- const tokens = zeroTokenUsage();
3478
- let usd = 0;
3479
- let iterations = 0;
3480
- for (const ev of events) {
3481
- if (ev.kind === "tokens") {
3482
- addTokenUsage(tokens, { input: ev.input, output: ev.output });
3483
- } else if (ev.kind === "cost") {
3484
- usd += ev.usd;
3485
- } else {
3486
- iterations += 1;
3487
- }
3488
- }
3489
- return { iterations, tokens, usd, ms: 0 };
3490
- }
3491
- async function foldUsage(events) {
3492
- if (Array.isArray(events)) return spendFromUsageEvents(events);
3493
- const tokens = zeroTokenUsage();
3494
- let usd = 0;
3495
- let iterations = 0;
3496
- for await (const ev of events) {
3497
- if (ev.kind === "tokens") {
3498
- addTokenUsage(tokens, { input: ev.input, output: ev.output });
3499
- } else if (ev.kind === "cost") {
3500
- usd += ev.usd;
3501
- } else {
3502
- iterations += 1;
3503
- }
3504
- }
3505
- return { iterations, tokens, usd, ms: 0 };
3506
- }
3507
- function totalTokens(usage) {
3508
- return usage.input + usage.output;
3509
- }
3510
- function createBudgetPool(root, now = Date.now) {
3511
- let freeTokens = root.maxTokens;
3512
- let reservedTokens = 0;
3513
- let committedTokens = 0;
3514
- const usdCapped = root.maxUsd !== void 0;
3515
- let freeUsd = root.maxUsd ?? 0;
3516
- let reservedUsd = 0;
3517
- let committedUsd = 0;
3518
- let freeIterations = root.maxIterations;
3519
- let reservedIterations = 0;
3520
- let committedIterations = 0;
3521
- const absoluteDeadlineMs = root.deadlineMs !== void 0 ? now() + root.deadlineMs : 0;
3522
- let nextTicketId = 0;
3523
- const open = /* @__PURE__ */ new Set();
3524
- function reserve(b) {
3525
- const wantTokens = b.maxTokens;
3526
- const wantUsd = b.maxUsd ?? 0;
3527
- const wantIterations = b.maxIterations;
3528
- if (wantTokens > freeTokens) return { ok: false, reason: "budget-exhausted" };
3529
- if (wantIterations > freeIterations) return { ok: false, reason: "budget-exhausted" };
3530
- if (wantUsd > 0 && (!usdCapped || wantUsd > freeUsd)) {
3531
- return { ok: false, reason: "budget-exhausted" };
3532
- }
3533
- freeTokens -= wantTokens;
3534
- reservedTokens += wantTokens;
3535
- freeIterations -= wantIterations;
3536
- reservedIterations += wantIterations;
3537
- if (wantUsd > 0) {
3538
- freeUsd -= wantUsd;
3539
- reservedUsd += wantUsd;
3540
- }
3541
- const id = nextTicketId++;
3542
- open.add(id);
3543
- return {
3544
- ok: true,
3545
- ticket: { id, reserved: { tokens: wantTokens, usd: wantUsd, iterations: wantIterations } }
3546
- };
3547
- }
3548
- function reconcile(ticket, spent) {
3549
- if (!open.has(ticket.id)) {
3550
- throw new Error(`budget pool: reconcile of unknown or already-settled ticket ${ticket.id}`);
3551
- }
3552
- open.delete(ticket.id);
3553
- const { tokens: rTokens, usd: rUsd, iterations: rIterations } = ticket.reserved;
3554
- const spentTokens = totalTokens(spent.tokens);
3555
- if (spentTokens > rTokens) {
3556
- throw new Error(
3557
- `budget pool: ticket ${ticket.id} spent ${spentTokens} tokens > reserved ${rTokens}`
3558
- );
3559
- }
3560
- if (spent.iterations > rIterations) {
3561
- throw new Error(
3562
- `budget pool: ticket ${ticket.id} spent ${spent.iterations} iterations > reserved ${rIterations}`
3563
- );
3564
- }
3565
- if (usdCapped && spent.usd > rUsd) {
3566
- throw new Error(`budget pool: ticket ${ticket.id} spent $${spent.usd} > reserved $${rUsd}`);
3567
- }
3568
- reservedTokens -= rTokens;
3569
- committedTokens += spentTokens;
3570
- freeTokens += rTokens - spentTokens;
3571
- reservedIterations -= rIterations;
3572
- committedIterations += spent.iterations;
3573
- freeIterations += rIterations - spent.iterations;
3574
- if (usdCapped && rUsd > 0) {
3575
- reservedUsd -= rUsd;
3576
- committedUsd += spent.usd;
3577
- freeUsd += rUsd - spent.usd;
3578
- } else {
3579
- committedUsd += spent.usd;
3580
- }
3581
- }
3582
- function observe(spend) {
3583
- const tokens = totalTokens(spend.tokens);
3584
- freeTokens -= tokens;
3585
- committedTokens += tokens;
3586
- freeIterations -= spend.iterations;
3587
- committedIterations += spend.iterations;
3588
- committedUsd += spend.usd;
3589
- if (usdCapped) freeUsd -= spend.usd;
3483
+ return String(out);
3590
3484
  }
3591
- function readout() {
3592
- return {
3593
- tokensLeft: freeTokens,
3594
- usdLeft: usdCapped ? freeUsd : 0,
3595
- usdCapped,
3596
- deadlineMs: absoluteDeadlineMs,
3597
- reservedTokens
3598
- };
3485
+ }
3486
+ function isAsyncIterable3(value) {
3487
+ return value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function";
3488
+ }
3489
+ var cliWorktreeExecutor = (spec, ctx) => {
3490
+ const seam = readSeam(ctx, cliWorktreeSeamKey, "cli-worktree");
3491
+ if (!seam.repoRoot || !seam.taskPrompt) {
3492
+ throw new ValidationError("cliWorktreeExecutor: CliWorktreeSeam.repoRoot + taskPrompt required");
3599
3493
  }
3600
- function assertNoOpenTickets() {
3601
- if (open.size > 0) {
3602
- throw new Error(
3603
- `budget pool: ${open.size} reservation(s) still open at join barrier (leaked ticket ids: ${[...open].join(", ")}) \u2014 conserved-pool invariant violated`
3604
- );
3605
- }
3494
+ if (seam.bridge) return bridgeWorktreeExecutor(spec, ctx, seam);
3495
+ if (!seam.harness) {
3496
+ throw new ValidationError(
3497
+ "cliWorktreeExecutor: CliWorktreeSeam.harness required when bridge is not set"
3498
+ );
3606
3499
  }
3607
- return {
3608
- reserve,
3609
- reconcile,
3610
- spendFrom: foldUsage,
3611
- readout,
3612
- observe,
3613
- assertNoOpenTickets
3500
+ return createWorktreeCliExecutor({
3501
+ repoRoot: seam.repoRoot,
3502
+ profile: spec.profile,
3503
+ harness: seam.harness,
3504
+ taskPrompt: seam.taskPrompt,
3505
+ ...seam.runId ? { runId: seam.runId } : {},
3506
+ ...seam.baseRef ? { baseRef: seam.baseRef } : {},
3507
+ ...seam.harnessTimeoutMs !== void 0 ? { harnessTimeoutMs: seam.harnessTimeoutMs } : {},
3508
+ ...seam.testCmd !== void 0 ? { testCmd: seam.testCmd } : {},
3509
+ ...seam.typecheckCmd !== void 0 ? { typecheckCmd: seam.typecheckCmd } : {},
3510
+ ...seam.checkTimeoutMs !== void 0 ? { checkTimeoutMs: seam.checkTimeoutMs } : {},
3511
+ ...seam.checkOutputCap !== void 0 ? { checkOutputCap: seam.checkOutputCap } : {},
3512
+ ...seam.runGit ? { runGit: seam.runGit } : {},
3513
+ ...seam.runCommand ? { runCommand: seam.runCommand } : {},
3514
+ ...seam.budgetExempt !== void 0 ? { budgetExempt: seam.budgetExempt } : {}
3515
+ });
3516
+ };
3517
+ function createExecutor(config) {
3518
+ return (spec, ctx) => {
3519
+ const { backend, ...seam } = config;
3520
+ const seamed = { ...ctx, seams: { ...ctx.seams, [backend]: seam } };
3521
+ switch (config.backend) {
3522
+ case "router":
3523
+ return routerInlineExecutor(spec, seamed);
3524
+ case "router-tools":
3525
+ return routerToolsInlineExecutor(spec, seamed);
3526
+ case "bridge":
3527
+ return bridgeExecutor(spec, seamed);
3528
+ case "cli":
3529
+ return cliExecutor(spec, seamed);
3530
+ case "cli-worktree":
3531
+ return cliWorktreeExecutor(spec, seamed);
3532
+ case "provider": {
3533
+ const providerSeam = readSeam(seamed, providerSeamKey, "provider");
3534
+ const provider = resolveAgentEnvironmentProvider(
3535
+ providerSeam.provider,
3536
+ providerSeam.registry
3537
+ );
3538
+ return providerAsExecutor(provider, providerSeam)(spec, seamed);
3539
+ }
3540
+ case "sandbox": {
3541
+ const harness = spec.harness ?? config.harness ?? null;
3542
+ return sandboxExecutor({ ...spec, harness }, seamed);
3543
+ }
3544
+ }
3614
3545
  };
3615
3546
  }
3616
-
3617
- // src/runtime/supervise/supervisor.ts
3618
- var defaultMaxDepth = 4;
3619
- function createSupervisor() {
3620
- let attached;
3621
- async function run(root, task, opts) {
3622
- const now = opts.now ?? Date.now;
3623
- const pool = createBudgetPool(opts.budget, now);
3624
- await opts.journal.beginTree(opts.runId, new Date(now()).toISOString());
3625
- await opts.journal.appendEvent(opts.runId, {
3626
- kind: "spawned",
3627
- id: opts.runId,
3628
- label: "root",
3629
- budget: opts.budget,
3630
- runtime: "inline",
3631
- seq: 0,
3632
- at: new Date(now()).toISOString()
3633
- });
3634
- const controller = new AbortController();
3635
- const cascadeAbort = (reason) => {
3636
- if (controller.signal.aborted) return;
3637
- controller.abort(reason);
3638
- };
3639
- const onCallerAbort = () => cascadeAbort("caller signal aborted");
3640
- if (opts.signal) {
3641
- if (opts.signal.aborted) cascadeAbort("caller signal aborted");
3642
- else opts.signal.addEventListener("abort", onCallerAbort, { once: true });
3643
- }
3644
- const breaker = createIntensityBreaker(opts, () => cascadeAbort("intensity breaker tripped"));
3645
- const journal = wrapJournalForBreaker(opts.journal, breaker);
3646
- const scope = createScope({
3647
- parentId: opts.runId,
3648
- root: opts.runId,
3649
- pool,
3650
- journal,
3651
- blobs: opts.blobs,
3652
- executors: opts.executors,
3653
- seams: {},
3654
- depth: 0,
3655
- maxDepth: opts.maxDepth ?? defaultMaxDepth,
3656
- signal: controller.signal,
3657
- now,
3658
- hooks: opts.hooks
3659
- });
3660
- const openScope = scope;
3661
- if (attached) {
3662
- attached.bind({ scope: openScope, cascadeAbort, signal: pushRootSignal(cascadeAbort) });
3663
- }
3664
- let actOutcome;
3665
- try {
3666
- const out = await root.act(task, scope);
3667
- actOutcome = { ok: true, out };
3668
- } catch (error) {
3669
- actOutcome = { ok: false, error };
3670
- } finally {
3671
- await drainLiveChildren(openScope, controller);
3672
- if (opts.signal) opts.signal.removeEventListener("abort", onCallerAbort);
3673
- if (attached) attached.unbind();
3674
- }
3675
- const tree = scope.view;
3676
- if (actOutcome.ok) {
3677
- pool.assertNoOpenTickets();
3678
- const out = actOutcome.out;
3679
- if (out !== void 0) {
3680
- const outRef = contentAddress(out);
3681
- await opts.blobs.put(outRef, out);
3682
- const { childWork, driverInference } = await spentFromJournal(journal, opts.runId);
3547
+ function createExecutorRegistry() {
3548
+ const factories = /* @__PURE__ */ new Map();
3549
+ factories.set("router", routerInlineExecutor);
3550
+ factories.set("inline", routerInlineExecutor);
3551
+ factories.set("sandbox", sandboxExecutor);
3552
+ factories.set("cli", cliExecutor);
3553
+ return {
3554
+ register(runtime, factory) {
3555
+ if (factories.has(runtime)) {
3556
+ throw new ValidationError(`executor registry: runtime "${runtime}" already registered`);
3557
+ }
3558
+ factories.set(runtime, factory);
3559
+ },
3560
+ resolve(spec) {
3561
+ if (spec.executor) {
3562
+ const byo = spec.executor;
3563
+ return { succeeded: true, value: (() => byo) };
3564
+ }
3565
+ if (spec.harness === null) {
3566
+ const f2 = factories.get("router");
3567
+ if (!f2) return { succeeded: false, error: 'executor registry: no "router" factory' };
3568
+ return { succeeded: true, value: f2 };
3569
+ }
3570
+ const runtimeTag = "sandbox";
3571
+ const f = factories.get(runtimeTag);
3572
+ if (!f) {
3683
3573
  return {
3684
- kind: "winner",
3685
- out,
3686
- outRef,
3687
- tree,
3688
- spentTotal: addSpend(childWork, driverInference),
3689
- ...isNonEmptySpend(driverInference) ? { spentBreakdown: { driverInference, childWork } } : {}
3574
+ succeeded: false,
3575
+ error: `executor registry: no factory for runtime "${runtimeTag}" (harness "${spec.harness}") and no BYO executor`
3690
3576
  };
3691
3577
  }
3692
- return noWinner();
3578
+ return { succeeded: true, value: f };
3693
3579
  }
3694
- return noWinner();
3695
- async function noWinner() {
3696
- const { childWork, driverInference } = await spentFromJournal(journal, opts.runId);
3697
- return {
3698
- kind: "no-winner",
3699
- reason: classifyNoWinner(controller, pool, opts, breaker),
3700
- tree,
3701
- downCount: breaker.downCount(),
3702
- spentTotal: addSpend(childWork, driverInference)
3703
- };
3580
+ };
3581
+ }
3582
+ function readSeam(ctx, key, who) {
3583
+ const seam = ctx.seams[key];
3584
+ if (seam === void 0 || seam === null) {
3585
+ throw new ValidationError(`${who} executor: missing required seam "${key}" on ExecutorContext`);
3586
+ }
3587
+ return seam;
3588
+ }
3589
+ function taskToPrompt(task) {
3590
+ if (typeof task === "string") return task;
3591
+ if (task && typeof task === "object") {
3592
+ const obj = task;
3593
+ for (const k of ["prompt", "content", "task", "message"]) {
3594
+ if (typeof obj[k] === "string") return obj[k];
3704
3595
  }
3705
3596
  }
3706
- function attach(h) {
3707
- const control = rootControls.get(h);
3708
- if (!control) {
3709
- throw new RuntimeRunStateError(
3710
- "supervisor.attach: handle was not minted by createRootHandle (no control channel)"
3711
- );
3597
+ return JSON.stringify(task);
3598
+ }
3599
+ function taskToMessages(task, spec) {
3600
+ const messages = [];
3601
+ const system = spec.profile.prompt?.systemPrompt;
3602
+ if (typeof system === "string" && system.length > 0) {
3603
+ messages.push({ role: "system", content: system });
3604
+ }
3605
+ messages.push({ role: "user", content: taskToPrompt(task) });
3606
+ return messages;
3607
+ }
3608
+ function singleShotDriver(maxIterations) {
3609
+ return {
3610
+ name: "leaf",
3611
+ plan(task, history) {
3612
+ return Promise.resolve(history.length >= maxIterations ? [] : [task]);
3613
+ },
3614
+ decide(history) {
3615
+ return history.length >= maxIterations ? "stop" : "continue";
3712
3616
  }
3713
- attached = control;
3617
+ };
3618
+ }
3619
+ function linkSignals2(a, b) {
3620
+ if (a.aborted || b.aborted) {
3621
+ const c2 = new AbortController();
3622
+ c2.abort();
3623
+ return c2.signal;
3624
+ }
3625
+ const c = new AbortController();
3626
+ const onAbort = () => c.abort();
3627
+ a.addEventListener("abort", onAbort, { once: true });
3628
+ b.addEventListener("abort", onAbort, { once: true });
3629
+ return c.signal;
3630
+ }
3631
+ function mergeAbortSignals(...signals) {
3632
+ const c = new AbortController();
3633
+ const onAbort = () => c.abort();
3634
+ for (const s of signals) {
3635
+ if (s.aborted) {
3636
+ c.abort();
3637
+ break;
3638
+ }
3639
+ s.addEventListener("abort", onAbort, { once: true });
3714
3640
  }
3715
- return { run, attach };
3641
+ return c.signal;
3716
3642
  }
3717
- var rootControls = /* @__PURE__ */ new WeakMap();
3718
- function pushRootSignal(cascadeAbort) {
3719
- return (msg) => {
3720
- if (msg.kind === "cancel") cascadeAbort(msg.reason ?? "root signal: cancel");
3721
- };
3643
+
3644
+ // src/runtime/supervise/driver-executor.ts
3645
+ var driverRuntime = "driver";
3646
+ var driverRole = "driver";
3647
+ function isDriverSpec(spec) {
3648
+ const role = spec.profile.metadata?.role;
3649
+ if (role !== driverRole) return false;
3650
+ const driver = spec.driver;
3651
+ if (!isAgent(driver)) {
3652
+ throw new ValidationError(
3653
+ "driverExecutor: a driver-role spec must carry a `driver` Agent to run inside its nested scope"
3654
+ );
3655
+ }
3656
+ return true;
3722
3657
  }
3723
- function createIntensityBreaker(opts, trip) {
3724
- const max = opts.maxRestarts;
3725
- const within = opts.withinMs;
3726
- const armed = max !== void 0 && within !== void 0;
3727
- const recent = [];
3728
- let total = 0;
3729
- let isTripped = false;
3658
+ var driverExecutorFactory = (spec, ctx) => {
3659
+ if (!isDriverSpec(spec)) {
3660
+ throw new ValidationError(
3661
+ 'driverExecutorFactory: spec is not a driver child (no role:"driver" marker)'
3662
+ );
3663
+ }
3664
+ const driver = spec.driver;
3665
+ const journal = spec.journal;
3666
+ const seam = readNestedScopeSeam(ctx);
3667
+ let artifact;
3668
+ let meteredSpend;
3730
3669
  return {
3731
- recordDown(at) {
3732
- total += 1;
3733
- if (!armed || isTripped) return;
3734
- recent.push(at);
3735
- const cutoff = at - within;
3736
- while (recent.length > 0 && recent[0] < cutoff) recent.shift();
3737
- if (recent.length > max) {
3738
- isTripped = true;
3739
- trip();
3670
+ runtime: driverRuntime,
3671
+ async execute(task, signal) {
3672
+ const nestedRoot = nestedTreeKey(seam, journal);
3673
+ await journal.beginTree(nestedRoot, (/* @__PURE__ */ new Date(0)).toISOString());
3674
+ const nestedScope = seam.mount(nestedRoot, signal);
3675
+ try {
3676
+ const out = await driver.act(task, nestedScope);
3677
+ const events = await loadTreeEvents(journal, nestedRoot);
3678
+ const settled = events.filter(isSettled);
3679
+ meteredSpend = nonZeroOrUndef(sumMetered(events));
3680
+ const verdict = deriveDeliveryVerdict(settled);
3681
+ artifact = {
3682
+ outRef: `${driverRuntime}:${nestedRoot}`,
3683
+ out,
3684
+ spent: sumSpend(settled),
3685
+ ...verdict ? { verdict } : {}
3686
+ };
3687
+ return artifact;
3688
+ } catch (err) {
3689
+ meteredSpend = await safeSumMetered(journal, nestedRoot);
3690
+ throw err;
3740
3691
  }
3741
3692
  },
3742
- tripped() {
3743
- return isTripped;
3693
+ metered() {
3694
+ return meteredSpend;
3744
3695
  },
3745
- downCount() {
3746
- return total;
3696
+ teardown() {
3697
+ return Promise.resolve({ destroyed: true });
3698
+ },
3699
+ resultArtifact() {
3700
+ if (!artifact) {
3701
+ throw new ValidationError("driverExecutor: resultArtifact() read before execute()");
3702
+ }
3703
+ return artifact;
3747
3704
  }
3748
3705
  };
3749
- }
3750
- function wrapJournalForBreaker(journal, breaker) {
3706
+ };
3707
+ function withDriverExecutor(base) {
3751
3708
  return {
3752
- loadTree: (root) => journal.loadTree(root),
3753
- beginTree: (root, at) => journal.beginTree(root, at),
3754
- appendEvent: (root, ev) => {
3755
- if (ev.kind === "settled" && ev.status === "down") breaker.recordDown(Date.parse(ev.at));
3756
- return journal.appendEvent(root, ev);
3709
+ register: base.register.bind(base),
3710
+ resolve(spec) {
3711
+ const role = spec.profile.metadata?.role;
3712
+ if (role === driverRole && !spec.executor) {
3713
+ return { succeeded: true, value: driverExecutorFactory };
3714
+ }
3715
+ return base.resolve(spec);
3757
3716
  }
3758
3717
  };
3759
3718
  }
3760
- async function drainLiveChildren(scope, controller) {
3761
- const hasLive = scope.view.inFlight > 0;
3762
- if (!hasLive) return;
3763
- if (!controller.signal.aborted) controller.abort();
3764
- await Promise.allSettled([drainCursor(scope)]);
3765
- }
3766
- async function drainCursor(scope) {
3767
- for (; ; ) {
3768
- const settled = await scope.next();
3769
- if (settled === null) return;
3770
- }
3771
- }
3772
- function classifyNoWinner(controller, pool, opts, breaker) {
3773
- if (breaker.tripped()) return "all-children-down";
3774
- if (controller.signal.aborted) return "aborted";
3775
- if (poolExhausted(pool, opts)) return "budget-exhausted";
3776
- return "all-children-down";
3719
+ function nestedTreeKey(seam, journal) {
3720
+ return `${seam.journalRoot}/d${nextNestOrdinal(journal)}`;
3777
3721
  }
3778
- function poolExhausted(pool, opts) {
3779
- const r = pool.readout();
3780
- if (r.tokensLeft <= 0) return true;
3781
- if (opts.budget.maxUsd !== void 0 && r.usdLeft <= 0) return true;
3782
- if (opts.budget.deadlineMs !== void 0 && r.deadlineMs > 0 && (opts.now ?? Date.now)() >= r.deadlineMs) {
3783
- return true;
3722
+ var nestCounters = /* @__PURE__ */ new WeakMap();
3723
+ function nextNestOrdinal(journal) {
3724
+ let c = nestCounters.get(journal);
3725
+ if (!c) {
3726
+ c = { n: 0 };
3727
+ nestCounters.set(journal, c);
3784
3728
  }
3785
- return false;
3729
+ return c.n++;
3786
3730
  }
3787
- async function spentFromJournal(journal, root) {
3788
- const events = await journal.loadTree(root);
3731
+ async function loadTreeEvents(journal, nestedRoot) {
3732
+ const events = await journal.loadTree(nestedRoot);
3789
3733
  if (events === void 0) {
3790
- throw new RuntimeRunStateError(
3791
- `supervisor: spawn tree '${root}' is missing from the journal after run (corrupted log)`
3734
+ throw new ValidationError(
3735
+ `driverExecutor: nested tree '${nestedRoot}' missing from the journal after run (corrupted log)`
3792
3736
  );
3793
3737
  }
3794
- const childWork = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
3795
- const driverInference = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
3738
+ return events;
3739
+ }
3740
+ function isSettled(ev) {
3741
+ return ev.kind === "settled";
3742
+ }
3743
+ function sumSpend(settled) {
3744
+ const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
3745
+ for (const ev of settled) {
3746
+ total.iterations += ev.spent.iterations;
3747
+ total.tokens.input += ev.spent.tokens.input;
3748
+ total.tokens.output += ev.spent.tokens.output;
3749
+ total.usd += ev.spent.usd;
3750
+ total.ms += ev.spent.ms;
3751
+ }
3752
+ return total;
3753
+ }
3754
+ function sumMetered(events) {
3755
+ const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
3796
3756
  for (const ev of events) {
3797
- if (ev.kind === "settled") accumulate(childWork, ev.spent);
3798
- else if (ev.kind === "metered") accumulate(driverInference, ev.spend);
3757
+ if (ev.kind !== "metered") continue;
3758
+ total.iterations += ev.spend.iterations;
3759
+ total.tokens.input += ev.spend.tokens.input;
3760
+ total.tokens.output += ev.spend.tokens.output;
3761
+ total.usd += ev.spend.usd;
3762
+ total.ms += ev.spend.ms;
3799
3763
  }
3800
- return { childWork, driverInference };
3764
+ return total;
3801
3765
  }
3802
- function accumulate(a, b) {
3803
- a.iterations += b.iterations;
3804
- a.tokens.input += b.tokens.input;
3805
- a.tokens.output += b.tokens.output;
3806
- a.usd += b.usd;
3807
- a.ms += b.ms;
3766
+ function isNonZeroSpend(s) {
3767
+ return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
3808
3768
  }
3809
- function addSpend(a, b) {
3769
+ function nonZeroOrUndef(s) {
3770
+ return isNonZeroSpend(s) ? s : void 0;
3771
+ }
3772
+ async function safeSumMetered(journal, nestedRoot) {
3773
+ try {
3774
+ return nonZeroOrUndef(sumMetered(await loadTreeEvents(journal, nestedRoot)));
3775
+ } catch {
3776
+ return void 0;
3777
+ }
3778
+ }
3779
+ function deriveDeliveryVerdict(settled) {
3780
+ let sawChild = false;
3781
+ let anyValid = false;
3782
+ let bestValidScore;
3783
+ let bestDoneScore;
3784
+ for (const ev of settled) {
3785
+ sawChild = true;
3786
+ if (ev.status !== "done") continue;
3787
+ const score = ev.verdict?.score;
3788
+ if (score !== void 0 && (bestDoneScore === void 0 || score > bestDoneScore)) {
3789
+ bestDoneScore = score;
3790
+ }
3791
+ if (ev.verdict?.valid === true) {
3792
+ anyValid = true;
3793
+ if (score !== void 0 && (bestValidScore === void 0 || score > bestValidScore)) {
3794
+ bestValidScore = score;
3795
+ }
3796
+ }
3797
+ }
3798
+ if (!sawChild) return void 0;
3810
3799
  return {
3811
- iterations: a.iterations + b.iterations,
3812
- tokens: { input: a.tokens.input + b.tokens.input, output: a.tokens.output + b.tokens.output },
3813
- usd: a.usd + b.usd,
3814
- ms: a.ms + b.ms
3800
+ valid: anyValid,
3801
+ score: anyValid ? bestValidScore ?? 1 : bestDoneScore ?? 0
3815
3802
  };
3816
3803
  }
3817
- function isNonEmptySpend(s) {
3818
- return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
3804
+ function readNestedScopeSeam(ctx) {
3805
+ const seam = ctx.seams[nestedScopeSeamKey];
3806
+ if (!seam || typeof seam.mount !== "function") {
3807
+ throw new ValidationError(
3808
+ `driverExecutor: missing required seam "${nestedScopeSeamKey}" \u2014 a driver child must be spawned through a Scope that seeds it (the keystone scope does)`
3809
+ );
3810
+ }
3811
+ return seam;
3812
+ }
3813
+ function isAgent(value) {
3814
+ return typeof value === "object" && value !== null && typeof value.act === "function" && typeof value.name === "string";
3815
+ }
3816
+
3817
+ // src/runtime/supervise/run-context.ts
3818
+ function createInMemoryRunContext(opts = {}) {
3819
+ const base = createExecutorRegistry();
3820
+ return {
3821
+ journal: new InMemorySpawnJournal(),
3822
+ blobs: new InMemoryResultBlobStore(),
3823
+ executors: opts.withDriver ? withDriverExecutor(base) : base
3824
+ };
3819
3825
  }
3820
3826
 
3821
3827
  // src/runtime/supervise/event-bus.ts
@@ -6545,11 +6551,14 @@ async function delegate(intent, opts = {}) {
6545
6551
  }
6546
6552
 
6547
6553
  export {
6548
- assertModelAllowed,
6549
- gateOnDeliverable,
6550
6554
  contentAddress,
6551
6555
  InMemoryResultBlobStore,
6552
6556
  InMemorySpawnJournal,
6557
+ assertModelAllowed,
6558
+ routerChatWithUsage,
6559
+ routerChatWithTools,
6560
+ routerToolLoop,
6561
+ routerBrain,
6553
6562
  defineRuntimeHooks,
6554
6563
  composeRuntimeHooks,
6555
6564
  notifyRuntimeHookEvent,
@@ -6557,14 +6566,14 @@ export {
6557
6566
  createScope,
6558
6567
  settledToIteration,
6559
6568
  withDriverExecutor,
6569
+ spendFromUsageEvents,
6570
+ createBudgetPool,
6571
+ createSupervisor,
6572
+ gateOnDeliverable,
6560
6573
  createWorktree,
6561
6574
  captureWorktreeDiff,
6562
6575
  removeWorktree,
6563
6576
  runWorktreeHarness,
6564
- routerChatWithUsage,
6565
- routerChatWithTools,
6566
- routerToolLoop,
6567
- routerBrain,
6568
6577
  acquireSandbox,
6569
6578
  probeSandboxCapabilities,
6570
6579
  createSandboxLineage,
@@ -6577,9 +6586,6 @@ export {
6577
6586
  createExecutor,
6578
6587
  createExecutorRegistry,
6579
6588
  createInMemoryRunContext,
6580
- spendFromUsageEvents,
6581
- createBudgetPool,
6582
- createSupervisor,
6583
6589
  createEventBus,
6584
6590
  createCoordinationTools,
6585
6591
  driverAgent,
@@ -6638,4 +6644,4 @@ export {
6638
6644
  workerFromBackend,
6639
6645
  supervise
6640
6646
  };
6641
- //# sourceMappingURL=chunk-QSNSMJSZ.js.map
6647
+ //# sourceMappingURL=chunk-NC66AM3S.js.map