@tangle-network/agent-runtime 0.56.0 → 0.57.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 (45) hide show
  1. package/dist/agent.d.ts +1 -1
  2. package/dist/agent.js +2 -2
  3. package/dist/analyst-loop.d.ts +1 -1
  4. package/dist/{chunk-5ZOYQWEB.js → chunk-6BOIKGSU.js} +2 -2
  5. package/dist/{chunk-3BI6PSP3.js → chunk-7QYOXFCD.js} +1 -1
  6. package/dist/chunk-7QYOXFCD.js.map +1 -0
  7. package/dist/{chunk-A265AJ2Q.js → chunk-NJST5D2G.js} +2 -2
  8. package/dist/{chunk-ZADWPBOE.js → chunk-RLDUT4JL.js} +759 -192
  9. package/dist/chunk-RLDUT4JL.js.map +1 -0
  10. package/dist/{chunk-UIQTCS6H.js → chunk-TVBXDW7C.js} +59 -79
  11. package/dist/chunk-TVBXDW7C.js.map +1 -0
  12. package/dist/{chunk-GFDCNQRO.js → chunk-WFEQCRQP.js} +5 -4
  13. package/dist/chunk-WFEQCRQP.js.map +1 -0
  14. package/dist/{coder-CdsknxGF.d.ts → coder-CybltHEm.d.ts} +4 -7
  15. package/dist/{coordination-CfMk-RpQ.d.ts → coordination-BydGBQCZ.d.ts} +164 -4
  16. package/dist/{delegates-htF7l_H6.d.ts → delegates-C94qchkz.d.ts} +12 -8
  17. package/dist/index.d.ts +6 -6
  18. package/dist/index.js +5 -5
  19. package/dist/intelligence.d.ts +1 -1
  20. package/dist/{loop-runner-bin-C4YyFGdT.d.ts → loop-runner-bin-Noz7P-mS.d.ts} +3 -3
  21. package/dist/loop-runner-bin.d.ts +4 -4
  22. package/dist/loop-runner-bin.js +4 -4
  23. package/dist/loops.d.ts +7 -6
  24. package/dist/loops.js +14 -2
  25. package/dist/mcp/bin.js +4 -4
  26. package/dist/mcp/index.d.ts +20 -82
  27. package/dist/mcp/index.js +5 -5
  28. package/dist/{openai-tools-CoeLQ7Uo.d.ts → openai-tools-d4GKwgya.d.ts} +1 -1
  29. package/dist/profiles.d.ts +2 -2
  30. package/dist/profiles.js +1 -1
  31. package/dist/{run-loop-DluzfJ2h.d.ts → run-loop-CcqfR_gy.d.ts} +1 -1
  32. package/dist/runtime.d.ts +255 -67
  33. package/dist/runtime.js +14 -2
  34. package/dist/{types-C8rNlxfV.d.ts → types-CUzjRFZ3.d.ts} +1 -1
  35. package/dist/workflow.d.ts +2 -2
  36. package/dist/workflow.js +2 -2
  37. package/package.json +2 -2
  38. package/skills/loop-writer/SKILL.md +1 -1
  39. package/skills/supervise/SKILL.md +1 -1
  40. package/dist/chunk-3BI6PSP3.js.map +0 -1
  41. package/dist/chunk-GFDCNQRO.js.map +0 -1
  42. package/dist/chunk-UIQTCS6H.js.map +0 -1
  43. package/dist/chunk-ZADWPBOE.js.map +0 -1
  44. /package/dist/{chunk-5ZOYQWEB.js.map → chunk-6BOIKGSU.js.map} +0 -0
  45. /package/dist/{chunk-A265AJ2Q.js.map → chunk-NJST5D2G.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runCoderChecks
3
- } from "./chunk-3BI6PSP3.js";
3
+ } from "./chunk-7QYOXFCD.js";
4
4
  import {
5
5
  UI_LENSES
6
6
  } from "./chunk-WIR4HOOJ.js";
@@ -305,8 +305,8 @@ var FileSpawnJournal = class {
305
305
  }
306
306
  };
307
307
  function assertSeqUnique(root, events, ev) {
308
- if (ev.kind === "spawned") return;
309
- if (events.some((e) => e.kind !== "spawned" && e.seq === ev.seq)) {
308
+ if (ev.kind === "spawned" || ev.kind === "metered") return;
309
+ if (events.some((e) => e.kind !== "spawned" && e.kind !== "metered" && e.seq === ev.seq)) {
310
310
  throw new Error(
311
311
  `spawn journal corrupted: duplicate cursor seq ${ev.seq} in tree '${root}'; the cursor order replay relies on is not unique`
312
312
  );
@@ -325,6 +325,7 @@ async function replaySpawnTree(journal, blobs, root) {
325
325
  const settled = [];
326
326
  for (const ev of ordered) {
327
327
  if (ev.kind === "spawned") continue;
328
+ if (ev.kind === "metered") continue;
328
329
  if (ev.kind === "cancelled") {
329
330
  settled.push({
330
331
  kind: "down",
@@ -384,7 +385,7 @@ function materializeTreeView(events) {
384
385
  const nodes = /* @__PURE__ */ new Map();
385
386
  let root;
386
387
  const spawns = events.filter((ev) => ev.kind === "spawned").sort((a, b) => a.seq - b.seq);
387
- const settlements = events.filter((ev) => ev.kind !== "spawned").sort((a, b) => a.seq - b.seq);
388
+ const settlements = events.filter((ev) => ev.kind !== "spawned" && ev.kind !== "metered").sort((a, b) => a.seq - b.seq);
388
389
  for (const ev of spawns) {
389
390
  if (ev.parent === void 0 && root === void 0) root = ev.id;
390
391
  nodes.set(ev.id, {
@@ -408,6 +409,11 @@ function materializeTreeView(events) {
408
409
  node.status = "cancelled";
409
410
  }
410
411
  }
412
+ for (const ev of events) {
413
+ if (ev.kind !== "metered") continue;
414
+ const node = requireNode(nodes, ev.id);
415
+ node.spent = addJournalSpend(node.spent, ev.spend);
416
+ }
411
417
  const snapshots = [...nodes.values()].map(freezeSnapshot);
412
418
  return {
413
419
  root: root ?? snapshots[0]?.id ?? "",
@@ -418,6 +424,14 @@ function materializeTreeView(events) {
418
424
  function zeroSpend() {
419
425
  return { iterations: 0, tokens: zeroTokenUsage(), usd: 0, ms: 0 };
420
426
  }
427
+ function addJournalSpend(a, b) {
428
+ return {
429
+ iterations: a.iterations + b.iterations,
430
+ tokens: { input: a.tokens.input + b.tokens.input, output: a.tokens.output + b.tokens.output },
431
+ usd: a.usd + b.usd,
432
+ ms: a.ms + b.ms
433
+ };
434
+ }
421
435
  function requireNode(nodes, id) {
422
436
  const node = nodes.get(id);
423
437
  if (!node) {
@@ -2130,6 +2144,7 @@ function createScope(args) {
2130
2144
  const children = /* @__PURE__ */ new Map();
2131
2145
  let spawnOrdinal = 0;
2132
2146
  let cursorSeq = 0;
2147
+ let meterSeq = 0;
2133
2148
  const now = args.now ?? Date.now;
2134
2149
  function spawn4(agent, task, opts) {
2135
2150
  if (args.maxDepth !== void 0 && args.depth >= args.maxDepth) {
@@ -2257,10 +2272,36 @@ function createScope(args) {
2257
2272
  child.deliver(msg);
2258
2273
  return true;
2259
2274
  }
2275
+ async function meter(spend, detail) {
2276
+ const seq = meterSeq++;
2277
+ args.pool.observe(spend);
2278
+ await args.journal.appendEvent(args.root, {
2279
+ kind: "metered",
2280
+ id: args.parentId,
2281
+ spend,
2282
+ seq,
2283
+ at: new Date(now()).toISOString()
2284
+ });
2285
+ notifyRuntimeHookEvent(
2286
+ args.hooks,
2287
+ {
2288
+ id: `${args.parentId}:meter:${seq}`,
2289
+ runId: args.root,
2290
+ target: "agent.turn",
2291
+ phase: "after",
2292
+ timestamp: now(),
2293
+ parentId: args.parentId,
2294
+ payload: { spend, ...detail ?? {} }
2295
+ },
2296
+ { signal: args.signal }
2297
+ );
2298
+ }
2260
2299
  return {
2261
2300
  spawn: spawn4,
2262
2301
  next,
2263
2302
  send,
2303
+ signal: args.signal,
2304
+ meter,
2264
2305
  get view() {
2265
2306
  return makeTreeView(args.parentId, children);
2266
2307
  },
@@ -2285,6 +2326,15 @@ async function finalizeSettlement(child, settlement, seq, args, now) {
2285
2326
  seq,
2286
2327
  at: new Date(now()).toISOString()
2287
2328
  });
2329
+ if (settlement.metered) {
2330
+ await args.journal.appendEvent(args.root, {
2331
+ kind: "metered",
2332
+ id: child.id,
2333
+ spend: settlement.metered,
2334
+ seq,
2335
+ at: new Date(now()).toISOString()
2336
+ });
2337
+ }
2288
2338
  notifyRuntimeHookEvent(
2289
2339
  args.hooks,
2290
2340
  {
@@ -2327,6 +2377,15 @@ async function finalizeSettlement(child, settlement, seq, args, now) {
2327
2377
  seq,
2328
2378
  at: new Date(now()).toISOString()
2329
2379
  });
2380
+ if (settlement.metered) {
2381
+ await args.journal.appendEvent(args.root, {
2382
+ kind: "metered",
2383
+ id: child.id,
2384
+ spend: settlement.metered,
2385
+ seq,
2386
+ at: new Date(now()).toISOString()
2387
+ });
2388
+ }
2330
2389
  notifyRuntimeHookEvent(
2331
2390
  args.hooks,
2332
2391
  {
@@ -2380,9 +2439,10 @@ async function runChild(live, executor, childAbort, task, opts, pool2, ticket, b
2380
2439
  artifact = terminal;
2381
2440
  reconcileOnce(terminal.spent);
2382
2441
  }
2442
+ const ownMetered = executor.metered?.();
2383
2443
  if (childAbort.signal.aborted) {
2384
2444
  await teardownSafe(executor, opts.shutdown ?? "brutalKill");
2385
- return downRecord("aborted before settle", true);
2445
+ return downRecord("aborted before settle", true, ownMetered);
2386
2446
  }
2387
2447
  const outRef = contentAddress(artifact.out);
2388
2448
  await blobs.put(outRef, artifact.out);
@@ -2392,13 +2452,14 @@ async function runChild(live, executor, childAbort, task, opts, pool2, ticket, b
2392
2452
  out: artifact.out,
2393
2453
  outRef,
2394
2454
  ...artifact.verdict ? { verdict: artifact.verdict } : {},
2395
- spent: live.spent
2455
+ spent: live.spent,
2456
+ ...ownMetered ? { metered: ownMetered } : {}
2396
2457
  };
2397
2458
  } catch (err) {
2398
2459
  reconcileOnce(live.spent);
2399
2460
  await teardownSafe(executor, "brutalKill");
2400
2461
  const aborted = childAbort.signal.aborted || isAbortError2(err);
2401
- return downRecord(errMessage(err), aborted || isInfraError(err));
2462
+ return downRecord(errMessage(err), aborted || isInfraError(err), executor.metered?.());
2402
2463
  }
2403
2464
  }
2404
2465
  function settledToIteration(settled) {
@@ -2485,8 +2546,8 @@ async function teardownSafe(executor, grace) {
2485
2546
  } catch {
2486
2547
  }
2487
2548
  }
2488
- function downRecord(reason, infra) {
2489
- return { kind: "down", reason, infra, restartCount: 0 };
2549
+ function downRecord(reason, infra, metered) {
2550
+ return { kind: "down", reason, infra, restartCount: 0, ...metered ? { metered } : {} };
2490
2551
  }
2491
2552
  function zeroSpend2() {
2492
2553
  return { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
@@ -3078,23 +3139,33 @@ var driverExecutorFactory = (spec, ctx) => {
3078
3139
  const journal = spec.journal;
3079
3140
  const seam = readNestedScopeSeam(ctx);
3080
3141
  let artifact;
3142
+ let meteredSpend;
3081
3143
  return {
3082
3144
  runtime: driverRuntime,
3083
3145
  async execute(task, signal) {
3084
3146
  const nestedRoot = nestedTreeKey(seam, journal);
3085
3147
  await journal.beginTree(nestedRoot, (/* @__PURE__ */ new Date(0)).toISOString());
3086
3148
  const nestedScope = seam.mount(nestedRoot, signal);
3087
- const out = await driver.act(task, nestedScope);
3088
- const settled = await loadSettled(journal, nestedRoot);
3089
- const spent = sumSpend(settled);
3090
- const verdict = deriveDeliveryVerdict(settled);
3091
- artifact = {
3092
- outRef: `${driverRuntime}:${nestedRoot}`,
3093
- out,
3094
- spent,
3095
- ...verdict ? { verdict } : {}
3096
- };
3097
- return artifact;
3149
+ try {
3150
+ const out = await driver.act(task, nestedScope);
3151
+ const events = await loadTreeEvents(journal, nestedRoot);
3152
+ const settled = events.filter(isSettled);
3153
+ meteredSpend = nonZeroOrUndef(sumMetered(events));
3154
+ const verdict = deriveDeliveryVerdict(settled);
3155
+ artifact = {
3156
+ outRef: `${driverRuntime}:${nestedRoot}`,
3157
+ out,
3158
+ spent: sumSpend(settled),
3159
+ ...verdict ? { verdict } : {}
3160
+ };
3161
+ return artifact;
3162
+ } catch (err) {
3163
+ meteredSpend = await safeSumMetered(journal, nestedRoot);
3164
+ throw err;
3165
+ }
3166
+ },
3167
+ metered() {
3168
+ return meteredSpend;
3098
3169
  },
3099
3170
  teardown() {
3100
3171
  return Promise.resolve({ destroyed: true });
@@ -3131,16 +3202,17 @@ function nextNestOrdinal(journal) {
3131
3202
  }
3132
3203
  return c.n++;
3133
3204
  }
3134
- async function loadSettled(journal, nestedRoot) {
3205
+ async function loadTreeEvents(journal, nestedRoot) {
3135
3206
  const events = await journal.loadTree(nestedRoot);
3136
3207
  if (events === void 0) {
3137
3208
  throw new ValidationError(
3138
3209
  `driverExecutor: nested tree '${nestedRoot}' missing from the journal after run (corrupted log)`
3139
3210
  );
3140
3211
  }
3141
- return events.filter(
3142
- (ev) => ev.kind === "settled"
3143
- );
3212
+ return events;
3213
+ }
3214
+ function isSettled(ev) {
3215
+ return ev.kind === "settled";
3144
3216
  }
3145
3217
  function sumSpend(settled) {
3146
3218
  const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
@@ -3153,6 +3225,31 @@ function sumSpend(settled) {
3153
3225
  }
3154
3226
  return total;
3155
3227
  }
3228
+ function sumMetered(events) {
3229
+ const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
3230
+ for (const ev of events) {
3231
+ if (ev.kind !== "metered") continue;
3232
+ total.iterations += ev.spend.iterations;
3233
+ total.tokens.input += ev.spend.tokens.input;
3234
+ total.tokens.output += ev.spend.tokens.output;
3235
+ total.usd += ev.spend.usd;
3236
+ total.ms += ev.spend.ms;
3237
+ }
3238
+ return total;
3239
+ }
3240
+ function isNonZeroSpend(s) {
3241
+ return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
3242
+ }
3243
+ function nonZeroOrUndef(s) {
3244
+ return isNonZeroSpend(s) ? s : void 0;
3245
+ }
3246
+ async function safeSumMetered(journal, nestedRoot) {
3247
+ try {
3248
+ return nonZeroOrUndef(sumMetered(await loadTreeEvents(journal, nestedRoot)));
3249
+ } catch {
3250
+ return void 0;
3251
+ }
3252
+ }
3156
3253
  function deriveDeliveryVerdict(settled) {
3157
3254
  let sawChild = false;
3158
3255
  let anyValid = false;
@@ -3326,10 +3423,57 @@ async function routerToolLoop(cfg, system, user, tools, execute, opts) {
3326
3423
  return { final: lastText, turns: maxTurns, toolCalls, toolTrace, usage, messages };
3327
3424
  }
3328
3425
 
3426
+ // src/runtime/supervise/inbox.ts
3427
+ function parseDown(msg) {
3428
+ if (!msg || typeof msg !== "object") return void 0;
3429
+ const m = msg;
3430
+ const interrupt = m.interrupt === true;
3431
+ if (typeof m.steer === "string") return { kind: "steer", text: m.steer, interrupt };
3432
+ if (typeof m.answer === "string")
3433
+ return {
3434
+ kind: "answer",
3435
+ text: m.answer,
3436
+ interrupt,
3437
+ ...typeof m.questionId === "string" ? { questionId: m.questionId } : {}
3438
+ };
3439
+ return void 0;
3440
+ }
3441
+ function createInbox() {
3442
+ const pending = [];
3443
+ let live = null;
3444
+ return {
3445
+ deliver(msg) {
3446
+ const m = parseDown(msg);
3447
+ if (!m) return;
3448
+ pending.push(m);
3449
+ if (m.interrupt && live && !live.signal.aborted) live.abort();
3450
+ },
3451
+ drain() {
3452
+ return pending.splice(0, pending.length);
3453
+ },
3454
+ pending: () => pending.length,
3455
+ freshInterrupt() {
3456
+ live = new AbortController();
3457
+ return live.signal;
3458
+ },
3459
+ fold(messages) {
3460
+ const lines = messages.map((m) => {
3461
+ if (m.kind === "answer")
3462
+ return `- Answer to your question${m.questionId ? ` (${m.questionId})` : ""}: ${m.text}`;
3463
+ return `- New instruction from your supervisor: ${m.text}`;
3464
+ });
3465
+ return `[SUPERVISOR] Out-of-band message(s) \u2014 address these before continuing:
3466
+ ${lines.join("\n")}`;
3467
+ }
3468
+ };
3469
+ }
3470
+
3329
3471
  // src/runtime/supervise/worktree-cli-executor.ts
3330
- import { spawn as spawn2 } from "child_process";
3331
3472
  import { randomUUID } from "crypto";
3332
3473
 
3474
+ // src/mcp/worktree-harness.ts
3475
+ import { spawn as spawn2 } from "child_process";
3476
+
3333
3477
  // src/mcp/worktree.ts
3334
3478
  import { spawn } from "child_process";
3335
3479
  async function runGitAsync(args, cwd, runner) {
@@ -3411,8 +3555,115 @@ async function removeWorktree(options) {
3411
3555
  ).catch(() => void 0);
3412
3556
  }
3413
3557
 
3558
+ // src/mcp/worktree-harness.ts
3559
+ var defaultCheckOutputCap = 16e3;
3560
+ async function runWorktreeHarness(opts) {
3561
+ const runHarness = opts.runHarness ?? runLocalHarness;
3562
+ const runCommand = opts.runCommand ?? defaultRunCommand;
3563
+ const checkTimeoutMs = opts.checkTimeoutMs ?? opts.harnessTimeoutMs ?? 5 * 60 * 1e3;
3564
+ const cap = opts.checkOutputCap ?? defaultCheckOutputCap;
3565
+ const worktree = await createWorktree({
3566
+ repoRoot: opts.repoRoot,
3567
+ runId: opts.runId,
3568
+ ...opts.baseRef ? { baseRef: opts.baseRef } : {},
3569
+ ...opts.runGit ? { runGit: opts.runGit } : {}
3570
+ });
3571
+ const cleanup = () => removeWorktree({
3572
+ worktree,
3573
+ repoRoot: opts.repoRoot,
3574
+ ...opts.runGit ? { runGit: opts.runGit } : {}
3575
+ }).catch(() => void 0);
3576
+ try {
3577
+ const { command, args } = harnessInvocation(opts.harness, opts.profile, opts.taskPrompt);
3578
+ const harnessResult = await runHarness({
3579
+ harness: opts.harness,
3580
+ cwd: worktree.path,
3581
+ taskPrompt: opts.taskPrompt,
3582
+ invocation: { command, args },
3583
+ ...opts.harnessTimeoutMs !== void 0 ? { timeoutMs: opts.harnessTimeoutMs } : {},
3584
+ ...opts.signal ? { signal: opts.signal } : {}
3585
+ });
3586
+ const diff = await captureWorktreeDiff({
3587
+ worktree,
3588
+ ...opts.runGit ? { runGit: opts.runGit } : {}
3589
+ });
3590
+ const checks = await runChecks({
3591
+ worktreePath: worktree.path,
3592
+ ...opts.testCmd !== void 0 ? { testCmd: opts.testCmd } : {},
3593
+ ...opts.typecheckCmd !== void 0 ? { typecheckCmd: opts.typecheckCmd } : {},
3594
+ timeoutMs: checkTimeoutMs,
3595
+ cap,
3596
+ runCommand,
3597
+ ...opts.signal ? { signal: opts.signal } : {}
3598
+ });
3599
+ const result = {
3600
+ branch: worktree.branch,
3601
+ patch: diff.patch,
3602
+ stats: diff.stats,
3603
+ harness: {
3604
+ name: opts.harness,
3605
+ exitCode: harnessResult.exitCode,
3606
+ timedOut: harnessResult.timedOut,
3607
+ killedBySignal: harnessResult.killedBySignal,
3608
+ durationMs: harnessResult.durationMs,
3609
+ stdout: harnessResult.stdout,
3610
+ stderr: harnessResult.stderr
3611
+ },
3612
+ ...checks ? { checks } : {}
3613
+ };
3614
+ return { worktree, result, cleanup };
3615
+ } catch (err) {
3616
+ await cleanup();
3617
+ throw err;
3618
+ }
3619
+ }
3620
+ async function runChecks(opts) {
3621
+ if (opts.testCmd === void 0 && opts.typecheckCmd === void 0) return void 0;
3622
+ const run = async (command) => {
3623
+ const res = await opts.runCommand({
3624
+ command,
3625
+ cwd: opts.worktreePath,
3626
+ timeoutMs: opts.timeoutMs,
3627
+ ...opts.signal ? { signal: opts.signal } : {}
3628
+ });
3629
+ return {
3630
+ command,
3631
+ passed: res.exitCode === 0,
3632
+ exitCode: res.exitCode,
3633
+ output: res.output.length > opts.cap ? res.output.slice(-opts.cap) : res.output
3634
+ };
3635
+ };
3636
+ const checks = {};
3637
+ if (opts.testCmd !== void 0) checks.tests = await run(opts.testCmd);
3638
+ if (opts.typecheckCmd !== void 0) checks.typecheck = await run(opts.typecheckCmd);
3639
+ return checks;
3640
+ }
3641
+ function defaultRunCommand(opts) {
3642
+ return new Promise((resolve, reject) => {
3643
+ const child = spawn2("/bin/sh", ["-c", opts.command], {
3644
+ cwd: opts.cwd,
3645
+ stdio: ["ignore", "pipe", "pipe"]
3646
+ });
3647
+ const chunks = [];
3648
+ let settled = false;
3649
+ const timer = setTimeout(() => child.kill("SIGTERM"), opts.timeoutMs);
3650
+ const onAbort = () => child.kill("SIGTERM");
3651
+ opts.signal?.addEventListener("abort", onAbort, { once: true });
3652
+ child.stdout?.on("data", (d) => chunks.push(String(d)));
3653
+ child.stderr?.on("data", (d) => chunks.push(String(d)));
3654
+ const finish = (fn) => {
3655
+ if (settled) return;
3656
+ settled = true;
3657
+ clearTimeout(timer);
3658
+ opts.signal?.removeEventListener("abort", onAbort);
3659
+ fn();
3660
+ };
3661
+ child.on("error", (err) => finish(() => reject(err)));
3662
+ child.on("close", (code) => finish(() => resolve({ exitCode: code, output: chunks.join("") })));
3663
+ });
3664
+ }
3665
+
3414
3666
  // src/runtime/supervise/worktree-cli-executor.ts
3415
- var checkOutputCap = 16e3;
3416
3667
  function createWorktreeCliExecutor(options) {
3417
3668
  if (!options.repoRoot) {
3418
3669
  throw new ValidationError("createWorktreeCliExecutor: repoRoot required");
@@ -3424,22 +3675,9 @@ function createWorktreeCliExecutor(options) {
3424
3675
  throw new ValidationError("createWorktreeCliExecutor: taskPrompt required");
3425
3676
  }
3426
3677
  const runId = options.runId ?? randomUUID();
3427
- const runHarness = options.runHarness ?? runLocalHarness;
3428
- const runCommand = options.runCommand ?? defaultRunCommand;
3429
- const checkTimeoutMs = options.checkTimeoutMs ?? options.harnessTimeoutMs ?? 5 * 60 * 1e3;
3430
3678
  const controller = new AbortController();
3431
- let worktree;
3679
+ let run;
3432
3680
  let artifact;
3433
- const removeIfPresent = async () => {
3434
- if (!worktree) return;
3435
- const wt = worktree;
3436
- worktree = void 0;
3437
- await removeWorktree({
3438
- worktree: wt,
3439
- repoRoot: options.repoRoot,
3440
- ...options.runGit ? { runGit: options.runGit } : {}
3441
- }).catch(() => void 0);
3442
- };
3443
3681
  return {
3444
3682
  runtime: "cli",
3445
3683
  // A harness CLI cannot account tokens — exclude it from the conserved pool + equal-k.
@@ -3447,52 +3685,22 @@ function createWorktreeCliExecutor(options) {
3447
3685
  async execute(_task, signal) {
3448
3686
  const linked = linkSignals(signal, controller.signal);
3449
3687
  const started = Date.now();
3450
- worktree = await createWorktree({
3688
+ run = await runWorktreeHarness({
3451
3689
  repoRoot: options.repoRoot,
3452
- runId,
3453
- ...options.baseRef ? { baseRef: options.baseRef } : {},
3454
- ...options.runGit ? { runGit: options.runGit } : {}
3455
- });
3456
- const { command, args } = harnessInvocation(
3457
- options.harness,
3458
- options.profile,
3459
- options.taskPrompt
3460
- );
3461
- const harnessResult = await runHarness({
3690
+ profile: options.profile,
3462
3691
  harness: options.harness,
3463
- cwd: worktree.path,
3464
3692
  taskPrompt: options.taskPrompt,
3465
- invocation: { command, args },
3466
- ...options.harnessTimeoutMs !== void 0 ? { timeoutMs: options.harnessTimeoutMs } : {},
3467
- ...linked ? { signal: linked } : {}
3468
- });
3469
- const checks = await runWorktreeChecks({
3470
- worktreePath: worktree.path,
3693
+ runId,
3694
+ ...options.baseRef ? { baseRef: options.baseRef } : {},
3471
3695
  ...options.testCmd !== void 0 ? { testCmd: options.testCmd } : {},
3472
3696
  ...options.typecheckCmd !== void 0 ? { typecheckCmd: options.typecheckCmd } : {},
3473
- timeoutMs: checkTimeoutMs,
3474
- runCommand,
3475
- ...linked ? { signal: linked } : {}
3476
- });
3477
- const diff = await captureWorktreeDiff({
3478
- worktree,
3479
- ...options.runGit ? { runGit: options.runGit } : {}
3697
+ ...options.harnessTimeoutMs !== void 0 ? { harnessTimeoutMs: options.harnessTimeoutMs } : {},
3698
+ ...options.checkTimeoutMs !== void 0 ? { checkTimeoutMs: options.checkTimeoutMs } : {},
3699
+ ...linked ? { signal: linked } : {},
3700
+ ...options.runGit ? { runGit: options.runGit } : {},
3701
+ ...options.runHarness ? { runHarness: options.runHarness } : {},
3702
+ ...options.runCommand ? { runCommand: options.runCommand } : {}
3480
3703
  });
3481
- const out = {
3482
- branch: worktree.branch,
3483
- patch: diff.patch,
3484
- stats: diff.stats,
3485
- harness: {
3486
- name: options.harness,
3487
- exitCode: harnessResult.exitCode,
3488
- timedOut: harnessResult.timedOut,
3489
- killedBySignal: harnessResult.killedBySignal,
3490
- durationMs: harnessResult.durationMs,
3491
- stdout: harnessResult.stdout,
3492
- stderr: harnessResult.stderr
3493
- },
3494
- ...checks ? { checks } : {}
3495
- };
3496
3704
  const spent = {
3497
3705
  iterations: 1,
3498
3706
  // budgetExempt: spend is recorded zero (not metered), never a fabricated cost.
@@ -3500,12 +3708,16 @@ function createWorktreeCliExecutor(options) {
3500
3708
  usd: 0,
3501
3709
  ms: Date.now() - started
3502
3710
  };
3503
- artifact = { outRef: contentAddress(out), out, spent };
3711
+ artifact = { outRef: contentAddress(run.result), out: run.result, spent };
3504
3712
  return artifact;
3505
3713
  },
3506
3714
  async teardown(_grace) {
3507
3715
  controller.abort();
3508
- await removeIfPresent();
3716
+ if (run) {
3717
+ const r = run;
3718
+ run = void 0;
3719
+ await r.cleanup();
3720
+ }
3509
3721
  return { destroyed: true };
3510
3722
  },
3511
3723
  resultArtifact() {
@@ -3518,51 +3730,6 @@ function createWorktreeCliExecutor(options) {
3518
3730
  }
3519
3731
  };
3520
3732
  }
3521
- async function runWorktreeChecks(opts) {
3522
- if (opts.testCmd === void 0 && opts.typecheckCmd === void 0) return void 0;
3523
- const run = async (command) => {
3524
- const res = await opts.runCommand({
3525
- command,
3526
- cwd: opts.worktreePath,
3527
- timeoutMs: opts.timeoutMs,
3528
- ...opts.signal ? { signal: opts.signal } : {}
3529
- });
3530
- return {
3531
- command,
3532
- passed: res.exitCode === 0,
3533
- exitCode: res.exitCode,
3534
- output: res.output.length > checkOutputCap ? res.output.slice(-checkOutputCap) : res.output
3535
- };
3536
- };
3537
- const checks = {};
3538
- if (opts.testCmd !== void 0) checks.tests = await run(opts.testCmd);
3539
- if (opts.typecheckCmd !== void 0) checks.typecheck = await run(opts.typecheckCmd);
3540
- return checks;
3541
- }
3542
- function defaultRunCommand(opts) {
3543
- return new Promise((resolve, reject) => {
3544
- const child = spawn2("/bin/sh", ["-c", opts.command], {
3545
- cwd: opts.cwd,
3546
- stdio: ["ignore", "pipe", "pipe"]
3547
- });
3548
- const chunks = [];
3549
- let settled = false;
3550
- const timer = setTimeout(() => child.kill("SIGTERM"), opts.timeoutMs);
3551
- const onAbort = () => child.kill("SIGTERM");
3552
- opts.signal?.addEventListener("abort", onAbort, { once: true });
3553
- child.stdout?.on("data", (d) => chunks.push(String(d)));
3554
- child.stderr?.on("data", (d) => chunks.push(String(d)));
3555
- const finish = (fn) => {
3556
- if (settled) return;
3557
- settled = true;
3558
- clearTimeout(timer);
3559
- opts.signal?.removeEventListener("abort", onAbort);
3560
- fn();
3561
- };
3562
- child.on("error", (err) => finish(() => reject(err)));
3563
- child.on("close", (code) => finish(() => resolve({ exitCode: code, output: chunks.join("") })));
3564
- });
3565
- }
3566
3733
  function linkSignals(a, b) {
3567
3734
  if (a.aborted || b.aborted) {
3568
3735
  const c2 = new AbortController();
@@ -3671,35 +3838,59 @@ var routerToolsInlineExecutor = (spec, ctx) => {
3671
3838
  };
3672
3839
  abortIfSignalled();
3673
3840
  if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
3841
+ const inbox = createInbox();
3674
3842
  let artifact;
3675
3843
  return {
3676
3844
  runtime: "router",
3845
+ deliver: (m) => inbox.deliver(m),
3677
3846
  async execute(task, signal) {
3678
3847
  const started = Date.now();
3679
- const linked = linkSignals2(signal, controller.signal);
3680
3848
  const messages = [
3681
3849
  ...taskToMessages(task, spec)
3682
3850
  ];
3683
3851
  const tokens = zeroTokenUsage();
3684
3852
  let turns = 0;
3685
3853
  let lastText = "";
3854
+ const flush = () => {
3855
+ const pending = inbox.drain();
3856
+ if (pending.length) messages.push({ role: "user", content: inbox.fold(pending) });
3857
+ return pending.length > 0;
3858
+ };
3859
+ const external = mergeAbortSignals(signal, controller.signal);
3686
3860
  for (let t = 0; t < maxTurns; t += 1) {
3861
+ flush();
3862
+ const interruptSig = inbox.freshInterrupt();
3863
+ const turnController = new AbortController();
3864
+ const abortTurn = () => turnController.abort();
3865
+ if (external.aborted) turnController.abort();
3866
+ else external.addEventListener("abort", abortTurn);
3867
+ interruptSig.addEventListener("abort", abortTurn, { once: true });
3868
+ const cleanup = () => external.removeEventListener("abort", abortTurn);
3869
+ let res;
3870
+ try {
3871
+ res = await fetch(`${seam.routerBaseUrl.replace(/\/$/, "")}/chat/completions`, {
3872
+ method: "POST",
3873
+ headers: {
3874
+ "content-type": "application/json",
3875
+ authorization: `Bearer ${seam.routerKey}`
3876
+ },
3877
+ body: JSON.stringify({
3878
+ model,
3879
+ messages,
3880
+ tools: seam.tools,
3881
+ tool_choice: "auto",
3882
+ temperature: 0.2
3883
+ }),
3884
+ signal: turnController.signal
3885
+ });
3886
+ } catch (e) {
3887
+ cleanup();
3888
+ const interruptAbort = e instanceof DOMException && e.name === "AbortError" && interruptSig.aborted && !signal.aborted && !controller.signal.aborted;
3889
+ if (interruptAbort) continue;
3890
+ throw e;
3891
+ }
3892
+ cleanup();
3687
3893
  turns += 1;
3688
- const res = await fetch(`${seam.routerBaseUrl.replace(/\/$/, "")}/chat/completions`, {
3689
- method: "POST",
3690
- headers: {
3691
- "content-type": "application/json",
3692
- authorization: `Bearer ${seam.routerKey}`
3693
- },
3694
- body: JSON.stringify({
3695
- model,
3696
- messages,
3697
- tools: seam.tools,
3698
- tool_choice: "auto",
3699
- temperature: 0.2
3700
- }),
3701
- ...linked ? { signal: linked } : {}
3702
- });
3703
3894
  if (!res.ok) {
3704
3895
  throw new ValidationError(
3705
3896
  `routerToolsInlineExecutor: router ${res.status}: ${(await res.text()).slice(0, 200)}`
@@ -3714,7 +3905,10 @@ var routerToolsInlineExecutor = (spec, ctx) => {
3714
3905
  const msg = data.choices?.[0]?.message;
3715
3906
  if (msg?.content) lastText = msg.content;
3716
3907
  const toolCalls = msg?.tool_calls ?? [];
3717
- if (toolCalls.length === 0) break;
3908
+ if (toolCalls.length === 0) {
3909
+ if (flush()) continue;
3910
+ break;
3911
+ }
3718
3912
  messages.push({
3719
3913
  role: "assistant",
3720
3914
  content: msg?.content ?? "",
@@ -3738,8 +3932,20 @@ var routerToolsInlineExecutor = (spec, ctx) => {
3738
3932
  });
3739
3933
  continue;
3740
3934
  }
3741
- const result = await seam.executeToolCall(tc?.function?.name ?? "", args, task);
3935
+ const toolName = tc?.function?.name ?? "";
3936
+ let result;
3937
+ let status = "ok";
3938
+ try {
3939
+ result = await seam.executeToolCall(toolName, args, task);
3940
+ } catch (e) {
3941
+ status = "error";
3942
+ result = `error: ${e instanceof Error ? e.message : String(e)}`;
3943
+ }
3742
3944
  messages.push({ role: "tool", tool_call_id: id, content: result });
3945
+ try {
3946
+ seam.onToolStep?.({ toolName, args, status });
3947
+ } catch {
3948
+ }
3743
3949
  }
3744
3950
  }
3745
3951
  const usd = isModelPriced2(model) ? estimateCost2(tokens.input, tokens.output, model) : 0;
@@ -4171,6 +4377,18 @@ function linkSignals2(a, b) {
4171
4377
  b.addEventListener("abort", onAbort, { once: true });
4172
4378
  return c.signal;
4173
4379
  }
4380
+ function mergeAbortSignals(...signals) {
4381
+ const c = new AbortController();
4382
+ const onAbort = () => c.abort();
4383
+ for (const s of signals) {
4384
+ if (s.aborted) {
4385
+ c.abort();
4386
+ break;
4387
+ }
4388
+ s.addEventListener("abort", onAbort, { once: true });
4389
+ }
4390
+ return c.signal;
4391
+ }
4174
4392
 
4175
4393
  // src/runtime/supervise/budget.ts
4176
4394
  function spendFromUsageEvents(events) {
@@ -4279,10 +4497,20 @@ function createBudgetPool(root, now = Date.now) {
4279
4497
  committedUsd += spent.usd;
4280
4498
  }
4281
4499
  }
4500
+ function observe2(spend) {
4501
+ const tokens = totalTokens(spend.tokens);
4502
+ freeTokens -= tokens;
4503
+ committedTokens += tokens;
4504
+ freeIterations -= spend.iterations;
4505
+ committedIterations += spend.iterations;
4506
+ committedUsd += spend.usd;
4507
+ if (usdCapped) freeUsd -= spend.usd;
4508
+ }
4282
4509
  function readout() {
4283
4510
  return {
4284
4511
  tokensLeft: freeTokens,
4285
4512
  usdLeft: usdCapped ? freeUsd : 0,
4513
+ usdCapped,
4286
4514
  deadlineMs: absoluteDeadlineMs,
4287
4515
  reservedTokens
4288
4516
  };
@@ -4299,6 +4527,7 @@ function createBudgetPool(root, now = Date.now) {
4299
4527
  reconcile,
4300
4528
  spendFrom: foldUsage,
4301
4529
  readout,
4530
+ observe: observe2,
4302
4531
  assertNoOpenTickets
4303
4532
  };
4304
4533
  }
@@ -4368,12 +4597,14 @@ function createSupervisor() {
4368
4597
  if (out !== void 0) {
4369
4598
  const outRef = contentAddress(out);
4370
4599
  await opts.blobs.put(outRef, out);
4600
+ const { childWork, driverInference } = await spentFromJournal(journal, opts.runId);
4371
4601
  return {
4372
4602
  kind: "winner",
4373
4603
  out,
4374
4604
  outRef,
4375
4605
  tree,
4376
- spentTotal: await spentTotalFromJournal(journal, opts.runId)
4606
+ spentTotal: addSpend(childWork, driverInference),
4607
+ ...isNonEmptySpend(driverInference) ? { spentBreakdown: { driverInference, childWork } } : {}
4377
4608
  };
4378
4609
  }
4379
4610
  return {
@@ -4505,23 +4736,38 @@ function poolExhausted(pool2, opts) {
4505
4736
  }
4506
4737
  return false;
4507
4738
  }
4508
- async function spentTotalFromJournal(journal, root) {
4739
+ async function spentFromJournal(journal, root) {
4509
4740
  const events = await journal.loadTree(root);
4510
4741
  if (events === void 0) {
4511
4742
  throw new RuntimeRunStateError(
4512
4743
  `supervisor: spawn tree '${root}' is missing from the journal after run (corrupted log)`
4513
4744
  );
4514
4745
  }
4515
- const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
4746
+ const childWork = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
4747
+ const driverInference = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
4516
4748
  for (const ev of events) {
4517
- if (ev.kind !== "settled") continue;
4518
- total.iterations += ev.spent.iterations;
4519
- total.tokens.input += ev.spent.tokens.input;
4520
- total.tokens.output += ev.spent.tokens.output;
4521
- total.usd += ev.spent.usd;
4522
- total.ms += ev.spent.ms;
4749
+ if (ev.kind === "settled") accumulate(childWork, ev.spent);
4750
+ else if (ev.kind === "metered") accumulate(driverInference, ev.spend);
4523
4751
  }
4524
- return total;
4752
+ return { childWork, driverInference };
4753
+ }
4754
+ function accumulate(a, b) {
4755
+ a.iterations += b.iterations;
4756
+ a.tokens.input += b.tokens.input;
4757
+ a.tokens.output += b.tokens.output;
4758
+ a.usd += b.usd;
4759
+ a.ms += b.ms;
4760
+ }
4761
+ function addSpend(a, b) {
4762
+ return {
4763
+ iterations: a.iterations + b.iterations,
4764
+ tokens: { input: a.tokens.input + b.tokens.input, output: a.tokens.output + b.tokens.output },
4765
+ usd: a.usd + b.usd,
4766
+ ms: a.ms + b.ms
4767
+ };
4768
+ }
4769
+ function isNonEmptySpend(s) {
4770
+ return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
4525
4771
  }
4526
4772
 
4527
4773
  // src/runtime/personify/registry.ts
@@ -4680,7 +4926,7 @@ async function trajectoryReport(journal, blobs, root, options = {}) {
4680
4926
  throw new Error(`trajectoryReport: no journaled tree for root '${root}'`);
4681
4927
  }
4682
4928
  const spawns = events.filter(isSpawned).sort(bySeq);
4683
- const closes = events.filter((ev) => ev.kind !== "spawned").sort(bySeq);
4929
+ const closes = events.filter((ev) => ev.kind !== "spawned" && ev.kind !== "metered").sort(bySeq);
4684
4930
  const nodes = /* @__PURE__ */ new Map();
4685
4931
  for (const ev of spawns) {
4686
4932
  nodes.set(ev.id, {
@@ -4704,6 +4950,11 @@ async function trajectoryReport(journal, blobs, root, options = {}) {
4704
4950
  node.verdict = ev.verdict;
4705
4951
  node.outRef = ev.outRef;
4706
4952
  }
4953
+ for (const ev of events) {
4954
+ if (ev.kind !== "metered") continue;
4955
+ const node = requireNode2(nodes, ev.id, root);
4956
+ node.ownSpend = addNodeSpend(node.ownSpend, ev.spend);
4957
+ }
4707
4958
  if (!nodes.has(root)) {
4708
4959
  throw new Error(
4709
4960
  `trajectoryReport: root '${root}' has no spawned event in its journaled tree (corrupted log)`
@@ -4766,7 +5017,7 @@ function rollUpSpend(nodes, root) {
4766
5017
  continue;
4767
5018
  }
4768
5019
  const sum = cloneSpend(node.ownSpend);
4769
- for (const child of node.children) addSpend(sum, requireSpend(rolled, child, root));
5020
+ for (const child of node.children) addSpend2(sum, requireSpend(rolled, child, root));
4770
5021
  rolled.set(frame.id, sum);
4771
5022
  }
4772
5023
  return rolled;
@@ -4811,6 +5062,14 @@ function countStatuses(reported) {
4811
5062
  function zeroSpend4() {
4812
5063
  return { iterations: 0, tokens: zeroTokenUsage(), usd: 0, ms: 0 };
4813
5064
  }
5065
+ function addNodeSpend(a, b) {
5066
+ return {
5067
+ iterations: a.iterations + b.iterations,
5068
+ tokens: { input: a.tokens.input + b.tokens.input, output: a.tokens.output + b.tokens.output },
5069
+ usd: a.usd + b.usd,
5070
+ ms: a.ms + b.ms
5071
+ };
5072
+ }
4814
5073
  function cloneSpend(spend) {
4815
5074
  return {
4816
5075
  iterations: spend.iterations,
@@ -4819,7 +5078,7 @@ function cloneSpend(spend) {
4819
5078
  ms: spend.ms
4820
5079
  };
4821
5080
  }
4822
- function addSpend(acc, delta) {
5081
+ function addSpend2(acc, delta) {
4823
5082
  acc.iterations += delta.iterations;
4824
5083
  addTokenUsage(acc.tokens, delta.tokens);
4825
5084
  acc.usd += delta.usd;
@@ -6569,7 +6828,7 @@ function supervisorSkill(opts) {
6569
6828
  ' \u2022 systemPrompt: rich, specific instructions for THIS sub-task \u2014 tell the worker exactly what to produce, how to use its tools fully, and what "done" means. Never a one-liner; write the prompt a power-user would write.',
6570
6829
  " \u2022 model: the model best suited to this sub-task (omit to use the default).",
6571
6830
  " NEVER spawn a worker with an empty profile. The quality of the worker IS the quality of the profile you write.",
6572
- "3. await_next to collect each worker. Its result says valid:true only if the deployable check passed.",
6831
+ "3. await_event (kinds:['settled']) to collect each worker. Its result says valid:true only if the deployable check passed.",
6573
6832
  "4. If a worker did NOT deliver, AUTHOR A NEW worker whose systemPrompt names the SPECIFIC failure and how to fix it \u2014 never just retry the same prompt.",
6574
6833
  "5. Stop (reply with no tool call) once the work is delivered. You cannot declare done yourself \u2014 only a delivered (valid:true) worker counts.",
6575
6834
  ...opts?.goal ? ["", `The goal: ${opts.goal}`] : []
@@ -6708,6 +6967,62 @@ function isArtifact(value) {
6708
6967
  return value !== null && typeof value === "object" && typeof value.patch === "string" && typeof value.stats === "object";
6709
6968
  }
6710
6969
 
6970
+ // src/runtime/supervise/event-bus.ts
6971
+ function createEventBus(now = Date.now) {
6972
+ const queue = [];
6973
+ const log = [];
6974
+ const subscribers = [];
6975
+ const byKind = {};
6976
+ let seq = 0;
6977
+ let pulled = 0;
6978
+ const matches = (r, kinds) => !kinds || kinds.includes(r.event.type);
6979
+ const bestIndex = (kinds) => {
6980
+ let best = -1;
6981
+ let bestPriority = Number.NEGATIVE_INFINITY;
6982
+ for (let i = 0; i < queue.length; i++) {
6983
+ const r = queue[i];
6984
+ if (!r || !matches(r, kinds)) continue;
6985
+ if (r.priority > bestPriority) {
6986
+ best = i;
6987
+ bestPriority = r.priority;
6988
+ }
6989
+ }
6990
+ return best;
6991
+ };
6992
+ return {
6993
+ async publish(event, opts) {
6994
+ const record = { seq: seq++, at: now(), priority: opts?.priority ?? 0, event };
6995
+ if (opts?.queue !== false) queue.push(record);
6996
+ log.push(record);
6997
+ byKind[event.type] = (byKind[event.type] ?? 0) + 1;
6998
+ for (const handler of subscribers) await handler(record);
6999
+ return record;
7000
+ },
7001
+ pull(kinds) {
7002
+ const i = bestIndex(kinds);
7003
+ if (i < 0) return void 0;
7004
+ pulled++;
7005
+ return queue.splice(i, 1)[0]?.event;
7006
+ },
7007
+ subscribe(handler) {
7008
+ subscribers.push(handler);
7009
+ return () => {
7010
+ const i = subscribers.indexOf(handler);
7011
+ if (i >= 0) subscribers.splice(i, 1);
7012
+ };
7013
+ },
7014
+ pending(kinds) {
7015
+ return kinds ? queue.filter((r) => matches(r, kinds)).length : queue.length;
7016
+ },
7017
+ history() {
7018
+ return log;
7019
+ },
7020
+ stats() {
7021
+ return { published: seq, pulled, byKind: { ...byKind } };
7022
+ }
7023
+ };
7024
+ }
7025
+
6711
7026
  // src/mcp/tools/coordination.ts
6712
7027
  var idArg = { type: "string", description: "The workerId returned by spawn_worker." };
6713
7028
  function createCoordinationTools(opts) {
@@ -6717,6 +7032,12 @@ function createCoordinationTools(opts) {
6717
7032
  const ledger = [];
6718
7033
  const questions = [];
6719
7034
  const questionPolicy = opts.questionPolicy ?? "auto";
7035
+ const bus = createEventBus();
7036
+ if (opts.onEvent) {
7037
+ const cb = opts.onEvent;
7038
+ bus.subscribe((rec) => cb(rec.event));
7039
+ }
7040
+ const urgencyPriority = (u) => u === "blocks-run" ? 20 : u === "blocks-step" ? 10 : 0;
6720
7041
  const str = (v, field) => {
6721
7042
  if (typeof v !== "string" || v.length === 0)
6722
7043
  throw new Error(`coordination tools: "${field}" must be a non-empty string`);
@@ -6748,6 +7069,43 @@ function createCoordinationTools(opts) {
6748
7069
  ledger.push(w);
6749
7070
  return w;
6750
7071
  };
7072
+ const drainSettlement = async () => {
7073
+ const s = await opts.scope.next();
7074
+ if (!s) return false;
7075
+ const w = recordSettled(s);
7076
+ await bus.publish({ type: "settled", worker: w });
7077
+ if (w.status === "done" && w.outRef && opts.analysts && opts.analyzeOnSettle?.length) {
7078
+ const trace = await opts.blobs.get(w.outRef);
7079
+ for (const analyst of opts.analyzeOnSettle) {
7080
+ const findings = await opts.analysts.run(analyst, trace);
7081
+ await bus.publish({ type: "finding", finding: { fromWorker: w.id, analyst, findings } });
7082
+ }
7083
+ }
7084
+ return true;
7085
+ };
7086
+ async function sendDown(type, down, questionId) {
7087
+ await bus.publish(
7088
+ type === "answer" ? { type, down, questionId: str(questionId, "questionId") } : { type, down },
7089
+ { queue: false }
7090
+ );
7091
+ }
7092
+ const projectEvent = (ev) => {
7093
+ if (ev.type === "settled") {
7094
+ const w = ev.worker;
7095
+ return w.status === "done" ? {
7096
+ type: "settled",
7097
+ settled: w.id,
7098
+ status: "done",
7099
+ score: w.score,
7100
+ valid: w.valid,
7101
+ outRef: w.outRef
7102
+ } : { type: "settled", settled: w.id, status: "down", reason: w.reason };
7103
+ }
7104
+ if (ev.type === "question") return { type: "question", question: ev.question };
7105
+ if (ev.type === "finding") return { type: "finding", ...ev.finding };
7106
+ if (ev.type === "answer") return { type: "answer", ...ev.down, questionId: ev.questionId };
7107
+ return { type: ev.type, ...ev.down };
7108
+ };
6751
7109
  const nextQuestionId = (from) => `${from}:q${questionSeq++}`;
6752
7110
  const normalizeQuestion = (q, fallbackFrom) => {
6753
7111
  const from = str(q.from ?? fallbackFrom, "from");
@@ -6781,7 +7139,11 @@ function createCoordinationTools(opts) {
6781
7139
  return { question: record, added: true };
6782
7140
  };
6783
7141
  const emitNewQuestion = async (record) => {
6784
- if (record.added) await opts.onEvent?.({ type: "question", question: record.question });
7142
+ if (record.added)
7143
+ await bus.publish(
7144
+ { type: "question", question: record.question },
7145
+ { priority: urgencyPriority(record.question.urgency) }
7146
+ );
6785
7147
  return record.question;
6786
7148
  };
6787
7149
  const decideQuestion = (questionId, decision) => {
@@ -6844,38 +7206,52 @@ function createCoordinationTools(opts) {
6844
7206
  },
6845
7207
  {
6846
7208
  name: "steer_worker",
6847
- description: "Deliver an out-of-band instruction to a running worker inbox.",
7209
+ description: "Send a message DOWN to a still-LIVE worker (parent\u2192child): a new instruction, a course correction, or a continuation. The worker drains it at its next step boundary \u2014 and before it may settle, so it cannot finish while a message it never read is pending. A worker that already settled is gone (returns delivered:false) \u2014 spawn a fresh one instead.",
6848
7210
  inputSchema: {
6849
7211
  type: "object",
6850
7212
  properties: {
6851
7213
  workerId: idArg,
6852
- instruction: { type: "string", description: "What the worker should do next." }
7214
+ instruction: { type: "string", description: "What the worker should do next." },
7215
+ interrupt: {
7216
+ type: "boolean",
7217
+ description: "true = forceful: abort the worker\u2019s in-flight inference so it re-plans on the NEXT turn (a tool already mid-execution finishes first; only the owned tool-loop honors this). false/omitted = queued: it flushes at the next step boundary (and before it may settle)."
7218
+ }
6853
7219
  },
6854
7220
  required: ["workerId", "instruction"]
6855
7221
  },
6856
- handler: (raw) => {
7222
+ handler: async (raw) => {
6857
7223
  const a = obj(raw);
6858
- const delivered = opts.scope.send(str(a.workerId, "workerId"), {
6859
- steer: str(a.instruction, "instruction")
6860
- });
6861
- return Promise.resolve({ delivered });
7224
+ const workerId = str(a.workerId, "workerId");
7225
+ const instruction = str(a.instruction, "instruction");
7226
+ const interrupt = a.interrupt === true;
7227
+ const delivered = opts.scope.send(workerId, { steer: instruction, interrupt });
7228
+ await sendDown("steer", { toWorker: workerId, instruction, delivered });
7229
+ return { delivered };
6862
7230
  }
6863
7231
  },
6864
7232
  {
6865
- name: "await_next",
6866
- description: "Wait for the next spawned worker to settle. Returns { idle: true } when none are live.",
6867
- inputSchema: { type: "object", properties: {} },
6868
- handler: async () => {
6869
- const s = await opts.scope.next();
6870
- if (!s) return { idle: true };
6871
- const w = recordSettled(s);
6872
- return w.status === "done" ? {
6873
- settled: w.id,
6874
- status: "done",
6875
- score: w.score,
6876
- valid: w.valid,
6877
- outRef: w.outRef
6878
- } : { settled: w.id, status: "down", reason: w.reason };
7233
+ name: "await_event",
7234
+ description: "Wait for and pull the next message a worker, sub-driver, or analyst sent up \u2014 the unified inbox. An event is one of: a settled worker output ('settled'), a question needing your answer ('question', from ask_parent / the worker's ask-user), or a trace-analyst finding ('finding', from analyze-on-settle). Pass kinds:['settled'] for just the next finished worker; omit `kinds` to also receive questions and findings. Returns { idle: true } when nothing is queued and no workers are live.",
7235
+ inputSchema: {
7236
+ type: "object",
7237
+ properties: {
7238
+ kinds: {
7239
+ type: "array",
7240
+ items: { type: "string", enum: ["settled", "question", "finding"] },
7241
+ description: "Restrict to these event kinds (any if omitted)."
7242
+ }
7243
+ }
7244
+ },
7245
+ handler: async (raw) => {
7246
+ const k = obj(raw).kinds;
7247
+ const kinds = Array.isArray(k) ? k.filter((x) => x === "settled" || x === "question" || x === "finding") : void 0;
7248
+ let ev = bus.pull(kinds);
7249
+ if (!ev) {
7250
+ const drained = await drainSettlement();
7251
+ ev = bus.pull(kinds);
7252
+ if (!ev) return { idle: !drained };
7253
+ }
7254
+ return projectEvent(ev);
6879
7255
  }
6880
7256
  },
6881
7257
  {
@@ -6899,17 +7275,24 @@ function createCoordinationTools(opts) {
6899
7275
  },
6900
7276
  required: ["questionId"]
6901
7277
  },
6902
- handler: (raw) => {
7278
+ handler: async (raw) => {
6903
7279
  const a = obj(raw);
6904
7280
  const questionId = str(a.questionId, "questionId");
6905
7281
  if (typeof a.answer === "string" && a.answer.length > 0) {
6906
- return Promise.resolve({
6907
- question: decideQuestion(questionId, {
6908
- kind: "answer",
6909
- answer: a.answer,
6910
- by: typeof a.by === "string" && a.by.length > 0 ? a.by : "user"
6911
- })
7282
+ const answer = a.answer;
7283
+ const question = decideQuestion(questionId, {
7284
+ kind: "answer",
7285
+ answer,
7286
+ by: typeof a.by === "string" && a.by.length > 0 ? a.by : "user"
6912
7287
  });
7288
+ const interrupt = question.urgency === "blocks-run" || question.urgency === "blocks-step";
7289
+ const delivered = opts.scope.send(question.from, { answer, questionId, interrupt });
7290
+ await sendDown(
7291
+ "answer",
7292
+ { toWorker: question.from, instruction: answer, delivered },
7293
+ questionId
7294
+ );
7295
+ return { question, delivered };
6913
7296
  }
6914
7297
  if (typeof a.deferReason === "string" && a.deferReason.length > 0) {
6915
7298
  return Promise.resolve({
@@ -7020,6 +7403,9 @@ function createCoordinationTools(opts) {
7020
7403
  }
7021
7404
  return {
7022
7405
  tools,
7406
+ history: () => bus.history(),
7407
+ raiseFinding: (finding) => bus.publish({ type: "finding", finding }).then(() => void 0),
7408
+ stats: () => bus.stats(),
7023
7409
  isStopped: () => stopped,
7024
7410
  stopReason: () => reason,
7025
7411
  settled: () => ledger,
@@ -7028,11 +7414,29 @@ function createCoordinationTools(opts) {
7028
7414
  }
7029
7415
 
7030
7416
  // src/runtime/supervise/coordination-driver.ts
7417
+ var runawayTripwireTurns = 2e3;
7418
+ function poolStarved(scope, perWorker) {
7419
+ const b = scope.budget;
7420
+ if (b.reservedTokens > 0) return false;
7421
+ const tokenStarved = b.tokensLeft < perWorker.maxTokens;
7422
+ const usdStarved = b.usdCapped && b.usdLeft <= 0;
7423
+ return tokenStarved || usdStarved;
7424
+ }
7425
+ function deadlinePassed(scope, now) {
7426
+ const b = scope.budget;
7427
+ return b.deadlineMs > 0 && now() >= b.deadlineMs;
7428
+ }
7031
7429
  function coordinationDriverAgent(opts) {
7032
7430
  if (typeof opts.chat?.next !== "function") {
7033
7431
  throw new ValidationError("coordinationDriverAgent: opts.chat.next must be a function");
7034
7432
  }
7035
- const maxTurns = opts.maxTurns ?? 16;
7433
+ if (opts.maxTurns !== void 0 && opts.maxTurns < 0) {
7434
+ throw new ValidationError(
7435
+ "coordinationDriverAgent: maxTurns must be >= 0 (0 lifts the turn cap; bounds become the conserved pool + deadline + abort)"
7436
+ );
7437
+ }
7438
+ const maxTurns = opts.maxTurns === 0 ? runawayTripwireTurns : opts.maxTurns ?? 16;
7439
+ const now = opts.now ?? Date.now;
7036
7440
  return {
7037
7441
  name: opts.name,
7038
7442
  async act(task, scope) {
@@ -7051,9 +7455,28 @@ function coordinationDriverAgent(opts) {
7051
7455
  const system = typeof opts.systemPrompt === "function" ? opts.systemPrompt(task) : opts.systemPrompt;
7052
7456
  const messages = [{ role: "user", content: stringifyTask(task) }];
7053
7457
  for (let turn = 0; turn < maxTurns; turn += 1) {
7054
- if (coord.isStopped()) break;
7458
+ if (coord.isStopped() || scope.signal.aborted) break;
7459
+ if (poolStarved(scope, opts.perWorker) || deadlinePassed(scope, now)) break;
7055
7460
  const res = await opts.chat.next({ system, messages, tools: toolSpecs });
7056
7461
  const calls = res.toolCalls ?? [];
7462
+ if (res.usage || res.costUsd !== void 0) {
7463
+ const turnSpend = {
7464
+ // iterations:0 — the conserved iteration channel (`maxIterations`) budgets CHILD rounds,
7465
+ // not driver turns; counting turns there would conflate the two AND make a driver arm's
7466
+ // iteration count diverge from a blind arm's. The driver is bounded by maxTurns + the
7467
+ // token/usd pool; its turn COUNT stays observable via the per-turn `agent.turn` events.
7468
+ iterations: 0,
7469
+ tokens: { input: res.usage?.input ?? 0, output: res.usage?.output ?? 0 },
7470
+ usd: res.costUsd ?? 0,
7471
+ ms: 0
7472
+ };
7473
+ await scope.meter(turnSpend, {
7474
+ kind: "driver-inference",
7475
+ driver: opts.name,
7476
+ turn,
7477
+ toolCalls: calls.map((c) => c.name)
7478
+ });
7479
+ }
7057
7480
  if (calls.length === 0) {
7058
7481
  return finalize2(coord, opts.blobs);
7059
7482
  }
@@ -9215,7 +9638,11 @@ async function serveCoordinationMcp(opts) {
9215
9638
  scope: opts.scope,
9216
9639
  blobs: opts.blobs,
9217
9640
  makeWorkerAgent: opts.makeWorkerAgent,
9218
- perWorker: opts.perWorker
9641
+ perWorker: opts.perWorker,
9642
+ ...opts.analysts ? { analysts: opts.analysts } : {},
9643
+ ...opts.analyzeOnSettle ? { analyzeOnSettle: opts.analyzeOnSettle } : {},
9644
+ ...opts.onEvent ? { onEvent: opts.onEvent } : {},
9645
+ ...opts.questionPolicy ? { questionPolicy: opts.questionPolicy } : {}
9219
9646
  });
9220
9647
  const mcp = createMcpServer({ extraTools: coord.tools, serverName: "coordination" });
9221
9648
  const host = opts.host ?? "127.0.0.1";
@@ -9265,12 +9692,145 @@ async function serveCoordinationMcp(opts) {
9265
9692
  port,
9266
9693
  settled: () => coord.settled(),
9267
9694
  isStopped: () => coord.isStopped(),
9695
+ history: () => coord.history(),
9696
+ stats: () => coord.stats(),
9697
+ raiseFinding: (finding) => coord.raiseFinding(finding),
9268
9698
  close: () => new Promise((resolve) => {
9269
9699
  server.close(() => resolve());
9270
9700
  })
9271
9701
  };
9272
9702
  }
9273
9703
 
9704
+ // src/runtime/supervise/detector-monitor.ts
9705
+ import {
9706
+ argHash,
9707
+ errorStreakDetector,
9708
+ observeAll,
9709
+ repeatedActionDetector
9710
+ } from "@tangle-network/agent-eval";
9711
+ function defaultToolDetectors() {
9712
+ return [repeatedActionDetector({ maxRepeated: 3 }), errorStreakDetector({ maxErrors: 3 })];
9713
+ }
9714
+ function createDetectorMonitor(opts = {}) {
9715
+ const detectors = opts.detectors ?? defaultToolDetectors();
9716
+ return {
9717
+ observeToolStep(step) {
9718
+ let fingerprint;
9719
+ try {
9720
+ fingerprint = `${step.toolName}|${argHash(step.args)}`;
9721
+ } catch {
9722
+ fingerprint = `${step.toolName}|<unhashable>`;
9723
+ }
9724
+ const signals = observeAll(detectors, {
9725
+ // Same fingerprint scheme as agent-eval's batch stuck-loop view: tool name + hashed args.
9726
+ actionFingerprint: fingerprint,
9727
+ ...step.status ? { status: step.status } : {},
9728
+ label: step.toolName
9729
+ });
9730
+ for (const s of signals) void opts.onSignal?.(s);
9731
+ return signals;
9732
+ },
9733
+ reset() {
9734
+ for (const d of detectors) d.reset();
9735
+ }
9736
+ };
9737
+ }
9738
+
9739
+ // src/runtime/supervise/router-driver-chat.ts
9740
+ function routerDriverChat(c, opts = {}) {
9741
+ const temperature = opts.temperature ?? 0.4;
9742
+ return {
9743
+ next: async ({ system, messages, tools }) => {
9744
+ const oa = [
9745
+ { role: "system", content: system },
9746
+ ...messages.map(toOpenAI)
9747
+ ];
9748
+ const oaTools = tools.map((t) => ({
9749
+ type: "function",
9750
+ function: { name: t.name, description: t.description, parameters: t.parameters }
9751
+ }));
9752
+ const r = await routerChatWithTools(c, oa, oaTools, { temperature, toolChoice: "auto" });
9753
+ return {
9754
+ ...r.content ? { content: r.content } : {},
9755
+ // Forward the router's REAL usage + cost so the driver meters its own inference against the
9756
+ // conserved pool (the integrity hole: these were dropped on the floor before).
9757
+ ...r.usage ? { usage: r.usage } : {},
9758
+ ...typeof r.costUsd === "number" ? { costUsd: r.costUsd } : {},
9759
+ toolCalls: r.toolCalls.map((tc) => ({
9760
+ id: tc.id,
9761
+ name: tc.name,
9762
+ arguments: safeParse(tc.arguments)
9763
+ }))
9764
+ };
9765
+ }
9766
+ };
9767
+ }
9768
+ function toOpenAI(m) {
9769
+ if (m.role === "assistant" && m.toolCalls?.length) {
9770
+ return {
9771
+ role: "assistant",
9772
+ content: m.content ?? "",
9773
+ tool_calls: m.toolCalls.map((tc) => ({
9774
+ id: tc.id ?? tc.name,
9775
+ type: "function",
9776
+ function: { name: tc.name, arguments: JSON.stringify(tc.arguments) }
9777
+ }))
9778
+ };
9779
+ }
9780
+ if (m.role === "tool") {
9781
+ return { role: "tool", tool_call_id: m.toolCallId ?? m.name ?? "call", content: m.content };
9782
+ }
9783
+ return { role: m.role, content: m.content };
9784
+ }
9785
+ function safeParse(s) {
9786
+ try {
9787
+ return JSON.parse(s);
9788
+ } catch {
9789
+ return {};
9790
+ }
9791
+ }
9792
+
9793
+ // src/runtime/supervise/trajectory-recorder.ts
9794
+ import { buildTrajectory, InMemoryTraceStore } from "@tangle-network/agent-eval";
9795
+ import { stuckLoopView, toolWasteView } from "@tangle-network/agent-eval/pipelines";
9796
+ function createTrajectoryRecorder(runId, now = Date.now) {
9797
+ let steps = [];
9798
+ return {
9799
+ observeToolStep(step) {
9800
+ steps.push({ ...step, at: now() });
9801
+ },
9802
+ async analyze() {
9803
+ const store = new InMemoryTraceStore();
9804
+ for (let i = 0; i < steps.length; i += 1) {
9805
+ const s = steps[i];
9806
+ if (!s) continue;
9807
+ const span = {
9808
+ spanId: `${runId}-t${i}`,
9809
+ runId,
9810
+ kind: "tool",
9811
+ name: s.toolName,
9812
+ toolName: s.toolName,
9813
+ args: s.args,
9814
+ status: s.status ?? "ok",
9815
+ startedAt: s.at,
9816
+ endedAt: s.at,
9817
+ ...s.result !== void 0 ? { result: s.result } : {}
9818
+ };
9819
+ await store.appendSpan(span);
9820
+ }
9821
+ const [trajectory, stuckLoop, toolWaste] = await Promise.all([
9822
+ buildTrajectory(store, runId),
9823
+ stuckLoopView(store, { runId }),
9824
+ toolWasteView(store, { runId })
9825
+ ]);
9826
+ return { trajectory, stuckLoop, toolWaste };
9827
+ },
9828
+ reset() {
9829
+ steps = [];
9830
+ }
9831
+ };
9832
+ }
9833
+
9274
9834
  // src/runtime/verifier-environment.ts
9275
9835
  var submitTool = {
9276
9836
  type: "function",
@@ -9618,9 +10178,11 @@ export {
9618
10178
  routerChatWithUsage,
9619
10179
  routerChatWithTools,
9620
10180
  routerToolLoop,
10181
+ createInbox,
9621
10182
  createWorktree,
9622
10183
  captureWorktreeDiff,
9623
10184
  removeWorktree,
10185
+ runWorktreeHarness,
9624
10186
  createWorktreeCliExecutor,
9625
10187
  cliWorktreeExecutor,
9626
10188
  createExecutor,
@@ -9661,6 +10223,7 @@ export {
9661
10223
  authoredWorker,
9662
10224
  coderDeliverable,
9663
10225
  worktreeCoderFanout,
10226
+ createEventBus,
9664
10227
  createCoordinationTools,
9665
10228
  coordinationDriverAgent,
9666
10229
  InMemoryFeedbackStore,
@@ -9715,6 +10278,10 @@ export {
9715
10278
  createMcpServer,
9716
10279
  createInProcessTransport,
9717
10280
  serveCoordinationMcp,
10281
+ defaultToolDetectors,
10282
+ createDetectorMonitor,
10283
+ routerDriverChat,
10284
+ createTrajectoryRecorder,
9718
10285
  createVerifierEnvironment,
9719
10286
  createWaterfallCollector,
9720
10287
  localShell,
@@ -9722,4 +10289,4 @@ export {
9722
10289
  jjWorkspace,
9723
10290
  runInWorkspace
9724
10291
  };
9725
- //# sourceMappingURL=chunk-ZADWPBOE.js.map
10292
+ //# sourceMappingURL=chunk-RLDUT4JL.js.map