@tangle-network/agent-runtime 0.83.0 → 0.85.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.
@@ -1162,480 +1162,6 @@ function hashJson(value) {
1162
1162
  return (h >>> 0).toString(16).padStart(8, "0");
1163
1163
  }
1164
1164
 
1165
- // src/runtime/supervise/scope.ts
1166
- var nestedScopeSeamKey = "nested-scope";
1167
- function makeNestedScopeSeam(args, childNodeId) {
1168
- return {
1169
- depth: args.depth,
1170
- ...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
1171
- journalRoot: args.root,
1172
- mount(nestedRoot, signal) {
1173
- return createScope({
1174
- parentId: childNodeId,
1175
- root: nestedRoot,
1176
- pool: args.pool,
1177
- journal: args.journal,
1178
- blobs: args.blobs,
1179
- executors: args.executors,
1180
- // Re-seed the parent's NON-recursion seams (sandbox/router for leaf grandchildren);
1181
- // the nested scope adds its OWN nested-scope seam per child in `spawn`.
1182
- seams: args.seams,
1183
- depth: args.depth + 1,
1184
- ...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
1185
- signal,
1186
- ...args.now ? { now: args.now } : {},
1187
- ...args.hooks ? { hooks: args.hooks } : {}
1188
- });
1189
- }
1190
- };
1191
- }
1192
- function createScope(args) {
1193
- const children = /* @__PURE__ */ new Map();
1194
- let spawnOrdinal = 0;
1195
- let cursorSeq = 0;
1196
- let meterSeq = 0;
1197
- const now = args.now ?? Date.now;
1198
- function spawn4(agent, task, opts) {
1199
- if (args.maxDepth !== void 0 && args.depth >= args.maxDepth) {
1200
- return { ok: false, reason: "depth-exceeded" };
1201
- }
1202
- const spec = agent.executorSpec;
1203
- if (!isAgentSpec(spec)) {
1204
- throw new ValidationError(
1205
- `scope.spawn: agent "${agent.name}" exposes no \`executorSpec\` (AgentSpec) to resolve a Executor`
1206
- );
1207
- }
1208
- const resolved = args.executors.resolve(spec);
1209
- if (!resolved.succeeded) throw new ValidationError(`scope.spawn: ${resolved.error}`);
1210
- const reservation = args.pool.reserve(opts.budget);
1211
- if (!reservation.ok) return { ok: false, reason: reservation.reason };
1212
- try {
1213
- const ordinal = spawnOrdinal++;
1214
- const id = `${args.parentId}:s${ordinal}`;
1215
- const childAbort = new AbortController();
1216
- const cascadeAbort = () => childAbort.abort();
1217
- if (args.signal.aborted) childAbort.abort();
1218
- else args.signal.addEventListener("abort", cascadeAbort, { once: true });
1219
- const ctx = {
1220
- signal: childAbort.signal,
1221
- seams: { ...args.seams, [nestedScopeSeamKey]: makeNestedScopeSeam(args, id) }
1222
- };
1223
- const executor = resolved.value(spec, ctx);
1224
- const handle = {
1225
- id,
1226
- label: opts.label,
1227
- get status() {
1228
- return children.get(id)?.status ?? "cancelled";
1229
- },
1230
- abort(reason) {
1231
- childAbort.abort(reason);
1232
- }
1233
- };
1234
- const live = {
1235
- id,
1236
- status: "acquiring",
1237
- runtime: executor.runtime,
1238
- budget: opts.budget,
1239
- label: opts.label,
1240
- spent: zeroSpend(),
1241
- settled: void 0,
1242
- delivered: false,
1243
- executorDone: false,
1244
- ...executor.deliver ? { deliver: executor.deliver.bind(executor) } : {}
1245
- };
1246
- children.set(id, live);
1247
- void args.journal.appendEvent(args.root, {
1248
- kind: "spawned",
1249
- id,
1250
- parent: args.parentId,
1251
- label: opts.label,
1252
- budget: opts.budget,
1253
- runtime: executor.runtime,
1254
- seq: ordinal,
1255
- at: new Date(now()).toISOString()
1256
- });
1257
- notifyRuntimeHookEvent(
1258
- args.hooks,
1259
- {
1260
- id: `${id}:spawn`,
1261
- runId: args.root,
1262
- target: "agent.spawn",
1263
- phase: "after",
1264
- timestamp: now(),
1265
- stepIndex: ordinal,
1266
- parentId: args.parentId,
1267
- payload: {
1268
- childId: id,
1269
- label: opts.label,
1270
- runtime: executor.runtime,
1271
- budget: opts.budget,
1272
- depth: args.depth
1273
- }
1274
- },
1275
- { signal: args.signal }
1276
- );
1277
- const settled = runChild(
1278
- live,
1279
- executor,
1280
- childAbort,
1281
- task,
1282
- opts,
1283
- args.pool,
1284
- reservation.ticket,
1285
- args.blobs
1286
- ).then((s) => {
1287
- live.resolved = s;
1288
- return s;
1289
- }).finally(() => {
1290
- args.signal.removeEventListener("abort", cascadeAbort);
1291
- });
1292
- live.settled = settled;
1293
- return { ok: true, handle };
1294
- } catch (err) {
1295
- args.pool.reconcile(reservation.ticket, zeroSpend());
1296
- throw err;
1297
- }
1298
- }
1299
- async function next() {
1300
- const undelivered = () => [...children.values()].filter((c) => !c.delivered);
1301
- if (undelivered().length === 0) return null;
1302
- for (; ; ) {
1303
- const pending = undelivered();
1304
- if (pending.length === 0) return null;
1305
- const ready = pending.find((c) => c.resolved !== void 0);
1306
- const chosen = ready ?? await raceFirstSettled(pending);
1307
- if (chosen.delivered) continue;
1308
- chosen.delivered = true;
1309
- const seq = cursorSeq++;
1310
- const settlement = chosen.resolved;
1311
- if (!settlement) {
1312
- throw new ValidationError(
1313
- `scope.next: child '${chosen.id}' won the settle race without a resolved value`
1314
- );
1315
- }
1316
- return finalizeSettlement(chosen, settlement, seq, args, now);
1317
- }
1318
- }
1319
- async function nextResolved() {
1320
- for (; ; ) {
1321
- const candidates = [...children.values()].filter((c) => !c.delivered);
1322
- const pick = candidates.find((c) => c.resolved !== void 0) ?? candidates.find((c) => c.executorDone);
1323
- if (!pick) return null;
1324
- const settlement = await pick.settled;
1325
- if (pick.delivered) continue;
1326
- pick.delivered = true;
1327
- const seq = cursorSeq++;
1328
- return finalizeSettlement(pick, settlement, seq, args, now);
1329
- }
1330
- }
1331
- function send(nodeId, msg) {
1332
- const child = children.get(nodeId);
1333
- if (!child || child.delivered || !child.deliver) return false;
1334
- child.deliver(msg);
1335
- return true;
1336
- }
1337
- async function meter(spend, detail) {
1338
- const seq = meterSeq++;
1339
- args.pool.observe(spend);
1340
- await args.journal.appendEvent(args.root, {
1341
- kind: "metered",
1342
- id: args.parentId,
1343
- spend,
1344
- seq,
1345
- at: new Date(now()).toISOString()
1346
- });
1347
- notifyRuntimeHookEvent(
1348
- args.hooks,
1349
- {
1350
- id: `${args.parentId}:meter:${seq}`,
1351
- runId: args.root,
1352
- target: "agent.turn",
1353
- phase: "after",
1354
- timestamp: now(),
1355
- parentId: args.parentId,
1356
- payload: { spend, ...detail ?? {} }
1357
- },
1358
- { signal: args.signal }
1359
- );
1360
- }
1361
- return {
1362
- spawn: spawn4,
1363
- next,
1364
- nextResolved,
1365
- send,
1366
- signal: args.signal,
1367
- meter,
1368
- get view() {
1369
- return makeTreeView(args.parentId, children);
1370
- },
1371
- get budget() {
1372
- return args.pool.readout();
1373
- }
1374
- };
1375
- }
1376
- async function raceFirstSettled(pending) {
1377
- return Promise.race(pending.map((c) => c.settled.then(() => c)));
1378
- }
1379
- async function finalizeSettlement(child, settlement, seq, args, now) {
1380
- const handle = frozenHandle(child);
1381
- if (settlement.kind === "down") {
1382
- child.status = "failed";
1383
- await args.journal.appendEvent(args.root, {
1384
- kind: "settled",
1385
- id: child.id,
1386
- status: "down",
1387
- spent: child.spent,
1388
- infra: settlement.infra,
1389
- seq,
1390
- at: new Date(now()).toISOString()
1391
- });
1392
- if (settlement.metered) {
1393
- await args.journal.appendEvent(args.root, {
1394
- kind: "metered",
1395
- id: child.id,
1396
- spend: settlement.metered,
1397
- seq,
1398
- at: new Date(now()).toISOString()
1399
- });
1400
- }
1401
- notifyRuntimeHookEvent(
1402
- args.hooks,
1403
- {
1404
- id: `${child.id}:settled`,
1405
- runId: args.root,
1406
- target: "agent.child",
1407
- phase: "after",
1408
- timestamp: now(),
1409
- stepIndex: seq,
1410
- parentId: args.parentId,
1411
- payload: {
1412
- childId: child.id,
1413
- status: "down",
1414
- reason: settlement.reason,
1415
- infra: settlement.infra,
1416
- spent: child.spent
1417
- }
1418
- },
1419
- { signal: args.signal }
1420
- );
1421
- return {
1422
- kind: "down",
1423
- handle,
1424
- reason: settlement.reason,
1425
- infra: settlement.infra,
1426
- restartCount: settlement.restartCount,
1427
- seq
1428
- };
1429
- }
1430
- child.status = "done";
1431
- child.outRef = settlement.outRef;
1432
- child.spent = settlement.spent;
1433
- await args.journal.appendEvent(args.root, {
1434
- kind: "settled",
1435
- id: child.id,
1436
- status: "done",
1437
- outRef: settlement.outRef,
1438
- ...settlement.verdict ? { verdict: settlement.verdict } : {},
1439
- spent: settlement.spent,
1440
- seq,
1441
- at: new Date(now()).toISOString()
1442
- });
1443
- if (settlement.metered) {
1444
- await args.journal.appendEvent(args.root, {
1445
- kind: "metered",
1446
- id: child.id,
1447
- spend: settlement.metered,
1448
- seq,
1449
- at: new Date(now()).toISOString()
1450
- });
1451
- }
1452
- notifyRuntimeHookEvent(
1453
- args.hooks,
1454
- {
1455
- id: `${child.id}:settled`,
1456
- runId: args.root,
1457
- target: "agent.child",
1458
- phase: "after",
1459
- timestamp: now(),
1460
- stepIndex: seq,
1461
- parentId: args.parentId,
1462
- payload: {
1463
- childId: child.id,
1464
- status: "done",
1465
- outRef: settlement.outRef,
1466
- score: settlement.verdict?.score,
1467
- valid: settlement.verdict?.valid,
1468
- spent: settlement.spent
1469
- }
1470
- },
1471
- { signal: args.signal }
1472
- );
1473
- return {
1474
- kind: "done",
1475
- handle,
1476
- out: settlement.out,
1477
- outRef: settlement.outRef,
1478
- ...settlement.verdict ? { verdict: settlement.verdict } : {},
1479
- spent: settlement.spent,
1480
- seq
1481
- };
1482
- }
1483
- async function runChild(live, executor, childAbort, task, opts, pool, ticket, blobs) {
1484
- let reconciled = false;
1485
- const reconcileOnce = (spend) => {
1486
- if (reconciled) return;
1487
- reconciled = true;
1488
- pool.reconcile(ticket, clampSpend(spend, opts.budget));
1489
- };
1490
- try {
1491
- live.status = "running";
1492
- const ran = executor.execute(task, childAbort.signal);
1493
- let artifact;
1494
- if (isAsyncIterable(ran)) {
1495
- const spend = await foldStream(ran);
1496
- live.spent = spend;
1497
- artifact = executor.resultArtifact();
1498
- reconcileOnce(spend);
1499
- } else {
1500
- const terminal = await ran;
1501
- live.spent = terminal.spent;
1502
- artifact = terminal;
1503
- reconcileOnce(terminal.spent);
1504
- }
1505
- live.executorDone = true;
1506
- const ownMetered = executor.metered?.();
1507
- if (childAbort.signal.aborted) {
1508
- await teardownSafe(executor, opts.shutdown ?? "brutalKill");
1509
- return downRecord("aborted before settle", true, ownMetered);
1510
- }
1511
- const outRef = contentAddress(artifact.out);
1512
- await blobs.put(outRef, artifact.out);
1513
- await teardownSafe(executor, opts.shutdown ?? "infinity");
1514
- return {
1515
- kind: "done",
1516
- out: artifact.out,
1517
- outRef,
1518
- ...artifact.verdict ? { verdict: artifact.verdict } : {},
1519
- spent: live.spent,
1520
- ...ownMetered ? { metered: ownMetered } : {}
1521
- };
1522
- } catch (err) {
1523
- live.executorDone = true;
1524
- reconcileOnce(live.spent);
1525
- await teardownSafe(executor, "brutalKill");
1526
- const aborted = childAbort.signal.aborted || isAbortError2(err);
1527
- return downRecord(errMessage(err), aborted || isInfraError(err), executor.metered?.());
1528
- }
1529
- }
1530
- function settledToIteration(settled) {
1531
- if (settled.kind === "down") {
1532
- throw new ValidationError(
1533
- `settledToIteration: cannot adapt a 'down' settlement (node '${settled.handle.id}', seq ${settled.seq}) to an Iteration`
1534
- );
1535
- }
1536
- return {
1537
- index: settled.seq,
1538
- task: void 0,
1539
- agentRunName: settled.handle.label,
1540
- output: settled.out,
1541
- ...settled.verdict ? { verdict: settled.verdict } : {},
1542
- events: [],
1543
- startedAt: 0,
1544
- endedAt: settled.spent.ms,
1545
- costUsd: settled.spent.usd,
1546
- tokenUsage: { input: settled.spent.tokens.input, output: settled.spent.tokens.output }
1547
- };
1548
- }
1549
- function makeTreeView(root, children) {
1550
- const nodes = [...children.values()].map((c) => ({
1551
- id: c.id,
1552
- parent: root,
1553
- label: c.label,
1554
- status: c.status,
1555
- runtime: c.runtime,
1556
- budget: c.budget,
1557
- spent: c.spent,
1558
- ...c.outRef ? { outRef: c.outRef } : {}
1559
- }));
1560
- return {
1561
- root,
1562
- nodes,
1563
- inFlight: nodes.filter((n) => n.status === "running" || n.status === "acquiring").length
1564
- };
1565
- }
1566
- function frozenHandle(child) {
1567
- return {
1568
- id: child.id,
1569
- label: child.label,
1570
- status: child.status,
1571
- abort() {
1572
- }
1573
- };
1574
- }
1575
- async function foldStream(stream) {
1576
- const tokens = { input: 0, output: 0 };
1577
- let usd = 0;
1578
- let iterations = 0;
1579
- for await (const ev of stream) {
1580
- if (ev.kind === "tokens") {
1581
- tokens.input += ev.input;
1582
- tokens.output += ev.output;
1583
- } else if (ev.kind === "cost") {
1584
- usd += ev.usd;
1585
- } else {
1586
- iterations += 1;
1587
- }
1588
- }
1589
- return { iterations, tokens, usd, ms: 0 };
1590
- }
1591
- function clampSpend(spend, budget) {
1592
- const totalTokens2 = spend.tokens.input + spend.tokens.output;
1593
- const tokensOk = totalTokens2 <= budget.maxTokens;
1594
- const itersOk = spend.iterations <= budget.maxIterations;
1595
- const usdOk = budget.maxUsd === void 0 || spend.usd <= budget.maxUsd;
1596
- if (tokensOk && itersOk && usdOk) return spend;
1597
- const ratio = !tokensOk && totalTokens2 > 0 ? budget.maxTokens / totalTokens2 : 1;
1598
- return {
1599
- iterations: Math.min(spend.iterations, budget.maxIterations),
1600
- tokens: ratio < 1 ? {
1601
- input: Math.floor(spend.tokens.input * ratio),
1602
- output: Math.floor(spend.tokens.output * ratio)
1603
- } : spend.tokens,
1604
- usd: budget.maxUsd === void 0 ? spend.usd : Math.min(spend.usd, budget.maxUsd),
1605
- ms: spend.ms
1606
- };
1607
- }
1608
- async function teardownSafe(executor, grace) {
1609
- try {
1610
- await executor.teardown(grace);
1611
- } catch {
1612
- }
1613
- }
1614
- function downRecord(reason, infra, metered) {
1615
- return { kind: "down", reason, infra, restartCount: 0, ...metered ? { metered } : {} };
1616
- }
1617
- function zeroSpend() {
1618
- return { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
1619
- }
1620
- function isAsyncIterable(value) {
1621
- return typeof value === "object" && value !== null && typeof value[Symbol.asyncIterator] === "function";
1622
- }
1623
- function isAgentSpec(value) {
1624
- if (typeof value !== "object" || value === null) return false;
1625
- const v = value;
1626
- return "profile" in v && "harness" in v;
1627
- }
1628
- function isAbortError2(err) {
1629
- return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
1630
- }
1631
- function isInfraError(err) {
1632
- return err instanceof ValidationError;
1633
- }
1634
- function errMessage(err) {
1635
- if (err instanceof Error) return err.message;
1636
- return String(err);
1637
- }
1638
-
1639
1165
  // src/mcp/worktree.ts
1640
1166
  import { spawn } from "child_process";
1641
1167
  async function runGitAsync(args, cwd, runner) {
@@ -2198,7 +1724,7 @@ function contentRef(prefix, value) {
2198
1724
  }
2199
1725
  return `${prefix}:${(h >>> 0).toString(16).padStart(8, "0")}`;
2200
1726
  }
2201
- function zeroSpend2() {
1727
+ function zeroSpend() {
2202
1728
  return { iterations: 0, tokens: zeroTokenUsage(), usd: 0, ms: 0 };
2203
1729
  }
2204
1730
  var routerInlineExecutor = (spec, ctx) => {
@@ -2604,7 +2130,7 @@ async function* streamCliLeaf(args) {
2604
2130
  );
2605
2131
  }
2606
2132
  const out = { content: chunks.join("") };
2607
- args.onArtifact({ outRef: contentRef("cli", out), out, spent: zeroSpend2() });
2133
+ args.onArtifact({ outRef: contentRef("cli", out), out, spent: zeroSpend() });
2608
2134
  yield { kind: "iteration" };
2609
2135
  }
2610
2136
  function killWithGrace(proc, grace) {
@@ -2637,590 +2163,1064 @@ function bridgeCellModel(seamModel, ctx) {
2637
2163
  if (!h) return m;
2638
2164
  return m.startsWith(`${h}/`) ? m : `${h}/${m}`;
2639
2165
  }
2640
- var bridgeExecutor = (spec, ctx) => {
2641
- const base = readSeam(ctx, bridgeSeamKey, "bridge");
2642
- const seam = { ...base, model: bridgeCellModel(base.model, ctx) };
2643
- if (!seam.bridgeUrl || !seam.bridgeBearer || !seam.model) {
2166
+ var bridgeExecutor = (spec, ctx) => {
2167
+ const base = readSeam(ctx, bridgeSeamKey, "bridge");
2168
+ const seam = { ...base, model: bridgeCellModel(base.model, ctx) };
2169
+ if (!seam.bridgeUrl || !seam.bridgeBearer || !seam.model) {
2170
+ throw new ValidationError(
2171
+ "bridgeExecutor: BridgeSeam.bridgeUrl + bridgeBearer + model required"
2172
+ );
2173
+ }
2174
+ const maxTurns = seam.maxTurns ?? 200;
2175
+ const sessionId = seam.sessionId ?? `bridge-${spec.profile.name ?? "worker"}-${randomUUID2()}`;
2176
+ const controller = new AbortController();
2177
+ const abortIfSignalled = () => {
2178
+ if (ctx.signal.aborted) controller.abort();
2179
+ };
2180
+ abortIfSignalled();
2181
+ if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
2182
+ const inbox = createInbox();
2183
+ let artifact;
2184
+ return {
2185
+ runtime: "cli",
2186
+ deliver: (m) => inbox.deliver(m),
2187
+ execute(task, signal) {
2188
+ return streamBridgeSession({
2189
+ task,
2190
+ signal,
2191
+ spec,
2192
+ seam,
2193
+ sessionId,
2194
+ maxTurns,
2195
+ inbox,
2196
+ controller,
2197
+ onArtifact: (a) => {
2198
+ artifact = a;
2199
+ }
2200
+ });
2201
+ },
2202
+ teardown(_grace) {
2203
+ controller.abort();
2204
+ return Promise.resolve({ destroyed: true });
2205
+ },
2206
+ resultArtifact() {
2207
+ if (!artifact) {
2208
+ throw new ValidationError("bridgeExecutor: resultArtifact() read before stream drained");
2209
+ }
2210
+ return { ...artifact, spent: artifact.spent };
2211
+ }
2212
+ };
2213
+ };
2214
+ async function* streamBridgeSession(args) {
2215
+ const { seam, inbox } = args;
2216
+ const started = Date.now();
2217
+ const external = mergeAbortSignals(args.signal, args.controller.signal);
2218
+ const tokens = zeroTokenUsage();
2219
+ let usd = 0;
2220
+ let turns = 0;
2221
+ let lastText = "";
2222
+ const toolCalls = [];
2223
+ let nextPrompt = taskToPrompt(args.task);
2224
+ const system = args.spec.profile.prompt?.systemPrompt;
2225
+ for (let t = 0; t < args.maxTurns; t += 1) {
2226
+ const pending = inbox.drain();
2227
+ if (pending.length) {
2228
+ const folded = inbox.fold(pending);
2229
+ nextPrompt = t === 0 && nextPrompt ? `${nextPrompt}
2230
+
2231
+ ${folded}` : folded;
2232
+ }
2233
+ if (nextPrompt === void 0) break;
2234
+ const messages = [];
2235
+ if (t === 0 && typeof system === "string" && system.length > 0) {
2236
+ messages.push({ role: "system", content: system });
2237
+ }
2238
+ messages.push({ role: "user", content: nextPrompt });
2239
+ nextPrompt = void 0;
2240
+ const interruptSig = inbox.freshInterrupt();
2241
+ const turnController = new AbortController();
2242
+ const abortTurn = () => turnController.abort();
2243
+ if (external.aborted) turnController.abort();
2244
+ else external.addEventListener("abort", abortTurn);
2245
+ interruptSig.addEventListener("abort", abortTurn, { once: true });
2246
+ const timer = seam.timeoutMs ? setTimeout(abortTurn, seam.timeoutMs) : void 0;
2247
+ const cleanup = () => {
2248
+ external.removeEventListener("abort", abortTurn);
2249
+ if (timer) clearTimeout(timer);
2250
+ };
2251
+ let res;
2252
+ try {
2253
+ res = await bridgeStreamPost(seam.bridgeUrl, {
2254
+ bearer: seam.bridgeBearer,
2255
+ sessionId: args.sessionId,
2256
+ body: {
2257
+ model: seam.model,
2258
+ stream: true,
2259
+ session_id: args.sessionId,
2260
+ ...seam.cwd ? { cwd: seam.cwd } : {},
2261
+ ...seam.agentProfile ? { agent_profile: seam.agentProfile } : {},
2262
+ messages
2263
+ },
2264
+ signal: turnController.signal
2265
+ });
2266
+ } catch (e) {
2267
+ cleanup();
2268
+ const interruptAbort = e instanceof DOMException && e.name === "AbortError" && interruptSig.aborted && !args.signal.aborted && !args.controller.signal.aborted;
2269
+ if (interruptAbort) continue;
2270
+ throw e;
2271
+ }
2272
+ if (!res.ok) {
2273
+ cleanup();
2274
+ throw new ValidationError(
2275
+ `bridgeExecutor: bridge ${res.status}: ${(await res.text()).slice(0, 300)}`
2276
+ );
2277
+ }
2278
+ if (!res.body) {
2279
+ cleanup();
2280
+ throw new ValidationError("bridgeExecutor: bridge response had no body to stream");
2281
+ }
2282
+ let turnText = "";
2283
+ try {
2284
+ for await (const chunk of parseSseChatStream(res.body)) {
2285
+ if (chunk.content) {
2286
+ turnText += chunk.content;
2287
+ yield { kind: "iteration" };
2288
+ }
2289
+ if (chunk.toolCall) toolCalls.push(chunk.toolCall);
2290
+ if (chunk.usage) {
2291
+ tokens.input += chunk.usage.input;
2292
+ tokens.output += chunk.usage.output;
2293
+ yield { kind: "tokens", input: chunk.usage.input, output: chunk.usage.output };
2294
+ }
2295
+ if (typeof chunk.cost === "number" && chunk.cost > 0) {
2296
+ usd += chunk.cost;
2297
+ yield { kind: "cost", usd: chunk.cost };
2298
+ }
2299
+ }
2300
+ } finally {
2301
+ cleanup();
2302
+ }
2303
+ turns += 1;
2304
+ if (turnText) lastText = turnText;
2305
+ if (inbox.pending() === 0) break;
2306
+ }
2307
+ const spent = {
2308
+ iterations: turns,
2309
+ tokens,
2310
+ usd,
2311
+ ms: Date.now() - started
2312
+ };
2313
+ const out = { content: lastText, toolCalls };
2314
+ args.onArtifact({
2315
+ outRef: contentRef("bridge", { model: seam.model, session: args.sessionId, content: lastText }),
2316
+ out,
2317
+ spent
2318
+ });
2319
+ }
2320
+ function bridgeStreamPost(url, args) {
2321
+ const target = new URL(`${url.replace(/\/$/, "")}/v1/chat/completions`);
2322
+ const payload = JSON.stringify(args.body);
2323
+ const requestFn = target.protocol === "https:" ? httpsRequest : httpRequest;
2324
+ return new Promise((resolve, reject) => {
2325
+ if (args.signal.aborted) {
2326
+ reject(new DOMException("bridgeExecutor: aborted before request", "AbortError"));
2327
+ return;
2328
+ }
2329
+ const req = requestFn(
2330
+ target,
2331
+ {
2332
+ method: "POST",
2333
+ headers: {
2334
+ "content-type": "application/json",
2335
+ authorization: `Bearer ${args.bearer}`,
2336
+ "x-session-id": args.sessionId,
2337
+ "content-length": Buffer.byteLength(payload)
2338
+ },
2339
+ // No header/body idle timeout: a slow bridge is a live bridge; the abort
2340
+ // signal is the sole deadline.
2341
+ timeout: 0
2342
+ },
2343
+ (res) => {
2344
+ const status = res.statusCode ?? 0;
2345
+ const ok = status >= 200 && status < 300;
2346
+ const body = Readable.toWeb(res);
2347
+ resolve({
2348
+ ok,
2349
+ status,
2350
+ body,
2351
+ text: async () => {
2352
+ const chunks = [];
2353
+ for await (const c of res) chunks.push(c);
2354
+ return Buffer.concat(chunks).toString("utf8");
2355
+ }
2356
+ });
2357
+ }
2358
+ );
2359
+ const onAbort = () => {
2360
+ req.destroy(new DOMException("bridgeExecutor: turn aborted", "AbortError"));
2361
+ };
2362
+ if (args.signal.aborted) onAbort();
2363
+ else args.signal.addEventListener("abort", onAbort, { once: true });
2364
+ req.on("error", (e) => {
2365
+ args.signal.removeEventListener("abort", onAbort);
2366
+ reject(e);
2367
+ });
2368
+ req.on("close", () => args.signal.removeEventListener("abort", onAbort));
2369
+ req.write(payload);
2370
+ req.end();
2371
+ });
2372
+ }
2373
+ async function* parseSseChatStream(body) {
2374
+ const reader = body.getReader();
2375
+ const decoder = new TextDecoder();
2376
+ let buf = "";
2377
+ try {
2378
+ for (; ; ) {
2379
+ const { done, value } = await reader.read();
2380
+ if (done) break;
2381
+ buf += decoder.decode(value, { stream: true });
2382
+ let sep = buf.indexOf("\n\n");
2383
+ while (sep !== -1) {
2384
+ const frame = buf.slice(0, sep);
2385
+ buf = buf.slice(sep + 2);
2386
+ const chunk = parseSseFrame(frame);
2387
+ if (chunk === "done") return;
2388
+ if (chunk) yield chunk;
2389
+ sep = buf.indexOf("\n\n");
2390
+ }
2391
+ }
2392
+ } finally {
2393
+ reader.releaseLock();
2394
+ }
2395
+ }
2396
+ function parseSseFrame(frame) {
2397
+ const dataLines = [];
2398
+ for (const rawLine of frame.split("\n")) {
2399
+ const line = rawLine.replace(/\r$/, "");
2400
+ if (!line || line.startsWith(":")) continue;
2401
+ if (line.startsWith("data:")) dataLines.push(line.slice("data:".length).trimStart());
2402
+ }
2403
+ if (dataLines.length === 0) return void 0;
2404
+ const data = dataLines.join("\n");
2405
+ if (data === "[DONE]") return "done";
2406
+ let parsed;
2407
+ try {
2408
+ parsed = JSON.parse(data);
2409
+ } catch {
2410
+ return void 0;
2411
+ }
2412
+ if (parsed.error) {
2413
+ throw new ValidationError(
2414
+ `bridgeExecutor: bridge stream error: ${parsed.error.message ?? "unknown"}`
2415
+ );
2416
+ }
2417
+ const out = {};
2418
+ const choice = parsed.choices?.[0];
2419
+ const content = choice?.delta?.content ?? choice?.message?.content;
2420
+ if (typeof content === "string" && content.length > 0) out.content = content;
2421
+ const toolName = choice?.delta?.tool_calls?.[0]?.function?.name;
2422
+ if (typeof toolName === "string" && toolName.length > 0) out.toolCall = toolName;
2423
+ const u = parsed.usage;
2424
+ if (u && (typeof u.prompt_tokens === "number" || typeof u.completion_tokens === "number")) {
2425
+ out.usage = { input: u.prompt_tokens ?? 0, output: u.completion_tokens ?? 0 };
2426
+ }
2427
+ if (typeof u?.cost === "number") out.cost = u.cost;
2428
+ return Object.keys(out).length > 0 ? out : void 0;
2429
+ }
2430
+ function bridgeWorktreeExecutor(spec, ctx, seam) {
2431
+ const bridge = seam.bridge;
2432
+ if (!bridge) {
2433
+ throw new ValidationError("cliWorktreeExecutor: bridge transport missing");
2434
+ }
2435
+ if (!bridge.bridgeUrl || !bridge.bridgeBearer) {
2644
2436
  throw new ValidationError(
2645
- "bridgeExecutor: BridgeSeam.bridgeUrl + bridgeBearer + model required"
2437
+ "cliWorktreeExecutor: bridge.bridgeUrl + bridge.bridgeBearer required"
2646
2438
  );
2647
2439
  }
2648
- const maxTurns = seam.maxTurns ?? 200;
2649
- const sessionId = seam.sessionId ?? `bridge-${spec.profile.name ?? "worker"}-${randomUUID2()}`;
2440
+ const runId = seam.runId ?? randomUUID2();
2441
+ const sessionId = bridge.sessionId ?? `bridge-worktree-${runId}`;
2650
2442
  const controller = new AbortController();
2651
- const abortIfSignalled = () => {
2652
- if (ctx.signal.aborted) controller.abort();
2653
- };
2654
- abortIfSignalled();
2655
- if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
2656
- const inbox = createInbox();
2443
+ const pending = [];
2444
+ let inner;
2445
+ let worktree;
2446
+ let removed = false;
2657
2447
  let artifact;
2448
+ const cleanupWorktree = async () => {
2449
+ if (!worktree || removed) return;
2450
+ const target = worktree;
2451
+ removed = true;
2452
+ worktree = void 0;
2453
+ await removeWorktree({
2454
+ worktree: target,
2455
+ repoRoot: seam.repoRoot,
2456
+ ...seam.runGit ? { runGit: seam.runGit } : {}
2457
+ }).catch(() => void 0);
2458
+ };
2459
+ const deliver = (msg) => {
2460
+ if (inner?.deliver) {
2461
+ inner.deliver(msg);
2462
+ return;
2463
+ }
2464
+ pending.push(msg);
2465
+ };
2658
2466
  return {
2659
2467
  runtime: "cli",
2660
- deliver: (m) => inbox.deliver(m),
2661
- execute(task, signal) {
2662
- return streamBridgeSession({
2663
- task,
2664
- signal,
2665
- spec,
2666
- seam,
2667
- sessionId,
2668
- maxTurns,
2669
- inbox,
2670
- controller,
2671
- onArtifact: (a) => {
2672
- artifact = a;
2468
+ budgetExempt: seam.budgetExempt ?? false,
2469
+ deliver,
2470
+ execute(_task, signal) {
2471
+ return (async function* bridgeWorktreeStream() {
2472
+ const started = Date.now();
2473
+ const linked = mergeAbortSignals(signal, controller.signal);
2474
+ let bridgeArtifact;
2475
+ try {
2476
+ worktree = await createWorktree({
2477
+ repoRoot: seam.repoRoot,
2478
+ runId,
2479
+ ...seam.baseRef ? { baseRef: seam.baseRef } : {},
2480
+ ...seam.runGit ? { runGit: seam.runGit } : {}
2481
+ });
2482
+ removed = false;
2483
+ const bridgeSeam = {
2484
+ bridgeUrl: bridge.bridgeUrl,
2485
+ bridgeBearer: bridge.bridgeBearer,
2486
+ model: resolveBridgeWorktreeModel(spec, bridge),
2487
+ cwd: worktree.path,
2488
+ sessionId,
2489
+ ...bridge.agentProfile ? { agentProfile: bridge.agentProfile } : { agentProfile: spec.profile },
2490
+ ...bridge.timeoutMs !== void 0 ? { timeoutMs: bridge.timeoutMs } : {},
2491
+ ...bridge.maxTurns !== void 0 ? { maxTurns: bridge.maxTurns } : {}
2492
+ };
2493
+ const bridgeCtx = {
2494
+ ...ctx,
2495
+ signal: linked,
2496
+ seams: { ...ctx.seams, [bridgeSeamKey]: bridgeSeam }
2497
+ };
2498
+ inner = bridgeExecutor(spec, bridgeCtx);
2499
+ for (const msg of pending.splice(0)) inner.deliver?.(msg);
2500
+ const run = inner.execute(seam.taskPrompt, linked);
2501
+ if (isAsyncIterable(run)) {
2502
+ for await (const event of run) yield event;
2503
+ bridgeArtifact = inner.resultArtifact();
2504
+ } else {
2505
+ bridgeArtifact = await run;
2506
+ }
2507
+ const diff = await captureWorktreeDiff({
2508
+ worktree,
2509
+ ...seam.runGit ? { runGit: seam.runGit } : {}
2510
+ });
2511
+ const checks = await runWorktreeChecks({
2512
+ worktreePath: worktree.path,
2513
+ ...seam.testCmd !== void 0 ? { testCmd: seam.testCmd } : {},
2514
+ ...seam.typecheckCmd !== void 0 ? { typecheckCmd: seam.typecheckCmd } : {},
2515
+ timeoutMs: seam.checkTimeoutMs ?? seam.harnessTimeoutMs ?? bridge.timeoutMs ?? 5 * 60 * 1e3,
2516
+ cap: seam.checkOutputCap ?? 16e3,
2517
+ ...seam.runCommand ? { runCommand: seam.runCommand } : {},
2518
+ signal: linked
2519
+ });
2520
+ const result = {
2521
+ branch: worktree.branch,
2522
+ patch: diff.patch,
2523
+ stats: diff.stats,
2524
+ harness: {
2525
+ name: "bridge",
2526
+ exitCode: null,
2527
+ timedOut: false,
2528
+ killedBySignal: null,
2529
+ durationMs: bridgeArtifact.spent.ms || Date.now() - started,
2530
+ stdout: bridgeOutputText(bridgeArtifact.out),
2531
+ stderr: ""
2532
+ },
2533
+ ...checks ? { checks } : {}
2534
+ };
2535
+ const spent = {
2536
+ ...bridgeArtifact.spent,
2537
+ ms: bridgeArtifact.spent.ms || Date.now() - started
2538
+ };
2539
+ artifact = {
2540
+ outRef: contentRef("bridge-worktree", { sessionId, result }),
2541
+ out: result,
2542
+ spent
2543
+ };
2544
+ } catch (err) {
2545
+ controller.abort();
2546
+ await inner?.teardown("brutalKill").catch(() => void 0);
2547
+ await cleanupWorktree();
2548
+ throw err;
2673
2549
  }
2674
- });
2550
+ })();
2675
2551
  },
2676
- teardown(_grace) {
2552
+ async teardown(grace) {
2677
2553
  controller.abort();
2678
- return Promise.resolve({ destroyed: true });
2679
- },
2680
- resultArtifact() {
2681
- if (!artifact) {
2682
- throw new ValidationError("bridgeExecutor: resultArtifact() read before stream drained");
2683
- }
2684
- return { ...artifact, spent: artifact.spent };
2685
- }
2686
- };
2687
- };
2688
- async function* streamBridgeSession(args) {
2689
- const { seam, inbox } = args;
2690
- const started = Date.now();
2691
- const external = mergeAbortSignals(args.signal, args.controller.signal);
2692
- const tokens = zeroTokenUsage();
2693
- let usd = 0;
2694
- let turns = 0;
2695
- let lastText = "";
2696
- const toolCalls = [];
2697
- let nextPrompt = taskToPrompt(args.task);
2698
- const system = args.spec.profile.prompt?.systemPrompt;
2699
- for (let t = 0; t < args.maxTurns; t += 1) {
2700
- const pending = inbox.drain();
2701
- if (pending.length) {
2702
- const folded = inbox.fold(pending);
2703
- nextPrompt = t === 0 && nextPrompt ? `${nextPrompt}
2704
-
2705
- ${folded}` : folded;
2706
- }
2707
- if (nextPrompt === void 0) break;
2708
- const messages = [];
2709
- if (t === 0 && typeof system === "string" && system.length > 0) {
2710
- messages.push({ role: "system", content: system });
2711
- }
2712
- messages.push({ role: "user", content: nextPrompt });
2713
- nextPrompt = void 0;
2714
- const interruptSig = inbox.freshInterrupt();
2715
- const turnController = new AbortController();
2716
- const abortTurn = () => turnController.abort();
2717
- if (external.aborted) turnController.abort();
2718
- else external.addEventListener("abort", abortTurn);
2719
- interruptSig.addEventListener("abort", abortTurn, { once: true });
2720
- const timer = seam.timeoutMs ? setTimeout(abortTurn, seam.timeoutMs) : void 0;
2721
- const cleanup = () => {
2722
- external.removeEventListener("abort", abortTurn);
2723
- if (timer) clearTimeout(timer);
2724
- };
2725
- let res;
2726
- try {
2727
- res = await bridgeStreamPost(seam.bridgeUrl, {
2728
- bearer: seam.bridgeBearer,
2729
- sessionId: args.sessionId,
2730
- body: {
2731
- model: seam.model,
2732
- stream: true,
2733
- session_id: args.sessionId,
2734
- ...seam.cwd ? { cwd: seam.cwd } : {},
2735
- ...seam.agentProfile ? { agent_profile: seam.agentProfile } : {},
2736
- messages
2737
- },
2738
- signal: turnController.signal
2739
- });
2740
- } catch (e) {
2741
- cleanup();
2742
- const interruptAbort = e instanceof DOMException && e.name === "AbortError" && interruptSig.aborted && !args.signal.aborted && !args.controller.signal.aborted;
2743
- if (interruptAbort) continue;
2744
- throw e;
2745
- }
2746
- if (!res.ok) {
2747
- cleanup();
2748
- throw new ValidationError(
2749
- `bridgeExecutor: bridge ${res.status}: ${(await res.text()).slice(0, 300)}`
2750
- );
2751
- }
2752
- if (!res.body) {
2753
- cleanup();
2754
- throw new ValidationError("bridgeExecutor: bridge response had no body to stream");
2755
- }
2756
- let turnText = "";
2757
- try {
2758
- for await (const chunk of parseSseChatStream(res.body)) {
2759
- if (chunk.content) {
2760
- turnText += chunk.content;
2761
- yield { kind: "iteration" };
2762
- }
2763
- if (chunk.toolCall) toolCalls.push(chunk.toolCall);
2764
- if (chunk.usage) {
2765
- tokens.input += chunk.usage.input;
2766
- tokens.output += chunk.usage.output;
2767
- yield { kind: "tokens", input: chunk.usage.input, output: chunk.usage.output };
2768
- }
2769
- if (typeof chunk.cost === "number" && chunk.cost > 0) {
2770
- usd += chunk.cost;
2771
- yield { kind: "cost", usd: chunk.cost };
2554
+ let destroyed = true;
2555
+ try {
2556
+ if (inner) {
2557
+ destroyed = (await inner.teardown(grace)).destroyed;
2772
2558
  }
2559
+ } finally {
2560
+ await cleanupWorktree();
2773
2561
  }
2774
- } finally {
2775
- cleanup();
2562
+ return { destroyed };
2563
+ },
2564
+ resultArtifact() {
2565
+ if (!artifact) {
2566
+ throw new ValidationError(
2567
+ "cliWorktreeExecutor: bridge resultArtifact() read before stream drained"
2568
+ );
2569
+ }
2570
+ return artifact;
2776
2571
  }
2777
- turns += 1;
2778
- if (turnText) lastText = turnText;
2779
- if (inbox.pending() === 0) break;
2780
- }
2781
- const spent = {
2782
- iterations: turns,
2783
- tokens,
2784
- usd,
2785
- ms: Date.now() - started
2786
2572
  };
2787
- const out = { content: lastText, toolCalls };
2788
- args.onArtifact({
2789
- outRef: contentRef("bridge", { model: seam.model, session: args.sessionId, content: lastText }),
2790
- out,
2791
- spent
2792
- });
2793
- }
2794
- function bridgeStreamPost(url, args) {
2795
- const target = new URL(`${url.replace(/\/$/, "")}/v1/chat/completions`);
2796
- const payload = JSON.stringify(args.body);
2797
- const requestFn = target.protocol === "https:" ? httpsRequest : httpRequest;
2798
- return new Promise((resolve, reject) => {
2799
- if (args.signal.aborted) {
2800
- reject(new DOMException("bridgeExecutor: aborted before request", "AbortError"));
2801
- return;
2802
- }
2803
- const req = requestFn(
2804
- target,
2805
- {
2806
- method: "POST",
2807
- headers: {
2808
- "content-type": "application/json",
2809
- authorization: `Bearer ${args.bearer}`,
2810
- "x-session-id": args.sessionId,
2811
- "content-length": Buffer.byteLength(payload)
2812
- },
2813
- // No header/body idle timeout: a slow bridge is a live bridge; the abort
2814
- // signal is the sole deadline.
2815
- timeout: 0
2816
- },
2817
- (res) => {
2818
- const status = res.statusCode ?? 0;
2819
- const ok = status >= 200 && status < 300;
2820
- const body = Readable.toWeb(res);
2821
- resolve({
2822
- ok,
2823
- status,
2824
- body,
2825
- text: async () => {
2826
- const chunks = [];
2827
- for await (const c of res) chunks.push(c);
2828
- return Buffer.concat(chunks).toString("utf8");
2829
- }
2830
- });
2831
- }
2832
- );
2833
- const onAbort = () => {
2834
- req.destroy(new DOMException("bridgeExecutor: turn aborted", "AbortError"));
2835
- };
2836
- if (args.signal.aborted) onAbort();
2837
- else args.signal.addEventListener("abort", onAbort, { once: true });
2838
- req.on("error", (e) => {
2839
- args.signal.removeEventListener("abort", onAbort);
2840
- reject(e);
2841
- });
2842
- req.on("close", () => args.signal.removeEventListener("abort", onAbort));
2843
- req.write(payload);
2844
- req.end();
2845
- });
2846
2573
  }
2847
- async function* parseSseChatStream(body) {
2848
- const reader = body.getReader();
2849
- const decoder = new TextDecoder();
2850
- let buf = "";
2851
- try {
2852
- for (; ; ) {
2853
- const { done, value } = await reader.read();
2854
- if (done) break;
2855
- buf += decoder.decode(value, { stream: true });
2856
- let sep = buf.indexOf("\n\n");
2857
- while (sep !== -1) {
2858
- const frame = buf.slice(0, sep);
2859
- buf = buf.slice(sep + 2);
2860
- const chunk = parseSseFrame(frame);
2861
- if (chunk === "done") return;
2862
- if (chunk) yield chunk;
2863
- sep = buf.indexOf("\n\n");
2864
- }
2865
- }
2866
- } finally {
2867
- reader.releaseLock();
2868
- }
2574
+ function resolveBridgeWorktreeModel(spec, bridge) {
2575
+ if (bridge.model) return bridge.model;
2576
+ const model = spec.profile.model?.default;
2577
+ if (typeof model === "string" && model.length > 0) return model;
2578
+ throw new ValidationError(
2579
+ "cliWorktreeExecutor: bridge.model or AgentProfile.model.default required"
2580
+ );
2869
2581
  }
2870
- function parseSseFrame(frame) {
2871
- const dataLines = [];
2872
- for (const rawLine of frame.split("\n")) {
2873
- const line = rawLine.replace(/\r$/, "");
2874
- if (!line || line.startsWith(":")) continue;
2875
- if (line.startsWith("data:")) dataLines.push(line.slice("data:".length).trimStart());
2582
+ function bridgeOutputText(out) {
2583
+ if (typeof out === "string") return out;
2584
+ if (out && typeof out === "object") {
2585
+ const content = out.content;
2586
+ if (typeof content === "string") return content;
2876
2587
  }
2877
- if (dataLines.length === 0) return void 0;
2878
- const data = dataLines.join("\n");
2879
- if (data === "[DONE]") return "done";
2880
- let parsed;
2881
2588
  try {
2882
- parsed = JSON.parse(data);
2589
+ return JSON.stringify(out) ?? String(out);
2883
2590
  } catch {
2884
- return void 0;
2591
+ return String(out);
2885
2592
  }
2886
- if (parsed.error) {
2593
+ }
2594
+ function isAsyncIterable(value) {
2595
+ return value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function";
2596
+ }
2597
+ var cliWorktreeExecutor = (spec, ctx) => {
2598
+ const seam = readSeam(ctx, cliWorktreeSeamKey, "cli-worktree");
2599
+ if (!seam.repoRoot || !seam.taskPrompt) {
2600
+ throw new ValidationError("cliWorktreeExecutor: CliWorktreeSeam.repoRoot + taskPrompt required");
2601
+ }
2602
+ if (seam.bridge) return bridgeWorktreeExecutor(spec, ctx, seam);
2603
+ if (!seam.harness) {
2887
2604
  throw new ValidationError(
2888
- `bridgeExecutor: bridge stream error: ${parsed.error.message ?? "unknown"}`
2605
+ "cliWorktreeExecutor: CliWorktreeSeam.harness required when bridge is not set"
2889
2606
  );
2890
2607
  }
2891
- const out = {};
2892
- const choice = parsed.choices?.[0];
2893
- const content = choice?.delta?.content ?? choice?.message?.content;
2894
- if (typeof content === "string" && content.length > 0) out.content = content;
2895
- const toolName = choice?.delta?.tool_calls?.[0]?.function?.name;
2896
- if (typeof toolName === "string" && toolName.length > 0) out.toolCall = toolName;
2897
- const u = parsed.usage;
2898
- if (u && (typeof u.prompt_tokens === "number" || typeof u.completion_tokens === "number")) {
2899
- out.usage = { input: u.prompt_tokens ?? 0, output: u.completion_tokens ?? 0 };
2608
+ return createWorktreeCliExecutor({
2609
+ repoRoot: seam.repoRoot,
2610
+ profile: spec.profile,
2611
+ harness: seam.harness,
2612
+ taskPrompt: seam.taskPrompt,
2613
+ ...seam.runId ? { runId: seam.runId } : {},
2614
+ ...seam.baseRef ? { baseRef: seam.baseRef } : {},
2615
+ ...seam.harnessTimeoutMs !== void 0 ? { harnessTimeoutMs: seam.harnessTimeoutMs } : {},
2616
+ ...seam.testCmd !== void 0 ? { testCmd: seam.testCmd } : {},
2617
+ ...seam.typecheckCmd !== void 0 ? { typecheckCmd: seam.typecheckCmd } : {},
2618
+ ...seam.checkTimeoutMs !== void 0 ? { checkTimeoutMs: seam.checkTimeoutMs } : {},
2619
+ ...seam.checkOutputCap !== void 0 ? { checkOutputCap: seam.checkOutputCap } : {},
2620
+ ...seam.runGit ? { runGit: seam.runGit } : {},
2621
+ ...seam.runCommand ? { runCommand: seam.runCommand } : {},
2622
+ ...seam.budgetExempt !== void 0 ? { budgetExempt: seam.budgetExempt } : {}
2623
+ });
2624
+ };
2625
+ function createExecutor(config) {
2626
+ return (spec, ctx) => {
2627
+ const { backend, ...seam } = config;
2628
+ const seamed = { ...ctx, seams: { ...ctx.seams, [backend]: seam } };
2629
+ switch (config.backend) {
2630
+ case "router":
2631
+ return routerInlineExecutor(spec, seamed);
2632
+ case "router-tools":
2633
+ return routerToolsInlineExecutor(spec, seamed);
2634
+ case "bridge":
2635
+ return bridgeExecutor(spec, seamed);
2636
+ case "cli":
2637
+ return cliExecutor(spec, seamed);
2638
+ case "cli-worktree":
2639
+ return cliWorktreeExecutor(spec, seamed);
2640
+ case "provider": {
2641
+ const providerSeam = readSeam(seamed, providerSeamKey, "provider");
2642
+ const provider = resolveAgentEnvironmentProvider(
2643
+ providerSeam.provider,
2644
+ providerSeam.registry
2645
+ );
2646
+ return providerAsExecutor(provider, providerSeam)(spec, seamed);
2647
+ }
2648
+ case "sandbox": {
2649
+ const harness = spec.harness ?? config.harness ?? null;
2650
+ return sandboxExecutor({ ...spec, harness }, seamed);
2651
+ }
2652
+ }
2653
+ };
2654
+ }
2655
+ function createExecutorRegistry() {
2656
+ const factories = /* @__PURE__ */ new Map();
2657
+ factories.set("router", routerInlineExecutor);
2658
+ factories.set("inline", routerInlineExecutor);
2659
+ factories.set("sandbox", sandboxExecutor);
2660
+ factories.set("cli", cliExecutor);
2661
+ return {
2662
+ register(runtime, factory) {
2663
+ if (factories.has(runtime)) {
2664
+ throw new ValidationError(`executor registry: runtime "${runtime}" already registered`);
2665
+ }
2666
+ factories.set(runtime, factory);
2667
+ },
2668
+ resolve(spec) {
2669
+ if (spec.executor) {
2670
+ const byo = spec.executor;
2671
+ return { succeeded: true, value: (() => byo) };
2672
+ }
2673
+ if (spec.harness === null) {
2674
+ const f2 = factories.get("router");
2675
+ if (!f2) return { succeeded: false, error: 'executor registry: no "router" factory' };
2676
+ return { succeeded: true, value: f2 };
2677
+ }
2678
+ const runtimeTag = "sandbox";
2679
+ const f = factories.get(runtimeTag);
2680
+ if (!f) {
2681
+ return {
2682
+ succeeded: false,
2683
+ error: `executor registry: no factory for runtime "${runtimeTag}" (harness "${spec.harness}") and no BYO executor`
2684
+ };
2685
+ }
2686
+ return { succeeded: true, value: f };
2687
+ }
2688
+ };
2689
+ }
2690
+ function readSeam(ctx, key, who) {
2691
+ const seam = ctx.seams[key];
2692
+ if (seam === void 0 || seam === null) {
2693
+ throw new ValidationError(`${who} executor: missing required seam "${key}" on ExecutorContext`);
2694
+ }
2695
+ return seam;
2696
+ }
2697
+ function taskToPrompt(task) {
2698
+ if (typeof task === "string") return task;
2699
+ if (task && typeof task === "object") {
2700
+ const obj = task;
2701
+ for (const k of ["prompt", "content", "task", "message"]) {
2702
+ if (typeof obj[k] === "string") return obj[k];
2703
+ }
2704
+ }
2705
+ return JSON.stringify(task);
2706
+ }
2707
+ function taskToMessages(task, spec) {
2708
+ const messages = [];
2709
+ const system = spec.profile.prompt?.systemPrompt;
2710
+ if (typeof system === "string" && system.length > 0) {
2711
+ messages.push({ role: "system", content: system });
2900
2712
  }
2901
- if (typeof u?.cost === "number") out.cost = u.cost;
2902
- return Object.keys(out).length > 0 ? out : void 0;
2713
+ messages.push({ role: "user", content: taskToPrompt(task) });
2714
+ return messages;
2903
2715
  }
2904
- function bridgeWorktreeExecutor(spec, ctx, seam) {
2905
- const bridge = seam.bridge;
2906
- if (!bridge) {
2907
- throw new ValidationError("cliWorktreeExecutor: bridge transport missing");
2716
+ function singleShotDriver(maxIterations) {
2717
+ return {
2718
+ name: "leaf",
2719
+ plan(task, history) {
2720
+ return Promise.resolve(history.length >= maxIterations ? [] : [task]);
2721
+ },
2722
+ decide(history) {
2723
+ return history.length >= maxIterations ? "stop" : "continue";
2724
+ }
2725
+ };
2726
+ }
2727
+ function linkSignals2(a, b) {
2728
+ if (a.aborted || b.aborted) {
2729
+ const c2 = new AbortController();
2730
+ c2.abort();
2731
+ return c2.signal;
2908
2732
  }
2909
- if (!bridge.bridgeUrl || !bridge.bridgeBearer) {
2910
- throw new ValidationError(
2911
- "cliWorktreeExecutor: bridge.bridgeUrl + bridge.bridgeBearer required"
2912
- );
2733
+ const c = new AbortController();
2734
+ const onAbort = () => c.abort();
2735
+ a.addEventListener("abort", onAbort, { once: true });
2736
+ b.addEventListener("abort", onAbort, { once: true });
2737
+ return c.signal;
2738
+ }
2739
+ function mergeAbortSignals(...signals) {
2740
+ const c = new AbortController();
2741
+ const onAbort = () => c.abort();
2742
+ for (const s of signals) {
2743
+ if (s.aborted) {
2744
+ c.abort();
2745
+ break;
2746
+ }
2747
+ s.addEventListener("abort", onAbort, { once: true });
2913
2748
  }
2914
- const runId = seam.runId ?? randomUUID2();
2915
- const sessionId = bridge.sessionId ?? `bridge-worktree-${runId}`;
2916
- const controller = new AbortController();
2917
- const pending = [];
2918
- let inner;
2919
- let worktree;
2920
- let removed = false;
2921
- let artifact;
2922
- const cleanupWorktree = async () => {
2923
- if (!worktree || removed) return;
2924
- const target = worktree;
2925
- removed = true;
2926
- worktree = void 0;
2927
- await removeWorktree({
2928
- worktree: target,
2929
- repoRoot: seam.repoRoot,
2930
- ...seam.runGit ? { runGit: seam.runGit } : {}
2931
- }).catch(() => void 0);
2932
- };
2933
- const deliver = (msg) => {
2934
- if (inner?.deliver) {
2935
- inner.deliver(msg);
2936
- return;
2749
+ return c.signal;
2750
+ }
2751
+
2752
+ // src/runtime/supervise/scope.ts
2753
+ var nestedScopeSeamKey = "nested-scope";
2754
+ function makeNestedScopeSeam(args, childNodeId) {
2755
+ return {
2756
+ depth: args.depth,
2757
+ ...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
2758
+ journalRoot: args.root,
2759
+ mount(nestedRoot, signal) {
2760
+ return createScope({
2761
+ parentId: childNodeId,
2762
+ root: nestedRoot,
2763
+ pool: args.pool,
2764
+ journal: args.journal,
2765
+ blobs: args.blobs,
2766
+ executors: args.executors,
2767
+ // Re-seed the parent's NON-recursion seams (sandbox/router for leaf grandchildren);
2768
+ // the nested scope adds its OWN nested-scope seam per child in `spawn`.
2769
+ seams: args.seams,
2770
+ depth: args.depth + 1,
2771
+ ...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
2772
+ signal,
2773
+ ...args.now ? { now: args.now } : {},
2774
+ ...args.hooks ? { hooks: args.hooks } : {}
2775
+ });
2937
2776
  }
2938
- pending.push(msg);
2939
2777
  };
2940
- return {
2941
- runtime: "cli",
2942
- budgetExempt: seam.budgetExempt ?? false,
2943
- deliver,
2944
- execute(_task, signal) {
2945
- return (async function* bridgeWorktreeStream() {
2946
- const started = Date.now();
2947
- const linked = mergeAbortSignals(signal, controller.signal);
2948
- let bridgeArtifact;
2949
- try {
2950
- worktree = await createWorktree({
2951
- repoRoot: seam.repoRoot,
2952
- runId,
2953
- ...seam.baseRef ? { baseRef: seam.baseRef } : {},
2954
- ...seam.runGit ? { runGit: seam.runGit } : {}
2955
- });
2956
- removed = false;
2957
- const bridgeSeam = {
2958
- bridgeUrl: bridge.bridgeUrl,
2959
- bridgeBearer: bridge.bridgeBearer,
2960
- model: resolveBridgeWorktreeModel(spec, bridge),
2961
- cwd: worktree.path,
2962
- sessionId,
2963
- ...bridge.agentProfile ? { agentProfile: bridge.agentProfile } : { agentProfile: spec.profile },
2964
- ...bridge.timeoutMs !== void 0 ? { timeoutMs: bridge.timeoutMs } : {},
2965
- ...bridge.maxTurns !== void 0 ? { maxTurns: bridge.maxTurns } : {}
2966
- };
2967
- const bridgeCtx = {
2968
- ...ctx,
2969
- signal: linked,
2970
- seams: { ...ctx.seams, [bridgeSeamKey]: bridgeSeam }
2971
- };
2972
- inner = bridgeExecutor(spec, bridgeCtx);
2973
- for (const msg of pending.splice(0)) inner.deliver?.(msg);
2974
- const run = inner.execute(seam.taskPrompt, linked);
2975
- if (isAsyncIterable2(run)) {
2976
- for await (const event of run) yield event;
2977
- bridgeArtifact = inner.resultArtifact();
2978
- } else {
2979
- bridgeArtifact = await run;
2980
- }
2981
- const diff = await captureWorktreeDiff({
2982
- worktree,
2983
- ...seam.runGit ? { runGit: seam.runGit } : {}
2984
- });
2985
- const checks = await runWorktreeChecks({
2986
- worktreePath: worktree.path,
2987
- ...seam.testCmd !== void 0 ? { testCmd: seam.testCmd } : {},
2988
- ...seam.typecheckCmd !== void 0 ? { typecheckCmd: seam.typecheckCmd } : {},
2989
- timeoutMs: seam.checkTimeoutMs ?? seam.harnessTimeoutMs ?? bridge.timeoutMs ?? 5 * 60 * 1e3,
2990
- cap: seam.checkOutputCap ?? 16e3,
2991
- ...seam.runCommand ? { runCommand: seam.runCommand } : {},
2992
- signal: linked
2993
- });
2994
- const result = {
2995
- branch: worktree.branch,
2996
- patch: diff.patch,
2997
- stats: diff.stats,
2998
- harness: {
2999
- name: "bridge",
3000
- exitCode: null,
3001
- timedOut: false,
3002
- killedBySignal: null,
3003
- durationMs: bridgeArtifact.spent.ms || Date.now() - started,
3004
- stdout: bridgeOutputText(bridgeArtifact.out),
3005
- stderr: ""
3006
- },
3007
- ...checks ? { checks } : {}
3008
- };
3009
- const spent = {
3010
- ...bridgeArtifact.spent,
3011
- ms: bridgeArtifact.spent.ms || Date.now() - started
3012
- };
3013
- artifact = {
3014
- outRef: contentRef("bridge-worktree", { sessionId, result }),
3015
- out: result,
3016
- spent
3017
- };
3018
- } catch (err) {
3019
- controller.abort();
3020
- await inner?.teardown("brutalKill").catch(() => void 0);
3021
- await cleanupWorktree();
3022
- throw err;
3023
- }
3024
- })();
3025
- },
3026
- async teardown(grace) {
3027
- controller.abort();
3028
- let destroyed = true;
3029
- try {
3030
- if (inner) {
3031
- destroyed = (await inner.teardown(grace)).destroyed;
2778
+ }
2779
+ function createScope(args) {
2780
+ const children = /* @__PURE__ */ new Map();
2781
+ let spawnOrdinal = 0;
2782
+ let cursorSeq = 0;
2783
+ let meterSeq = 0;
2784
+ const now = args.now ?? Date.now;
2785
+ function spawn4(agent, task, opts) {
2786
+ if (args.maxDepth !== void 0 && args.depth >= args.maxDepth) {
2787
+ return { ok: false, reason: "depth-exceeded" };
2788
+ }
2789
+ const spec = agent.executorSpec;
2790
+ if (!isAgentSpec(spec)) {
2791
+ throw new ValidationError(
2792
+ `scope.spawn: agent "${agent.name}" exposes no \`executorSpec\` (AgentSpec) to resolve a Executor`
2793
+ );
2794
+ }
2795
+ const resolved = args.executors.resolve(spec);
2796
+ if (!resolved.succeeded) throw new ValidationError(`scope.spawn: ${resolved.error}`);
2797
+ const reservation = args.pool.reserve(opts.budget);
2798
+ if (!reservation.ok) return { ok: false, reason: reservation.reason };
2799
+ try {
2800
+ const ordinal = spawnOrdinal++;
2801
+ const id = `${args.parentId}:s${ordinal}`;
2802
+ const childAbort = new AbortController();
2803
+ const cascadeAbort = () => childAbort.abort();
2804
+ if (args.signal.aborted) childAbort.abort();
2805
+ else args.signal.addEventListener("abort", cascadeAbort, { once: true });
2806
+ const ctx = {
2807
+ signal: childAbort.signal,
2808
+ seams: { ...args.seams, [nestedScopeSeamKey]: makeNestedScopeSeam(args, id) }
2809
+ };
2810
+ const executor = resolved.value(spec, ctx);
2811
+ const handle = {
2812
+ id,
2813
+ label: opts.label,
2814
+ get status() {
2815
+ return children.get(id)?.status ?? "cancelled";
2816
+ },
2817
+ abort(reason) {
2818
+ childAbort.abort(reason);
3032
2819
  }
3033
- } finally {
3034
- await cleanupWorktree();
3035
- }
3036
- return { destroyed };
3037
- },
3038
- resultArtifact() {
3039
- if (!artifact) {
2820
+ };
2821
+ const live = {
2822
+ id,
2823
+ status: "acquiring",
2824
+ runtime: executor.runtime,
2825
+ budget: opts.budget,
2826
+ label: opts.label,
2827
+ spent: zeroSpend2(),
2828
+ settled: void 0,
2829
+ delivered: false,
2830
+ executorDone: false,
2831
+ ...executor.deliver ? { deliver: executor.deliver.bind(executor) } : {}
2832
+ };
2833
+ children.set(id, live);
2834
+ void args.journal.appendEvent(args.root, {
2835
+ kind: "spawned",
2836
+ id,
2837
+ parent: args.parentId,
2838
+ label: opts.label,
2839
+ budget: opts.budget,
2840
+ runtime: executor.runtime,
2841
+ seq: ordinal,
2842
+ at: new Date(now()).toISOString()
2843
+ });
2844
+ notifyRuntimeHookEvent(
2845
+ args.hooks,
2846
+ {
2847
+ id: `${id}:spawn`,
2848
+ runId: args.root,
2849
+ target: "agent.spawn",
2850
+ phase: "after",
2851
+ timestamp: now(),
2852
+ stepIndex: ordinal,
2853
+ parentId: args.parentId,
2854
+ payload: {
2855
+ childId: id,
2856
+ label: opts.label,
2857
+ runtime: executor.runtime,
2858
+ budget: opts.budget,
2859
+ depth: args.depth
2860
+ }
2861
+ },
2862
+ { signal: args.signal }
2863
+ );
2864
+ const settled = runChild(
2865
+ live,
2866
+ executor,
2867
+ childAbort,
2868
+ task,
2869
+ opts,
2870
+ args.pool,
2871
+ reservation.ticket,
2872
+ args.blobs
2873
+ ).then((s) => {
2874
+ live.resolved = s;
2875
+ return s;
2876
+ }).finally(() => {
2877
+ args.signal.removeEventListener("abort", cascadeAbort);
2878
+ });
2879
+ live.settled = settled;
2880
+ return { ok: true, handle };
2881
+ } catch (err) {
2882
+ args.pool.reconcile(reservation.ticket, zeroSpend2());
2883
+ throw err;
2884
+ }
2885
+ }
2886
+ async function next() {
2887
+ const undelivered = () => [...children.values()].filter((c) => !c.delivered);
2888
+ if (undelivered().length === 0) return null;
2889
+ for (; ; ) {
2890
+ const pending = undelivered();
2891
+ if (pending.length === 0) return null;
2892
+ const ready = pending.find((c) => c.resolved !== void 0);
2893
+ const chosen = ready ?? await raceFirstSettled(pending);
2894
+ if (chosen.delivered) continue;
2895
+ chosen.delivered = true;
2896
+ const seq = cursorSeq++;
2897
+ const settlement = chosen.resolved;
2898
+ if (!settlement) {
3040
2899
  throw new ValidationError(
3041
- "cliWorktreeExecutor: bridge resultArtifact() read before stream drained"
2900
+ `scope.next: child '${chosen.id}' won the settle race without a resolved value`
3042
2901
  );
3043
2902
  }
3044
- return artifact;
2903
+ return finalizeSettlement(chosen, settlement, seq, args, now);
2904
+ }
2905
+ }
2906
+ async function nextResolved() {
2907
+ for (; ; ) {
2908
+ const candidates = [...children.values()].filter((c) => !c.delivered);
2909
+ const pick = candidates.find((c) => c.resolved !== void 0) ?? candidates.find((c) => c.executorDone);
2910
+ if (!pick) return null;
2911
+ const settlement = await pick.settled;
2912
+ if (pick.delivered) continue;
2913
+ pick.delivered = true;
2914
+ const seq = cursorSeq++;
2915
+ return finalizeSettlement(pick, settlement, seq, args, now);
2916
+ }
2917
+ }
2918
+ function send(nodeId, msg) {
2919
+ const child = children.get(nodeId);
2920
+ if (!child || child.delivered || !child.deliver) return false;
2921
+ child.deliver(msg);
2922
+ return true;
2923
+ }
2924
+ async function meter(spend, detail) {
2925
+ const seq = meterSeq++;
2926
+ args.pool.observe(spend);
2927
+ await args.journal.appendEvent(args.root, {
2928
+ kind: "metered",
2929
+ id: args.parentId,
2930
+ spend,
2931
+ seq,
2932
+ at: new Date(now()).toISOString()
2933
+ });
2934
+ notifyRuntimeHookEvent(
2935
+ args.hooks,
2936
+ {
2937
+ id: `${args.parentId}:meter:${seq}`,
2938
+ runId: args.root,
2939
+ target: "agent.turn",
2940
+ phase: "after",
2941
+ timestamp: now(),
2942
+ parentId: args.parentId,
2943
+ payload: { spend, ...detail ?? {} }
2944
+ },
2945
+ { signal: args.signal }
2946
+ );
2947
+ }
2948
+ return {
2949
+ spawn: spawn4,
2950
+ next,
2951
+ nextResolved,
2952
+ send,
2953
+ signal: args.signal,
2954
+ meter,
2955
+ get view() {
2956
+ return makeTreeView(args.parentId, children);
2957
+ },
2958
+ get budget() {
2959
+ return args.pool.readout();
3045
2960
  }
3046
2961
  };
3047
2962
  }
3048
- function resolveBridgeWorktreeModel(spec, bridge) {
3049
- if (bridge.model) return bridge.model;
3050
- const model = spec.profile.model?.default;
3051
- if (typeof model === "string" && model.length > 0) return model;
3052
- throw new ValidationError(
3053
- "cliWorktreeExecutor: bridge.model or AgentProfile.model.default required"
3054
- );
2963
+ async function raceFirstSettled(pending) {
2964
+ return Promise.race(pending.map((c) => c.settled.then(() => c)));
3055
2965
  }
3056
- function bridgeOutputText(out) {
3057
- if (typeof out === "string") return out;
3058
- if (out && typeof out === "object") {
3059
- const content = out.content;
3060
- if (typeof content === "string") return content;
2966
+ async function finalizeSettlement(child, settlement, seq, args, now) {
2967
+ const handle = frozenHandle(child);
2968
+ if (settlement.kind === "down") {
2969
+ child.status = "failed";
2970
+ await args.journal.appendEvent(args.root, {
2971
+ kind: "settled",
2972
+ id: child.id,
2973
+ status: "down",
2974
+ spent: child.spent,
2975
+ infra: settlement.infra,
2976
+ seq,
2977
+ at: new Date(now()).toISOString()
2978
+ });
2979
+ if (settlement.metered) {
2980
+ await args.journal.appendEvent(args.root, {
2981
+ kind: "metered",
2982
+ id: child.id,
2983
+ spend: settlement.metered,
2984
+ seq,
2985
+ at: new Date(now()).toISOString()
2986
+ });
2987
+ }
2988
+ notifyRuntimeHookEvent(
2989
+ args.hooks,
2990
+ {
2991
+ id: `${child.id}:settled`,
2992
+ runId: args.root,
2993
+ target: "agent.child",
2994
+ phase: "after",
2995
+ timestamp: now(),
2996
+ stepIndex: seq,
2997
+ parentId: args.parentId,
2998
+ payload: {
2999
+ childId: child.id,
3000
+ status: "down",
3001
+ reason: settlement.reason,
3002
+ infra: settlement.infra,
3003
+ spent: child.spent
3004
+ }
3005
+ },
3006
+ { signal: args.signal }
3007
+ );
3008
+ return {
3009
+ kind: "down",
3010
+ handle,
3011
+ reason: settlement.reason,
3012
+ infra: settlement.infra,
3013
+ restartCount: settlement.restartCount,
3014
+ seq
3015
+ };
3016
+ }
3017
+ child.status = "done";
3018
+ child.outRef = settlement.outRef;
3019
+ child.spent = settlement.spent;
3020
+ await args.journal.appendEvent(args.root, {
3021
+ kind: "settled",
3022
+ id: child.id,
3023
+ status: "done",
3024
+ outRef: settlement.outRef,
3025
+ ...settlement.verdict ? { verdict: settlement.verdict } : {},
3026
+ spent: settlement.spent,
3027
+ seq,
3028
+ at: new Date(now()).toISOString()
3029
+ });
3030
+ if (settlement.metered) {
3031
+ await args.journal.appendEvent(args.root, {
3032
+ kind: "metered",
3033
+ id: child.id,
3034
+ spend: settlement.metered,
3035
+ seq,
3036
+ at: new Date(now()).toISOString()
3037
+ });
3061
3038
  }
3039
+ notifyRuntimeHookEvent(
3040
+ args.hooks,
3041
+ {
3042
+ id: `${child.id}:settled`,
3043
+ runId: args.root,
3044
+ target: "agent.child",
3045
+ phase: "after",
3046
+ timestamp: now(),
3047
+ stepIndex: seq,
3048
+ parentId: args.parentId,
3049
+ payload: {
3050
+ childId: child.id,
3051
+ status: "done",
3052
+ outRef: settlement.outRef,
3053
+ score: settlement.verdict?.score,
3054
+ valid: settlement.verdict?.valid,
3055
+ spent: settlement.spent
3056
+ }
3057
+ },
3058
+ { signal: args.signal }
3059
+ );
3060
+ return {
3061
+ kind: "done",
3062
+ handle,
3063
+ out: settlement.out,
3064
+ outRef: settlement.outRef,
3065
+ ...settlement.verdict ? { verdict: settlement.verdict } : {},
3066
+ spent: settlement.spent,
3067
+ seq
3068
+ };
3069
+ }
3070
+ async function runChild(live, executor, childAbort, task, opts, pool, ticket, blobs) {
3071
+ let reconciled = false;
3072
+ const reconcileOnce = (spend) => {
3073
+ if (reconciled) return;
3074
+ reconciled = true;
3075
+ pool.reconcile(ticket, clampSpend(spend, opts.budget));
3076
+ };
3062
3077
  try {
3063
- return JSON.stringify(out) ?? String(out);
3064
- } catch {
3065
- return String(out);
3078
+ live.status = "running";
3079
+ const ran = executor.execute(task, childAbort.signal);
3080
+ let artifact;
3081
+ if (isAsyncIterable2(ran)) {
3082
+ const spend = await foldStream(ran);
3083
+ live.spent = spend;
3084
+ artifact = executor.resultArtifact();
3085
+ reconcileOnce(spend);
3086
+ } else {
3087
+ const terminal = await ran;
3088
+ live.spent = terminal.spent;
3089
+ artifact = terminal;
3090
+ reconcileOnce(terminal.spent);
3091
+ }
3092
+ live.executorDone = true;
3093
+ const ownMetered = executor.metered?.();
3094
+ if (childAbort.signal.aborted) {
3095
+ await teardownSafe(executor, opts.shutdown ?? "brutalKill");
3096
+ return downRecord("aborted before settle", true, ownMetered);
3097
+ }
3098
+ const outRef = contentAddress(artifact.out);
3099
+ await blobs.put(outRef, artifact.out);
3100
+ await teardownSafe(executor, opts.shutdown ?? "infinity");
3101
+ return {
3102
+ kind: "done",
3103
+ out: artifact.out,
3104
+ outRef,
3105
+ ...artifact.verdict ? { verdict: artifact.verdict } : {},
3106
+ spent: live.spent,
3107
+ ...ownMetered ? { metered: ownMetered } : {}
3108
+ };
3109
+ } catch (err) {
3110
+ live.executorDone = true;
3111
+ reconcileOnce(live.spent);
3112
+ await teardownSafe(executor, "brutalKill");
3113
+ const aborted = childAbort.signal.aborted || isAbortError2(err);
3114
+ return downRecord(errMessage(err), aborted || isInfraError(err), executor.metered?.());
3066
3115
  }
3067
3116
  }
3068
- function isAsyncIterable2(value) {
3069
- return value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function";
3070
- }
3071
- var cliWorktreeExecutor = (spec, ctx) => {
3072
- const seam = readSeam(ctx, cliWorktreeSeamKey, "cli-worktree");
3073
- if (!seam.repoRoot || !seam.taskPrompt) {
3074
- throw new ValidationError("cliWorktreeExecutor: CliWorktreeSeam.repoRoot + taskPrompt required");
3075
- }
3076
- if (seam.bridge) return bridgeWorktreeExecutor(spec, ctx, seam);
3077
- if (!seam.harness) {
3117
+ function settledToIteration(settled) {
3118
+ if (settled.kind === "down") {
3078
3119
  throw new ValidationError(
3079
- "cliWorktreeExecutor: CliWorktreeSeam.harness required when bridge is not set"
3120
+ `settledToIteration: cannot adapt a 'down' settlement (node '${settled.handle.id}', seq ${settled.seq}) to an Iteration`
3080
3121
  );
3081
3122
  }
3082
- return createWorktreeCliExecutor({
3083
- repoRoot: seam.repoRoot,
3084
- profile: spec.profile,
3085
- harness: seam.harness,
3086
- taskPrompt: seam.taskPrompt,
3087
- ...seam.runId ? { runId: seam.runId } : {},
3088
- ...seam.baseRef ? { baseRef: seam.baseRef } : {},
3089
- ...seam.harnessTimeoutMs !== void 0 ? { harnessTimeoutMs: seam.harnessTimeoutMs } : {},
3090
- ...seam.testCmd !== void 0 ? { testCmd: seam.testCmd } : {},
3091
- ...seam.typecheckCmd !== void 0 ? { typecheckCmd: seam.typecheckCmd } : {},
3092
- ...seam.checkTimeoutMs !== void 0 ? { checkTimeoutMs: seam.checkTimeoutMs } : {},
3093
- ...seam.checkOutputCap !== void 0 ? { checkOutputCap: seam.checkOutputCap } : {},
3094
- ...seam.runGit ? { runGit: seam.runGit } : {},
3095
- ...seam.runCommand ? { runCommand: seam.runCommand } : {},
3096
- ...seam.budgetExempt !== void 0 ? { budgetExempt: seam.budgetExempt } : {}
3097
- });
3098
- };
3099
- function createExecutor(config) {
3100
- return (spec, ctx) => {
3101
- const { backend, ...seam } = config;
3102
- const seamed = { ...ctx, seams: { ...ctx.seams, [backend]: seam } };
3103
- switch (config.backend) {
3104
- case "router":
3105
- return routerInlineExecutor(spec, seamed);
3106
- case "router-tools":
3107
- return routerToolsInlineExecutor(spec, seamed);
3108
- case "bridge":
3109
- return bridgeExecutor(spec, seamed);
3110
- case "cli":
3111
- return cliExecutor(spec, seamed);
3112
- case "cli-worktree":
3113
- return cliWorktreeExecutor(spec, seamed);
3114
- case "provider": {
3115
- const providerSeam = readSeam(seamed, providerSeamKey, "provider");
3116
- const provider = resolveAgentEnvironmentProvider(
3117
- providerSeam.provider,
3118
- providerSeam.registry
3119
- );
3120
- return providerAsExecutor(provider, providerSeam)(spec, seamed);
3121
- }
3122
- case "sandbox": {
3123
- const harness = spec.harness ?? config.harness ?? null;
3124
- return sandboxExecutor({ ...spec, harness }, seamed);
3125
- }
3126
- }
3123
+ return {
3124
+ index: settled.seq,
3125
+ task: void 0,
3126
+ agentRunName: settled.handle.label,
3127
+ output: settled.out,
3128
+ ...settled.verdict ? { verdict: settled.verdict } : {},
3129
+ events: [],
3130
+ startedAt: 0,
3131
+ endedAt: settled.spent.ms,
3132
+ costUsd: settled.spent.usd,
3133
+ tokenUsage: { input: settled.spent.tokens.input, output: settled.spent.tokens.output }
3127
3134
  };
3128
3135
  }
3129
- function createExecutorRegistry() {
3130
- const factories = /* @__PURE__ */ new Map();
3131
- factories.set("router", routerInlineExecutor);
3132
- factories.set("inline", routerInlineExecutor);
3133
- factories.set("sandbox", sandboxExecutor);
3134
- factories.set("cli", cliExecutor);
3136
+ function makeTreeView(root, children) {
3137
+ const nodes = [...children.values()].map((c) => ({
3138
+ id: c.id,
3139
+ parent: root,
3140
+ label: c.label,
3141
+ status: c.status,
3142
+ runtime: c.runtime,
3143
+ budget: c.budget,
3144
+ spent: c.spent,
3145
+ ...c.outRef ? { outRef: c.outRef } : {}
3146
+ }));
3135
3147
  return {
3136
- register(runtime, factory) {
3137
- if (factories.has(runtime)) {
3138
- throw new ValidationError(`executor registry: runtime "${runtime}" already registered`);
3139
- }
3140
- factories.set(runtime, factory);
3141
- },
3142
- resolve(spec) {
3143
- if (spec.executor) {
3144
- const byo = spec.executor;
3145
- return { succeeded: true, value: (() => byo) };
3146
- }
3147
- if (spec.harness === null) {
3148
- const f2 = factories.get("router");
3149
- if (!f2) return { succeeded: false, error: 'executor registry: no "router" factory' };
3150
- return { succeeded: true, value: f2 };
3151
- }
3152
- const runtimeTag = "sandbox";
3153
- const f = factories.get(runtimeTag);
3154
- if (!f) {
3155
- return {
3156
- succeeded: false,
3157
- error: `executor registry: no factory for runtime "${runtimeTag}" (harness "${spec.harness}") and no BYO executor`
3158
- };
3159
- }
3160
- return { succeeded: true, value: f };
3161
- }
3148
+ root,
3149
+ nodes,
3150
+ inFlight: nodes.filter((n) => n.status === "running" || n.status === "acquiring").length
3162
3151
  };
3163
3152
  }
3164
- function readSeam(ctx, key, who) {
3165
- const seam = ctx.seams[key];
3166
- if (seam === void 0 || seam === null) {
3167
- throw new ValidationError(`${who} executor: missing required seam "${key}" on ExecutorContext`);
3168
- }
3169
- return seam;
3170
- }
3171
- function taskToPrompt(task) {
3172
- if (typeof task === "string") return task;
3173
- if (task && typeof task === "object") {
3174
- const obj = task;
3175
- for (const k of ["prompt", "content", "task", "message"]) {
3176
- if (typeof obj[k] === "string") return obj[k];
3153
+ function frozenHandle(child) {
3154
+ return {
3155
+ id: child.id,
3156
+ label: child.label,
3157
+ status: child.status,
3158
+ abort() {
3177
3159
  }
3178
- }
3179
- return JSON.stringify(task);
3160
+ };
3180
3161
  }
3181
- function taskToMessages(task, spec) {
3182
- const messages = [];
3183
- const system = spec.profile.prompt?.systemPrompt;
3184
- if (typeof system === "string" && system.length > 0) {
3185
- messages.push({ role: "system", content: system });
3162
+ async function foldStream(stream) {
3163
+ const tokens = { input: 0, output: 0 };
3164
+ let usd = 0;
3165
+ let iterations = 0;
3166
+ for await (const ev of stream) {
3167
+ if (ev.kind === "tokens") {
3168
+ tokens.input += ev.input;
3169
+ tokens.output += ev.output;
3170
+ } else if (ev.kind === "cost") {
3171
+ usd += ev.usd;
3172
+ } else {
3173
+ iterations += 1;
3174
+ }
3186
3175
  }
3187
- messages.push({ role: "user", content: taskToPrompt(task) });
3188
- return messages;
3176
+ return { iterations, tokens, usd, ms: 0 };
3189
3177
  }
3190
- function singleShotDriver(maxIterations) {
3178
+ function clampSpend(spend, budget) {
3179
+ const totalTokens2 = spend.tokens.input + spend.tokens.output;
3180
+ const tokensOk = totalTokens2 <= budget.maxTokens;
3181
+ const itersOk = spend.iterations <= budget.maxIterations;
3182
+ const usdOk = budget.maxUsd === void 0 || spend.usd <= budget.maxUsd;
3183
+ if (tokensOk && itersOk && usdOk) return spend;
3184
+ const ratio = !tokensOk && totalTokens2 > 0 ? budget.maxTokens / totalTokens2 : 1;
3191
3185
  return {
3192
- name: "leaf",
3193
- plan(task, history) {
3194
- return Promise.resolve(history.length >= maxIterations ? [] : [task]);
3195
- },
3196
- decide(history) {
3197
- return history.length >= maxIterations ? "stop" : "continue";
3198
- }
3186
+ iterations: Math.min(spend.iterations, budget.maxIterations),
3187
+ tokens: ratio < 1 ? {
3188
+ input: Math.floor(spend.tokens.input * ratio),
3189
+ output: Math.floor(spend.tokens.output * ratio)
3190
+ } : spend.tokens,
3191
+ usd: budget.maxUsd === void 0 ? spend.usd : Math.min(spend.usd, budget.maxUsd),
3192
+ ms: spend.ms
3199
3193
  };
3200
3194
  }
3201
- function linkSignals2(a, b) {
3202
- if (a.aborted || b.aborted) {
3203
- const c2 = new AbortController();
3204
- c2.abort();
3205
- return c2.signal;
3195
+ async function teardownSafe(executor, grace) {
3196
+ try {
3197
+ await executor.teardown(grace);
3198
+ } catch {
3206
3199
  }
3207
- const c = new AbortController();
3208
- const onAbort = () => c.abort();
3209
- a.addEventListener("abort", onAbort, { once: true });
3210
- b.addEventListener("abort", onAbort, { once: true });
3211
- return c.signal;
3212
3200
  }
3213
- function mergeAbortSignals(...signals) {
3214
- const c = new AbortController();
3215
- const onAbort = () => c.abort();
3216
- for (const s of signals) {
3217
- if (s.aborted) {
3218
- c.abort();
3219
- break;
3220
- }
3221
- s.addEventListener("abort", onAbort, { once: true });
3222
- }
3223
- return c.signal;
3201
+ function downRecord(reason, infra, metered) {
3202
+ return { kind: "down", reason, infra, restartCount: 0, ...metered ? { metered } : {} };
3203
+ }
3204
+ function zeroSpend2() {
3205
+ return { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
3206
+ }
3207
+ function isAsyncIterable2(value) {
3208
+ return typeof value === "object" && value !== null && typeof value[Symbol.asyncIterator] === "function";
3209
+ }
3210
+ function isAgentSpec(value) {
3211
+ if (typeof value !== "object" || value === null) return false;
3212
+ const v = value;
3213
+ return "profile" in v && "harness" in v;
3214
+ }
3215
+ function isAbortError2(err) {
3216
+ return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
3217
+ }
3218
+ function isInfraError(err) {
3219
+ return err instanceof ValidationError;
3220
+ }
3221
+ function errMessage(err) {
3222
+ if (err instanceof Error) return err.message;
3223
+ return String(err);
3224
3224
  }
3225
3225
 
3226
3226
  // src/runtime/supervise/budget.ts
@@ -6511,9 +6511,6 @@ export {
6511
6511
  runLoop,
6512
6512
  createSandboxForSpec,
6513
6513
  defaultSelectWinner,
6514
- createScope,
6515
- settledToIteration,
6516
- withDriverExecutor,
6517
6514
  createWorktree,
6518
6515
  captureWorktreeDiff,
6519
6516
  removeWorktree,
@@ -6527,6 +6524,9 @@ export {
6527
6524
  cliWorktreeExecutor,
6528
6525
  createExecutor,
6529
6526
  createExecutorRegistry,
6527
+ createScope,
6528
+ settledToIteration,
6529
+ withDriverExecutor,
6530
6530
  spendFromUsageEvents,
6531
6531
  createBudgetPool,
6532
6532
  createSupervisor,
@@ -6590,4 +6590,4 @@ export {
6590
6590
  createInProcessTransport,
6591
6591
  serveCoordinationMcp
6592
6592
  };
6593
- //# sourceMappingURL=chunk-SSA6OERV.js.map
6593
+ //# sourceMappingURL=chunk-XN5TIJGP.js.map