@tangle-network/agent-runtime 0.102.2 → 0.103.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 (77) hide show
  1. package/README.md +73 -10
  2. package/dist/agent.d.ts +1 -1
  3. package/dist/agent.js +5 -5
  4. package/dist/analyst-loop.d.ts +1 -1
  5. package/dist/candidate-execution/index.d.ts +5 -41
  6. package/dist/candidate-execution/index.js +8 -6
  7. package/dist/{chunk-QCXD7NHM.js → chunk-3LJF5XSE.js} +79 -334
  8. package/dist/chunk-3LJF5XSE.js.map +1 -0
  9. package/dist/{chunk-3DW5DEKQ.js → chunk-5AITUUHO.js} +3 -3
  10. package/dist/{chunk-SQ3ZJH3Y.js → chunk-6WZZXQV5.js} +467 -23
  11. package/dist/chunk-6WZZXQV5.js.map +1 -0
  12. package/dist/{chunk-SCOHHMKU.js → chunk-AAN2MB2X.js} +3 -3
  13. package/dist/{chunk-KSM6GFHP.js → chunk-B7K7V22Y.js} +2 -2
  14. package/dist/{chunk-ZUCUAFVF.js → chunk-EAQ5YRRY.js} +1382 -62
  15. package/dist/chunk-EAQ5YRRY.js.map +1 -0
  16. package/dist/chunk-ISPWRSEC.js +408 -0
  17. package/dist/chunk-ISPWRSEC.js.map +1 -0
  18. package/dist/{chunk-7FJJUC73.js → chunk-LFM4JBRW.js} +29 -6
  19. package/dist/{chunk-7FJJUC73.js.map → chunk-LFM4JBRW.js.map} +1 -1
  20. package/dist/{chunk-ROILMKDL.js → chunk-QYCKIV6C.js} +3 -3
  21. package/dist/{chunk-LJLG734E.js → chunk-SBTWKPVR.js} +10 -11
  22. package/dist/{chunk-LJLG734E.js.map → chunk-SBTWKPVR.js.map} +1 -1
  23. package/dist/{chunk-CK76OZPM.js → chunk-SNSMRT6H.js} +56 -386
  24. package/dist/chunk-SNSMRT6H.js.map +1 -0
  25. package/dist/{chunk-EKUMRQNR.js → chunk-TUSOOIDV.js} +2 -2
  26. package/dist/{chunk-J6V6DWWS.js → chunk-ZOYN3JR5.js} +5 -5
  27. package/dist/{chunk-377GOJHJ.js → chunk-ZXICDSAK.js} +333 -4
  28. package/dist/chunk-ZXICDSAK.js.map +1 -0
  29. package/dist/{completion-gate-C6dhVN-N.d.ts → completion-gate-DLINnrkM.d.ts} +1 -1
  30. package/dist/{coordination-D9l_YmwW.d.ts → coordination-DTehA977.d.ts} +250 -8
  31. package/dist/environment-provider.d.ts +2 -2
  32. package/dist/index.d.ts +12 -12
  33. package/dist/index.js +17 -15
  34. package/dist/index.js.map +1 -1
  35. package/dist/intelligence.d.ts +26 -8
  36. package/dist/intelligence.js +24 -7
  37. package/dist/intelligence.js.map +1 -1
  38. package/dist/knowledge.d.ts +6 -6
  39. package/dist/knowledge.js +9 -9
  40. package/dist/{loop-runner-bin-PnWslIw4.d.ts → loop-runner-bin-DhIXsLAd.d.ts} +2 -2
  41. package/dist/loop-runner-bin.d.ts +5 -5
  42. package/dist/loop-runner-bin.js +6 -6
  43. package/dist/loops.d.ts +305 -148
  44. package/dist/loops.js +82 -14
  45. package/dist/mcp/bin.js +3 -3
  46. package/dist/mcp/index.d.ts +6 -6
  47. package/dist/mcp/index.js +6 -6
  48. package/dist/profiles.d.ts +1 -1
  49. package/dist/{profile-DbfaMTdk.d.ts → protected-model-port-DzkUY3HQ.d.ts} +41 -3
  50. package/dist/{structural-rollout-D7xik9XR.d.ts → structural-rollout-BFPIy3iw.d.ts} +3 -3
  51. package/dist/supervise-Cp8bNcJi.d.ts +379 -0
  52. package/dist/testing.js +10 -10
  53. package/dist/testing.js.map +1 -1
  54. package/dist/{types-CWqfCO8s.d.ts → types-Cbp72qyu.d.ts} +1 -1
  55. package/dist/{types-CLat4bT9.d.ts → types-DK11_O4L.d.ts} +1 -1
  56. package/dist/types-lZTE_LBd.d.ts +957 -0
  57. package/dist/{worktree-fanout-DsIR4d6w.d.ts → worktree-fanout-DiiJWjJO.d.ts} +17 -5
  58. package/package.json +3 -2
  59. package/skills/build-with-agent-runtime/SKILL.md +7 -5
  60. package/skills/generate-eval/SKILL.md +45 -52
  61. package/skills/loop-writer/SKILL.md +79 -147
  62. package/skills/supervise/SKILL.md +33 -14
  63. package/dist/chunk-377GOJHJ.js.map +0 -1
  64. package/dist/chunk-CK76OZPM.js.map +0 -1
  65. package/dist/chunk-IMSNJSXH.js +0 -73
  66. package/dist/chunk-IMSNJSXH.js.map +0 -1
  67. package/dist/chunk-QCXD7NHM.js.map +0 -1
  68. package/dist/chunk-SQ3ZJH3Y.js.map +0 -1
  69. package/dist/chunk-ZUCUAFVF.js.map +0 -1
  70. package/dist/supervise-BQdNAXtK.d.ts +0 -131
  71. package/dist/types-D870JlH0.d.ts +0 -500
  72. /package/dist/{chunk-3DW5DEKQ.js.map → chunk-5AITUUHO.js.map} +0 -0
  73. /package/dist/{chunk-SCOHHMKU.js.map → chunk-AAN2MB2X.js.map} +0 -0
  74. /package/dist/{chunk-KSM6GFHP.js.map → chunk-B7K7V22Y.js.map} +0 -0
  75. /package/dist/{chunk-ROILMKDL.js.map → chunk-QYCKIV6C.js.map} +0 -0
  76. /package/dist/{chunk-EKUMRQNR.js.map → chunk-TUSOOIDV.js.map} +0 -0
  77. /package/dist/{chunk-J6V6DWWS.js.map → chunk-ZOYN3JR5.js.map} +0 -0
@@ -7,6 +7,7 @@ import {
7
7
  import {
8
8
  assertModelAllowed,
9
9
  captureWorktreeDiff,
10
+ createActivityLog,
10
11
  createSupervisor,
11
12
  createWorktree,
12
13
  notifyRuntimeHookEvent,
@@ -17,12 +18,14 @@ import {
17
18
  runWorktreeChecks,
18
19
  runWorktreeHarness,
19
20
  withDriverExecutor
20
- } from "./chunk-SQ3ZJH3Y.js";
21
+ } from "./chunk-6WZZXQV5.js";
21
22
  import {
23
+ FileResultBlobStore,
24
+ FileSpawnJournal,
22
25
  InMemoryResultBlobStore,
23
26
  InMemorySpawnJournal,
24
27
  contentAddress
25
- } from "./chunk-IMSNJSXH.js";
28
+ } from "./chunk-ISPWRSEC.js";
26
29
  import {
27
30
  providerAsExecutor,
28
31
  resolveAgentEnvironmentProvider
@@ -1037,15 +1040,15 @@ function copyPlainSpine(value, seen) {
1037
1040
  return copy;
1038
1041
  }
1039
1042
  function hashJson(value) {
1040
- let str;
1043
+ let str2;
1041
1044
  try {
1042
- str = JSON.stringify(value) ?? String(value);
1045
+ str2 = JSON.stringify(value) ?? String(value);
1043
1046
  } catch {
1044
- str = String(value);
1047
+ str2 = String(value);
1045
1048
  }
1046
1049
  let h = 2166136261;
1047
- for (let i = 0; i < str.length; i += 1) {
1048
- h ^= str.charCodeAt(i);
1050
+ for (let i = 0; i < str2.length; i += 1) {
1051
+ h ^= str2.charCodeAt(i);
1049
1052
  h = Math.imul(h, 16777619);
1050
1053
  }
1051
1054
  return (h >>> 0).toString(16).padStart(8, "0");
@@ -1096,6 +1099,735 @@ ${lines.join("\n")}`;
1096
1099
  };
1097
1100
  }
1098
1101
 
1102
+ // src/runtime/supervise/trace-source.ts
1103
+ function toToolSpan(input, runId, seq, at) {
1104
+ const startedAt = typeof input.startedAt === "number" ? input.startedAt : at;
1105
+ const endedAt = typeof input.endedAt === "number" ? input.endedAt : startedAt;
1106
+ return {
1107
+ spanId: `${runId}-t${seq}`,
1108
+ runId,
1109
+ kind: "tool",
1110
+ name: input.toolName,
1111
+ toolName: input.toolName,
1112
+ args: input.args,
1113
+ status: input.status ?? "ok",
1114
+ startedAt,
1115
+ endedAt,
1116
+ ...input.result !== void 0 ? { result: input.result } : {}
1117
+ };
1118
+ }
1119
+ var obj = (v) => v && typeof v === "object" ? v : void 0;
1120
+ var str = (v) => typeof v === "string" && v ? v : void 0;
1121
+ var decodeOpencodePart = (raw) => {
1122
+ if (str(raw.type)?.toLowerCase() !== "tool" || !str(raw.tool)) return void 0;
1123
+ const part = raw;
1124
+ const state = obj(part.state);
1125
+ const status = state?.status;
1126
+ if (!status || status === "pending" || status === "running") return void 0;
1127
+ return {
1128
+ toolName: part.tool,
1129
+ args: state?.input ?? {},
1130
+ // ToolStateError carries 'error' OR 'failed' — the reverse-engineered decoder missed 'failed'.
1131
+ status: status === "error" || status === "failed" ? "error" : "ok",
1132
+ ...str(part.callID) ? { callId: part.callID } : {}
1133
+ };
1134
+ };
1135
+ var decodeAnthropicPart = (p) => {
1136
+ if (str(p.type)?.toLowerCase() !== "tool_use") return void 0;
1137
+ const name = str(p.name) ?? str(p.tool);
1138
+ if (!name) return void 0;
1139
+ const id = str(p.id) ?? str(p.tool_use_id);
1140
+ return {
1141
+ toolName: name,
1142
+ args: p.input ?? {},
1143
+ ...id ? { callId: id } : {}
1144
+ };
1145
+ };
1146
+ var decodeOpenAiPart = (p) => {
1147
+ const type = str(p.type)?.toLowerCase();
1148
+ const fn = obj(p.function);
1149
+ if (type !== "function" && type !== "tool_call") return void 0;
1150
+ const name = str(fn?.name) ?? str(p.name);
1151
+ if (!name) return void 0;
1152
+ const rawArgs = fn?.arguments ?? p.arguments;
1153
+ return {
1154
+ toolName: name,
1155
+ args: typeof rawArgs === "string" ? safeParse(rawArgs) : rawArgs ?? {},
1156
+ ...str(p.id) ? { callId: p.id } : str(fn?.id) ? { callId: fn?.id } : {}
1157
+ };
1158
+ };
1159
+ var toolPartDecoders = {
1160
+ opencode: decodeOpencodePart,
1161
+ "claude-code": decodeAnthropicPart,
1162
+ anthropic: decodeAnthropicPart,
1163
+ codex: decodeOpenAiPart,
1164
+ openai: decodeOpenAiPart,
1165
+ router: decodeOpenAiPart,
1166
+ kimi: decodeOpenAiPart
1167
+ };
1168
+ function decodeToolPart(part, harness) {
1169
+ const p = obj(part);
1170
+ if (!p) return void 0;
1171
+ const specific = harness ? toolPartDecoders[harness] : void 0;
1172
+ if (specific) return specific(p);
1173
+ for (const decode of new Set(Object.values(toolPartDecoders))) {
1174
+ const step = decode(p);
1175
+ if (step) return step;
1176
+ }
1177
+ return void 0;
1178
+ }
1179
+ function safeParse(s) {
1180
+ try {
1181
+ return JSON.parse(s);
1182
+ } catch {
1183
+ return s;
1184
+ }
1185
+ }
1186
+ var runSeq = 0;
1187
+ function createPushTraceSource(opts = {}) {
1188
+ const runId = opts.runId ?? `push-${runSeq++}`;
1189
+ const now = opts.now ?? Date.now;
1190
+ const spans = [];
1191
+ const subs = /* @__PURE__ */ new Set();
1192
+ return {
1193
+ record(input) {
1194
+ const span = toToolSpan(input, runId, spans.length, now());
1195
+ spans.push(span);
1196
+ for (const fn of subs) {
1197
+ try {
1198
+ fn(span);
1199
+ } catch {
1200
+ }
1201
+ }
1202
+ return span;
1203
+ },
1204
+ source: {
1205
+ onSpan(handler) {
1206
+ subs.add(handler);
1207
+ return () => subs.delete(handler);
1208
+ },
1209
+ collect: () => Promise.resolve([...spans])
1210
+ }
1211
+ };
1212
+ }
1213
+ function createPartsTraceSource(opts) {
1214
+ const runId = opts.runId ?? `parts-${runSeq++}`;
1215
+ const now = opts.now ?? Date.now;
1216
+ const subs = /* @__PURE__ */ new Set();
1217
+ const seenLive = /* @__PURE__ */ new Set();
1218
+ let liveSeq = 0;
1219
+ let unsub;
1220
+ const startLive = () => {
1221
+ if (unsub || !opts.subscribeParts) return;
1222
+ unsub = opts.subscribeParts((part) => {
1223
+ const step = decodeToolPart(part, opts.harness);
1224
+ if (!step) return;
1225
+ if (step.callId) {
1226
+ if (seenLive.has(step.callId)) return;
1227
+ seenLive.add(step.callId);
1228
+ }
1229
+ const span = toToolSpan(step, runId, liveSeq++, now());
1230
+ for (const fn of subs) {
1231
+ try {
1232
+ fn(span);
1233
+ } catch {
1234
+ }
1235
+ }
1236
+ });
1237
+ };
1238
+ return {
1239
+ onSpan(handler) {
1240
+ subs.add(handler);
1241
+ startLive();
1242
+ return () => {
1243
+ subs.delete(handler);
1244
+ if (subs.size === 0 && unsub) {
1245
+ unsub();
1246
+ unsub = void 0;
1247
+ }
1248
+ };
1249
+ },
1250
+ async collect() {
1251
+ const parts = await opts.collectParts();
1252
+ const spans = [];
1253
+ const seen = /* @__PURE__ */ new Set();
1254
+ for (const part of parts) {
1255
+ const step = decodeToolPart(part, opts.harness);
1256
+ if (!step) continue;
1257
+ if (step.callId) {
1258
+ if (seen.has(step.callId)) continue;
1259
+ seen.add(step.callId);
1260
+ }
1261
+ spans.push(toToolSpan(step, runId, spans.length, now()));
1262
+ }
1263
+ return spans;
1264
+ }
1265
+ };
1266
+ }
1267
+ function sandboxSessionTraceSource(box, sessionId, opts = {}) {
1268
+ return createPartsTraceSource({
1269
+ collectParts: async () => {
1270
+ const msgs = await box.messages({ sessionId });
1271
+ return msgs.flatMap((m) => m.parts ? [...m.parts] : []);
1272
+ },
1273
+ ...opts.harness ? { harness: opts.harness } : {},
1274
+ ...opts.subscribeParts ? { subscribeParts: opts.subscribeParts } : {},
1275
+ runId: opts.runId ?? `box-${sessionId}`,
1276
+ ...opts.now ? { now: opts.now } : {}
1277
+ });
1278
+ }
1279
+
1280
+ // src/runtime/supervise/pi-executor.ts
1281
+ import { spawn } from "child_process";
1282
+ var PI_RUNTIME = "pi";
1283
+ var piSeamKey = "pi";
1284
+ var piExecutor = (spec, ctx) => {
1285
+ const seam = readPiSeam(ctx);
1286
+ const inbox = createInbox();
1287
+ const activity = createActivityLog(seam.activityWindow ?? 12);
1288
+ const trace = createPushTraceSource({
1289
+ runId: `pi-${spec.profile.name ?? "worker"}-${Date.now()}`
1290
+ });
1291
+ const controller = new AbortController();
1292
+ const cascade = () => controller.abort();
1293
+ if (ctx.signal.aborted) controller.abort();
1294
+ else ctx.signal.addEventListener("abort", cascade, { once: true });
1295
+ const state = {
1296
+ turns: 0,
1297
+ lastText: "",
1298
+ note: "starting",
1299
+ proc: void 0,
1300
+ artifact: void 0
1301
+ };
1302
+ return {
1303
+ runtime: PI_RUNTIME,
1304
+ // pi owns the queue; `deliver` only routes. A forceful message becomes pi's `steer` (which
1305
+ // pi delivers after the current tool batch and before the next model call); a queued one
1306
+ // becomes `follow_up` (after the turn finishes). Never throws — the inbox contract.
1307
+ deliver: (m) => inbox.deliver(m),
1308
+ progress: () => ({
1309
+ turns: state.turns,
1310
+ pendingMessages: inbox.pending(),
1311
+ recentActivity: activity.read(),
1312
+ note: state.note
1313
+ }),
1314
+ traceSource: () => trace.source,
1315
+ execute(task, signal) {
1316
+ return streamPiSession({
1317
+ task,
1318
+ signal,
1319
+ controller,
1320
+ seam,
1321
+ spec,
1322
+ inbox,
1323
+ activity,
1324
+ record: (step) => {
1325
+ trace.record(step);
1326
+ },
1327
+ state
1328
+ });
1329
+ },
1330
+ async teardown(grace) {
1331
+ controller.abort();
1332
+ const proc = state.proc;
1333
+ if (!proc || proc.exitCode !== null || proc.killed) return { destroyed: true };
1334
+ sendCommand(proc, { type: "abort" });
1335
+ return killPi(proc, grace);
1336
+ },
1337
+ resultArtifact() {
1338
+ if (!state.artifact) {
1339
+ throw new ValidationError("piExecutor: resultArtifact() read before stream drained");
1340
+ }
1341
+ return state.artifact;
1342
+ }
1343
+ };
1344
+ };
1345
+ async function* streamPiSession(args) {
1346
+ const { seam, inbox, activity, state } = args;
1347
+ const started = Date.now();
1348
+ const tokens = { input: 0, output: 0 };
1349
+ let usd = 0;
1350
+ const proc = spawnPi(seam);
1351
+ state.proc = proc;
1352
+ state.note = "connected";
1353
+ const events = [];
1354
+ let idle = false;
1355
+ let failure;
1356
+ let wake;
1357
+ const notify = () => {
1358
+ const w = wake;
1359
+ wake = void 0;
1360
+ w?.();
1361
+ };
1362
+ const stdoutLines = readJsonLines(proc, (value) => {
1363
+ const ev = value;
1364
+ if (ev.type === "agent_end") idle = true;
1365
+ events.push(ev);
1366
+ notify();
1367
+ });
1368
+ proc.once("exit", (code) => {
1369
+ if (!idle && code !== 0 && code !== null) {
1370
+ failure = new ValidationError(`piExecutor: pi exited with code ${code}`);
1371
+ }
1372
+ idle = true;
1373
+ notify();
1374
+ });
1375
+ proc.once("error", (e) => {
1376
+ failure = new ValidationError(`piExecutor: pi failed to start: ${e.message}`);
1377
+ idle = true;
1378
+ notify();
1379
+ });
1380
+ const abortAll = () => {
1381
+ sendCommand(proc, { type: "abort" });
1382
+ idle = true;
1383
+ notify();
1384
+ };
1385
+ if (args.signal.aborted || args.controller.signal.aborted) abortAll();
1386
+ else {
1387
+ args.signal.addEventListener("abort", abortAll, { once: true });
1388
+ args.controller.signal.addEventListener("abort", abortAll, { once: true });
1389
+ }
1390
+ const system = args.spec.profile.prompt?.systemPrompt;
1391
+ const opening = system ? `${system}
1392
+
1393
+ ${taskText(args.task)}` : taskText(args.task);
1394
+ const deadline = seam.turnTimeoutMs ? Date.now() + seam.turnTimeoutMs : void 0;
1395
+ try {
1396
+ sendCommand(proc, { type: "prompt", message: opening });
1397
+ state.note = "turn 0";
1398
+ for (; ; ) {
1399
+ forwardPending(proc, inbox, activity);
1400
+ while (events.length > 0) {
1401
+ const ev = events.shift();
1402
+ for (const usage of projectPiEvent(ev, args, tokens)) {
1403
+ if (usage.kind === "cost") usd += usage.usd;
1404
+ yield usage;
1405
+ }
1406
+ }
1407
+ if (failure) throw failure;
1408
+ if (deadline !== void 0 && Date.now() > deadline) {
1409
+ throw new ValidationError("piExecutor: turn exceeded turnTimeoutMs");
1410
+ }
1411
+ if (idle) {
1412
+ const pending = inbox.drain();
1413
+ if (pending.length === 0) break;
1414
+ idle = false;
1415
+ sendCommand(proc, { type: "prompt", message: inbox.fold(pending) });
1416
+ state.note = `turn ${state.turns}`;
1417
+ continue;
1418
+ }
1419
+ await new Promise((resolve) => {
1420
+ wake = resolve;
1421
+ const t = setTimeout(() => {
1422
+ if (wake === resolve) {
1423
+ wake = void 0;
1424
+ resolve();
1425
+ }
1426
+ }, 50);
1427
+ if (typeof t.unref === "function") t.unref();
1428
+ });
1429
+ }
1430
+ } finally {
1431
+ stdoutLines();
1432
+ state.note = "settled";
1433
+ args.signal.removeEventListener("abort", abortAll);
1434
+ args.controller.signal.removeEventListener("abort", abortAll);
1435
+ await killPi(proc, 2e3).catch(() => ({ destroyed: false }));
1436
+ }
1437
+ const spent = { iterations: state.turns, tokens, usd, ms: Date.now() - started };
1438
+ state.artifact = {
1439
+ outRef: `pi:${hash(state.lastText)}`,
1440
+ out: { content: state.lastText, turns: state.turns },
1441
+ spent
1442
+ };
1443
+ }
1444
+ function forwardPending(proc, inbox, activity) {
1445
+ const pending = inbox.drain();
1446
+ for (const m of pending) {
1447
+ sendCommand(proc, {
1448
+ type: m.interrupt ? "steer" : "follow_up",
1449
+ message: renderOne(m)
1450
+ });
1451
+ activity.push({
1452
+ at: Date.now(),
1453
+ kind: "note",
1454
+ label: m.interrupt ? "steer" : "follow-up",
1455
+ detail: m.text.length > 80 ? `${m.text.slice(0, 77)}...` : m.text
1456
+ });
1457
+ }
1458
+ }
1459
+ function renderOne(m) {
1460
+ if (m.kind === "answer") {
1461
+ return `Answer to your question${m.questionId ? ` (${m.questionId})` : ""}: ${m.text}`;
1462
+ }
1463
+ return `New instruction from your supervisor: ${m.text}`;
1464
+ }
1465
+ function projectPiEvent(ev, args, tokens) {
1466
+ const out = [];
1467
+ const at = Date.now();
1468
+ if (ev.type === "tool_execution_start" && typeof ev.toolName === "string") {
1469
+ args.activity.push({ at, kind: "tool", label: ev.toolName, detail: describeArgs(ev.args) });
1470
+ return out;
1471
+ }
1472
+ if (ev.type === "tool_execution_end" && typeof ev.toolName === "string") {
1473
+ const status = ev.isError === true ? "error" : "ok";
1474
+ args.activity.push({ at, kind: "tool", label: ev.toolName, status });
1475
+ args.record({
1476
+ toolName: ev.toolName,
1477
+ args: ev.args ?? {},
1478
+ status,
1479
+ ...typeof ev.toolCallId === "string" ? { callId: ev.toolCallId } : {}
1480
+ });
1481
+ return out;
1482
+ }
1483
+ if (ev.type === "turn_end" || ev.type === "message_end") {
1484
+ const usage = readUsage(ev.message);
1485
+ if (usage && (usage.input || usage.output)) {
1486
+ tokens.input += usage.input;
1487
+ tokens.output += usage.output;
1488
+ out.push({ kind: "tokens", input: usage.input, output: usage.output });
1489
+ }
1490
+ if (usage?.usd) out.push({ kind: "cost", usd: usage.usd });
1491
+ const text = readText(ev.message);
1492
+ if (text) args.state.lastText = text;
1493
+ if (ev.type === "turn_end") {
1494
+ args.state.turns += 1;
1495
+ args.activity.push({ at, kind: "turn", label: `turn ${args.state.turns}` });
1496
+ out.push({ kind: "iteration" });
1497
+ }
1498
+ }
1499
+ return out;
1500
+ }
1501
+ function readUsage(message) {
1502
+ if (!message || typeof message !== "object") return void 0;
1503
+ const usage = message.usage;
1504
+ if (!usage || typeof usage !== "object") return void 0;
1505
+ const u = usage;
1506
+ const input = num(u.input) ?? num(u.inputTokens) ?? num(u.prompt_tokens) ?? 0;
1507
+ const output = num(u.output) ?? num(u.outputTokens) ?? num(u.completion_tokens) ?? 0;
1508
+ const costRaw = u.cost;
1509
+ const usd = num(costRaw) ?? (costRaw && typeof costRaw === "object" ? num(costRaw.totalCost) ?? 0 : 0);
1510
+ return { input, output, usd };
1511
+ }
1512
+ function readText(message) {
1513
+ if (!message || typeof message !== "object") return void 0;
1514
+ const content = message.content;
1515
+ if (typeof content === "string" && content.length > 0) return content;
1516
+ if (!Array.isArray(content)) return void 0;
1517
+ const text = content.map(
1518
+ (block) => block && typeof block === "object" && typeof block.text === "string" ? block.text ?? "" : ""
1519
+ ).join("");
1520
+ return text.length > 0 ? text : void 0;
1521
+ }
1522
+ function num(v) {
1523
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
1524
+ }
1525
+ function describeArgs(argsValue) {
1526
+ if (!argsValue || typeof argsValue !== "object") return void 0;
1527
+ const a = argsValue;
1528
+ for (const key of ["filePath", "file_path", "path", "file", "command", "cmd", "pattern"]) {
1529
+ const v = a[key];
1530
+ if (typeof v === "string" && v.length > 0) return v.length > 120 ? `${v.slice(0, 117)}...` : v;
1531
+ }
1532
+ return void 0;
1533
+ }
1534
+ function taskText(task) {
1535
+ if (typeof task === "string") return task;
1536
+ try {
1537
+ return JSON.stringify(task) ?? String(task);
1538
+ } catch {
1539
+ return String(task);
1540
+ }
1541
+ }
1542
+ function spawnPi(seam) {
1543
+ const bin = seam.bin ?? "pi";
1544
+ const argv = ["--mode", "rpc"];
1545
+ if (seam.model) {
1546
+ const slash = seam.model.indexOf("/");
1547
+ if (slash > 0) {
1548
+ argv.push("--provider", seam.model.slice(0, slash), "--model", seam.model.slice(slash + 1));
1549
+ } else {
1550
+ argv.push("--model", seam.model);
1551
+ }
1552
+ }
1553
+ if (seam.args) argv.push(...seam.args);
1554
+ return spawn(bin, argv, {
1555
+ ...seam.cwd ? { cwd: seam.cwd } : {},
1556
+ env: { ...process.env, ...seam.env ?? {} },
1557
+ stdio: ["pipe", "pipe", "pipe"]
1558
+ });
1559
+ }
1560
+ function sendCommand(proc, command) {
1561
+ try {
1562
+ proc.stdin?.write(`${JSON.stringify(command)}
1563
+ `);
1564
+ } catch {
1565
+ }
1566
+ }
1567
+ function readJsonLines(proc, onValue) {
1568
+ let buffer = "";
1569
+ const onData = (chunk) => {
1570
+ buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
1571
+ for (; ; ) {
1572
+ const nl = buffer.indexOf("\n");
1573
+ if (nl < 0) break;
1574
+ const line = buffer.slice(0, nl).trim();
1575
+ buffer = buffer.slice(nl + 1);
1576
+ if (!line) continue;
1577
+ try {
1578
+ onValue(JSON.parse(line));
1579
+ } catch {
1580
+ }
1581
+ }
1582
+ };
1583
+ proc.stdout?.on("data", onData);
1584
+ return () => {
1585
+ proc.stdout?.off("data", onData);
1586
+ };
1587
+ }
1588
+ var PI_ABORT_GRACE_MS = 500;
1589
+ async function killPi(proc, grace) {
1590
+ if (proc.exitCode !== null || proc.killed) return { destroyed: true };
1591
+ if (grace === "brutalKill") {
1592
+ endStdin(proc);
1593
+ proc.kill("SIGKILL");
1594
+ return { destroyed: true };
1595
+ }
1596
+ const total = grace === "infinity" ? 1e4 : Math.max(0, grace);
1597
+ if (await waitForExit(proc, Math.min(PI_ABORT_GRACE_MS, total))) return { destroyed: true };
1598
+ endStdin(proc);
1599
+ proc.kill("SIGTERM");
1600
+ if (await waitForExit(proc, Math.max(0, total - PI_ABORT_GRACE_MS))) return { destroyed: true };
1601
+ proc.kill("SIGKILL");
1602
+ return { destroyed: true };
1603
+ }
1604
+ function endStdin(proc) {
1605
+ try {
1606
+ proc.stdin?.end();
1607
+ } catch {
1608
+ }
1609
+ }
1610
+ function waitForExit(proc, ms) {
1611
+ if (proc.exitCode !== null) return Promise.resolve(true);
1612
+ return new Promise((resolve) => {
1613
+ const timer = setTimeout(() => resolve(false), ms);
1614
+ if (typeof timer.unref === "function") timer.unref();
1615
+ proc.once("exit", () => {
1616
+ clearTimeout(timer);
1617
+ resolve(true);
1618
+ });
1619
+ });
1620
+ }
1621
+ function readPiSeam(ctx) {
1622
+ const seam = ctx.seams[piSeamKey];
1623
+ if (seam === void 0) return {};
1624
+ if (!seam || typeof seam !== "object") {
1625
+ throw new ValidationError(`piExecutor: seams['${piSeamKey}'] must be a PiSeam object`);
1626
+ }
1627
+ return seam;
1628
+ }
1629
+ function hash(value) {
1630
+ let h = 2166136261;
1631
+ for (let i = 0; i < value.length; i += 1) {
1632
+ h ^= value.charCodeAt(i);
1633
+ h = Math.imul(h, 16777619);
1634
+ }
1635
+ return (h >>> 0).toString(16).padStart(8, "0");
1636
+ }
1637
+
1638
+ // src/runtime/supervise/sandbox-session.ts
1639
+ var DEFAULT_SANDBOX_STEERING_MAX_TURNS = 24;
1640
+ function createSteerableSandboxSession(args) {
1641
+ const now = args.now ?? Date.now;
1642
+ const maxTurns = Math.max(
1643
+ 1,
1644
+ Math.floor(args.options?.maxTurns ?? DEFAULT_SANDBOX_STEERING_MAX_TURNS)
1645
+ );
1646
+ const activity = createActivityLog(args.options?.activityWindow ?? 12);
1647
+ const trace = createPushTraceSource({ runId: `sandbox-${args.harness}-${now()}`, now });
1648
+ const state = {
1649
+ turns: 0,
1650
+ lastText: "",
1651
+ seenToolCalls: /* @__PURE__ */ new Set(),
1652
+ artifact: void 0,
1653
+ teardown: void 0,
1654
+ note: "starting"
1655
+ };
1656
+ const recordEvent = (event) => {
1657
+ const at = now();
1658
+ const data = event.data;
1659
+ const part = data && typeof data === "object" ? data.part : void 0;
1660
+ if (part !== void 0) {
1661
+ const step = decodeToolPart(part, args.harness);
1662
+ if (step) {
1663
+ const key = step.callId ?? `${step.toolName}:${activity.size()}`;
1664
+ if (!state.seenToolCalls.has(key)) {
1665
+ state.seenToolCalls.add(key);
1666
+ trace.record(step);
1667
+ const note = {
1668
+ at,
1669
+ kind: "tool",
1670
+ label: step.toolName,
1671
+ ...step.status ? { status: step.status } : {},
1672
+ ...describeArgs2(step.args) ? { detail: describeArgs2(step.args) } : {}
1673
+ };
1674
+ activity.push(note);
1675
+ }
1676
+ }
1677
+ }
1678
+ const text = readFinalText(event);
1679
+ if (text) state.lastText = text;
1680
+ };
1681
+ async function* stream(task, signal) {
1682
+ const capabilities = await probeSandboxCapabilities(args.sandboxClient);
1683
+ const lineage = createSandboxLineage(args.sandboxClient, capabilities, { maxConcurrency: 1 });
1684
+ state.teardown = () => lineage.teardown();
1685
+ const spec = {
1686
+ profile: args.profile,
1687
+ taskToPrompt: args.taskToPrompt,
1688
+ name: args.profile.name ?? String(args.harness),
1689
+ sandboxOverrides: { backend: { type: args.harness } }
1690
+ };
1691
+ const promptOptions = readPromptOptions(args.loopCtx);
1692
+ const started = now();
1693
+ const tokens = zeroTokenUsage();
1694
+ let usd = 0;
1695
+ let handle;
1696
+ let nextPrompt = args.taskToPrompt(task);
1697
+ try {
1698
+ for (let turn = 0; turn < maxTurns; turn += 1) {
1699
+ const pending = args.inbox.drain();
1700
+ if (pending.length > 0) {
1701
+ const folded = args.inbox.fold(pending);
1702
+ nextPrompt = turn === 0 && nextPrompt ? `${nextPrompt}
1703
+
1704
+ ${folded}` : folded;
1705
+ activity.push({
1706
+ at: now(),
1707
+ kind: "note",
1708
+ label: "steer-folded",
1709
+ detail: `${pending.length} message(s)`
1710
+ });
1711
+ }
1712
+ if (nextPrompt === void 0) break;
1713
+ const prompt = nextPrompt;
1714
+ nextPrompt = void 0;
1715
+ state.note = `turn ${turn}`;
1716
+ const interruptSig = args.inbox.freshInterrupt();
1717
+ const turnController = new AbortController();
1718
+ const abortTurn = () => turnController.abort();
1719
+ if (signal.aborted || args.controller.signal.aborted) turnController.abort();
1720
+ else {
1721
+ signal.addEventListener("abort", abortTurn);
1722
+ args.controller.signal.addEventListener("abort", abortTurn);
1723
+ }
1724
+ interruptSig.addEventListener("abort", abortTurn, { once: true });
1725
+ const timer = args.options?.turnTimeoutMs ? setTimeout(abortTurn, args.options.turnTimeoutMs) : void 0;
1726
+ const cleanup = () => {
1727
+ signal.removeEventListener("abort", abortTurn);
1728
+ args.controller.signal.removeEventListener("abort", abortTurn);
1729
+ if (timer) clearTimeout(timer);
1730
+ };
1731
+ let events;
1732
+ try {
1733
+ if (!handle) {
1734
+ const opened = await lineage.start(spec, prompt, turnController.signal, promptOptions);
1735
+ handle = opened.handle;
1736
+ events = opened.events;
1737
+ } else {
1738
+ events = await lineage.continue(handle, prompt, turnController.signal, promptOptions);
1739
+ }
1740
+ for await (const event of events) {
1741
+ recordEvent(event);
1742
+ const call = extractLlmCallEvent(event, spec.name ?? String(args.harness));
1743
+ if (!call) continue;
1744
+ const input = call.tokensIn ?? 0;
1745
+ const output = call.tokensOut ?? 0;
1746
+ if (input || output) {
1747
+ tokens.input += input;
1748
+ tokens.output += output;
1749
+ yield { kind: "tokens", input, output };
1750
+ }
1751
+ if (typeof call.costUsd === "number" && call.costUsd > 0) {
1752
+ usd += call.costUsd;
1753
+ yield { kind: "cost", usd: call.costUsd };
1754
+ }
1755
+ }
1756
+ } catch (e) {
1757
+ cleanup();
1758
+ if (isInterruptAbort(e, interruptSig, signal, args.controller.signal)) {
1759
+ activity.push({ at: now(), kind: "note", label: "interrupted", detail: "replanning" });
1760
+ continue;
1761
+ }
1762
+ throw e;
1763
+ }
1764
+ cleanup();
1765
+ state.turns += 1;
1766
+ activity.push({ at: now(), kind: "turn", label: `turn ${turn}` });
1767
+ yield { kind: "iteration" };
1768
+ if (args.inbox.pending() === 0) break;
1769
+ }
1770
+ } finally {
1771
+ state.note = "settled";
1772
+ await lineage.teardown().catch(() => {
1773
+ });
1774
+ state.teardown = void 0;
1775
+ }
1776
+ const spent = { iterations: state.turns, tokens, usd, ms: now() - started };
1777
+ const out = {
1778
+ content: state.lastText,
1779
+ turns: state.turns,
1780
+ toolCalls: activity.read().filter((n) => n.kind === "tool").map((n) => n.label)
1781
+ };
1782
+ state.artifact = {
1783
+ outRef: args.contentRef("sandbox-steerable", { harness: args.harness, out }),
1784
+ out,
1785
+ spent
1786
+ };
1787
+ }
1788
+ return {
1789
+ stream,
1790
+ progress: () => ({
1791
+ turns: state.turns,
1792
+ pendingMessages: args.inbox.pending(),
1793
+ recentActivity: activity.read(),
1794
+ note: state.note
1795
+ }),
1796
+ traceSource: () => trace.source,
1797
+ artifact: () => state.artifact,
1798
+ async teardown() {
1799
+ await state.teardown?.().catch(() => {
1800
+ });
1801
+ }
1802
+ };
1803
+ }
1804
+ function isInterruptAbort(err, interrupt, external, owned) {
1805
+ const aborted = err instanceof DOMException && err.name === "AbortError" || typeof err === "object" && err !== null && err.name === "AbortError";
1806
+ return aborted && interrupt.aborted && !external.aborted && !owned.aborted;
1807
+ }
1808
+ function readFinalText(event) {
1809
+ const data = event.data;
1810
+ if (!data || typeof data !== "object") return void 0;
1811
+ const text = data.finalText;
1812
+ if (typeof text === "string" && text.length > 0) return text;
1813
+ const alt = data.text;
1814
+ return typeof alt === "string" && alt.length > 0 ? alt : void 0;
1815
+ }
1816
+ function describeArgs2(argsValue) {
1817
+ if (!argsValue || typeof argsValue !== "object") return void 0;
1818
+ const a = argsValue;
1819
+ for (const key of ["filePath", "file_path", "path", "file", "command", "cmd", "pattern"]) {
1820
+ const v = a[key];
1821
+ if (typeof v === "string" && v.length > 0) return v.length > 120 ? `${v.slice(0, 117)}...` : v;
1822
+ }
1823
+ return void 0;
1824
+ }
1825
+ function readPromptOptions(loopCtx) {
1826
+ const opts = loopCtx?.promptOptions;
1827
+ if (!opts || typeof opts !== "object") return void 0;
1828
+ return opts;
1829
+ }
1830
+
1099
1831
  // src/runtime/supervise/worktree-cli-executor.ts
1100
1832
  import { randomUUID } from "crypto";
1101
1833
  function createWorktreeCliExecutor(options) {
@@ -1203,7 +1935,7 @@ function linkSignals(a, b) {
1203
1935
  }
1204
1936
 
1205
1937
  // src/runtime/supervise/runtime.ts
1206
- import { spawn } from "child_process";
1938
+ import { spawn as spawn2 } from "child_process";
1207
1939
  import { randomUUID as randomUUID2 } from "crypto";
1208
1940
  import { request as httpRequest } from "http";
1209
1941
  import { request as httpsRequest } from "https";
@@ -1216,15 +1948,15 @@ var bridgeSeamKey = "bridge";
1216
1948
  var cliWorktreeSeamKey = "cli-worktree";
1217
1949
  var providerSeamKey = "provider";
1218
1950
  function contentRef(prefix, value) {
1219
- let str;
1951
+ let str2;
1220
1952
  try {
1221
- str = JSON.stringify(value) ?? String(value);
1953
+ str2 = JSON.stringify(value) ?? String(value);
1222
1954
  } catch {
1223
- str = String(value);
1955
+ str2 = String(value);
1224
1956
  }
1225
1957
  let h = 2166136261;
1226
- for (let i = 0; i < str.length; i += 1) {
1227
- h ^= str.charCodeAt(i);
1958
+ for (let i = 0; i < str2.length; i += 1) {
1959
+ h ^= str2.charCodeAt(i);
1228
1960
  h = Math.imul(h, 16777619);
1229
1961
  }
1230
1962
  return `${prefix}:${(h >>> 0).toString(16).padStart(8, "0")}`;
@@ -1463,6 +2195,43 @@ var sandboxExecutor = (spec, ctx) => {
1463
2195
  abortIfSignalled();
1464
2196
  if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
1465
2197
  let artifact;
2198
+ if (seam.steering) {
2199
+ const inbox = createInbox();
2200
+ const session = createSteerableSandboxSession({
2201
+ controller,
2202
+ profile: spec.profile,
2203
+ harness,
2204
+ sandboxClient: seam.sandboxClient,
2205
+ inbox,
2206
+ taskToPrompt: (t) => taskToPrompt(t),
2207
+ options: seam.steering,
2208
+ ...seam.loopCtx ? { loopCtx: seam.loopCtx } : {},
2209
+ contentRef
2210
+ });
2211
+ return {
2212
+ runtime: "sandbox",
2213
+ deliver: (m) => inbox.deliver(m),
2214
+ progress: () => session.progress(),
2215
+ traceSource: () => session.traceSource(),
2216
+ execute(task, signal) {
2217
+ return session.stream(task, signal);
2218
+ },
2219
+ async teardown(_grace) {
2220
+ controller.abort();
2221
+ await session.teardown();
2222
+ return { destroyed: true };
2223
+ },
2224
+ resultArtifact() {
2225
+ const a = session.artifact();
2226
+ if (!a) {
2227
+ throw new ValidationError(
2228
+ "sandboxExecutor(steering): resultArtifact() read before stream drained"
2229
+ );
2230
+ }
2231
+ return a;
2232
+ }
2233
+ };
2234
+ }
1466
2235
  const output = {
1467
2236
  parse(events) {
1468
2237
  return { events };
@@ -1598,7 +2367,7 @@ var cliExecutor = (_spec, ctx) => {
1598
2367
  };
1599
2368
  async function* streamCliLeaf(args) {
1600
2369
  const prompt = taskToPrompt(args.task);
1601
- const proc = spawn(args.seam.bin, args.seam.args ?? [], {
2370
+ const proc = spawn2(args.seam.bin, args.seam.args ?? [], {
1602
2371
  ...args.seam.cwd ? { cwd: args.seam.cwd } : {},
1603
2372
  env: { ...process.env, ...args.seam.env ?? {} },
1604
2373
  stdio: ["pipe", "pipe", "pipe"]
@@ -2164,6 +2933,8 @@ function createExecutor(config) {
2164
2933
  return cliExecutor(spec, seamed);
2165
2934
  case "cli-worktree":
2166
2935
  return cliWorktreeExecutor(spec, seamed);
2936
+ case "pi":
2937
+ return piExecutor(spec, seamed);
2167
2938
  case "provider": {
2168
2939
  const providerSeam = readSeam(seamed, providerSeamKey, "provider");
2169
2940
  const provider = resolveAgentEnvironmentProvider(
@@ -2185,6 +2956,7 @@ function createExecutorRegistry() {
2185
2956
  factories.set("inline", routerInlineExecutor);
2186
2957
  factories.set("sandbox", sandboxExecutor);
2187
2958
  factories.set("cli", cliExecutor);
2959
+ factories.set(PI_RUNTIME, piExecutor);
2188
2960
  return {
2189
2961
  register(runtime, factory) {
2190
2962
  if (factories.has(runtime)) {
@@ -2224,9 +2996,9 @@ function readSeam(ctx, key, who) {
2224
2996
  function taskToPrompt(task) {
2225
2997
  if (typeof task === "string") return task;
2226
2998
  if (task && typeof task === "object") {
2227
- const obj = task;
2999
+ const obj2 = task;
2228
3000
  for (const k of ["prompt", "content", "task", "message"]) {
2229
- if (typeof obj[k] === "string") return obj[k];
3001
+ if (typeof obj2[k] === "string") return obj2[k];
2230
3002
  }
2231
3003
  }
2232
3004
  return JSON.stringify(task);
@@ -2285,6 +3057,119 @@ function createInMemoryRunContext(opts = {}) {
2285
3057
  executors: opts.withDriver ? withDriverExecutor(base) : base
2286
3058
  };
2287
3059
  }
3060
+ function createFileRunContext(dir, opts = {}) {
3061
+ const base = createExecutorRegistry();
3062
+ return {
3063
+ journal: new FileSpawnJournal(`${dir}/spawn-journal.jsonl`),
3064
+ blobs: new FileResultBlobStore(`${dir}/blobs`),
3065
+ executors: opts.withDriver ? withDriverExecutor(base) : base,
3066
+ resume: true
3067
+ };
3068
+ }
3069
+
3070
+ // src/runtime/supervise/detector-monitor.ts
3071
+ import {
3072
+ argHash,
3073
+ errorStreakDetector,
3074
+ observeAll,
3075
+ repeatedActionDetector
3076
+ } from "@tangle-network/agent-eval";
3077
+ function defaultToolDetectors() {
3078
+ return [repeatedActionDetector({ maxRepeated: 3 }), errorStreakDetector({ maxErrors: 3 })];
3079
+ }
3080
+ function watchTrace(source, opts = {}) {
3081
+ const detectors = opts.detectors ?? defaultToolDetectors();
3082
+ return source.onSpan((span) => {
3083
+ let fingerprint;
3084
+ try {
3085
+ fingerprint = `${span.toolName}|${argHash(span.args)}`;
3086
+ } catch {
3087
+ fingerprint = `${span.toolName}|<unhashable>`;
3088
+ }
3089
+ const signals = observeAll(detectors, {
3090
+ actionFingerprint: fingerprint,
3091
+ ...span.status ? { status: span.status } : {},
3092
+ label: span.toolName
3093
+ });
3094
+ for (const s of signals) void opts.onSignal?.(s, span);
3095
+ });
3096
+ }
3097
+
3098
+ // src/runtime/supervise/dispatch.ts
3099
+ async function rollingDispatch(scope, opts) {
3100
+ if (!Number.isInteger(opts.width) || opts.width < 1) {
3101
+ throw new ValidationError(
3102
+ `rollingDispatch: width must be a positive integer, got ${String(opts.width)}`
3103
+ );
3104
+ }
3105
+ const settled = [];
3106
+ const rejected = [];
3107
+ const live = /* @__PURE__ */ new Set();
3108
+ let admitted = 0;
3109
+ let peakLive = 0;
3110
+ let queueDry = false;
3111
+ let stopReason = "drained";
3112
+ const fill = async () => {
3113
+ while (live.size < opts.width) {
3114
+ if (queueDry) return;
3115
+ if (scope.signal.aborted) {
3116
+ stopReason = "aborted";
3117
+ queueDry = true;
3118
+ return;
3119
+ }
3120
+ if (opts.shouldStop?.() === true) {
3121
+ stopReason = "stopped";
3122
+ queueDry = true;
3123
+ return;
3124
+ }
3125
+ const unit = await opts.nextUnit();
3126
+ if (unit === void 0) {
3127
+ queueDry = true;
3128
+ return;
3129
+ }
3130
+ const res = scope.spawn(unit.agent, unit.task, unit.opts);
3131
+ if (!res.ok) {
3132
+ rejected.push(`${unit.opts.label}: ${res.reason}`);
3133
+ stopReason = "not-admitted";
3134
+ queueDry = true;
3135
+ return;
3136
+ }
3137
+ live.add(res.handle.id);
3138
+ admitted += 1;
3139
+ if (live.size > peakLive) peakLive = live.size;
3140
+ }
3141
+ };
3142
+ await fill();
3143
+ while (live.size > 0) {
3144
+ const s = await scope.next();
3145
+ if (s === null) break;
3146
+ live.delete(s.handle.id);
3147
+ settled.push(s);
3148
+ await opts.onSettled?.(s);
3149
+ await fill();
3150
+ }
3151
+ return { settled, admitted, rejected, stopReason, peakLive };
3152
+ }
3153
+ function freeSlots(liveCount, cap) {
3154
+ if (cap === void 0 || cap <= 0) return null;
3155
+ return Math.max(0, cap - liveCount);
3156
+ }
3157
+ function effectiveConcurrency(caps) {
3158
+ const applicable = [caps.maxLiveWorkers, caps.maxSandboxes].filter(
3159
+ (c) => typeof c === "number" && c > 0
3160
+ );
3161
+ if (applicable.length === 0) return void 0;
3162
+ return Math.min(...applicable);
3163
+ }
3164
+ function queueOf(units, budget) {
3165
+ let i = 0;
3166
+ return () => {
3167
+ const u = units[i];
3168
+ if (u === void 0) return void 0;
3169
+ i += 1;
3170
+ return { agent: u.agent, task: u.task, opts: { budget, label: u.label } };
3171
+ };
3172
+ }
2288
3173
 
2289
3174
  // src/runtime/supervise/event-bus.ts
2290
3175
  function createEventBus(now = Date.now) {
@@ -2370,12 +3255,12 @@ function createCoordinationTools(opts) {
2370
3255
  bus.subscribe((rec) => cb(rec.event));
2371
3256
  }
2372
3257
  const urgencyPriority = (u) => u === "blocks-run" ? 20 : u === "blocks-step" ? 10 : 0;
2373
- const str = (v, field) => {
3258
+ const str2 = (v, field) => {
2374
3259
  if (typeof v !== "string" || v.length === 0)
2375
3260
  throw new Error(`coordination tools: "${field}" must be a non-empty string`);
2376
3261
  return v;
2377
3262
  };
2378
- const obj = (raw) => {
3263
+ const obj2 = (raw) => {
2379
3264
  if (!raw || typeof raw !== "object")
2380
3265
  throw new Error("coordination tools: arguments must be an object");
2381
3266
  return raw;
@@ -2413,14 +3298,17 @@ function createCoordinationTools(opts) {
2413
3298
  );
2414
3299
  };
2415
3300
  const recordSettled = (s) => {
3301
+ const settledAt = Date.now();
2416
3302
  const w = s.kind === "done" ? {
2417
3303
  id: s.handle.id,
2418
3304
  status: "done",
2419
3305
  score: s.verdict?.score ?? 0,
2420
3306
  valid: s.verdict?.valid ?? false,
2421
- outRef: s.outRef
2422
- } : { id: s.handle.id, status: "down", reason: s.reason };
3307
+ outRef: s.outRef,
3308
+ settledAt
3309
+ } : { id: s.handle.id, status: "down", reason: s.reason, settledAt };
2423
3310
  ledger.push(w);
3311
+ unwatchWorker(w.id);
2424
3312
  return w;
2425
3313
  };
2426
3314
  const drainSettlement = async () => {
@@ -2449,7 +3337,7 @@ function createCoordinationTools(opts) {
2449
3337
  };
2450
3338
  async function sendDown(type, down, questionId) {
2451
3339
  await bus.publish(
2452
- type === "answer" ? { type, down, questionId: str(questionId, "questionId") } : { type, down },
3340
+ type === "answer" ? { type, down, questionId: str2(questionId, "questionId") } : { type, down },
2453
3341
  { queue: false }
2454
3342
  );
2455
3343
  }
@@ -2472,13 +3360,13 @@ function createCoordinationTools(opts) {
2472
3360
  };
2473
3361
  const nextQuestionId = (from) => `${from}:q${questionSeq++}`;
2474
3362
  const normalizeQuestion = (q, fallbackFrom) => {
2475
- const from = str(q.from ?? fallbackFrom, "from");
3363
+ const from = str2(q.from ?? fallbackFrom, "from");
2476
3364
  return {
2477
3365
  id: typeof q.id === "string" && q.id.length > 0 ? q.id : nextQuestionId(from),
2478
3366
  from,
2479
3367
  level: level(q.level),
2480
- question: str(q.question, "question"),
2481
- reason: str(q.reason, "reason"),
3368
+ question: str2(q.question, "question"),
3369
+ reason: str2(q.reason, "reason"),
2482
3370
  ...q.options ? { options: q.options } : {},
2483
3371
  urgency: urgency(q.urgency)
2484
3372
  };
@@ -2532,6 +3420,69 @@ function createCoordinationTools(opts) {
2532
3420
  const isLive = (status) => status !== "done" && status !== "failed" && status !== "cancelled";
2533
3421
  const liveWorkerCount = () => opts.scope.view.nodes.filter((n) => isLive(n.status)).length;
2534
3422
  const liveSnapshot = () => opts.scope.view.nodes.filter((n) => isLive(n.status)).map((n) => ({ id: n.id, status: n.status, spent: n.spent }));
3423
+ const freeWorkerSlots = () => freeSlots(liveWorkerCount(), maxLiveWorkers);
3424
+ const readProgress = (id) => {
3425
+ const scope = opts.scope;
3426
+ if (typeof scope.progress !== "function") return void 0;
3427
+ try {
3428
+ return scope.progress(
3429
+ id,
3430
+ opts.stallAfterMs !== void 0 ? { stallAfterMs: opts.stallAfterMs } : {}
3431
+ );
3432
+ } catch {
3433
+ return void 0;
3434
+ }
3435
+ };
3436
+ const watchers = /* @__PURE__ */ new Map();
3437
+ const watchWorker = (id) => {
3438
+ const watch = opts.watchWorkers;
3439
+ if (!watch) return;
3440
+ const scope = opts.scope;
3441
+ if (typeof scope.traceSource !== "function") return;
3442
+ let source;
3443
+ try {
3444
+ source = scope.traceSource(id);
3445
+ } catch {
3446
+ return;
3447
+ }
3448
+ if (!source) return;
3449
+ const cap = watch.maxFindingsPerWorker ?? 3;
3450
+ let raised = 0;
3451
+ const unsub = watchTrace(source, {
3452
+ ...watch.detectors ? { detectors: watch.detectors } : {},
3453
+ onSignal: async (signal, span) => {
3454
+ if (cap > 0 && raised >= cap) return;
3455
+ raised += 1;
3456
+ await bus.publish({
3457
+ type: "finding",
3458
+ finding: {
3459
+ fromWorker: id,
3460
+ analyst: `online:${signal.detector}`,
3461
+ findings: {
3462
+ detector: signal.detector,
3463
+ severity: signal.severity,
3464
+ reason: signal.reason,
3465
+ streak: signal.streak,
3466
+ ...signal.failureClass ? { failureClass: signal.failureClass } : {},
3467
+ toolName: span.toolName,
3468
+ at: span.endedAt,
3469
+ progress: readProgress(id)
3470
+ }
3471
+ }
3472
+ });
3473
+ }
3474
+ });
3475
+ watchers.set(id, unsub);
3476
+ };
3477
+ const unwatchWorker = (id) => {
3478
+ const unsub = watchers.get(id);
3479
+ if (!unsub) return;
3480
+ watchers.delete(id);
3481
+ try {
3482
+ unsub();
3483
+ } catch {
3484
+ }
3485
+ };
2535
3486
  const awaitTimeoutMs = opts.awaitTimeoutMs ?? DEFAULT_AWAIT_EVENT_TIMEOUT_MS;
2536
3487
  let inFlightDrain = null;
2537
3488
  const ensureDrain = () => {
@@ -2557,7 +3508,7 @@ function createCoordinationTools(opts) {
2557
3508
  const tools = [
2558
3509
  {
2559
3510
  name: "spawn_agent",
2560
- description: 'Start a worker the driver will drive. `profile` is the worker or another driver; `task` is what it should do. Reserves budget from the conserved pool and fails closed. Pass an optional `budget` (per-field) to give a hard sub-task more than the default \u2014 it merges over the per-worker default; the conserved pool is still the hard fence. When a max-live-workers cap is set it also fails closed (`error: "max-live-workers"`) while that many workers are still in flight \u2014 settle or steer one before spawning another.',
3511
+ description: 'Start a worker the driver will drive. `profile` is the worker or another driver; `task` is what it should do. Reserves budget from the conserved pool and fails closed. Pass an optional `budget` (per-field) to give a hard sub-task more than the default \u2014 it merges over the per-worker default; the conserved pool is still the hard fence. When a max-live-workers cap is set it also fails closed (`error: "max-live-workers"`) while that many workers are still in flight \u2014 settle or steer one before spawning another. Returns `freeSlots`: how many MORE workers you can start right now (`null` = uncapped). While `freeSlots > 0` there is idle capacity \u2014 call this again to fill it rather than waiting; parallel workers finish the run sooner than one at a time.',
2561
3512
  inputSchema: {
2562
3513
  type: "object",
2563
3514
  properties: {
@@ -2578,32 +3529,41 @@ function createCoordinationTools(opts) {
2578
3529
  required: ["profile", "task"]
2579
3530
  },
2580
3531
  handler: (raw) => {
2581
- const a = obj(raw);
3532
+ const a = obj2(raw);
2582
3533
  if (maxLiveWorkers !== void 0 && maxLiveWorkers > 0 && liveWorkerCount() >= maxLiveWorkers)
2583
- return Promise.resolve({ error: "max-live-workers" });
3534
+ return Promise.resolve({
3535
+ error: "max-live-workers",
3536
+ live: liveWorkerCount(),
3537
+ freeSlots: freeWorkerSlots()
3538
+ });
2584
3539
  const agent = opts.makeWorkerAgent(a.profile);
2585
3540
  const budget = a.budget === void 0 ? opts.perWorker : mergeBudget(opts.perWorker, a.budget);
2586
3541
  const res = opts.scope.spawn(agent, a.task, {
2587
3542
  budget,
2588
3543
  label: typeof a.label === "string" ? a.label : "worker"
2589
3544
  });
2590
- return Promise.resolve(res.ok ? { workerId: res.handle.id } : { error: res.reason });
3545
+ if (res.ok) watchWorker(res.handle.id);
3546
+ return Promise.resolve(
3547
+ res.ok ? { workerId: res.handle.id, live: liveWorkerCount(), freeSlots: freeWorkerSlots() } : { error: res.reason, live: liveWorkerCount(), freeSlots: freeWorkerSlots() }
3548
+ );
2591
3549
  }
2592
3550
  },
2593
3551
  {
2594
3552
  name: "observe_agent",
2595
- description: "Inspect a worker status, spend, and settled output artifact when available.",
3553
+ description: "Inspect a worker WHILE IT RUNS, not only after it finishes: status, spend so far, and `progress` \u2014 how long since it last did anything (`idleMs`), whether that counts as stalled, how many turns it has taken, the last tools/files it touched (`recentActivity`), whether a steer can even reach it (`steerable`), and how many steers it has not yet read (`pendingMessages`). Returns the settled output artifact once it exists. Use this BEFORE steer_agent: a steer is only worth sending when the progress says the worker is on the wrong path or has stopped making any.",
2596
3554
  inputSchema: { type: "object", properties: { workerId: idArg }, required: ["workerId"] },
2597
3555
  handler: async (raw) => {
2598
- const id = str(obj(raw).workerId, "workerId");
3556
+ const id = str2(obj2(raw).workerId, "workerId");
2599
3557
  const node = opts.scope.view.nodes.find((n) => n.id === id);
2600
3558
  if (!node) return { error: `unknown workerId ${JSON.stringify(id)}` };
2601
3559
  const output = node.outRef ? await opts.blobs.get(node.outRef) : void 0;
3560
+ const progress = readProgress(id);
2602
3561
  return {
2603
3562
  status: node.status,
2604
3563
  spent: node.spent,
2605
3564
  outRef: node.outRef ?? null,
2606
- output: output ?? null
3565
+ output: output ?? null,
3566
+ progress: progress ?? null
2607
3567
  };
2608
3568
  }
2609
3569
  },
@@ -2623,18 +3583,21 @@ function createCoordinationTools(opts) {
2623
3583
  required: ["workerId", "instruction"]
2624
3584
  },
2625
3585
  handler: async (raw) => {
2626
- const a = obj(raw);
2627
- const workerId = str(a.workerId, "workerId");
2628
- const instruction = str(a.instruction, "instruction");
3586
+ const a = obj2(raw);
3587
+ const workerId = str2(a.workerId, "workerId");
3588
+ const instruction = str2(a.instruction, "instruction");
2629
3589
  const interrupt = a.interrupt === true;
2630
3590
  const delivered = opts.scope.send(workerId, { steer: instruction, interrupt });
2631
3591
  await sendDown("steer", { toWorker: workerId, instruction, delivered });
2632
- return { delivered };
3592
+ if (delivered) return { delivered, progress: readProgress(workerId) ?? null };
3593
+ const progress = readProgress(workerId);
3594
+ const reason2 = !progress ? "unknown-worker" : !progress.live ? "already-settled" : "runtime-has-no-inbox";
3595
+ return { delivered, reason: reason2, progress: progress ?? null };
2633
3596
  }
2634
3597
  },
2635
3598
  {
2636
3599
  name: "await_event",
2637
- 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. If a worker is still running when the wait elapses, returns { pending: true, live: [...] } (the workers still in flight) instead of blocking indefinitely \u2014 call await_event again to keep waiting; the settlement is not lost.",
3600
+ 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. If a worker is still running when the wait elapses, returns { pending: true, live: [...] } (the workers still in flight) instead of blocking indefinitely \u2014 call await_event again to keep waiting; the settlement is not lost. Every reply carries `freeSlots`: how many more workers you can start right now (`null` = uncapped). A settled worker frees its slot, so `freeSlots > 0` means capacity is sitting idle \u2014 spawn into it before waiting again.",
2638
3601
  inputSchema: {
2639
3602
  type: "object",
2640
3603
  properties: {
@@ -2646,15 +3609,16 @@ function createCoordinationTools(opts) {
2646
3609
  }
2647
3610
  },
2648
3611
  handler: async (raw) => {
2649
- const k = obj(raw).kinds;
3612
+ const k = obj2(raw).kinds;
2650
3613
  const kinds = Array.isArray(k) ? k.filter((x) => x === "settled" || x === "question" || x === "finding") : void 0;
2651
3614
  let ev = bus.pull(kinds);
2652
- if (ev) return projectEvent(ev);
3615
+ if (ev) return { ...projectEvent(ev), freeSlots: freeWorkerSlots() };
2653
3616
  const raced = await raceDrainWithTimeout(ensureDrain());
2654
- if (raced === void 0) return { pending: true, live: liveSnapshot() };
3617
+ if (raced === void 0)
3618
+ return { pending: true, live: liveSnapshot(), freeSlots: freeWorkerSlots() };
2655
3619
  ev = bus.pull(kinds);
2656
- if (!ev) return { idle: !raced.drained };
2657
- return projectEvent(ev);
3620
+ if (!ev) return { idle: !raced.drained, freeSlots: freeWorkerSlots() };
3621
+ return { ...projectEvent(ev), freeSlots: freeWorkerSlots() };
2658
3622
  }
2659
3623
  },
2660
3624
  {
@@ -2679,8 +3643,8 @@ function createCoordinationTools(opts) {
2679
3643
  required: ["questionId"]
2680
3644
  },
2681
3645
  handler: async (raw) => {
2682
- const a = obj(raw);
2683
- const questionId = str(a.questionId, "questionId");
3646
+ const a = obj2(raw);
3647
+ const questionId = str2(a.questionId, "questionId");
2684
3648
  if (typeof a.answer === "string" && a.answer.length > 0) {
2685
3649
  const answer = a.answer;
2686
3650
  const question = decideQuestion(questionId, {
@@ -2733,15 +3697,15 @@ function createCoordinationTools(opts) {
2733
3697
  required: ["from", "level", "question", "reason", "urgency"]
2734
3698
  },
2735
3699
  handler: async (raw) => {
2736
- const a = obj(raw);
2737
- const from = str(a.from, "from");
3700
+ const a = obj2(raw);
3701
+ const from = str2(a.from, "from");
2738
3702
  const q = await emitNewQuestion(
2739
3703
  addQuestion(
2740
3704
  {
2741
3705
  from,
2742
3706
  level: level(a.level),
2743
- question: str(a.question, "question"),
2744
- reason: str(a.reason, "reason"),
3707
+ question: str2(a.question, "question"),
3708
+ reason: str2(a.reason, "reason"),
2745
3709
  urgency: urgency(a.urgency)
2746
3710
  },
2747
3711
  from,
@@ -2768,7 +3732,7 @@ function createCoordinationTools(opts) {
2768
3732
  });
2769
3733
  }
2770
3734
  stopped = true;
2771
- const r = obj(raw).reason;
3735
+ const r = obj2(raw).reason;
2772
3736
  reason = typeof r === "string" ? r : void 0;
2773
3737
  return Promise.resolve({ stopped: true });
2774
3738
  }
@@ -2793,14 +3757,14 @@ function createCoordinationTools(opts) {
2793
3757
  required: ["kind", "workerId"]
2794
3758
  },
2795
3759
  handler: async (raw) => {
2796
- const a = obj(raw);
2797
- const id = str(a.workerId, "workerId");
3760
+ const a = obj2(raw);
3761
+ const id = str2(a.workerId, "workerId");
2798
3762
  const node = opts.scope.view.nodes.find((n) => n.id === id);
2799
3763
  if (!node) return { error: `unknown workerId ${JSON.stringify(id)}` };
2800
3764
  if (!node.outRef)
2801
3765
  return { error: `worker ${JSON.stringify(id)} has not settled \u2014 no trace to analyze yet` };
2802
3766
  const trace = await opts.blobs.get(node.outRef);
2803
- return { findings: await opts.analysts?.run(str(a.kind, "kind"), trace) };
3767
+ return { findings: await opts.analysts?.run(str2(a.kind, "kind"), trace) };
2804
3768
  }
2805
3769
  });
2806
3770
  }
@@ -2817,6 +3781,287 @@ function createCoordinationTools(opts) {
2817
3781
  };
2818
3782
  }
2819
3783
 
3784
+ // src/runtime/anytime.ts
3785
+ function bestSoFar(values) {
3786
+ const out = [];
3787
+ let best = 0;
3788
+ for (const v of values) {
3789
+ if (typeof v === "number" && v > best) best = v;
3790
+ out.push(best);
3791
+ }
3792
+ return out;
3793
+ }
3794
+ function areaUnderCurve(curve) {
3795
+ if (curve.length === 0) return 0;
3796
+ return curve.reduce((s, v) => s + v, 0) / curve.length;
3797
+ }
3798
+ function plateauLength(curve, minDelta) {
3799
+ if (curve.length === 0) return 0;
3800
+ const last = curve[curve.length - 1];
3801
+ let i = curve.length - 1;
3802
+ while (i > 0 && last - curve[i - 1] <= minDelta) i -= 1;
3803
+ return curve.length - 1 - i;
3804
+ }
3805
+ var median = (xs) => {
3806
+ if (xs.length === 0) return null;
3807
+ const s = [...xs].sort((a, b) => a - b);
3808
+ const mid = Math.floor(s.length / 2);
3809
+ return s.length % 2 === 1 ? s[mid] : (s[mid - 1] + s[mid]) / 2;
3810
+ };
3811
+ function anytimeReport(spans, opts) {
3812
+ const targets = opts?.targets ?? [1];
3813
+ const byRun = /* @__PURE__ */ new Map();
3814
+ for (const s of spans) {
3815
+ if (!s.label.startsWith("shot:")) continue;
3816
+ const list = byRun.get(s.runId) ?? [];
3817
+ list.push(s);
3818
+ byRun.set(s.runId, list);
3819
+ }
3820
+ const perTask = [];
3821
+ for (const [runId, shots] of byRun) {
3822
+ const m = runId.match(/^agentic:(.+):(.+)$/);
3823
+ const strategy = m?.[1] ?? runId;
3824
+ const taskId = m?.[2] ?? runId;
3825
+ const ordered = [...shots].sort((a, b) => (a.endMs ?? a.startMs) - (b.endMs ?? b.startMs));
3826
+ const t0 = Math.min(...ordered.map((s) => s.startMs));
3827
+ const taskTargets = opts?.targetFor ? [opts.targetFor(taskId)] : targets;
3828
+ const bests = bestSoFar(ordered.map((s) => typeof s.score === "number" ? s.score : void 0));
3829
+ let cumUsd = 0;
3830
+ const points = [];
3831
+ const hits = {};
3832
+ for (const t of taskTargets) hits[String(t)] = null;
3833
+ for (const [i, s] of ordered.entries()) {
3834
+ cumUsd += s.usd;
3835
+ const best = bests[i];
3836
+ const elapsedMs = (s.endMs ?? s.startMs) - t0;
3837
+ points.push({ elapsedMs, cumUsd, best });
3838
+ for (const t of taskTargets) {
3839
+ if (hits[String(t)] === null && best >= t) {
3840
+ hits[String(t)] = { ms: elapsedMs, shots: points.length, usd: cumUsd };
3841
+ }
3842
+ }
3843
+ }
3844
+ perTask.push({ taskId, strategy, points, hits });
3845
+ }
3846
+ const byStrategy = /* @__PURE__ */ new Map();
3847
+ for (const t of perTask) {
3848
+ const list = byStrategy.get(t.strategy) ?? [];
3849
+ list.push(t);
3850
+ byStrategy.set(t.strategy, list);
3851
+ }
3852
+ const perStrategy = [];
3853
+ for (const [strategy, tasks] of byStrategy) {
3854
+ const totalMs = tasks.reduce((s, t) => s + (t.points[t.points.length - 1]?.elapsedMs ?? 0), 0);
3855
+ const totalUsd = tasks.reduce((s, t) => s + (t.points[t.points.length - 1]?.cumUsd ?? 0), 0);
3856
+ const maxShots = Math.max(0, ...tasks.map((t) => t.points.length));
3857
+ const curveByShot = [];
3858
+ for (let i = 0; i < maxShots; i += 1) {
3859
+ const vals = tasks.map(
3860
+ (t) => t.points[Math.min(i, t.points.length - 1)].best
3861
+ );
3862
+ curveByShot.push(vals.reduce((s, v) => s + v, 0) / vals.length);
3863
+ }
3864
+ const auc = areaUnderCurve(curveByShot);
3865
+ const summaryTargets = opts?.targetFor ? [Number.NaN] : targets;
3866
+ for (const t of summaryTargets) {
3867
+ const key = (taskCurve) => opts?.targetFor ? Object.values(taskCurve.hits)[0] ?? null : taskCurve.hits[String(t)] ?? null;
3868
+ const reached = tasks.filter((x) => key(x) !== null);
3869
+ perStrategy.push({
3870
+ strategy,
3871
+ target: t,
3872
+ tasks: tasks.length,
3873
+ reachedTarget: reached.length,
3874
+ medianTttMs: median(reached.map((x) => key(x).ms)),
3875
+ medianShotsToTarget: median(reached.map((x) => key(x).shots)),
3876
+ ertMs: reached.length > 0 ? totalMs / reached.length : null,
3877
+ erUsd: reached.length > 0 ? totalUsd / reached.length : null,
3878
+ curveByShot,
3879
+ auc
3880
+ });
3881
+ }
3882
+ }
3883
+ perStrategy.sort((a, b) => a.strategy.localeCompare(b.strategy) || a.target - b.target);
3884
+ return { targets, perTask, perStrategy };
3885
+ }
3886
+ function renderAnytimeTable(report) {
3887
+ const lines = [
3888
+ `anytime metrics \xB7 satisficing targets [${report.targets.join(", ")}] \xB7 ERT = \u03A3 all wall-time / #successes (COCO)`,
3889
+ "strategy \u2265tgt reach med-TTT med-shots ERT(all-in) $/success AUC curve"
3890
+ ];
3891
+ for (const s of report.perStrategy) {
3892
+ const curve = s.curveByShot.map((v) => "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588"[Math.min(7, Math.floor(v * 8))]).join("");
3893
+ const tgt = Number.isNaN(s.target) ? "task" : s.target.toFixed(2);
3894
+ lines.push(
3895
+ `${s.strategy.padEnd(19)} ${tgt.padStart(4)} ${String(s.reachedTarget).padStart(4)}/${String(s.tasks).padEnd(3)} ${s.medianTttMs === null ? " \u2014" : `${(s.medianTttMs / 1e3).toFixed(1).padStart(6)}s`} ${s.medianShotsToTarget === null ? " \u2014" : String(s.medianShotsToTarget).padStart(5)} ${s.ertMs === null ? " \u2014" : `${(s.ertMs / 1e3).toFixed(1).padStart(9)}s`} ${s.erUsd === null ? " \u2014" : `$${s.erUsd.toFixed(4)}`} ${s.auc.toFixed(2)} ${curve}`
3896
+ );
3897
+ }
3898
+ return lines.join("\n");
3899
+ }
3900
+
3901
+ // src/runtime/supervise/stop-rules.ts
3902
+ var CONTINUE = { stop: false };
3903
+ function createProgressTracker(opts = {}) {
3904
+ const now = opts.now ?? Date.now;
3905
+ const requireDelivered = opts.requireDelivered ?? true;
3906
+ const minImprovement = opts.minImprovement ?? 0;
3907
+ const seen = /* @__PURE__ */ new Set();
3908
+ const recorded = [];
3909
+ return {
3910
+ record(sample) {
3911
+ if (seen.has(sample.id)) return false;
3912
+ seen.add(sample.id);
3913
+ recorded.push(sample);
3914
+ return true;
3915
+ },
3916
+ samples: () => [...recorded],
3917
+ view(scope, viewOpts) {
3918
+ const objectives = recorded.map(
3919
+ (s) => requireDelivered && !s.delivered ? void 0 : s.objective
3920
+ );
3921
+ const curve = bestSoFar(objectives);
3922
+ const best = curve.length > 0 ? curve[curve.length - 1] : 0;
3923
+ let lastImprovementAt = 0;
3924
+ let lastImprovementIdx = -1;
3925
+ let prev = 0;
3926
+ for (const [i, value] of curve.entries()) {
3927
+ if (value - prev > minImprovement) {
3928
+ lastImprovementAt = recorded[i].at;
3929
+ lastImprovementIdx = i;
3930
+ }
3931
+ prev = value;
3932
+ }
3933
+ const treeView = scope?.view;
3934
+ const workers = [];
3935
+ if (scope && treeView) {
3936
+ for (const node of treeView.nodes) {
3937
+ if (node.status === "done" || node.status === "failed" || node.status === "cancelled") {
3938
+ continue;
3939
+ }
3940
+ if (node.status === "waiting") continue;
3941
+ const p = scope.progress(
3942
+ node.id,
3943
+ viewOpts?.stallAfterMs !== void 0 ? { now: now(), stallAfterMs: viewOpts.stallAfterMs } : { now: now() }
3944
+ );
3945
+ if (p) workers.push(p);
3946
+ }
3947
+ }
3948
+ return {
3949
+ now: now(),
3950
+ settles: recorded.length,
3951
+ delivered: recorded.filter((s) => s.delivered).length,
3952
+ curve,
3953
+ best,
3954
+ auc: areaUnderCurve(curve),
3955
+ lastSettleAt: recorded.length > 0 ? recorded[recorded.length - 1].at : 0,
3956
+ lastImprovementAt,
3957
+ settlesSinceImprovement: lastImprovementIdx < 0 ? recorded.length : recorded.length - 1 - lastImprovementIdx,
3958
+ workers,
3959
+ inFlight: treeView?.inFlight ?? 0,
3960
+ waiting: treeView?.waiting ?? 0
3961
+ };
3962
+ },
3963
+ evaluate(rule, scope, viewOpts) {
3964
+ return rule(this.view(scope, viewOpts));
3965
+ }
3966
+ };
3967
+ }
3968
+ function sampleFromSettled(settled, at) {
3969
+ if (settled.kind === "down") {
3970
+ return { id: settled.handle.id, at, delivered: false };
3971
+ }
3972
+ return {
3973
+ id: settled.handle.id,
3974
+ at,
3975
+ ...settled.verdict?.score !== void 0 ? { objective: settled.verdict.score } : {},
3976
+ delivered: settled.verdict?.valid === true
3977
+ };
3978
+ }
3979
+ function noProgressFor(opts) {
3980
+ if (opts.ms === void 0 && opts.settles === void 0) {
3981
+ throw new ValidationError("noProgressFor: set at least one of { ms, settles }");
3982
+ }
3983
+ if (opts.ms !== void 0 && opts.ms <= 0) {
3984
+ throw new ValidationError("noProgressFor: ms must be > 0");
3985
+ }
3986
+ if (opts.settles !== void 0 && opts.settles < 1) {
3987
+ throw new ValidationError("noProgressFor: settles must be >= 1");
3988
+ }
3989
+ const minSettles = opts.minSettles ?? 1;
3990
+ return (v) => {
3991
+ if (v.settles < minSettles) return CONTINUE;
3992
+ if (opts.settles !== void 0 && v.settlesSinceImprovement >= opts.settles) {
3993
+ return {
3994
+ stop: true,
3995
+ reason: `no-progress: ${v.settlesSinceImprovement} settles with no improvement (limit ${opts.settles}), best=${v.best}`
3996
+ };
3997
+ }
3998
+ if (opts.ms !== void 0 && v.waiting === 0 && v.lastSettleAt > 0) {
3999
+ const idle = v.now - v.lastSettleAt;
4000
+ if (idle >= opts.ms) {
4001
+ return {
4002
+ stop: true,
4003
+ reason: `no-progress: ${idle}ms since the last settlement (limit ${opts.ms}ms)`
4004
+ };
4005
+ }
4006
+ }
4007
+ return CONTINUE;
4008
+ };
4009
+ }
4010
+ function plateau(opts) {
4011
+ if (!Number.isInteger(opts.window) || opts.window < 1) {
4012
+ throw new ValidationError("plateau: window must be a positive integer");
4013
+ }
4014
+ if (!Number.isFinite(opts.minDelta) || opts.minDelta < 0) {
4015
+ throw new ValidationError("plateau: minDelta must be >= 0");
4016
+ }
4017
+ const minSettles = opts.minSettles ?? opts.window;
4018
+ return (v) => {
4019
+ if (v.settles < minSettles) return CONTINUE;
4020
+ const flat = plateauLength(v.curve, opts.minDelta);
4021
+ if (flat >= opts.window) {
4022
+ return {
4023
+ stop: true,
4024
+ reason: `plateau: best-so-far rose <= ${opts.minDelta} over the last ${flat} settles (window ${opts.window}), best=${v.best}, auc=${v.auc.toFixed(3)}`
4025
+ };
4026
+ }
4027
+ return CONTINUE;
4028
+ };
4029
+ }
4030
+ function allWorkersStalled(opts = {}) {
4031
+ const minWorkers = opts.minWorkers ?? 1;
4032
+ return (v) => {
4033
+ if (v.waiting > 0) return CONTINUE;
4034
+ if (v.workers.length < minWorkers) return CONTINUE;
4035
+ if (!v.workers.every((w) => w.stalled)) return CONTINUE;
4036
+ const worst = Math.max(...v.workers.map((w) => w.idleMs));
4037
+ return {
4038
+ stop: true,
4039
+ reason: `all-stalled: ${v.workers.length} live workers idle, worst ${worst}ms`
4040
+ };
4041
+ };
4042
+ }
4043
+ function anyOf(...rules) {
4044
+ return (v) => {
4045
+ for (const rule of rules) {
4046
+ const d = rule(v);
4047
+ if (d.stop) return d;
4048
+ }
4049
+ return CONTINUE;
4050
+ };
4051
+ }
4052
+ function allOf(...rules) {
4053
+ if (rules.length === 0) throw new ValidationError("allOf: needs at least one rule");
4054
+ return (v) => {
4055
+ const reasons = [];
4056
+ for (const rule of rules) {
4057
+ const d = rule(v);
4058
+ if (!d.stop) return CONTINUE;
4059
+ reasons.push(d.reason);
4060
+ }
4061
+ return { stop: true, reason: reasons.join(" AND ") };
4062
+ };
4063
+ }
4064
+
2820
4065
  // src/runtime/supervise/coordination-driver.ts
2821
4066
  var distillInstruction = "CONTEXT COMPACTION. Your detailed turn-by-turn history is about to be discarded to free your context window. Write a COMPLETE, compact handoff note for your future self so you can keep going without it. Cover: (1) what you have accomplished; (2) every worker you spawned and its current status/result; (3) what subtasks remain unfinished, failing, or unverified \u2014 be specific and exhaustive here, this is the part you must not lose; (4) your immediate next action. Do not call any tools; respond with the note only.";
2822
4067
  function summarizeRoster(view, settled) {
@@ -2842,6 +4087,17 @@ function deadlinePassed(scope, now) {
2842
4087
  const b = scope.budget;
2843
4088
  return b.deadlineMs > 0 && now() >= b.deadlineMs;
2844
4089
  }
4090
+ function progressStop(tracker, rule, coord, scope, now, stallAfterMs) {
4091
+ for (const w of coord.settled()) {
4092
+ tracker.record({
4093
+ id: w.id,
4094
+ at: w.settledAt ?? now(),
4095
+ ...w.score !== void 0 ? { objective: w.score } : {},
4096
+ delivered: w.status === "done" && w.valid === true
4097
+ });
4098
+ }
4099
+ return tracker.evaluate(rule, scope, stallAfterMs !== void 0 ? { stallAfterMs } : void 0);
4100
+ }
2845
4101
  function driverAgent(opts) {
2846
4102
  if (typeof opts.brain !== "function") {
2847
4103
  throw new ValidationError("driverAgent: opts.brain must be a function");
@@ -2881,7 +4137,9 @@ function driverAgent(opts) {
2881
4137
  perWorker: opts.perWorker,
2882
4138
  ...opts.maxLiveWorkers !== void 0 ? { maxLiveWorkers: opts.maxLiveWorkers } : {},
2883
4139
  ...opts.analysts ? { analysts: opts.analysts } : {},
2884
- ...opts.analyzeOnSettle ? { analyzeOnSettle: opts.analyzeOnSettle } : {}
4140
+ ...opts.analyzeOnSettle ? { analyzeOnSettle: opts.analyzeOnSettle } : {},
4141
+ ...opts.watchWorkers ? { watchWorkers: opts.watchWorkers } : {},
4142
+ ...opts.stallAfterMs !== void 0 ? { stallAfterMs: opts.stallAfterMs } : {}
2885
4143
  });
2886
4144
  const byName = new Map(coord.tools.map((t) => [t.name, t]));
2887
4145
  const toolSpecs = [
@@ -2896,6 +4154,8 @@ function driverAgent(opts) {
2896
4154
  }))
2897
4155
  ];
2898
4156
  const system = typeof opts.systemPrompt === "function" ? opts.systemPrompt(task) : opts.systemPrompt;
4157
+ const tracker = opts.stopRule ? createProgressTracker({ now }) : void 0;
4158
+ let progressStopReason;
2899
4159
  let driverTurn = 0;
2900
4160
  const meteredBrain = async (messages, tools, detail) => {
2901
4161
  const res = await opts.brain(messages, tools);
@@ -2971,7 +4231,26 @@ Summary unavailable: ${errMessage(e)}`;
2971
4231
  // that can no longer spawn (pool starved) or has run past the deadline stops here instead of
2972
4232
  // burning turns. Checked before each inference turn.
2973
4233
  hooks: {
2974
- stopBefore: () => coord.isStopped() || scope.signal.aborted || poolStarved(scope, opts.perWorker) || deadlinePassed(scope, now)
4234
+ stopBefore: () => {
4235
+ if (coord.isStopped() || scope.signal.aborted || poolStarved(scope, opts.perWorker) || deadlinePassed(scope, now)) {
4236
+ return true;
4237
+ }
4238
+ if (!opts.stopRule || !tracker) return false;
4239
+ const decision = progressStop(
4240
+ tracker,
4241
+ opts.stopRule,
4242
+ coord,
4243
+ scope,
4244
+ now,
4245
+ opts.stallAfterMs
4246
+ );
4247
+ if (!decision.stop) return false;
4248
+ if (progressStopReason === void 0) {
4249
+ progressStopReason = decision.reason;
4250
+ opts.onProgressStop?.(decision.reason);
4251
+ }
4252
+ return true;
4253
+ }
2975
4254
  }
2976
4255
  });
2977
4256
  await coord.drainResolved();
@@ -3755,15 +5034,15 @@ function randomTaskId() {
3755
5034
  return `dlg-${t}-${r}`;
3756
5035
  }
3757
5036
  function hashIdempotencyInput(value) {
3758
- let str;
5037
+ let str2;
3759
5038
  try {
3760
- str = JSON.stringify(canonicalize(value));
5039
+ str2 = JSON.stringify(canonicalize(value));
3761
5040
  } catch {
3762
- str = String(value);
5041
+ str2 = String(value);
3763
5042
  }
3764
5043
  let h = 2166136261;
3765
- for (let i = 0; i < str.length; i += 1) {
3766
- h ^= str.charCodeAt(i);
5044
+ for (let i = 0; i < str2.length; i += 1) {
5045
+ h ^= str2.charCodeAt(i);
3767
5046
  h = Math.imul(h, 16777619);
3768
5047
  }
3769
5048
  return (h >>> 0).toString(16).padStart(8, "0");
@@ -4774,6 +6053,8 @@ async function serveCoordinationMcp(opts) {
4774
6053
  ...opts.awaitTimeoutMs !== void 0 ? { awaitTimeoutMs: opts.awaitTimeoutMs } : {},
4775
6054
  ...opts.analysts ? { analysts: opts.analysts } : {},
4776
6055
  ...opts.analyzeOnSettle ? { analyzeOnSettle: opts.analyzeOnSettle } : {},
6056
+ ...opts.watchWorkers ? { watchWorkers: opts.watchWorkers } : {},
6057
+ ...opts.stallAfterMs !== void 0 ? { stallAfterMs: opts.stallAfterMs } : {},
4777
6058
  ...opts.onEvent ? { onEvent: opts.onEvent } : {},
4778
6059
  ...opts.questionPolicy ? { questionPolicy: opts.questionPolicy } : {}
4779
6060
  });
@@ -4876,6 +6157,10 @@ function supervisorAgent(profile, deps) {
4876
6157
  ...deps.executeExtraTool ? { executeExtraTool: deps.executeExtraTool } : {},
4877
6158
  ...deps.analysts ? { analysts: deps.analysts } : {},
4878
6159
  ...deps.analyzeOnSettle ? { analyzeOnSettle: deps.analyzeOnSettle } : {},
6160
+ ...deps.watchWorkers ? { watchWorkers: deps.watchWorkers } : {},
6161
+ ...deps.stallAfterMs !== void 0 ? { stallAfterMs: deps.stallAfterMs } : {},
6162
+ ...deps.stopRule ? { stopRule: deps.stopRule } : {},
6163
+ ...deps.onProgressStop ? { onProgressStop: deps.onProgressStop } : {},
4879
6164
  ...deps.maxTurns !== void 0 ? { maxTurns: deps.maxTurns } : {},
4880
6165
  ...deps.compaction ? { compaction: deps.compaction } : {}
4881
6166
  });
@@ -4896,7 +6181,9 @@ function supervisorAgent(profile, deps) {
4896
6181
  perWorker: deps.perWorker,
4897
6182
  ...deps.maxLiveWorkers !== void 0 ? { maxLiveWorkers: deps.maxLiveWorkers } : {},
4898
6183
  ...deps.analysts ? { analysts: deps.analysts } : {},
4899
- ...deps.analyzeOnSettle ? { analyzeOnSettle: deps.analyzeOnSettle } : {}
6184
+ ...deps.analyzeOnSettle ? { analyzeOnSettle: deps.analyzeOnSettle } : {},
6185
+ ...deps.watchWorkers ? { watchWorkers: deps.watchWorkers } : {},
6186
+ ...deps.stallAfterMs !== void 0 ? { stallAfterMs: deps.stallAfterMs } : {}
4900
6187
  });
4901
6188
  try {
4902
6189
  await driveHarness({ profile, task, scope, coordinationMcpUrl: mcp.url });
@@ -4943,7 +6230,7 @@ function supervise(profile, task, opts) {
4943
6230
  typeof backendModel === "string" ? backendModel : void 0,
4944
6231
  opts.allowedModels
4945
6232
  );
4946
- const ctx = createInMemoryRunContext({ withDriver: true });
6233
+ const ctx = opts.runDir !== void 0 ? createFileRunContext(opts.runDir, { withDriver: true }) : createInMemoryRunContext({ withDriver: true });
4947
6234
  const blobs = opts.blobs ?? ctx.blobs;
4948
6235
  const perWorker = opts.perWorker ?? defaultPerWorker(opts.budget);
4949
6236
  let makeWorkerAgent = opts.makeWorkerAgent;
@@ -4967,16 +6254,22 @@ function supervise(profile, task, opts) {
4967
6254
  ...opts.executeExtraTool ? { executeExtraTool: opts.executeExtraTool } : {},
4968
6255
  ...opts.analysts ? { analysts: opts.analysts } : {},
4969
6256
  ...opts.analyzeOnSettle ? { analyzeOnSettle: opts.analyzeOnSettle } : {},
6257
+ ...opts.watchWorkers ? { watchWorkers: opts.watchWorkers } : {},
6258
+ ...opts.stallAfterMs !== void 0 ? { stallAfterMs: opts.stallAfterMs } : {},
6259
+ ...opts.stopRule ? { stopRule: opts.stopRule } : {},
6260
+ ...opts.onProgressStop ? { onProgressStop: opts.onProgressStop } : {},
4970
6261
  ...opts.maxTurns !== void 0 ? { maxTurns: opts.maxTurns } : {},
4971
6262
  ...opts.compaction ? { compaction: opts.compaction } : {}
4972
6263
  });
4973
6264
  return createSupervisor().run(agent, task, {
4974
6265
  budget: opts.budget,
4975
6266
  runId: opts.runId ?? "supervise",
4976
- journal: ctx.journal,
6267
+ journal: opts.journal ?? ctx.journal,
4977
6268
  blobs,
4978
6269
  executors: ctx.executors,
4979
6270
  maxDepth: opts.maxDepth ?? 8,
6271
+ ...opts.probes ? { probes: opts.probes } : {},
6272
+ ...ctx.resume === true ? { resume: true } : {},
4980
6273
  ...opts.now ? { now: opts.now } : {}
4981
6274
  });
4982
6275
  }
@@ -5021,13 +6314,40 @@ export {
5021
6314
  createSandboxForSpec,
5022
6315
  defaultSelectWinner,
5023
6316
  createInbox,
6317
+ decodeToolPart,
6318
+ createPushTraceSource,
6319
+ sandboxSessionTraceSource,
6320
+ PI_RUNTIME,
6321
+ piSeamKey,
6322
+ piExecutor,
6323
+ DEFAULT_SANDBOX_STEERING_MAX_TURNS,
6324
+ createSteerableSandboxSession,
5024
6325
  createWorktreeCliExecutor,
5025
6326
  cliWorktreeExecutor,
5026
6327
  createExecutor,
5027
6328
  createExecutorRegistry,
5028
6329
  createInMemoryRunContext,
6330
+ createFileRunContext,
6331
+ defaultToolDetectors,
6332
+ watchTrace,
6333
+ rollingDispatch,
6334
+ freeSlots,
6335
+ effectiveConcurrency,
6336
+ queueOf,
5029
6337
  createEventBus,
5030
6338
  createCoordinationTools,
6339
+ bestSoFar,
6340
+ areaUnderCurve,
6341
+ plateauLength,
6342
+ anytimeReport,
6343
+ renderAnytimeTable,
6344
+ createProgressTracker,
6345
+ sampleFromSettled,
6346
+ noProgressFor,
6347
+ plateau,
6348
+ allWorkersStalled,
6349
+ anyOf,
6350
+ allOf,
5031
6351
  driverAgent,
5032
6352
  finalizeBestDelivered,
5033
6353
  InMemoryFeedbackStore,
@@ -5084,4 +6404,4 @@ export {
5084
6404
  workerFromBackend,
5085
6405
  supervise
5086
6406
  };
5087
- //# sourceMappingURL=chunk-ZUCUAFVF.js.map
6407
+ //# sourceMappingURL=chunk-EAQ5YRRY.js.map