@tangle-network/agent-runtime 0.43.0 → 0.45.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 (89) hide show
  1. package/README.md +96 -202
  2. package/dist/agent.d.ts +5 -4
  3. package/dist/agent.js +5 -7
  4. package/dist/agent.js.map +1 -1
  5. package/dist/analyst-loop.d.ts +65 -4
  6. package/dist/analyst-loop.js +6 -1
  7. package/dist/audit.d.ts +93 -0
  8. package/dist/audit.js +312 -0
  9. package/dist/audit.js.map +1 -0
  10. package/dist/chunk-4B6U4CVQ.js +15 -0
  11. package/dist/chunk-4B6U4CVQ.js.map +1 -0
  12. package/dist/chunk-FK53TXOP.js +603 -0
  13. package/dist/chunk-FK53TXOP.js.map +1 -0
  14. package/dist/{chunk-MJDGCRAT.js → chunk-IJ6FGOPO.js} +5 -5
  15. package/dist/chunk-IJ6FGOPO.js.map +1 -0
  16. package/dist/{chunk-HVYOHJHK.js → chunk-IJGS6J7X.js} +2 -2
  17. package/dist/chunk-IJGS6J7X.js.map +1 -0
  18. package/dist/chunk-KEWO4KI6.js +3599 -0
  19. package/dist/chunk-KEWO4KI6.js.map +1 -0
  20. package/dist/{chunk-NRZOXCJK.js → chunk-KSMX62JF.js} +2 -2
  21. package/dist/{chunk-C5HMTTNY.js → chunk-NYN5RTLP.js} +13 -12
  22. package/dist/chunk-NYN5RTLP.js.map +1 -0
  23. package/dist/chunk-PRX45WE2.js +264 -0
  24. package/dist/chunk-PRX45WE2.js.map +1 -0
  25. package/dist/{chunk-3HMHSN22.js → chunk-QR4UUC5P.js} +6 -6
  26. package/dist/chunk-QR4UUC5P.js.map +1 -0
  27. package/dist/chunk-WIR4HOOJ.js +27 -0
  28. package/dist/chunk-WIR4HOOJ.js.map +1 -0
  29. package/dist/{chunk-MNCB4SJ5.js → chunk-Z2QXVBA6.js} +296 -8
  30. package/dist/chunk-Z2QXVBA6.js.map +1 -0
  31. package/dist/coder-CczgMqFx.d.ts +114 -0
  32. package/dist/dynamic-BvllHV6M.d.ts +221 -0
  33. package/dist/{improvement-adapter-BC4HhuAR.d.ts → improvement-adapter-CWegd3vw.d.ts} +1 -1
  34. package/dist/improvement.d.ts +2 -3
  35. package/dist/improvement.js +0 -5
  36. package/dist/improvement.js.map +1 -1
  37. package/dist/index.d.ts +123 -10
  38. package/dist/index.js +407 -19
  39. package/dist/index.js.map +1 -1
  40. package/dist/{kb-gate-DTBum3vH.d.ts → kb-gate-D9GBocLN.d.ts} +82 -5
  41. package/dist/{loop-runner-bin-CVoCBmYk.d.ts → loop-runner-bin-CPrCoKqC.d.ts} +14 -10
  42. package/dist/loop-runner-bin.d.ts +9 -7
  43. package/dist/loop-runner-bin.js +6 -8
  44. package/dist/loops.d.ts +7 -371
  45. package/dist/loops.js +96 -19
  46. package/dist/mcp/bin.js +7 -7
  47. package/dist/mcp/bin.js.map +1 -1
  48. package/dist/mcp/index.d.ts +284 -11
  49. package/dist/mcp/index.js +341 -9
  50. package/dist/mcp/index.js.map +1 -1
  51. package/dist/{otel-export-BzvF1Ela.d.ts → otel-export-Dy2DyUCU.d.ts} +1 -1
  52. package/dist/profiles.d.ts +385 -86
  53. package/dist/profiles.js +549 -4
  54. package/dist/profiles.js.map +1 -1
  55. package/dist/run-loop--hSoIknW.d.ts +112 -0
  56. package/dist/runtime-hooks-C7JwKb9E.d.ts +70 -0
  57. package/dist/runtime.d.ts +1860 -0
  58. package/dist/runtime.js +114 -0
  59. package/dist/runtime.js.map +1 -0
  60. package/dist/substrate-CUgk7F7s.d.ts +77 -0
  61. package/dist/topology.d.ts +73 -0
  62. package/dist/topology.js +111 -0
  63. package/dist/topology.js.map +1 -0
  64. package/dist/types-1HbsFa7H.d.ts +438 -0
  65. package/dist/{types-p8dWBIXL.d.ts → types-BtRLF2U3.d.ts} +1 -1
  66. package/dist/{types-Bcp071Jg.d.ts → types-DdzkffAm.d.ts} +95 -1
  67. package/dist/workflow.d.ts +551 -0
  68. package/dist/workflow.js +1778 -0
  69. package/dist/workflow.js.map +1 -0
  70. package/package.json +53 -16
  71. package/skills/agent-runtime-adoption/SKILL.md +29 -26
  72. package/dist/chunk-3HMHSN22.js.map +0 -1
  73. package/dist/chunk-C5HMTTNY.js.map +0 -1
  74. package/dist/chunk-EKBSQYZE.js +0 -813
  75. package/dist/chunk-EKBSQYZE.js.map +0 -1
  76. package/dist/chunk-HVYOHJHK.js.map +0 -1
  77. package/dist/chunk-MJDGCRAT.js.map +0 -1
  78. package/dist/chunk-MNCB4SJ5.js.map +0 -1
  79. package/dist/chunk-PY6NMZYX.js +0 -52
  80. package/dist/chunk-PY6NMZYX.js.map +0 -1
  81. package/dist/chunk-SQSCRJ7U.js +0 -65
  82. package/dist/chunk-SQSCRJ7U.js.map +0 -1
  83. package/dist/chunk-VOX6Z3II.js +0 -90
  84. package/dist/chunk-VOX6Z3II.js.map +0 -1
  85. package/dist/chunk-XBUG326M.js +0 -261
  86. package/dist/chunk-XBUG326M.js.map +0 -1
  87. package/dist/dynamic-B_7GgCwu.d.ts +0 -108
  88. package/dist/optimize-prompt-D-urF2wW.d.ts +0 -129
  89. /package/dist/{chunk-NRZOXCJK.js.map → chunk-KSMX62JF.js.map} +0 -0
@@ -0,0 +1,3599 @@
1
+ import {
2
+ AnalystError,
3
+ PlannerError,
4
+ RuntimeRunStateError,
5
+ ValidationError,
6
+ addTokenUsage,
7
+ deleteBoxSafe,
8
+ extractLlmCallEvent,
9
+ mapWithConcurrency,
10
+ randomSuffix,
11
+ randomUuid,
12
+ sleep,
13
+ stringifySafe,
14
+ throwAbort,
15
+ throwIfAborted,
16
+ withTimeout,
17
+ zeroTokenUsage
18
+ } from "./chunk-PRX45WE2.js";
19
+
20
+ // src/durable/spawn-journal.ts
21
+ import { createHash } from "crypto";
22
+ function contentAddress(artifact) {
23
+ const hex = createHash("sha256").update(stableStringify(artifact), "utf-8").digest("hex");
24
+ return `sha256:${hex}`;
25
+ }
26
+ function stableStringify(value) {
27
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
28
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
29
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
30
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
31
+ }
32
+ var InMemoryResultBlobStore = class {
33
+ blobs = /* @__PURE__ */ new Map();
34
+ async put(outRef, artifact) {
35
+ assertContentAddress(outRef, artifact);
36
+ this.blobs.set(outRef, artifact);
37
+ }
38
+ async get(outRef) {
39
+ return this.blobs.has(outRef) ? this.blobs.get(outRef) : void 0;
40
+ }
41
+ };
42
+ var FileResultBlobStore = class {
43
+ constructor(dir) {
44
+ this.dir = dir;
45
+ }
46
+ dir;
47
+ async put(outRef, artifact) {
48
+ assertContentAddress(outRef, artifact);
49
+ const fs = await import("fs/promises");
50
+ await fs.mkdir(this.dir, { recursive: true });
51
+ const fh = await fs.open(this.blobPath(outRef), "w");
52
+ try {
53
+ await fh.write(JSON.stringify(artifact));
54
+ await fh.sync();
55
+ } finally {
56
+ await fh.close();
57
+ }
58
+ }
59
+ async get(outRef) {
60
+ const fs = await import("fs/promises");
61
+ let text;
62
+ try {
63
+ text = await fs.readFile(this.blobPath(outRef), "utf8");
64
+ } catch (err) {
65
+ if (isNoEntError(err)) return void 0;
66
+ throw err;
67
+ }
68
+ return JSON.parse(text);
69
+ }
70
+ blobPath(outRef) {
71
+ return `${this.dir}/${outRef.replace(/:/g, "-")}.json`;
72
+ }
73
+ };
74
+ function assertContentAddress(outRef, artifact) {
75
+ const expected = contentAddress(artifact);
76
+ if (outRef !== expected) {
77
+ throw new Error(
78
+ `blob outRef '${outRef}' does not match the artifact content hash '${expected}'; a content-addressed store refuses a mismatched ref (breaks the replay invariant)`
79
+ );
80
+ }
81
+ }
82
+ var InMemorySpawnJournal = class {
83
+ trees = /* @__PURE__ */ new Map();
84
+ async loadTree(root) {
85
+ const tree = this.trees.get(root);
86
+ if (!tree) return void 0;
87
+ return tree.events.map((ev) => ({ ...ev }));
88
+ }
89
+ async beginTree(root, at) {
90
+ const existing = this.trees.get(root);
91
+ if (existing) {
92
+ if (existing.begunAt !== at) {
93
+ throw new Error(
94
+ `spawn tree '${root}' already begun at ${existing.begunAt}; refusing to overwrite with ${at}`
95
+ );
96
+ }
97
+ return;
98
+ }
99
+ this.trees.set(root, { begunAt: at, events: [] });
100
+ }
101
+ async appendEvent(root, ev) {
102
+ const tree = this.trees.get(root);
103
+ if (!tree) {
104
+ throw new Error(`appendEvent called for unknown spawn tree '${root}'; call beginTree first`);
105
+ }
106
+ assertSeqUnique(root, tree.events, ev);
107
+ tree.events.push({ ...ev });
108
+ }
109
+ };
110
+ var FileSpawnJournal = class {
111
+ constructor(path) {
112
+ this.path = path;
113
+ }
114
+ path;
115
+ async loadTree(root) {
116
+ const fs = await import("fs/promises");
117
+ let text;
118
+ try {
119
+ text = await fs.readFile(this.path, "utf8");
120
+ } catch (err) {
121
+ if (isNoEntError(err)) return void 0;
122
+ throw err;
123
+ }
124
+ const lines = text.split("\n").filter((line) => line.length > 0);
125
+ let begun = false;
126
+ const events = [];
127
+ for (const line of lines) {
128
+ const record = JSON.parse(line);
129
+ if (record.root !== root) continue;
130
+ if (record.kind === "begin") {
131
+ begun = true;
132
+ } else {
133
+ if (!begun) {
134
+ throw new Error(
135
+ `spawn journal corrupted: event for tree '${root}' precedes its begin record`
136
+ );
137
+ }
138
+ assertSeqUnique(root, events, record.event);
139
+ events.push(record.event);
140
+ }
141
+ }
142
+ return begun ? events : void 0;
143
+ }
144
+ async beginTree(root, at) {
145
+ const existing = await this.loadTreeBegin(root);
146
+ if (existing) {
147
+ if (existing !== at) {
148
+ throw new Error(
149
+ `spawn tree '${root}' already begun in ${this.path} at ${existing}; refusing to overwrite with ${at}`
150
+ );
151
+ }
152
+ return;
153
+ }
154
+ await this.appendRecord({ kind: "begin", root, at });
155
+ }
156
+ async appendEvent(root, ev) {
157
+ const events = await this.loadTree(root);
158
+ if (events === void 0) {
159
+ throw new Error(`appendEvent called for unknown spawn tree '${root}'; call beginTree first`);
160
+ }
161
+ assertSeqUnique(root, events, ev);
162
+ await this.appendRecord({ kind: "event", root, event: ev });
163
+ }
164
+ async loadTreeBegin(root) {
165
+ const fs = await import("fs/promises");
166
+ let text;
167
+ try {
168
+ text = await fs.readFile(this.path, "utf8");
169
+ } catch (err) {
170
+ if (isNoEntError(err)) return void 0;
171
+ throw err;
172
+ }
173
+ const lines = text.split("\n").filter((line) => line.length > 0);
174
+ for (const line of lines) {
175
+ const record = JSON.parse(line);
176
+ if (record.root === root && record.kind === "begin") return record.at;
177
+ }
178
+ return void 0;
179
+ }
180
+ async appendRecord(record) {
181
+ const fs = await import("fs/promises");
182
+ const path = await import("path");
183
+ await fs.mkdir(path.dirname(this.path), { recursive: true });
184
+ const fh = await fs.open(this.path, "a");
185
+ try {
186
+ await fh.write(`${JSON.stringify(record)}
187
+ `);
188
+ await fh.sync();
189
+ } finally {
190
+ await fh.close();
191
+ }
192
+ }
193
+ };
194
+ function assertSeqUnique(root, events, ev) {
195
+ if (ev.kind === "spawned") return;
196
+ if (events.some((e) => e.kind !== "spawned" && e.seq === ev.seq)) {
197
+ throw new Error(
198
+ `spawn journal corrupted: duplicate cursor seq ${ev.seq} in tree '${root}'; the cursor order replay relies on is not unique`
199
+ );
200
+ }
201
+ }
202
+ async function replaySpawnTree(journal, blobs, root) {
203
+ const events = await journal.loadTree(root);
204
+ if (events === void 0) {
205
+ throw new Error(`replaySpawnTree: no journaled tree for root '${root}'`);
206
+ }
207
+ const ordered = [...events].sort((a, b) => a.seq - b.seq);
208
+ const labels = /* @__PURE__ */ new Map();
209
+ for (const ev of ordered) {
210
+ if (ev.kind === "spawned") labels.set(ev.id, ev.label);
211
+ }
212
+ const settled = [];
213
+ for (const ev of ordered) {
214
+ if (ev.kind === "spawned") continue;
215
+ if (ev.kind === "cancelled") {
216
+ settled.push({
217
+ kind: "down",
218
+ handle: replayHandle(ev.id, labels.get(ev.id) ?? ev.id, "cancelled"),
219
+ reason: ev.reason,
220
+ infra: false,
221
+ restartCount: 0,
222
+ seq: ev.seq
223
+ });
224
+ continue;
225
+ }
226
+ if (ev.status === "down") {
227
+ settled.push({
228
+ kind: "down",
229
+ handle: replayHandle(ev.id, labels.get(ev.id) ?? ev.id, "failed"),
230
+ reason: ev.verdict?.notes ?? "child down",
231
+ infra: ev.infra === true,
232
+ restartCount: 0,
233
+ seq: ev.seq
234
+ });
235
+ continue;
236
+ }
237
+ if (ev.outRef === void 0) {
238
+ throw new Error(
239
+ `replaySpawnTree: settled-done event for '${ev.id}' (seq ${ev.seq}) has no outRef; cannot rehydrate the result the driver branched on`
240
+ );
241
+ }
242
+ const out = await blobs.get(ev.outRef);
243
+ if (out === void 0) {
244
+ throw new Error(
245
+ `replaySpawnTree: blob store has no artifact for outRef '${ev.outRef}' (node '${ev.id}', seq ${ev.seq})`
246
+ );
247
+ }
248
+ settled.push({
249
+ kind: "done",
250
+ handle: replayHandle(ev.id, labels.get(ev.id) ?? ev.id, "done"),
251
+ out,
252
+ outRef: ev.outRef,
253
+ verdict: ev.verdict,
254
+ spent: ev.spent,
255
+ seq: ev.seq
256
+ });
257
+ }
258
+ return settled;
259
+ }
260
+ function replayHandle(id, label, status) {
261
+ return {
262
+ id,
263
+ label,
264
+ status,
265
+ abort() {
266
+ throw new Error(`cannot abort node '${id}': replayed handles are terminal, not live`);
267
+ }
268
+ };
269
+ }
270
+ function materializeTreeView(events) {
271
+ const nodes = /* @__PURE__ */ new Map();
272
+ let root;
273
+ const spawns = events.filter((ev) => ev.kind === "spawned").sort((a, b) => a.seq - b.seq);
274
+ const settlements = events.filter((ev) => ev.kind !== "spawned").sort((a, b) => a.seq - b.seq);
275
+ for (const ev of spawns) {
276
+ if (ev.parent === void 0 && root === void 0) root = ev.id;
277
+ nodes.set(ev.id, {
278
+ id: ev.id,
279
+ parent: ev.parent,
280
+ label: ev.label,
281
+ status: "pending",
282
+ runtime: ev.runtime,
283
+ budget: ev.budget,
284
+ spent: zeroSpend()
285
+ });
286
+ }
287
+ for (const ev of settlements) {
288
+ if (ev.kind === "settled") {
289
+ const node = requireNode(nodes, ev.id);
290
+ node.status = ev.status === "done" ? "done" : "failed";
291
+ node.spent = ev.spent;
292
+ node.outRef = ev.outRef;
293
+ } else {
294
+ const node = requireNode(nodes, ev.id);
295
+ node.status = "cancelled";
296
+ }
297
+ }
298
+ const snapshots = [...nodes.values()].map(freezeSnapshot);
299
+ return {
300
+ root: root ?? snapshots[0]?.id ?? "",
301
+ nodes: snapshots,
302
+ inFlight: snapshots.filter((n) => n.status === "running" || n.status === "acquiring").length
303
+ };
304
+ }
305
+ function zeroSpend() {
306
+ return { iterations: 0, tokens: zeroTokenUsage(), usd: 0, ms: 0 };
307
+ }
308
+ function requireNode(nodes, id) {
309
+ const node = nodes.get(id);
310
+ if (!node) {
311
+ throw new Error(`spawn journal corrupted: settle/cancel for node '${id}' with no prior spawn`);
312
+ }
313
+ return node;
314
+ }
315
+ function freezeSnapshot(node) {
316
+ return {
317
+ id: node.id,
318
+ parent: node.parent,
319
+ label: node.label,
320
+ status: node.status,
321
+ runtime: node.runtime,
322
+ budget: node.budget,
323
+ spent: node.spent,
324
+ outRef: node.outRef
325
+ };
326
+ }
327
+ function isNoEntError(err) {
328
+ return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
329
+ }
330
+
331
+ // src/runtime/completion.ts
332
+ function completionAuthorizes(v, policy) {
333
+ if (!v?.done) return false;
334
+ if (v.determinism === "deterministic") return true;
335
+ return (v.confidence ?? 0) >= (policy?.minConfidence ?? 0.8);
336
+ }
337
+ function stopSentinel(seed) {
338
+ let h = 2166136261;
339
+ for (let i = 0; i < seed.length; i += 1) {
340
+ h ^= seed.charCodeAt(i);
341
+ h = Math.imul(h, 16777619);
342
+ }
343
+ return `<<<{{STOP:${(h >>> 0).toString(16).padStart(8, "0")}}}>>>`;
344
+ }
345
+ function sentinelCompletion(sentinel, opts) {
346
+ return {
347
+ assess({ history }) {
348
+ const last = history[history.length - 1];
349
+ const out = typeof last?.output === "string" ? last.output : "";
350
+ const done = out.includes(sentinel);
351
+ return {
352
+ done,
353
+ determinism: "probabilistic",
354
+ confidence: done ? opts?.confidence ?? 0.9 : 0,
355
+ reasons: done ? "agent emitted its assigned stop sentinel" : void 0,
356
+ evidence: done && last ? [{ kind: "artifact", uri: `attempt:${last.index}` }] : []
357
+ };
358
+ }
359
+ };
360
+ }
361
+ function deterministicCompletion(check) {
362
+ return {
363
+ assess({ history }) {
364
+ const last = history[history.length - 1];
365
+ if (last?.output === void 0) {
366
+ return { done: false, determinism: "deterministic", reasons: "no output yet" };
367
+ }
368
+ const r = check(last.output, history);
369
+ return {
370
+ done: r.passed,
371
+ determinism: "deterministic",
372
+ reasons: r.reasons,
373
+ evidence: [{ kind: "artifact", uri: `attempt:${last.index}` }]
374
+ };
375
+ }
376
+ };
377
+ }
378
+
379
+ // src/runtime/dynamic.ts
380
+ function createDynamicDriver(options) {
381
+ if (typeof options.planner !== "function") {
382
+ throw new ValidationError("createDynamicDriver: planner must be a function");
383
+ }
384
+ const maxIterations = options.maxIterations ?? 8;
385
+ if (!Number.isFinite(maxIterations) || maxIterations <= 0) {
386
+ throw new ValidationError("createDynamicDriver: maxIterations must be > 0");
387
+ }
388
+ const maxFanout = options.maxFanout ?? 4;
389
+ if (!Number.isFinite(maxFanout) || maxFanout < 1) {
390
+ throw new ValidationError("createDynamicDriver: maxFanout must be >= 1");
391
+ }
392
+ let pending;
393
+ return {
394
+ name: options.name ?? "dynamic",
395
+ async plan(task, history) {
396
+ if (history.length >= maxIterations) {
397
+ pending = { kind: "stop", rationale: `maxIterations (${maxIterations}) reached` };
398
+ return [];
399
+ }
400
+ const analyses = options.analyze && history.length > 0 ? await runAnalyze(options.analyze, task, history) : void 0;
401
+ if (options.complete && history.length > 0) {
402
+ const verdict = await runComplete(options.complete, task, history);
403
+ if (completionAuthorizes(verdict, options.completionPolicy)) {
404
+ pending = {
405
+ kind: "stop",
406
+ rationale: `complete (${verdict.determinism}): ${verdict.reasons ?? "satisfied"}`
407
+ };
408
+ return [];
409
+ }
410
+ }
411
+ const move = await options.planner({
412
+ task,
413
+ history,
414
+ iterationsSpent: history.length,
415
+ iterationsRemaining: maxIterations - history.length,
416
+ ...analyses ? { analyses } : {}
417
+ });
418
+ pending = validateMove(move, maxFanout);
419
+ if (pending.kind === "select") {
420
+ const iter = history[pending.index];
421
+ if (!iter || iter.output === void 0) {
422
+ throw new PlannerError(
423
+ `dynamic planner select.index ${pending.index} is not a completed iteration with output (history length ${history.length})`
424
+ );
425
+ }
426
+ }
427
+ switch (pending.kind) {
428
+ case "refine":
429
+ return [pending.task];
430
+ case "fanout":
431
+ return pending.tasks;
432
+ case "stop":
433
+ case "select":
434
+ return [];
435
+ }
436
+ },
437
+ decide() {
438
+ return pending?.kind === "stop" || pending?.kind === "select" ? "done" : "continue";
439
+ },
440
+ describePlan() {
441
+ if (!pending) return void 0;
442
+ const out = { kind: pending.kind };
443
+ if (pending.rationale !== void 0) out.rationale = pending.rationale;
444
+ if ((pending.kind === "refine" || pending.kind === "fanout") && pending.parentIndex !== void 0) {
445
+ out.parentIndex = pending.parentIndex;
446
+ }
447
+ return out;
448
+ },
449
+ selectWinner(history) {
450
+ if (pending?.kind !== "select") return void 0;
451
+ const iter = history[pending.index];
452
+ if (!iter || iter.output === void 0) return void 0;
453
+ return {
454
+ task: iter.task,
455
+ output: iter.output,
456
+ verdict: iter.verdict,
457
+ iterationIndex: iter.index,
458
+ agentRunName: iter.agentRunName
459
+ };
460
+ }
461
+ };
462
+ }
463
+ function validateMove(move, maxFanout) {
464
+ if (!move || typeof move !== "object" || typeof move.kind !== "string") {
465
+ throw new PlannerError(`dynamic planner returned a non-move value: ${stringifySafe(move)}`);
466
+ }
467
+ switch (move.kind) {
468
+ case "refine":
469
+ return move;
470
+ case "stop":
471
+ return move;
472
+ case "select": {
473
+ if (!Number.isInteger(move.index) || move.index < 0) {
474
+ throw new PlannerError(
475
+ `dynamic planner select move must carry a non-negative integer index, got ${stringifySafe(move.index)}`
476
+ );
477
+ }
478
+ return move;
479
+ }
480
+ case "fanout": {
481
+ if (!Array.isArray(move.tasks) || move.tasks.length === 0) {
482
+ throw new PlannerError("dynamic planner fanout move must carry a non-empty tasks[]");
483
+ }
484
+ if (move.tasks.length <= maxFanout) return move;
485
+ return {
486
+ kind: "fanout",
487
+ tasks: move.tasks.slice(0, maxFanout),
488
+ rationale: `${move.rationale ?? ""} [clamped ${move.tasks.length}\u2192${maxFanout}]`.trim()
489
+ };
490
+ }
491
+ default:
492
+ throw new PlannerError(
493
+ `dynamic planner returned unknown move kind: ${stringifySafe(move.kind)}`
494
+ );
495
+ }
496
+ }
497
+ async function runAnalyze(analyze, task, history) {
498
+ const findings = await analyze({ task, history });
499
+ if (!Array.isArray(findings)) {
500
+ throw new PlannerError(
501
+ `createDynamicDriver: analyze hook must return AnalystFinding[], got ${stringifySafe(findings)}`
502
+ );
503
+ }
504
+ assertTraceDerivedFindings(findings);
505
+ return findings;
506
+ }
507
+ async function runComplete(complete, task, history) {
508
+ const verdict = await complete.assess({ task, history });
509
+ if (!verdict || typeof verdict.done !== "boolean" || verdict.determinism !== "deterministic" && verdict.determinism !== "probabilistic") {
510
+ throw new PlannerError(
511
+ `createDynamicDriver: complete.assess must return a CompletionVerdict {done, determinism}, got ${stringifySafe(verdict)}`
512
+ );
513
+ }
514
+ return verdict;
515
+ }
516
+ var JUDGE_EVIDENCE_URI = /^(verdict|judge|score)\b/i;
517
+ function assertTraceDerivedFindings(findings) {
518
+ for (const f of findings) {
519
+ for (const ref of f.evidence_refs ?? []) {
520
+ if (ref.kind === "metric" && JUDGE_EVIDENCE_URI.test(ref.uri)) {
521
+ throw new PlannerError(
522
+ `steer-firewall: finding ${stringifySafe(f.finding_id)} cites judge-derived evidence (${stringifySafe(ref.uri)}); analyses fed to the driver must be trace-derived, not judge-derived (selector \u2260 judge)`
523
+ );
524
+ }
525
+ }
526
+ }
527
+ }
528
+ function renderAnalyses(findings) {
529
+ if (findings.length === 0) return "";
530
+ const rows = findings.map((f) => {
531
+ const action = f.recommended_action ? ` \u2192 ${f.recommended_action}` : "";
532
+ return ` - [${f.severity}/${f.area}] ${f.claim}${action} (conf ${f.confidence.toFixed(2)})`;
533
+ });
534
+ return `Trace-analyst findings (diagnosis of the attempts so far \u2014 steer from these, not the verdict score alone):
535
+ ${rows.join("\n")}`;
536
+ }
537
+
538
+ // src/runtime/report-usage.ts
539
+ function reportLoopUsage(cost, result, source = "loop") {
540
+ cost.observe(result.costUsd, source);
541
+ cost.observeTokens(result.tokenUsage);
542
+ }
543
+
544
+ // src/runtime-hooks.ts
545
+ function defineRuntimeHooks(hooks) {
546
+ return hooks;
547
+ }
548
+ function composeRuntimeHooks(...entries) {
549
+ const hooks = entries.filter((entry) => !!entry);
550
+ return {
551
+ onEvent: hooks.some((hook) => hook.onEvent) ? (event, context) => {
552
+ const pending = [];
553
+ for (const hook of hooks) {
554
+ const result = hook.onEvent?.(event, context);
555
+ if (isThenable(result)) pending.push(Promise.resolve(result));
556
+ }
557
+ if (pending.length > 0) return Promise.all(pending).then(() => void 0);
558
+ return void 0;
559
+ } : void 0,
560
+ onDecisionPoint: hooks.some((hook) => hook.onDecisionPoint) ? (point, context) => {
561
+ const pending = [];
562
+ for (const hook of hooks) {
563
+ const result = hook.onDecisionPoint?.(point, context);
564
+ if (isThenable(result)) pending.push(Promise.resolve(result));
565
+ }
566
+ if (pending.length > 0) return Promise.all(pending).then(() => void 0);
567
+ return void 0;
568
+ } : void 0,
569
+ onHookError: hooks.some((hook) => hook.onHookError) ? (error, context) => {
570
+ const pending = [];
571
+ for (const hook of hooks) {
572
+ const result = hook.onHookError?.(error, context);
573
+ if (isThenable(result)) pending.push(Promise.resolve(result));
574
+ }
575
+ if (pending.length > 0) return Promise.all(pending).then(() => void 0);
576
+ return void 0;
577
+ } : void 0
578
+ };
579
+ }
580
+ function notifyRuntimeHookEvent(hooks, event, context = {}) {
581
+ const onEvent = hooks?.onEvent;
582
+ if (!onEvent) return;
583
+ try {
584
+ const result = onEvent(event, context);
585
+ if (isThenable(result)) {
586
+ void result.catch((error) => {
587
+ notifyRuntimeHookError(hooks, toError(error), {
588
+ hook: "onEvent",
589
+ eventId: event.id,
590
+ target: event.target,
591
+ phase: event.phase
592
+ });
593
+ });
594
+ }
595
+ } catch (error) {
596
+ notifyRuntimeHookError(hooks, toError(error), {
597
+ hook: "onEvent",
598
+ eventId: event.id,
599
+ target: event.target,
600
+ phase: event.phase
601
+ });
602
+ }
603
+ }
604
+ function notifyRuntimeDecisionPoint(hooks, point, context = {}) {
605
+ const onDecisionPoint = hooks?.onDecisionPoint;
606
+ if (!onDecisionPoint) return;
607
+ try {
608
+ const result = onDecisionPoint(point, context);
609
+ if (isThenable(result)) {
610
+ void result.catch((error) => {
611
+ notifyRuntimeHookError(hooks, toError(error), {
612
+ hook: "onDecisionPoint",
613
+ decisionId: point.id,
614
+ decisionKind: point.kind
615
+ });
616
+ });
617
+ }
618
+ } catch (error) {
619
+ notifyRuntimeHookError(hooks, toError(error), {
620
+ hook: "onDecisionPoint",
621
+ decisionId: point.id,
622
+ decisionKind: point.kind
623
+ });
624
+ }
625
+ }
626
+ function notifyRuntimeHookError(hooks, error, context) {
627
+ try {
628
+ const result = hooks?.onHookError?.(error, context);
629
+ if (isThenable(result)) void result.catch(() => void 0);
630
+ } catch {
631
+ }
632
+ }
633
+ function isThenable(value) {
634
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
635
+ }
636
+ function toError(error) {
637
+ return error instanceof Error ? error : new Error(String(error));
638
+ }
639
+
640
+ // src/runtime/sandbox-acquire.ts
641
+ var RETRYABLE_HTTP = /* @__PURE__ */ new Set([502, 503, 504, 522, 524, 408, 425, 429]);
642
+ var TERMINAL_STATUS = /* @__PURE__ */ new Set(["failed", "expired", "stopped"]);
643
+ async function acquireSandbox(client, options, acquire = {}) {
644
+ if (!client || typeof client.create !== "function") {
645
+ throw new ValidationError("acquireSandbox: client.create is required");
646
+ }
647
+ const now = acquire.now ?? Date.now;
648
+ const sleep2 = acquire.sleep ?? ((ms) => sleep(ms, acquire.signal));
649
+ const pollMs = acquire.pollIntervalMs ?? 3e3;
650
+ const deadline = now() + (acquire.readyTimeoutMs ?? 6e5);
651
+ const name = options.name ?? acquire.name ?? `loop-sbx-${randomUuid()}`;
652
+ const createOpts = { ...options, name };
653
+ const c = client;
654
+ let lastErr;
655
+ let attempt = 0;
656
+ while (now() < deadline) {
657
+ throwIfAborted(acquire.signal);
658
+ try {
659
+ const box = await client.create(createOpts);
660
+ return await waitReadyOrDestroy(box, deadline, pollMs, acquire.signal, now, sleep2);
661
+ } catch (err) {
662
+ throwIfAborted(acquire.signal);
663
+ if (!isRetryable(err)) throw err;
664
+ lastErr = err;
665
+ if (typeof c.list === "function") {
666
+ const found = (await c.list().catch(() => []))?.find((b) => b.name === name);
667
+ if (found)
668
+ return await waitReadyOrDestroy(found, deadline, pollMs, acquire.signal, now, sleep2);
669
+ }
670
+ attempt += 1;
671
+ await sleep2(Math.min(pollMs * attempt, 15e3));
672
+ }
673
+ }
674
+ throw new ValidationError(
675
+ `acquireSandbox: could not acquire a running sandbox "${name}" within budget`,
676
+ { cause: lastErr instanceof Error ? lastErr : void 0 }
677
+ );
678
+ }
679
+ async function waitReadyOrDestroy(box, deadline, pollMs, signal, now, sleep2) {
680
+ try {
681
+ return await waitUntilReady(box, deadline, pollMs, signal, now, sleep2);
682
+ } catch (err) {
683
+ await deleteBoxSafe(box);
684
+ throw err;
685
+ }
686
+ }
687
+ async function waitUntilReady(box, deadline, pollMs, signal, now, sleep2) {
688
+ for (; ; ) {
689
+ throwIfAborted(signal);
690
+ const status = readStatus(box);
691
+ if (status === void 0 || status === "running") return box;
692
+ if (TERMINAL_STATUS.has(status)) {
693
+ throw new ValidationError(
694
+ `acquireSandbox: sandbox ${box.id ?? "(unknown)"} is ${status}${box.error ? `: ${box.error}` : ""}`
695
+ );
696
+ }
697
+ if (now() >= deadline) {
698
+ throw new ValidationError(
699
+ `acquireSandbox: sandbox ${box.id ?? "(unknown)"} not running within budget (last status: ${status})`
700
+ );
701
+ }
702
+ await sleep2(pollMs);
703
+ if (typeof box.refresh === "function") await box.refresh();
704
+ }
705
+ }
706
+ function readStatus(box) {
707
+ const s = box.status;
708
+ return typeof s === "string" ? s : void 0;
709
+ }
710
+ function isRetryable(err) {
711
+ if (!err || typeof err !== "object") return false;
712
+ const e = err;
713
+ const status = e.status ?? e.statusCode;
714
+ if (typeof status === "number" && RETRYABLE_HTTP.has(status)) return true;
715
+ const name = e.name ?? "";
716
+ if (name === "TimeoutError" || name === "ServerError" || name === "NetworkError") return true;
717
+ return /\b(timed out|timeout|gateway|temporarily unavailable|ECONNRESET|ETIMEDOUT|EAI_AGAIN)\b/i.test(
718
+ e.message ?? ""
719
+ );
720
+ }
721
+
722
+ // src/runtime/sandbox-backend.ts
723
+ function resolveBackendType(profile, override) {
724
+ if (override?.type) return override.type;
725
+ const explicit = profile.metadata?.backendType;
726
+ if (typeof explicit === "string") return explicit;
727
+ return "opencode";
728
+ }
729
+ function buildBackendOptions(profile, overrides) {
730
+ const base = overrides ?? {};
731
+ const overrideBackend = base.backend;
732
+ return {
733
+ ...base,
734
+ backend: {
735
+ type: resolveBackendType(profile, overrideBackend),
736
+ profile,
737
+ ...overrideBackend?.model ? { model: overrideBackend.model } : {},
738
+ ...overrideBackend?.server ? { server: overrideBackend.server } : {}
739
+ }
740
+ };
741
+ }
742
+
743
+ // src/runtime/sandbox-capabilities.ts
744
+ var probeCache = /* @__PURE__ */ new WeakMap();
745
+ function probeSandboxCapabilities(client) {
746
+ const key = client;
747
+ const cached = probeCache.get(key);
748
+ if (cached) return cached;
749
+ const probe = resolveCapabilities(client);
750
+ probeCache.set(key, probe);
751
+ return probe;
752
+ }
753
+ async function resolveCapabilities(client) {
754
+ const criuStatus = client.criuStatus;
755
+ if (typeof criuStatus !== "function") return { canFork: false };
756
+ try {
757
+ const status = await criuStatus.call(client);
758
+ return { canFork: status?.available === true };
759
+ } catch {
760
+ return { canFork: false };
761
+ }
762
+ }
763
+
764
+ // src/runtime/sandbox-lineage.ts
765
+ var TEARDOWN_TIMEOUT_MS = 15e3;
766
+ var DEFAULT_FORK_CONCURRENCY = 4;
767
+ function createSandboxLineage(client, capabilities, options = {}) {
768
+ if (!client || typeof client.create !== "function") {
769
+ throw new ValidationError("createSandboxLineage: client.create is required");
770
+ }
771
+ const forkConcurrency = Math.max(
772
+ 1,
773
+ Math.floor(options.maxConcurrency ?? DEFAULT_FORK_CONCURRENCY)
774
+ );
775
+ const owned = [];
776
+ const acquireFresh = async (spec, signal) => {
777
+ if (signal.aborted) throwAbort();
778
+ const opts = buildBackendOptions(spec.profile, spec.sandboxOverrides);
779
+ const box = await acquireSandbox(client, opts, { signal });
780
+ owned.push(box);
781
+ return box;
782
+ };
783
+ return {
784
+ async start(spec, prompt, signal) {
785
+ const box = await acquireFresh(spec, signal);
786
+ const sessionId = mintSessionId();
787
+ const events = box.streamPrompt(prompt, { sessionId, signal });
788
+ return { handle: { box, sessionId }, events };
789
+ },
790
+ async continue(handle, prompt, signal) {
791
+ if (signal.aborted) throwAbort();
792
+ await assertSessionLive(handle.box, handle.sessionId);
793
+ return handle.box.streamPrompt(prompt, { sessionId: handle.sessionId, signal });
794
+ },
795
+ async fork(parent, prompts, specs, signal) {
796
+ if (prompts.length === 0) {
797
+ throw new ValidationError("SandboxLineage.fork: prompts must be non-empty");
798
+ }
799
+ if (signal.aborted) throwAbort();
800
+ const checkpointId = capabilities.canFork ? await checkpointForFork(parent.box, signal) : void 0;
801
+ return mapWithConcurrency(prompts, forkConcurrency, async (prompt, i) => {
802
+ throwIfAborted(signal);
803
+ const spec = specs[i % specs.length];
804
+ if (!spec) throw new ValidationError("SandboxLineage.fork: no AgentRunSpec for branch");
805
+ if (checkpointId !== void 0) {
806
+ const box2 = await forkFromCheckpoint(parent.box, checkpointId, signal);
807
+ owned.push(box2);
808
+ const sessionId2 = mintSessionId();
809
+ return {
810
+ handle: { box: box2, sessionId: sessionId2 },
811
+ events: box2.streamPrompt(prompt, { sessionId: sessionId2, signal })
812
+ };
813
+ }
814
+ const box = await acquireFresh(spec, signal);
815
+ const sessionId = mintSessionId();
816
+ return {
817
+ handle: { box, sessionId },
818
+ events: box.streamPrompt(prompt, { sessionId, signal })
819
+ };
820
+ });
821
+ },
822
+ async prune(keep) {
823
+ const keepBoxes = /* @__PURE__ */ new Set();
824
+ for (const handle of keep) keepBoxes.add(handle.box);
825
+ const survivors = [];
826
+ const doomed = [];
827
+ for (const box of owned) (keepBoxes.has(box) ? survivors : doomed).push(box);
828
+ if (doomed.length === 0) return;
829
+ owned.length = 0;
830
+ owned.push(...survivors);
831
+ await Promise.allSettled(doomed.map((box) => destroyBounded(box)));
832
+ },
833
+ async teardown() {
834
+ const boxes = owned.splice(0, owned.length);
835
+ await Promise.allSettled(boxes.map((box) => destroyBounded(box)));
836
+ }
837
+ };
838
+ }
839
+ function mintSessionId() {
840
+ return `loop-sess-${randomUuid()}`;
841
+ }
842
+ async function checkpointForFork(box, signal) {
843
+ const checkpoint = box.checkpoint;
844
+ if (typeof checkpoint !== "function") return void 0;
845
+ if (signal.aborted) throwAbort();
846
+ const result = await checkpoint.call(box, { leaveRunning: true });
847
+ const id = result?.checkpointId;
848
+ return typeof id === "string" && id.length > 0 ? id : void 0;
849
+ }
850
+ async function forkFromCheckpoint(box, checkpointId, signal) {
851
+ const fork = box.fork;
852
+ if (typeof fork !== "function") {
853
+ throw new ValidationError(
854
+ "SandboxLineage.fork: capabilities report canFork but the box has no fork() method"
855
+ );
856
+ }
857
+ if (signal.aborted) throwAbort();
858
+ return fork.call(box, checkpointId);
859
+ }
860
+ async function assertSessionLive(box, sessionId) {
861
+ const session = box.session;
862
+ if (typeof session !== "function") return;
863
+ const info = await session.call(box, sessionId).status();
864
+ if (info === null) {
865
+ throw new ValidationError(
866
+ `SandboxLineage.continue: session ${sessionId} is not known to the sandbox \u2014 the platform did not preserve the client-minted session id (or it was reaped). Continuing would run without prior context; refusing to silently lose conversation continuity.`
867
+ );
868
+ }
869
+ }
870
+ async function destroyBounded(box) {
871
+ await withTimeout(deleteBoxSafe(box), TEARDOWN_TIMEOUT_MS);
872
+ }
873
+
874
+ // src/runtime/run-loop.ts
875
+ var DEFAULT_MAX_ITERATIONS = 10;
876
+ var DEFAULT_MAX_CONCURRENCY = 4;
877
+ async function runLoop(options) {
878
+ const specs = resolveAgentRuns(options);
879
+ const maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS;
880
+ if (!Number.isFinite(maxIterations) || maxIterations <= 0) {
881
+ throw new ValidationError("runLoop: maxIterations must be > 0");
882
+ }
883
+ const maxConcurrency = options.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY;
884
+ if (!Number.isFinite(maxConcurrency) || maxConcurrency <= 0) {
885
+ throw new ValidationError("runLoop: maxConcurrency must be > 0");
886
+ }
887
+ if (!options.ctx?.sandboxClient || typeof options.ctx.sandboxClient.create !== "function") {
888
+ throw new ValidationError("runLoop: ctx.sandboxClient.create is required");
889
+ }
890
+ const now = options.now ?? Date.now;
891
+ const runId = options.runId ?? `loop-${randomSuffix()}`;
892
+ const loopStart = now();
893
+ const driverName = options.driver.name ?? "driver";
894
+ const iterations = [];
895
+ let round = 0;
896
+ const ownedBoxes = [];
897
+ const collectBox = options.onWorkerBox ? (box) => {
898
+ ownedBoxes.push(box);
899
+ options.onWorkerBox?.(box);
900
+ } : void 0;
901
+ const lineageState = await setUpLineage(options, maxConcurrency);
902
+ emitRunLoopHook(options, {
903
+ target: "agent.run",
904
+ phase: "before",
905
+ runId,
906
+ timestamp: now(),
907
+ payload: {
908
+ driver: driverName,
909
+ agentRunNames: specs.map((spec) => spec.name ?? spec.profile.name ?? "agent"),
910
+ maxIterations,
911
+ maxConcurrency
912
+ }
913
+ });
914
+ await emitTrace(options.ctx.traceEmitter, {
915
+ kind: "loop.started",
916
+ runId,
917
+ timestamp: now(),
918
+ payload: {
919
+ driver: driverName,
920
+ agentRunNames: specs.map((spec) => spec.name ?? spec.profile.name ?? "agent"),
921
+ maxIterations,
922
+ maxConcurrency
923
+ }
924
+ });
925
+ const controller = new AbortController();
926
+ const onOuterAbort = () => controller.abort();
927
+ if (options.ctx.signal) {
928
+ if (options.ctx.signal.aborted) controller.abort();
929
+ else options.ctx.signal.addEventListener("abort", onOuterAbort, { once: true });
930
+ }
931
+ try {
932
+ while (iterations.length < maxIterations) {
933
+ if (controller.signal.aborted) throwAbort();
934
+ emitRunLoopHook(options, {
935
+ target: "agent.plan",
936
+ phase: "before",
937
+ runId,
938
+ timestamp: now(),
939
+ stepIndex: round,
940
+ payload: { roundIndex: round, historyLength: iterations.length }
941
+ });
942
+ const planned = await options.driver.plan(options.task, iterations);
943
+ if (controller.signal.aborted) throwAbort();
944
+ const planDesc = options.driver.describePlan?.();
945
+ const roundIndex = round;
946
+ const baseIndex = iterations.length;
947
+ const remaining = maxIterations - iterations.length;
948
+ const slice = planned.slice(0, remaining);
949
+ const parentIndex = planDesc?.parentIndex ?? (roundIndex === 0 ? void 0 : branchPoint(iterations));
950
+ const childIndices = slice.map((_, i) => baseIndex + i);
951
+ const moveKind = planDesc?.kind ?? (planned.length === 0 ? "stop" : planned.length === 1 ? "refine" : "fanout");
952
+ emitRunLoopHook(options, {
953
+ target: "agent.plan",
954
+ phase: "after",
955
+ runId,
956
+ timestamp: now(),
957
+ stepIndex: roundIndex,
958
+ payload: {
959
+ roundIndex,
960
+ plannedCount: planned.length,
961
+ moveKind,
962
+ parentIndex,
963
+ childIndices
964
+ }
965
+ });
966
+ await emitTrace(options.ctx.traceEmitter, {
967
+ kind: "loop.plan",
968
+ runId,
969
+ timestamp: now(),
970
+ payload: {
971
+ roundIndex,
972
+ plannedCount: planned.length,
973
+ moveKind,
974
+ rationale: planDesc?.rationale,
975
+ parentIndex,
976
+ childIndices
977
+ }
978
+ });
979
+ round += 1;
980
+ if (planned.length === 0) break;
981
+ for (let i = 0; i < slice.length; i += 1) {
982
+ const spec = specs[(baseIndex + i) % specs.length];
983
+ iterations.push({
984
+ index: baseIndex + i,
985
+ task: slice[i],
986
+ agentRunName: spec.name ?? spec.profile.name ?? "agent",
987
+ events: [],
988
+ startedAt: now(),
989
+ endedAt: 0,
990
+ costUsd: 0,
991
+ tokenUsage: zeroTokenUsage()
992
+ });
993
+ }
994
+ const lineagePlan = lineageState ? planLineageRound(lineageState, specs, slice, parentIndex, controller.signal) : void 0;
995
+ await runBatch({
996
+ slice,
997
+ baseIndex,
998
+ iterations,
999
+ specs,
1000
+ output: options.output,
1001
+ validator: options.validator,
1002
+ maxConcurrency,
1003
+ signal: controller.signal,
1004
+ ctx: options.ctx,
1005
+ runId,
1006
+ now,
1007
+ roundIndex,
1008
+ parentIndex,
1009
+ collectBox,
1010
+ lineagePlan,
1011
+ lineageState
1012
+ });
1013
+ if (controller.signal.aborted) throwAbort();
1014
+ emitRunLoopHook(options, {
1015
+ target: "agent.decision",
1016
+ phase: "before",
1017
+ runId,
1018
+ timestamp: now(),
1019
+ stepIndex: roundIndex,
1020
+ payload: { historyLength: iterations.length }
1021
+ });
1022
+ const decision = await options.driver.decide(iterations);
1023
+ emitRunLoopHook(options, {
1024
+ target: "agent.decision",
1025
+ phase: "after",
1026
+ runId,
1027
+ timestamp: now(),
1028
+ stepIndex: roundIndex,
1029
+ payload: { decision: stringifySafe(decision), historyLength: iterations.length }
1030
+ });
1031
+ await emitTrace(options.ctx.traceEmitter, {
1032
+ kind: "loop.decision",
1033
+ runId,
1034
+ timestamp: now(),
1035
+ payload: { decision: stringifySafe(decision), historyLength: iterations.length }
1036
+ });
1037
+ if (isTerminalDecision(decision)) {
1038
+ return await finalizeAndEmitEnded(options, decision, iterations, loopStart, now, runId);
1039
+ }
1040
+ if (lineageState) await pruneLineage(lineageState, iterations);
1041
+ }
1042
+ return await decideAndFinalize(options, iterations, loopStart, now, runId);
1043
+ } finally {
1044
+ if (options.ctx.signal) options.ctx.signal.removeEventListener("abort", onOuterAbort);
1045
+ await Promise.allSettled(
1046
+ ownedBoxes.map((b) => destroySandboxSafe(b, options.ctx.traceEmitter, runId, now))
1047
+ );
1048
+ if (options.onWorkerBox) options.onWorkerBox(void 0);
1049
+ if (lineageState) await lineageState.lineage.teardown();
1050
+ }
1051
+ }
1052
+ async function setUpLineage(options, maxConcurrency) {
1053
+ const lineageOpts = options.lineage;
1054
+ if (!lineageOpts || !lineageOpts.sessionContinuity && !lineageOpts.forkFanout) return void 0;
1055
+ if (options.onWorkerBox) {
1056
+ throw new ValidationError(
1057
+ "runLoop: `lineage` and `onWorkerBox` both own worker boxes \u2014 pass only one"
1058
+ );
1059
+ }
1060
+ const capabilities = await probeSandboxCapabilities(options.ctx.sandboxClient);
1061
+ return {
1062
+ lineage: createSandboxLineage(options.ctx.sandboxClient, capabilities, { maxConcurrency }),
1063
+ options: lineageOpts,
1064
+ handles: /* @__PURE__ */ new Map(),
1065
+ canPrune: typeof options.driver.describePlan !== "function"
1066
+ };
1067
+ }
1068
+ function planLineageRound(state, specs, slice, parentIndex, signal) {
1069
+ const lineage = state.lineage;
1070
+ const parent = parentIndex !== void 0 ? state.handles.get(parentIndex) : void 0;
1071
+ const promptFor = (offset) => {
1072
+ const spec = specs[offset % specs.length];
1073
+ if (!spec) throw new ValidationError("runLoop: no AgentRunSpec available for lineage iteration");
1074
+ return spec.taskToPrompt(slice[offset]);
1075
+ };
1076
+ const specAt = (offset) => {
1077
+ const spec = specs[offset % specs.length];
1078
+ if (!spec) throw new ValidationError("runLoop: no AgentRunSpec available for lineage iteration");
1079
+ return spec;
1080
+ };
1081
+ if (slice.length === 1 && parent && state.options.sessionContinuity) {
1082
+ return [
1083
+ {
1084
+ async acquire() {
1085
+ const events = await lineage.continue(parent, promptFor(0), signal);
1086
+ return { events, handle: parent };
1087
+ }
1088
+ }
1089
+ ];
1090
+ }
1091
+ if (slice.length > 1 && parent && state.options.forkFanout) {
1092
+ const prompts = slice.map((_, offset) => promptFor(offset));
1093
+ const childSpecs = slice.map((_, offset) => specAt(offset));
1094
+ let forked;
1095
+ const ensureForked = () => {
1096
+ forked ??= lineage.fork(parent, prompts, childSpecs, signal);
1097
+ return forked;
1098
+ };
1099
+ return slice.map((_, offset) => ({
1100
+ async acquire() {
1101
+ const branches = await ensureForked();
1102
+ const branch = branches[offset];
1103
+ if (!branch)
1104
+ throw new ValidationError("runLoop: lineage fork produced no branch for offset");
1105
+ return branch;
1106
+ }
1107
+ }));
1108
+ }
1109
+ return slice.map((_, offset) => ({
1110
+ async acquire() {
1111
+ return lineage.start(specAt(offset), promptFor(offset), signal);
1112
+ }
1113
+ }));
1114
+ }
1115
+ async function pruneLineage(state, iterations) {
1116
+ if (!state.canPrune) return;
1117
+ const keepIndex = branchPoint(iterations);
1118
+ if (keepIndex === void 0) return;
1119
+ const keep = state.handles.get(keepIndex);
1120
+ if (!keep) return;
1121
+ await state.lineage.prune([keep]);
1122
+ const stale = [];
1123
+ for (const [index, handle] of state.handles) {
1124
+ if (handle.box !== keep.box) stale.push(index);
1125
+ }
1126
+ for (const index of stale) state.handles.delete(index);
1127
+ }
1128
+ async function runBatch(args) {
1129
+ const queue = args.slice.map((task, offset) => ({ task, index: args.baseIndex + offset }));
1130
+ const inflight = /* @__PURE__ */ new Set();
1131
+ const started = [];
1132
+ let firstError;
1133
+ try {
1134
+ while (queue.length > 0 || inflight.size > 0) {
1135
+ while (inflight.size < args.maxConcurrency && queue.length > 0) {
1136
+ const item = queue.shift();
1137
+ const p = executeIteration({ ...args, item }).finally(() => inflight.delete(p));
1138
+ started.push(p);
1139
+ inflight.add(p);
1140
+ }
1141
+ if (inflight.size === 0) break;
1142
+ try {
1143
+ await Promise.race(inflight);
1144
+ } catch (err) {
1145
+ if (firstError === void 0) firstError = err;
1146
+ queue.length = 0;
1147
+ break;
1148
+ }
1149
+ }
1150
+ } finally {
1151
+ const settled = await Promise.allSettled(started);
1152
+ if (firstError === void 0) {
1153
+ const rejected = settled.find((s) => s.status === "rejected");
1154
+ if (rejected && rejected.status === "rejected") firstError = rejected.reason;
1155
+ }
1156
+ }
1157
+ if (firstError !== void 0) throw firstError;
1158
+ }
1159
+ async function executeIteration(args) {
1160
+ const slot = args.iterations[args.item.index];
1161
+ if (!slot)
1162
+ throw new ValidationError(`runLoop: missing iteration slot at index ${args.item.index}`);
1163
+ const spec = args.specs[args.item.index % args.specs.length];
1164
+ if (!spec) throw new ValidationError("runLoop: no AgentRunSpec available for iteration");
1165
+ slot.startedAt = args.now();
1166
+ slot.agentRunName = spec.name ?? spec.profile.name ?? "agent";
1167
+ await emitTrace(args.ctx.traceEmitter, {
1168
+ kind: "loop.iteration.started",
1169
+ runId: args.runId,
1170
+ timestamp: args.now(),
1171
+ payload: {
1172
+ iterationIndex: args.item.index,
1173
+ agentRunName: slot.agentRunName,
1174
+ taskHash: hashJson(args.item.task),
1175
+ groupId: args.roundIndex,
1176
+ parentIndex: args.parentIndex
1177
+ }
1178
+ });
1179
+ let box;
1180
+ let lineageOwned = false;
1181
+ try {
1182
+ let stream;
1183
+ const source = args.lineagePlan?.[args.item.index - args.baseIndex];
1184
+ if (source) {
1185
+ const acquired = await source.acquire();
1186
+ box = acquired.handle.box;
1187
+ lineageOwned = true;
1188
+ args.lineageState?.handles.set(args.item.index, acquired.handle);
1189
+ stream = acquired.events;
1190
+ } else {
1191
+ box = await createSandboxForSpec(args.ctx.sandboxClient, spec, args.signal);
1192
+ stream = box.streamPrompt(spec.taskToPrompt(args.item.task), { signal: args.signal });
1193
+ }
1194
+ const placement = describeSandboxPlacement(args.ctx.sandboxClient, box);
1195
+ await emitTrace(args.ctx.traceEmitter, {
1196
+ kind: "loop.iteration.dispatch",
1197
+ runId: args.runId,
1198
+ timestamp: args.now(),
1199
+ payload: {
1200
+ iterationIndex: args.item.index,
1201
+ agentRunName: slot.agentRunName,
1202
+ placement: placement.kind,
1203
+ sandboxId: placement.sandboxId,
1204
+ fleetId: placement.fleetId,
1205
+ machineId: placement.machineId,
1206
+ groupId: args.roundIndex,
1207
+ parentIndex: args.parentIndex
1208
+ }
1209
+ });
1210
+ const events = [];
1211
+ for await (const event of stream) {
1212
+ events.push(event);
1213
+ const llmCall = extractLlmCallEvent(event, slot.agentRunName);
1214
+ if (llmCall) {
1215
+ slot.costUsd += llmCall.costUsd ?? 0;
1216
+ addTokenUsage(slot.tokenUsage, { input: llmCall.tokensIn, output: llmCall.tokensOut });
1217
+ args.ctx.runHandle?.observe(llmCall);
1218
+ }
1219
+ }
1220
+ slot.events = events;
1221
+ slot.output = args.output.parse(events);
1222
+ if (args.validator) {
1223
+ slot.verdict = await args.validator.validate(slot.output, {
1224
+ iteration: args.item.index,
1225
+ signal: args.signal,
1226
+ traceEmitter: args.ctx.traceEmitter
1227
+ });
1228
+ }
1229
+ } catch (err) {
1230
+ slot.error = err instanceof Error ? err : new Error(String(err));
1231
+ } finally {
1232
+ slot.endedAt = args.now();
1233
+ await emitTrace(args.ctx.traceEmitter, {
1234
+ kind: "loop.iteration.ended",
1235
+ runId: args.runId,
1236
+ timestamp: args.now(),
1237
+ payload: {
1238
+ iterationIndex: args.item.index,
1239
+ agentRunName: slot.agentRunName,
1240
+ outputHash: slot.output !== void 0 ? hashJson(slot.output) : void 0,
1241
+ verdict: slot.verdict,
1242
+ error: slot.error?.message,
1243
+ costUsd: slot.costUsd,
1244
+ durationMs: slot.endedAt - slot.startedAt,
1245
+ tokenUsage: slot.tokenUsage.input || slot.tokenUsage.output ? { ...slot.tokenUsage } : void 0,
1246
+ groupId: args.roundIndex,
1247
+ parentIndex: args.parentIndex,
1248
+ outputPreview: slot.output !== void 0 ? stringifySafe(slot.output, { max: 280 }) : void 0
1249
+ }
1250
+ });
1251
+ if (lineageOwned) {
1252
+ } else if (args.collectBox && box) {
1253
+ args.collectBox(box);
1254
+ } else {
1255
+ await destroySandboxSafe(box, args.ctx.traceEmitter, args.runId, args.now);
1256
+ }
1257
+ }
1258
+ if (isAbortError(slot.error) || args.signal.aborted) {
1259
+ if (slot.error) throw slot.error;
1260
+ throwAbort();
1261
+ }
1262
+ if (slot.error instanceof ValidationError) throw slot.error;
1263
+ }
1264
+ function isAbortError(err) {
1265
+ return err instanceof Error && err.name === "AbortError";
1266
+ }
1267
+ var TEARDOWN_TIMEOUT_MS2 = 15e3;
1268
+ async function destroySandboxSafe(box, trace, runId, now) {
1269
+ if (!box || typeof box.delete !== "function") return;
1270
+ const emitFailed = async (reason) => {
1271
+ if (!trace || !runId) return;
1272
+ await emitTrace(trace, {
1273
+ kind: "loop.teardown.failed",
1274
+ runId,
1275
+ timestamp: (now ?? Date.now)(),
1276
+ payload: { sandboxId: readSandboxId(box), reason }
1277
+ });
1278
+ };
1279
+ const outcome = await withTimeout(deleteBoxSafe(box), TEARDOWN_TIMEOUT_MS2);
1280
+ if (outcome === void 0) await emitFailed("timeout");
1281
+ else if (outcome === false) await emitFailed("delete threw");
1282
+ }
1283
+ function branchPoint(iterations) {
1284
+ if (iterations.length === 0) return void 0;
1285
+ let best = iterations.length - 1;
1286
+ let bestScore = -Infinity;
1287
+ for (const iter of iterations) {
1288
+ if (iter.verdict?.valid !== true) continue;
1289
+ const score = iter.verdict.score ?? 0;
1290
+ if (score > bestScore) {
1291
+ bestScore = score;
1292
+ best = iter.index;
1293
+ }
1294
+ }
1295
+ return best;
1296
+ }
1297
+ function describeSandboxPlacement(client, box) {
1298
+ if (typeof client.describePlacement === "function") {
1299
+ try {
1300
+ const result = client.describePlacement(box);
1301
+ if (result && typeof result === "object" && (result.kind === "sibling" || result.kind === "fleet")) {
1302
+ return {
1303
+ kind: result.kind,
1304
+ sandboxId: result.sandboxId ?? readSandboxId(box),
1305
+ fleetId: result.fleetId,
1306
+ machineId: result.machineId
1307
+ };
1308
+ }
1309
+ } catch {
1310
+ }
1311
+ }
1312
+ return { kind: "sibling", sandboxId: readSandboxId(box) };
1313
+ }
1314
+ function readSandboxId(box) {
1315
+ const raw = box.id;
1316
+ return typeof raw === "string" && raw.length > 0 ? raw : void 0;
1317
+ }
1318
+ async function createSandboxForSpec(client, spec, signal) {
1319
+ const opts = buildBackendOptions(spec.profile, spec.sandboxOverrides);
1320
+ if (signal.aborted) throwAbort();
1321
+ return acquireSandbox(client, opts, { signal });
1322
+ }
1323
+ function finalize(args) {
1324
+ const winner = args.options.selectWinner ? args.options.selectWinner(args.iterations) : args.options.driver.selectWinner?.(args.iterations) ?? defaultSelectWinner(args.iterations);
1325
+ const costUsd = args.iterations.reduce((sum, iter) => sum + (iter.costUsd || 0), 0);
1326
+ const tokenUsage = args.iterations.reduce((acc, iter) => {
1327
+ addTokenUsage(acc, iter.tokenUsage);
1328
+ return acc;
1329
+ }, zeroTokenUsage());
1330
+ const result = {
1331
+ decision: args.decision,
1332
+ iterations: args.iterations,
1333
+ winner,
1334
+ durationMs: args.now() - args.startMs,
1335
+ costUsd,
1336
+ tokenUsage
1337
+ };
1338
+ return result;
1339
+ }
1340
+ async function decideAndFinalize(options, iterations, startMs, now, runId) {
1341
+ emitRunLoopHook(options, {
1342
+ target: "agent.decision",
1343
+ phase: "before",
1344
+ runId,
1345
+ timestamp: now(),
1346
+ payload: { historyLength: iterations.length }
1347
+ });
1348
+ const decision = await options.driver.decide(iterations);
1349
+ emitRunLoopHook(options, {
1350
+ target: "agent.decision",
1351
+ phase: "after",
1352
+ runId,
1353
+ timestamp: now(),
1354
+ payload: { decision: stringifySafe(decision), historyLength: iterations.length }
1355
+ });
1356
+ await emitTrace(options.ctx.traceEmitter, {
1357
+ kind: "loop.decision",
1358
+ runId,
1359
+ timestamp: now(),
1360
+ payload: { decision: stringifySafe(decision), historyLength: iterations.length }
1361
+ });
1362
+ return finalizeAndEmitEnded(options, decision, iterations, startMs, now, runId);
1363
+ }
1364
+ async function finalizeAndEmitEnded(options, decision, iterations, startMs, now, runId) {
1365
+ const result = finalize({ options, decision, iterations, startMs, now, runId });
1366
+ emitRunLoopHook(options, {
1367
+ target: "agent.run",
1368
+ phase: "after",
1369
+ runId,
1370
+ timestamp: now(),
1371
+ payload: {
1372
+ decision: stringifySafe(decision),
1373
+ winnerIterationIndex: result.winner?.iterationIndex,
1374
+ totalCostUsd: result.costUsd,
1375
+ durationMs: result.durationMs,
1376
+ iterations: iterations.length
1377
+ }
1378
+ });
1379
+ await emitTrace(options.ctx.traceEmitter, {
1380
+ kind: "loop.ended",
1381
+ runId,
1382
+ timestamp: now(),
1383
+ payload: {
1384
+ winnerIterationIndex: result.winner?.iterationIndex,
1385
+ totalCostUsd: result.costUsd,
1386
+ durationMs: result.durationMs,
1387
+ iterations: iterations.length
1388
+ }
1389
+ });
1390
+ return result;
1391
+ }
1392
+ function defaultSelectWinner(iterations) {
1393
+ const candidates = iterations.filter((iter) => iter.output !== void 0 && !iter.error);
1394
+ if (candidates.length === 0) return void 0;
1395
+ const valid = candidates.filter((iter) => iter.verdict?.valid === true);
1396
+ const pool = valid.length > 0 ? valid : candidates;
1397
+ const sorted = [...pool].sort(
1398
+ (a, b) => (b.verdict?.score ?? 0) - (a.verdict?.score ?? 0) || a.index - b.index
1399
+ );
1400
+ const top = sorted[0];
1401
+ if (!top || top.output === void 0) return void 0;
1402
+ return {
1403
+ task: top.task,
1404
+ output: top.output,
1405
+ verdict: top.verdict,
1406
+ iterationIndex: top.index,
1407
+ agentRunName: top.agentRunName
1408
+ };
1409
+ }
1410
+ function resolveAgentRuns(options) {
1411
+ if (options.agentRun && options.agentRuns) {
1412
+ throw new ValidationError("runLoop: pass exactly one of `agentRun` or `agentRuns`");
1413
+ }
1414
+ if (options.agentRun) return [options.agentRun];
1415
+ if (options.agentRuns && options.agentRuns.length > 0) return options.agentRuns;
1416
+ throw new ValidationError("runLoop: `agentRun` or non-empty `agentRuns` is required");
1417
+ }
1418
+ function isTerminalDecision(decision) {
1419
+ return decision === "stop" || decision === "pick-winner" || decision === "fail" || decision === "done";
1420
+ }
1421
+ function emitRunLoopHook(options, event) {
1422
+ notifyRuntimeHookEvent(
1423
+ options.ctx.hooks,
1424
+ {
1425
+ id: `${event.runId}:${event.target}:${event.phase}${event.stepIndex === void 0 ? "" : `:${event.stepIndex}`}`,
1426
+ runId: event.runId,
1427
+ target: event.target,
1428
+ phase: event.phase,
1429
+ timestamp: event.timestamp,
1430
+ stepIndex: event.stepIndex,
1431
+ payload: event.payload,
1432
+ metadata: { producer: "run-loop" }
1433
+ },
1434
+ { signal: options.ctx.signal }
1435
+ );
1436
+ }
1437
+ async function emitTrace(emitter, event) {
1438
+ if (!emitter) return;
1439
+ await emitter.emit(event);
1440
+ }
1441
+ function hashJson(value) {
1442
+ let str;
1443
+ try {
1444
+ str = JSON.stringify(value) ?? String(value);
1445
+ } catch {
1446
+ str = String(value);
1447
+ }
1448
+ let h = 2166136261;
1449
+ for (let i = 0; i < str.length; i += 1) {
1450
+ h ^= str.charCodeAt(i);
1451
+ h = Math.imul(h, 16777619);
1452
+ }
1453
+ return (h >>> 0).toString(16).padStart(8, "0");
1454
+ }
1455
+
1456
+ // src/runtime/loop-dispatch.ts
1457
+ function campaignTraceToLoopEmitter(trace) {
1458
+ return {
1459
+ emit(event) {
1460
+ trace.span(event.kind, { runId: event.runId, timestamp: event.timestamp, ...event.payload }).end();
1461
+ }
1462
+ };
1463
+ }
1464
+ async function runLoopForCell(opts, scenario, profile, ctx) {
1465
+ const loopOptions = opts.toLoopOptions(scenario, profile);
1466
+ const result = await runLoop({
1467
+ ...loopOptions,
1468
+ ctx: {
1469
+ sandboxClient: opts.sandboxClient,
1470
+ signal: ctx.signal,
1471
+ traceEmitter: opts.forwardTrace === false ? void 0 : campaignTraceToLoopEmitter(ctx.trace)
1472
+ }
1473
+ });
1474
+ reportLoopUsage(ctx.cost, result, opts.costSource ?? "loop");
1475
+ const toArtifact = opts.toArtifact ?? ((r) => r.winner?.output);
1476
+ return toArtifact(result);
1477
+ }
1478
+ function loopDispatch(opts) {
1479
+ return (profile, scenario, ctx) => runLoopForCell(opts, scenario, profile, ctx);
1480
+ }
1481
+
1482
+ // src/runtime/personify/analyst.ts
1483
+ var judgeEvidenceUri = /^(verdict|judge|score)\b/i;
1484
+ var assertTraceDerivedFindings2 = (findings) => {
1485
+ for (const f of findings) {
1486
+ for (const ref of f.evidence_refs ?? []) {
1487
+ if (ref.kind === "metric" && judgeEvidenceUri.test(ref.uri)) {
1488
+ throw new PlannerError(
1489
+ `steer-firewall: finding ${stringifySafe(f.finding_id)} cites judge-derived evidence (${stringifySafe(ref.uri)}); findings fed to a combinator's steer decision must be trace-derived, not judge-derived (selector \u2260 judge)`
1490
+ );
1491
+ }
1492
+ }
1493
+ }
1494
+ };
1495
+ function createScopeAnalyst(scope, options) {
1496
+ if (!options.analyst || typeof options.analyst.act !== "function") {
1497
+ throw new AnalystError("createScopeAnalyst: analyst must be an Agent with an act() method");
1498
+ }
1499
+ const label = options.label ?? "analyst";
1500
+ return {
1501
+ async analyze(input) {
1502
+ const task = options.buildTask(input);
1503
+ const spawned = scope.spawn(options.analyst, task, {
1504
+ budget: options.budget,
1505
+ label
1506
+ });
1507
+ if (!spawned.ok) {
1508
+ throw new AnalystError(
1509
+ `createScopeAnalyst: analyst spawn refused by the conserved pool (${spawned.reason}); cannot steer node ${stringifySafe(input.nodeId)} on an unrun analyst`
1510
+ );
1511
+ }
1512
+ const settled = await drainAnalystSettlement(scope, spawned.handle.id);
1513
+ const findings = readAnalystFindings(settled);
1514
+ assertTraceDerivedFindings2(findings);
1515
+ return findings;
1516
+ }
1517
+ };
1518
+ }
1519
+ async function drainAnalystSettlement(scope, analystId) {
1520
+ for (; ; ) {
1521
+ const settled = await scope.next();
1522
+ if (settled === null) {
1523
+ throw new AnalystError(
1524
+ `createScopeAnalyst: scope drained before analyst ${stringifySafe(analystId)} settled`
1525
+ );
1526
+ }
1527
+ if (settled.handle.id === analystId) return settled;
1528
+ }
1529
+ }
1530
+ function readAnalystFindings(settled) {
1531
+ if (settled.kind === "down") {
1532
+ throw new AnalystError(
1533
+ `createScopeAnalyst: analyst ${stringifySafe(settled.handle.id)} settled down (${settled.infra ? "infra" : "result"}): ${stringifySafe(settled.reason)}`
1534
+ );
1535
+ }
1536
+ const out = settled.out;
1537
+ if (!Array.isArray(out)) {
1538
+ throw new PlannerError(
1539
+ `createScopeAnalyst: analyst ${stringifySafe(settled.handle.id)} must return AnalystFinding[], got ${stringifySafe(out)}`
1540
+ );
1541
+ }
1542
+ return out;
1543
+ }
1544
+ function buildSteerContext(findings, settledSoFar) {
1545
+ assertTraceDerivedFindings2(findings);
1546
+ const lastValidScore = observedBestScore(settledSoFar);
1547
+ return {
1548
+ findings,
1549
+ settledSoFar,
1550
+ ...lastValidScore !== void 0 ? { lastValidScore } : {}
1551
+ };
1552
+ }
1553
+ function observedBestScore(settledSoFar) {
1554
+ let best;
1555
+ for (const s of settledSoFar) {
1556
+ if (s.kind !== "done") continue;
1557
+ const v = s.verdict;
1558
+ if (!v || v.valid !== true || typeof v.score !== "number") continue;
1559
+ if (best === void 0 || v.score > best) best = v.score;
1560
+ }
1561
+ return best;
1562
+ }
1563
+
1564
+ // src/runtime/supervise/scope.ts
1565
+ function createScope(args) {
1566
+ const children = /* @__PURE__ */ new Map();
1567
+ let spawnOrdinal = 0;
1568
+ let cursorSeq = 0;
1569
+ const now = args.now ?? Date.now;
1570
+ function spawn2(agent, task, opts) {
1571
+ if (args.maxDepth !== void 0 && args.depth >= args.maxDepth) {
1572
+ return { ok: false, reason: "depth-exceeded" };
1573
+ }
1574
+ const spec = agent.executorSpec;
1575
+ if (!isAgentSpec(spec)) {
1576
+ throw new ValidationError(
1577
+ `scope.spawn: agent "${agent.name}" exposes no \`executorSpec\` (AgentSpec) to resolve a LeafExecutor`
1578
+ );
1579
+ }
1580
+ const resolved = args.executors.resolve(spec);
1581
+ if (!resolved.succeeded) throw new ValidationError(`scope.spawn: ${resolved.error}`);
1582
+ const reservation = args.pool.reserve(opts.budget);
1583
+ if (!reservation.ok) return { ok: false, reason: reservation.reason };
1584
+ try {
1585
+ const ordinal = spawnOrdinal++;
1586
+ const id = `${args.parentId}:s${ordinal}`;
1587
+ const childAbort = new AbortController();
1588
+ const cascadeAbort = () => childAbort.abort();
1589
+ if (args.signal.aborted) childAbort.abort();
1590
+ else args.signal.addEventListener("abort", cascadeAbort, { once: true });
1591
+ const ctx = { signal: childAbort.signal, seams: args.seams };
1592
+ const executor = resolved.value(spec, ctx);
1593
+ const handle = {
1594
+ id,
1595
+ label: opts.label,
1596
+ get status() {
1597
+ return children.get(id)?.status ?? "cancelled";
1598
+ },
1599
+ abort(reason) {
1600
+ childAbort.abort(reason);
1601
+ }
1602
+ };
1603
+ const live = {
1604
+ id,
1605
+ status: "acquiring",
1606
+ runtime: executor.runtime,
1607
+ budget: opts.budget,
1608
+ label: opts.label,
1609
+ spent: zeroSpend2(),
1610
+ settled: void 0,
1611
+ delivered: false,
1612
+ ...executor.deliver ? { deliver: executor.deliver.bind(executor) } : {}
1613
+ };
1614
+ children.set(id, live);
1615
+ void args.journal.appendEvent(args.root, {
1616
+ kind: "spawned",
1617
+ id,
1618
+ parent: args.parentId,
1619
+ label: opts.label,
1620
+ budget: opts.budget,
1621
+ runtime: executor.runtime,
1622
+ seq: ordinal,
1623
+ at: new Date(now()).toISOString()
1624
+ });
1625
+ notifyRuntimeHookEvent(
1626
+ args.hooks,
1627
+ {
1628
+ id: `${id}:spawn`,
1629
+ runId: args.root,
1630
+ target: "agent.spawn",
1631
+ phase: "after",
1632
+ timestamp: now(),
1633
+ stepIndex: ordinal,
1634
+ parentId: args.parentId,
1635
+ payload: {
1636
+ childId: id,
1637
+ label: opts.label,
1638
+ runtime: executor.runtime,
1639
+ budget: opts.budget,
1640
+ depth: args.depth
1641
+ }
1642
+ },
1643
+ { signal: args.signal }
1644
+ );
1645
+ const settled = runChild(
1646
+ live,
1647
+ executor,
1648
+ childAbort,
1649
+ task,
1650
+ opts,
1651
+ args.pool,
1652
+ reservation.ticket,
1653
+ args.blobs
1654
+ ).then((s) => {
1655
+ live.resolved = s;
1656
+ return s;
1657
+ }).finally(() => {
1658
+ args.signal.removeEventListener("abort", cascadeAbort);
1659
+ });
1660
+ live.settled = settled;
1661
+ return { ok: true, handle };
1662
+ } catch (err) {
1663
+ args.pool.reconcile(reservation.ticket, zeroSpend2());
1664
+ throw err;
1665
+ }
1666
+ }
1667
+ async function next() {
1668
+ const undelivered = () => [...children.values()].filter((c) => !c.delivered);
1669
+ if (undelivered().length === 0) return null;
1670
+ for (; ; ) {
1671
+ const pending = undelivered();
1672
+ if (pending.length === 0) return null;
1673
+ const ready = pending.find((c) => c.resolved !== void 0);
1674
+ const chosen = ready ?? await raceFirstSettled(pending);
1675
+ if (chosen.delivered) continue;
1676
+ chosen.delivered = true;
1677
+ const seq = cursorSeq++;
1678
+ const settlement = chosen.resolved;
1679
+ if (!settlement) {
1680
+ throw new ValidationError(
1681
+ `scope.next: child '${chosen.id}' won the settle race without a resolved value`
1682
+ );
1683
+ }
1684
+ return finalizeSettlement(chosen, settlement, seq, args, now);
1685
+ }
1686
+ }
1687
+ function send(nodeId, msg) {
1688
+ const child = children.get(nodeId);
1689
+ if (!child || child.delivered || !child.deliver) return false;
1690
+ child.deliver(msg);
1691
+ return true;
1692
+ }
1693
+ return {
1694
+ spawn: spawn2,
1695
+ next,
1696
+ send,
1697
+ get view() {
1698
+ return makeTreeView(args.parentId, children);
1699
+ },
1700
+ get budget() {
1701
+ return args.pool.readout();
1702
+ }
1703
+ };
1704
+ }
1705
+ async function raceFirstSettled(pending) {
1706
+ return Promise.race(pending.map((c) => c.settled.then(() => c)));
1707
+ }
1708
+ async function finalizeSettlement(child, settlement, seq, args, now) {
1709
+ const handle = frozenHandle(child);
1710
+ if (settlement.kind === "down") {
1711
+ child.status = "failed";
1712
+ await args.journal.appendEvent(args.root, {
1713
+ kind: "settled",
1714
+ id: child.id,
1715
+ status: "down",
1716
+ spent: child.spent,
1717
+ infra: settlement.infra,
1718
+ seq,
1719
+ at: new Date(now()).toISOString()
1720
+ });
1721
+ notifyRuntimeHookEvent(
1722
+ args.hooks,
1723
+ {
1724
+ id: `${child.id}:settled`,
1725
+ runId: args.root,
1726
+ target: "agent.child",
1727
+ phase: "after",
1728
+ timestamp: now(),
1729
+ stepIndex: seq,
1730
+ parentId: args.parentId,
1731
+ payload: {
1732
+ childId: child.id,
1733
+ status: "down",
1734
+ reason: settlement.reason,
1735
+ infra: settlement.infra,
1736
+ spent: child.spent
1737
+ }
1738
+ },
1739
+ { signal: args.signal }
1740
+ );
1741
+ return {
1742
+ kind: "down",
1743
+ handle,
1744
+ reason: settlement.reason,
1745
+ infra: settlement.infra,
1746
+ restartCount: settlement.restartCount,
1747
+ seq
1748
+ };
1749
+ }
1750
+ child.status = "done";
1751
+ child.outRef = settlement.outRef;
1752
+ child.spent = settlement.spent;
1753
+ await args.journal.appendEvent(args.root, {
1754
+ kind: "settled",
1755
+ id: child.id,
1756
+ status: "done",
1757
+ outRef: settlement.outRef,
1758
+ ...settlement.verdict ? { verdict: settlement.verdict } : {},
1759
+ spent: settlement.spent,
1760
+ seq,
1761
+ at: new Date(now()).toISOString()
1762
+ });
1763
+ notifyRuntimeHookEvent(
1764
+ args.hooks,
1765
+ {
1766
+ id: `${child.id}:settled`,
1767
+ runId: args.root,
1768
+ target: "agent.child",
1769
+ phase: "after",
1770
+ timestamp: now(),
1771
+ stepIndex: seq,
1772
+ parentId: args.parentId,
1773
+ payload: {
1774
+ childId: child.id,
1775
+ status: "done",
1776
+ outRef: settlement.outRef,
1777
+ score: settlement.verdict?.score,
1778
+ valid: settlement.verdict?.valid,
1779
+ spent: settlement.spent
1780
+ }
1781
+ },
1782
+ { signal: args.signal }
1783
+ );
1784
+ return {
1785
+ kind: "done",
1786
+ handle,
1787
+ out: settlement.out,
1788
+ outRef: settlement.outRef,
1789
+ ...settlement.verdict ? { verdict: settlement.verdict } : {},
1790
+ spent: settlement.spent,
1791
+ seq
1792
+ };
1793
+ }
1794
+ async function runChild(live, executor, childAbort, task, opts, pool, ticket, blobs) {
1795
+ let reconciled = false;
1796
+ const reconcileOnce = (spend) => {
1797
+ if (reconciled) return;
1798
+ reconciled = true;
1799
+ pool.reconcile(ticket, clampSpend(spend, opts.budget));
1800
+ };
1801
+ try {
1802
+ live.status = "running";
1803
+ const ran = executor.execute(task, childAbort.signal);
1804
+ let artifact;
1805
+ if (isAsyncIterable(ran)) {
1806
+ const spend = await foldStream(ran);
1807
+ live.spent = spend;
1808
+ artifact = executor.resultArtifact();
1809
+ reconcileOnce(spend);
1810
+ } else {
1811
+ const terminal = await ran;
1812
+ live.spent = terminal.spent;
1813
+ artifact = terminal;
1814
+ reconcileOnce(terminal.spent);
1815
+ }
1816
+ if (childAbort.signal.aborted) {
1817
+ await teardownSafe(executor, opts.shutdown ?? "brutalKill");
1818
+ return downRecord("aborted before settle", true);
1819
+ }
1820
+ const outRef = contentAddress(artifact.out);
1821
+ await blobs.put(outRef, artifact.out);
1822
+ await teardownSafe(executor, opts.shutdown ?? "infinity");
1823
+ return {
1824
+ kind: "done",
1825
+ out: artifact.out,
1826
+ outRef,
1827
+ ...artifact.verdict ? { verdict: artifact.verdict } : {},
1828
+ spent: live.spent
1829
+ };
1830
+ } catch (err) {
1831
+ reconcileOnce(live.spent);
1832
+ await teardownSafe(executor, "brutalKill");
1833
+ const aborted = childAbort.signal.aborted || isAbortError2(err);
1834
+ return downRecord(errMessage(err), aborted || isInfraError(err));
1835
+ }
1836
+ }
1837
+ function settledToIteration(settled) {
1838
+ if (settled.kind === "down") {
1839
+ throw new ValidationError(
1840
+ `settledToIteration: cannot adapt a 'down' settlement (node '${settled.handle.id}', seq ${settled.seq}) to an Iteration`
1841
+ );
1842
+ }
1843
+ return {
1844
+ index: settled.seq,
1845
+ task: void 0,
1846
+ agentRunName: settled.handle.label,
1847
+ output: settled.out,
1848
+ ...settled.verdict ? { verdict: settled.verdict } : {},
1849
+ events: [],
1850
+ startedAt: 0,
1851
+ endedAt: settled.spent.ms,
1852
+ costUsd: settled.spent.usd,
1853
+ tokenUsage: { input: settled.spent.tokens.input, output: settled.spent.tokens.output }
1854
+ };
1855
+ }
1856
+ function makeTreeView(root, children) {
1857
+ const nodes = [...children.values()].map((c) => ({
1858
+ id: c.id,
1859
+ parent: root,
1860
+ label: c.label,
1861
+ status: c.status,
1862
+ runtime: c.runtime,
1863
+ budget: c.budget,
1864
+ spent: c.spent,
1865
+ ...c.outRef ? { outRef: c.outRef } : {}
1866
+ }));
1867
+ return {
1868
+ root,
1869
+ nodes,
1870
+ inFlight: nodes.filter((n) => n.status === "running" || n.status === "acquiring").length
1871
+ };
1872
+ }
1873
+ function frozenHandle(child) {
1874
+ return {
1875
+ id: child.id,
1876
+ label: child.label,
1877
+ status: child.status,
1878
+ abort() {
1879
+ }
1880
+ };
1881
+ }
1882
+ async function foldStream(stream) {
1883
+ const tokens = { input: 0, output: 0 };
1884
+ let usd = 0;
1885
+ let iterations = 0;
1886
+ for await (const ev of stream) {
1887
+ if (ev.kind === "tokens") {
1888
+ tokens.input += ev.input;
1889
+ tokens.output += ev.output;
1890
+ } else if (ev.kind === "cost") {
1891
+ usd += ev.usd;
1892
+ } else {
1893
+ iterations += 1;
1894
+ }
1895
+ }
1896
+ return { iterations, tokens, usd, ms: 0 };
1897
+ }
1898
+ function clampSpend(spend, budget) {
1899
+ const totalTokens2 = spend.tokens.input + spend.tokens.output;
1900
+ const tokensOk = totalTokens2 <= budget.maxTokens;
1901
+ const itersOk = spend.iterations <= budget.maxIterations;
1902
+ const usdOk = budget.maxUsd === void 0 || spend.usd <= budget.maxUsd;
1903
+ if (tokensOk && itersOk && usdOk) return spend;
1904
+ const ratio = !tokensOk && totalTokens2 > 0 ? budget.maxTokens / totalTokens2 : 1;
1905
+ return {
1906
+ iterations: Math.min(spend.iterations, budget.maxIterations),
1907
+ tokens: ratio < 1 ? {
1908
+ input: Math.floor(spend.tokens.input * ratio),
1909
+ output: Math.floor(spend.tokens.output * ratio)
1910
+ } : spend.tokens,
1911
+ usd: budget.maxUsd === void 0 ? spend.usd : Math.min(spend.usd, budget.maxUsd),
1912
+ ms: spend.ms
1913
+ };
1914
+ }
1915
+ async function teardownSafe(executor, grace) {
1916
+ try {
1917
+ await executor.teardown(grace);
1918
+ } catch {
1919
+ }
1920
+ }
1921
+ function downRecord(reason, infra) {
1922
+ return { kind: "down", reason, infra, restartCount: 0 };
1923
+ }
1924
+ function zeroSpend2() {
1925
+ return { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
1926
+ }
1927
+ function isAsyncIterable(value) {
1928
+ return typeof value === "object" && value !== null && typeof value[Symbol.asyncIterator] === "function";
1929
+ }
1930
+ function isAgentSpec(value) {
1931
+ if (typeof value !== "object" || value === null) return false;
1932
+ const v = value;
1933
+ return "profile" in v && "harness" in v;
1934
+ }
1935
+ function isAbortError2(err) {
1936
+ return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
1937
+ }
1938
+ function isInfraError(err) {
1939
+ return err instanceof ValidationError;
1940
+ }
1941
+ function errMessage(err) {
1942
+ if (err instanceof Error) return err.message;
1943
+ return String(err);
1944
+ }
1945
+
1946
+ // src/runtime/personify/combinators.ts
1947
+ function pipeline(stages) {
1948
+ if (stages.length === 0) {
1949
+ throw new ValidationError("pipeline: at least one stage is required");
1950
+ }
1951
+ return (ctx) => ({
1952
+ name: `${ctx.persona.name}/pipeline`,
1953
+ async act(task, scope) {
1954
+ let carry = task;
1955
+ for (const stage of stages) {
1956
+ const child = ctx.spawnChild(stage.label, ctx.persona.root);
1957
+ const res = scope.spawn(child, stage.feed(carry, ctx, task), {
1958
+ budget: ctx.budget.perChild,
1959
+ label: stage.label
1960
+ });
1961
+ if (!res.ok) {
1962
+ return blocked([`${stage.label}: not admitted (${res.reason})`]);
1963
+ }
1964
+ const settled = await drainOne(scope, stage.label);
1965
+ const out = stage.collect(settled);
1966
+ if (out.kind === "blocked") return out;
1967
+ carry = out.deliverable;
1968
+ }
1969
+ return { kind: "done", deliverable: carry };
1970
+ }
1971
+ });
1972
+ }
1973
+ function fanout(items, opts) {
1974
+ return (ctx) => ({
1975
+ name: `${ctx.persona.name}/fanout`,
1976
+ async act(_task, scope) {
1977
+ const rejected = [];
1978
+ let opened = 0;
1979
+ for (const [i, item] of items.entries()) {
1980
+ const label = opts.label ? opts.label(item, i) : `item:${i}`;
1981
+ const child = ctx.spawnChild(label, ctx.persona.root);
1982
+ const res = scope.spawn(child, opts.itemTask(item, i, ctx), {
1983
+ budget: ctx.budget.perChild,
1984
+ label
1985
+ });
1986
+ if (res.ok) opened += 1;
1987
+ else rejected.push(`${label}: not admitted (${res.reason})`);
1988
+ }
1989
+ if (opened === 0) {
1990
+ return blocked(
1991
+ rejected.length > 0 ? rejected : ["fanout: budget admitted no item (fanout fully rejected)"]
1992
+ );
1993
+ }
1994
+ const drained = await drain(scope);
1995
+ if (drained.done.length === 0) {
1996
+ return blocked(
1997
+ orderedBlockers(
1998
+ drained.blockers,
1999
+ rejected,
2000
+ "fanout: every item settled without a usable result"
2001
+ )
2002
+ );
2003
+ }
2004
+ if (!opts.synthesize) {
2005
+ const winner = defaultSelectWinner(drained.iterations);
2006
+ if (!winner || winner.output === void 0) {
2007
+ return blocked(
2008
+ orderedBlockers(drained.blockers, rejected, "fanout: no item survived selection")
2009
+ );
2010
+ }
2011
+ return { kind: "done", deliverable: winner.output };
2012
+ }
2013
+ const synthLabel = "synthesize";
2014
+ const synthChild = ctx.spawnChild(synthLabel, ctx.persona.root);
2015
+ const synthRes = scope.spawn(synthChild, opts.synthesize.synthesisTask(drained.done, ctx), {
2016
+ budget: ctx.budget.perChild,
2017
+ label: synthLabel
2018
+ });
2019
+ if (!synthRes.ok) {
2020
+ return blocked([...drained.blockers, `${synthLabel}: not admitted (${synthRes.reason})`]);
2021
+ }
2022
+ const synthSettled = await drainOne(scope, synthLabel);
2023
+ const out = opts.synthesize.collect(synthSettled);
2024
+ if (out.kind === "blocked") return out;
2025
+ return out;
2026
+ }
2027
+ });
2028
+ }
2029
+ function loopUntil(seed, spec) {
2030
+ return (ctx) => ({
2031
+ name: `${ctx.persona.name}/loopUntil`,
2032
+ async act(task, scope) {
2033
+ let state = { round: 0, value: seed };
2034
+ const blockers = [];
2035
+ for (; ; ) {
2036
+ const label = spec.label ? spec.label(state.round) : `step:${state.round}`;
2037
+ const child = ctx.spawnChild(label, ctx.persona.root);
2038
+ const res = scope.spawn(child, spec.step(task, state, ctx), {
2039
+ budget: ctx.budget.perChild,
2040
+ label
2041
+ });
2042
+ if (!res.ok) {
2043
+ if (state.round === 0) return blocked([`${label}: not admitted (${res.reason})`]);
2044
+ break;
2045
+ }
2046
+ const settled = await drainOne(scope, label);
2047
+ if (settled.kind === "down") blockers.push(blockerFromDown(settled));
2048
+ state = spec.fold(state, settled);
2049
+ const reached = spec.until(state, []);
2050
+ if (reached) return reached;
2051
+ state = { round: state.round + 1, value: state.value };
2052
+ }
2053
+ return blocked(
2054
+ blockers.length > 0 ? blockers : ["loopUntil: budget exhausted before the satisfiability gate was reached"]
2055
+ );
2056
+ }
2057
+ });
2058
+ }
2059
+ function panel(spec) {
2060
+ if (spec.judges.length === 0) {
2061
+ throw new ValidationError("panel: at least one judge is required");
2062
+ }
2063
+ return (ctx) => ({
2064
+ name: `${ctx.persona.name}/panel`,
2065
+ async act(task, scope) {
2066
+ const artifact = task;
2067
+ const byLabel = /* @__PURE__ */ new Map();
2068
+ const rejected = [];
2069
+ let opened = 0;
2070
+ for (const judge of spec.judges) {
2071
+ const child = ctx.spawnChild(judge.label, ctx.persona.root);
2072
+ const res = scope.spawn(child, spec.judgeTask(artifact, judge, ctx), {
2073
+ budget: ctx.budget.perChild,
2074
+ label: judge.label
2075
+ });
2076
+ if (res.ok) {
2077
+ byLabel.set(judge.label, judge);
2078
+ opened += 1;
2079
+ } else {
2080
+ rejected.push(`${judge.label}: not admitted (${res.reason})`);
2081
+ }
2082
+ }
2083
+ if (opened === 0) {
2084
+ return blocked(rejected.length > 0 ? rejected : ["panel: budget admitted no judge"]);
2085
+ }
2086
+ const verdicts = [];
2087
+ for (let s = await scope.next(); s !== null; s = await scope.next()) {
2088
+ const judge = byLabel.get(s.handle.label);
2089
+ if (!judge) {
2090
+ throw new ValidationError(
2091
+ `panel: settled child "${s.handle.label}" has no judge descriptor`
2092
+ );
2093
+ }
2094
+ if (s.kind === "down") {
2095
+ verdicts.push({ judge, down: true });
2096
+ continue;
2097
+ }
2098
+ verdicts.push({
2099
+ judge,
2100
+ down: false,
2101
+ ...s.verdict ? { verdict: s.verdict } : {},
2102
+ output: s.out
2103
+ });
2104
+ }
2105
+ return spec.merge(verdicts, artifact);
2106
+ }
2107
+ });
2108
+ }
2109
+ function verify(spec) {
2110
+ return (ctx) => ({
2111
+ name: `${ctx.persona.name}/verify`,
2112
+ async act(task, scope) {
2113
+ const implementLabel = spec.implementLabel ?? "implement";
2114
+ const implChild = ctx.spawnChild(implementLabel, ctx.persona.root);
2115
+ const implRes = scope.spawn(implChild, spec.implement(task, ctx), {
2116
+ budget: ctx.budget.perChild,
2117
+ label: implementLabel
2118
+ });
2119
+ if (!implRes.ok) return blocked([`${implementLabel}: not admitted (${implRes.reason})`]);
2120
+ const candidate = await drainOne(scope, implementLabel);
2121
+ if (candidate.kind === "down") return blocked([blockerFromDown(candidate)]);
2122
+ const verifierLabel = spec.verifierLabel ?? "verify";
2123
+ const verifierChild = ctx.spawnChild(verifierLabel, ctx.persona.root);
2124
+ const verifierRes = scope.spawn(
2125
+ verifierChild,
2126
+ spec.verifier(candidate, ctx),
2127
+ { budget: ctx.budget.perChild, label: verifierLabel }
2128
+ );
2129
+ if (!verifierRes.ok)
2130
+ return blocked([`${verifierLabel}: not admitted (${verifierRes.reason})`]);
2131
+ const gate = await drainOne(scope, verifierLabel);
2132
+ if (gate.kind === "down") return blocked([blockerFromDown(gate)]);
2133
+ if (!gate.verdict || gate.verdict.valid !== true) {
2134
+ return blocked([`${verifierLabel}: gate rejected the candidate (${verdictDetail(gate)})`]);
2135
+ }
2136
+ return spec.collect(candidate, gate.verdict);
2137
+ }
2138
+ });
2139
+ }
2140
+ function widen(spec) {
2141
+ return (ctx) => ({
2142
+ name: `${ctx.persona.name}/widen`,
2143
+ async act(_task, scope) {
2144
+ let opened = 0;
2145
+ for (const [i, seed] of spec.seeds.entries()) {
2146
+ const label = `seed:${i}`;
2147
+ const child = ctx.spawnChild(label, ctx.persona.root);
2148
+ const res = scope.spawn(child, spec.seedTask(seed, i, ctx), {
2149
+ budget: ctx.budget.perChild,
2150
+ label
2151
+ });
2152
+ if (res.ok) opened += 1;
2153
+ }
2154
+ if (opened === 0) {
2155
+ return blocked(["widen: budget admitted no seed lineage"]);
2156
+ }
2157
+ const gathered = [];
2158
+ let widenIndex = 0;
2159
+ for (let s = await scope.next(); s !== null; s = await scope.next()) {
2160
+ gathered.push(s);
2161
+ const decision = spec.gate.decide(s, [], scope.budget);
2162
+ if (decision.kind !== "widen") continue;
2163
+ const label = `widen:${widenIndex}`;
2164
+ widenIndex += 1;
2165
+ const child = ctx.spawnChild(label, ctx.persona.root);
2166
+ scope.spawn(child, spec.widenTask(decision.toward, ctx), {
2167
+ budget: ctx.budget.perChild,
2168
+ label
2169
+ });
2170
+ }
2171
+ const done = gathered.filter(isDone);
2172
+ if (done.length === 0) {
2173
+ return blocked(
2174
+ orderedBlockers(
2175
+ gathered.filter(isDown).map(blockerFromDown),
2176
+ [],
2177
+ "widen: every lineage settled without a usable result"
2178
+ )
2179
+ );
2180
+ }
2181
+ return spec.synthesize(done, ctx);
2182
+ }
2183
+ });
2184
+ }
2185
+ function flatWidenGate() {
2186
+ return {
2187
+ decide() {
2188
+ return { kind: "stop" };
2189
+ }
2190
+ };
2191
+ }
2192
+ async function drain(scope) {
2193
+ const iterations = [];
2194
+ const done = [];
2195
+ const blockers = [];
2196
+ for (let s = await scope.next(); s !== null; s = await scope.next()) {
2197
+ if (s.kind === "down") {
2198
+ blockers.push(blockerFromDown(s));
2199
+ continue;
2200
+ }
2201
+ iterations.push(settledToIteration(s));
2202
+ done.push(s);
2203
+ }
2204
+ return { iterations, done, blockers };
2205
+ }
2206
+ async function drainOne(scope, label) {
2207
+ const s = await scope.next();
2208
+ if (s === null) {
2209
+ throw new ValidationError(`combinator: child "${label}" was spawned but never settled`);
2210
+ }
2211
+ return s;
2212
+ }
2213
+ function isDone(s) {
2214
+ return s.kind === "done";
2215
+ }
2216
+ function isDown(s) {
2217
+ return s.kind === "down";
2218
+ }
2219
+ function blockerFromDown(s) {
2220
+ return `${s.handle.label}: ${s.infra ? "infra" : "failed"} \u2014 ${s.reason}`;
2221
+ }
2222
+ function verdictDetail(s) {
2223
+ if (!s.verdict) return "no verdict";
2224
+ return s.verdict.notes ?? `score ${s.verdict.score}`;
2225
+ }
2226
+ function orderedBlockers(drained, rejected, fallback) {
2227
+ const all = [...drained, ...rejected];
2228
+ return all.length > 0 ? all : [fallback];
2229
+ }
2230
+ function blocked(blockers) {
2231
+ if (blockers.length === 0) {
2232
+ throw new ValidationError("combinator: a blocked outcome must name at least one blocker");
2233
+ }
2234
+ return { kind: "blocked", blockers };
2235
+ }
2236
+
2237
+ // src/runtime/personify/corpus.ts
2238
+ var corpusSchemaVersion = "1.0.0";
2239
+ function assertCorpusRecord(value, where) {
2240
+ if (typeof value !== "object" || value === null) {
2241
+ throw new Error(`${where}: corpus record is not an object`);
2242
+ }
2243
+ const r = value;
2244
+ if (r.schemaVersion !== corpusSchemaVersion) {
2245
+ throw new Error(
2246
+ `${where}: corpus record schemaVersion '${String(r.schemaVersion)}' != '${corpusSchemaVersion}'`
2247
+ );
2248
+ }
2249
+ requireNonEmptyString(r.id, `${where}.id`);
2250
+ requireNonEmptyString(r.runId, `${where}.runId`);
2251
+ requireNonEmptyString(r.producedAt, `${where}.producedAt`);
2252
+ requireNonEmptyString(r.area, `${where}.area`);
2253
+ requireNonEmptyString(r.claim, `${where}.claim`);
2254
+ if (r.rationale !== void 0 && typeof r.rationale !== "string") {
2255
+ throw new Error(`${where}.rationale: expected string, got ${typeof r.rationale}`);
2256
+ }
2257
+ if (!Array.isArray(r.tags) || r.tags.some((t) => typeof t !== "string")) {
2258
+ throw new Error(`${where}.tags: expected string[]`);
2259
+ }
2260
+ if (typeof r.confidence !== "number" || !Number.isFinite(r.confidence)) {
2261
+ throw new Error(`${where}.confidence: expected a finite number, got ${String(r.confidence)}`);
2262
+ }
2263
+ if (r.confidence < 0 || r.confidence > 1) {
2264
+ throw new Error(`${where}.confidence: ${r.confidence} is outside the [0, 1] range`);
2265
+ }
2266
+ if (r.evidence !== void 0) {
2267
+ if (!Array.isArray(r.evidence)) {
2268
+ throw new Error(`${where}.evidence: expected an array`);
2269
+ }
2270
+ for (let i = 0; i < r.evidence.length; i++) {
2271
+ const ev = r.evidence[i];
2272
+ if (typeof ev !== "object" || ev === null) {
2273
+ throw new Error(`${where}.evidence[${i}]: not an object`);
2274
+ }
2275
+ requireNonEmptyString(ev.kind, `${where}.evidence[${i}].kind`);
2276
+ requireNonEmptyString(ev.uri, `${where}.evidence[${i}].uri`);
2277
+ }
2278
+ }
2279
+ }
2280
+ function requireNonEmptyString(value, where) {
2281
+ if (typeof value !== "string" || value.length === 0) {
2282
+ throw new Error(`${where}: expected a non-empty string, got ${describe(value)}`);
2283
+ }
2284
+ }
2285
+ function describe(value) {
2286
+ if (value === void 0) return "undefined";
2287
+ if (value === null) return "null";
2288
+ if (typeof value === "string") return `'${value}'`;
2289
+ return typeof value;
2290
+ }
2291
+ function recordsEqual(a, b) {
2292
+ return stableStringify2(a) === stableStringify2(b);
2293
+ }
2294
+ function stableStringify2(value) {
2295
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
2296
+ if (Array.isArray(value)) return `[${value.map(stableStringify2).join(",")}]`;
2297
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([x], [y]) => x < y ? -1 : x > y ? 1 : 0);
2298
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify2(v)}`).join(",")}}`;
2299
+ }
2300
+ function applyFilter(records, filter) {
2301
+ const matched = records.filter((r) => {
2302
+ if (filter.area !== void 0 && r.area !== filter.area) return false;
2303
+ if (filter.runId !== void 0 && r.runId !== filter.runId) return false;
2304
+ if (filter.minConfidence !== void 0 && r.confidence < filter.minConfidence) return false;
2305
+ if (filter.tags !== void 0 && !filter.tags.every((t) => r.tags.includes(t))) return false;
2306
+ return true;
2307
+ });
2308
+ const ordered = [...matched].sort(
2309
+ (a, b) => b.confidence !== a.confidence ? b.confidence - a.confidence : b.producedAt < a.producedAt ? -1 : b.producedAt > a.producedAt ? 1 : 0
2310
+ );
2311
+ if (filter.limit !== void 0) {
2312
+ if (!Number.isInteger(filter.limit) || filter.limit < 0) {
2313
+ throw new Error(
2314
+ `corpus query: filter.limit must be a non-negative integer, got ${filter.limit}`
2315
+ );
2316
+ }
2317
+ return ordered.slice(0, filter.limit);
2318
+ }
2319
+ return ordered;
2320
+ }
2321
+ var InMemoryCorpus = class {
2322
+ byId = /* @__PURE__ */ new Map();
2323
+ async append(record) {
2324
+ try {
2325
+ assertCorpusRecord(record, "append: record");
2326
+ } catch (err) {
2327
+ return { succeeded: false, error: err instanceof Error ? err.message : String(err) };
2328
+ }
2329
+ const existing = this.byId.get(record.id);
2330
+ if (existing) {
2331
+ if (recordsEqual(existing, record)) return { succeeded: true };
2332
+ return {
2333
+ succeeded: false,
2334
+ error: `corpus conflict: id '${record.id}' is already stored with a different record; a learned fact is append-only \u2014 re-mint the id or reconcile before re-appending`
2335
+ };
2336
+ }
2337
+ this.byId.set(record.id, freeze(record));
2338
+ return { succeeded: true };
2339
+ }
2340
+ async query(filter) {
2341
+ return applyFilter([...this.byId.values()], filter);
2342
+ }
2343
+ };
2344
+ var FileCorpus = class {
2345
+ constructor(path) {
2346
+ this.path = path;
2347
+ }
2348
+ path;
2349
+ async append(record) {
2350
+ try {
2351
+ assertCorpusRecord(record, "append: record");
2352
+ } catch (err) {
2353
+ return { succeeded: false, error: err instanceof Error ? err.message : String(err) };
2354
+ }
2355
+ let stored;
2356
+ try {
2357
+ stored = await this.load();
2358
+ } catch (err) {
2359
+ return { succeeded: false, error: err instanceof Error ? err.message : String(err) };
2360
+ }
2361
+ const existing = stored.get(record.id);
2362
+ if (existing) {
2363
+ if (recordsEqual(existing, record)) return { succeeded: true };
2364
+ return {
2365
+ succeeded: false,
2366
+ error: `corpus conflict: id '${record.id}' is already stored in ${this.path} with a different record; a learned fact is append-only \u2014 re-mint the id or reconcile before re-appending`
2367
+ };
2368
+ }
2369
+ await this.appendLine(record);
2370
+ return { succeeded: true };
2371
+ }
2372
+ async query(filter) {
2373
+ const stored = await this.load();
2374
+ return applyFilter([...stored.values()], filter);
2375
+ }
2376
+ async load() {
2377
+ const fs = await import("fs/promises");
2378
+ let text;
2379
+ try {
2380
+ text = await fs.readFile(this.path, "utf8");
2381
+ } catch (err) {
2382
+ if (isNoEntError2(err)) return /* @__PURE__ */ new Map();
2383
+ throw err;
2384
+ }
2385
+ const lines = text.split("\n").filter((line) => line.length > 0);
2386
+ const byId = /* @__PURE__ */ new Map();
2387
+ for (let i = 0; i < lines.length; i++) {
2388
+ const line = lines[i];
2389
+ let parsed;
2390
+ try {
2391
+ parsed = JSON.parse(line);
2392
+ } catch (err) {
2393
+ throw new Error(
2394
+ `corpus corrupted: ${this.path} line ${i + 1} is not valid JSON: ` + (err instanceof Error ? err.message : String(err))
2395
+ );
2396
+ }
2397
+ assertCorpusRecord(parsed, `corpus ${this.path} line ${i + 1}`);
2398
+ const existing = byId.get(parsed.id);
2399
+ if (existing && !recordsEqual(existing, parsed)) {
2400
+ throw new Error(
2401
+ `corpus corrupted: ${this.path} has two different records for id '${parsed.id}'; an append-only corpus must never hold a conflicting re-append under one id`
2402
+ );
2403
+ }
2404
+ byId.set(parsed.id, freeze(parsed));
2405
+ }
2406
+ return byId;
2407
+ }
2408
+ async appendLine(record) {
2409
+ const fs = await import("fs/promises");
2410
+ const path = await import("path");
2411
+ await fs.mkdir(path.dirname(this.path), { recursive: true });
2412
+ const fh = await fs.open(this.path, "a");
2413
+ try {
2414
+ await fh.write(`${JSON.stringify(record)}
2415
+ `);
2416
+ await fh.sync();
2417
+ } finally {
2418
+ await fh.close();
2419
+ }
2420
+ }
2421
+ };
2422
+ async function renderCorpusToInstructions(opts) {
2423
+ const { corpus, filter, profile, target = "prompt", maxLines } = opts;
2424
+ if (maxLines !== void 0 && (!Number.isInteger(maxLines) || maxLines < 0)) {
2425
+ throw new Error(
2426
+ `renderCorpusToInstructions: maxLines must be a non-negative integer, got ${maxLines}`
2427
+ );
2428
+ }
2429
+ const matched = await corpus.query(filter);
2430
+ const capped = maxLines !== void 0 ? matched.slice(0, maxLines) : matched;
2431
+ const lines = capped.map(renderLine);
2432
+ if (lines.length === 0) return structuredClone(profile);
2433
+ const next = structuredClone(profile);
2434
+ if (target === "resources") {
2435
+ const resources = next.resources ?? {};
2436
+ const prior = resources.instructions;
2437
+ if (prior !== void 0 && typeof prior !== "string") {
2438
+ throw new Error(
2439
+ "renderCorpusToInstructions: resources.instructions is an AgentProfileResourceRef, not a string; cannot string-append corpus facts without dropping the ref (pass target: 'prompt' or pre-resolve the ref)"
2440
+ );
2441
+ }
2442
+ const blob = [prior, ...lines].filter((s) => typeof s === "string" && s.length > 0);
2443
+ next.resources = { ...resources, instructions: blob.join("\n") };
2444
+ return next;
2445
+ }
2446
+ const prompt = next.prompt ?? {};
2447
+ next.prompt = { ...prompt, instructions: [...prompt.instructions ?? [], ...lines] };
2448
+ return next;
2449
+ }
2450
+ function renderLine(record) {
2451
+ return record.rationale ? `${record.claim} (${record.rationale})` : record.claim;
2452
+ }
2453
+ function freeze(record) {
2454
+ return Object.freeze({ ...record });
2455
+ }
2456
+ function isNoEntError2(err) {
2457
+ return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
2458
+ }
2459
+
2460
+ // src/runtime/supervise/runtime.ts
2461
+ import { spawn } from "child_process";
2462
+ import { estimateCost, isModelPriced } from "@tangle-network/agent-eval";
2463
+ var routerSeamKey = "router";
2464
+ var sandboxSeamKey = "sandbox";
2465
+ var cliSeamKey = "cli";
2466
+ function contentRef(prefix, value) {
2467
+ let str;
2468
+ try {
2469
+ str = JSON.stringify(value) ?? String(value);
2470
+ } catch {
2471
+ str = String(value);
2472
+ }
2473
+ let h = 2166136261;
2474
+ for (let i = 0; i < str.length; i += 1) {
2475
+ h ^= str.charCodeAt(i);
2476
+ h = Math.imul(h, 16777619);
2477
+ }
2478
+ return `${prefix}:${(h >>> 0).toString(16).padStart(8, "0")}`;
2479
+ }
2480
+ function zeroSpend3() {
2481
+ return { iterations: 0, tokens: zeroTokenUsage(), usd: 0, ms: 0 };
2482
+ }
2483
+ var routerInlineExecutor = (spec, ctx) => {
2484
+ const seam = readSeam(ctx, routerSeamKey, "router/inline");
2485
+ const model = seam.model ?? spec.profile.model?.default;
2486
+ if (!model) {
2487
+ throw new ValidationError(
2488
+ "routerInlineExecutor: no model \u2014 set RouterSeam.model or AgentProfile.model.default"
2489
+ );
2490
+ }
2491
+ if (!seam.routerBaseUrl || !seam.routerKey) {
2492
+ throw new ValidationError("routerInlineExecutor: RouterSeam.routerBaseUrl + routerKey required");
2493
+ }
2494
+ const controller = new AbortController();
2495
+ const abortIfSignalled = () => {
2496
+ if (ctx.signal.aborted) controller.abort();
2497
+ };
2498
+ abortIfSignalled();
2499
+ if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
2500
+ let artifact;
2501
+ return {
2502
+ runtime: "router",
2503
+ async execute(task, signal) {
2504
+ const messages = taskToMessages(task, spec);
2505
+ const started = Date.now();
2506
+ const linked = linkSignals(signal, controller.signal);
2507
+ const res = await fetch(`${seam.routerBaseUrl.replace(/\/$/, "")}/chat/completions`, {
2508
+ method: "POST",
2509
+ headers: { "content-type": "application/json", authorization: `Bearer ${seam.routerKey}` },
2510
+ body: JSON.stringify({ model, messages, temperature: 0.2 }),
2511
+ ...linked ? { signal: linked } : {}
2512
+ });
2513
+ if (!res.ok) {
2514
+ throw new ValidationError(
2515
+ `routerInlineExecutor: router ${res.status}: ${(await res.text()).slice(0, 200)}`
2516
+ );
2517
+ }
2518
+ const data = await res.json();
2519
+ const u = data.usage;
2520
+ const usage = u && typeof u.prompt_tokens === "number" && typeof u.completion_tokens === "number" ? { input: u.prompt_tokens, output: u.completion_tokens } : void 0;
2521
+ const usd = usage && isModelPriced(model) ? estimateCost(usage.input, usage.output, model) : 0;
2522
+ const content = data.choices?.[0]?.message?.content ?? "";
2523
+ const spent = {
2524
+ iterations: 1,
2525
+ tokens: usage ? { input: usage.input, output: usage.output } : zeroTokenUsage(),
2526
+ usd,
2527
+ ms: Date.now() - started
2528
+ };
2529
+ const out = { content };
2530
+ artifact = { outRef: contentRef("router", { model, content }), out, spent };
2531
+ return artifact;
2532
+ },
2533
+ teardown(_grace) {
2534
+ controller.abort();
2535
+ return Promise.resolve({ destroyed: true });
2536
+ },
2537
+ resultArtifact() {
2538
+ if (!artifact) {
2539
+ throw new ValidationError("routerInlineExecutor: resultArtifact() read before execute()");
2540
+ }
2541
+ return { ...artifact, spent: artifact.spent };
2542
+ }
2543
+ };
2544
+ };
2545
+ var sandboxExecutor = (spec, ctx) => {
2546
+ if (spec.harness === null) {
2547
+ throw new ValidationError("sandboxExecutor: harness is null (router/inline) \u2014 wrong executor");
2548
+ }
2549
+ const harness = spec.harness;
2550
+ const seam = readSeam(ctx, sandboxSeamKey, "sandbox");
2551
+ if (!seam.sandboxClient || typeof seam.sandboxClient.create !== "function") {
2552
+ throw new ValidationError("sandboxExecutor: SandboxSeam.sandboxClient.create required");
2553
+ }
2554
+ const maxIterations = seam.maxIterations ?? 1;
2555
+ if (!Number.isFinite(maxIterations) || maxIterations <= 0) {
2556
+ throw new ValidationError("sandboxExecutor: maxIterations must be > 0");
2557
+ }
2558
+ const controller = new AbortController();
2559
+ const abortIfSignalled = () => {
2560
+ if (ctx.signal.aborted) controller.abort();
2561
+ };
2562
+ abortIfSignalled();
2563
+ if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
2564
+ let artifact;
2565
+ const output = {
2566
+ parse(events) {
2567
+ return { events };
2568
+ }
2569
+ };
2570
+ const driver = singleShotDriver(maxIterations);
2571
+ return {
2572
+ runtime: "sandbox",
2573
+ execute(task, signal) {
2574
+ return streamSandboxLeaf({
2575
+ task,
2576
+ signal,
2577
+ harness,
2578
+ spec,
2579
+ seam,
2580
+ output,
2581
+ driver,
2582
+ maxIterations,
2583
+ controller,
2584
+ loopCtx: seam.loopCtx,
2585
+ onArtifact: (a) => {
2586
+ artifact = a;
2587
+ }
2588
+ });
2589
+ },
2590
+ teardown(_grace) {
2591
+ controller.abort();
2592
+ return Promise.resolve({ destroyed: true });
2593
+ },
2594
+ resultArtifact() {
2595
+ if (!artifact) {
2596
+ throw new ValidationError("sandboxExecutor: resultArtifact() read before stream drained");
2597
+ }
2598
+ return artifact;
2599
+ }
2600
+ };
2601
+ };
2602
+ async function* streamSandboxLeaf(args) {
2603
+ const linked = new AbortController();
2604
+ const cascade = () => linked.abort();
2605
+ if (args.signal.aborted || args.controller.signal.aborted) linked.abort();
2606
+ else {
2607
+ args.signal.addEventListener("abort", cascade, { once: true });
2608
+ args.controller.signal.addEventListener("abort", cascade, { once: true });
2609
+ }
2610
+ const agentRun = {
2611
+ profile: args.spec.profile,
2612
+ taskToPrompt: (t) => taskToPrompt(t),
2613
+ name: args.spec.profile.name ?? args.harness,
2614
+ sandboxOverrides: { backend: { type: args.harness } }
2615
+ };
2616
+ const started = Date.now();
2617
+ const loopOptions = {
2618
+ driver: args.driver,
2619
+ agentRun,
2620
+ output: args.output,
2621
+ task: args.task,
2622
+ maxIterations: args.maxIterations,
2623
+ maxConcurrency: 1,
2624
+ ctx: {
2625
+ ...args.loopCtx ?? {},
2626
+ sandboxClient: args.seam.sandboxClient,
2627
+ signal: linked.signal
2628
+ },
2629
+ ...args.seam.lineage !== void 0 ? { lineage: args.seam.lineage } : {}
2630
+ };
2631
+ try {
2632
+ const result = await runLoop(loopOptions);
2633
+ const out = result.winner?.output ?? { events: [] };
2634
+ const verdict = result.winner?.verdict;
2635
+ const spent = {
2636
+ iterations: result.iterations.length,
2637
+ tokens: { input: result.tokenUsage.input, output: result.tokenUsage.output },
2638
+ usd: result.costUsd,
2639
+ ms: Date.now() - started
2640
+ };
2641
+ args.onArtifact({
2642
+ outRef: contentRef("sandbox", { harness: args.harness, out }),
2643
+ out,
2644
+ ...verdict ? { verdict } : {},
2645
+ spent
2646
+ });
2647
+ for (let i = 0; i < result.iterations.length; i += 1) yield { kind: "iteration" };
2648
+ if (result.tokenUsage.input || result.tokenUsage.output) {
2649
+ yield { kind: "tokens", input: result.tokenUsage.input, output: result.tokenUsage.output };
2650
+ }
2651
+ if (result.costUsd) yield { kind: "cost", usd: result.costUsd };
2652
+ } finally {
2653
+ args.signal.removeEventListener("abort", cascade);
2654
+ args.controller.signal.removeEventListener("abort", cascade);
2655
+ }
2656
+ }
2657
+ var cliExecutor = (_spec, ctx) => {
2658
+ const seam = readSeam(ctx, cliSeamKey, "cli");
2659
+ if (!seam.bin) throw new ValidationError("cliExecutor: CliSeam.bin required");
2660
+ const controller = new AbortController();
2661
+ const abortIfSignalled = () => {
2662
+ if (ctx.signal.aborted) controller.abort();
2663
+ };
2664
+ abortIfSignalled();
2665
+ if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
2666
+ let proc;
2667
+ let artifact;
2668
+ return {
2669
+ runtime: "cli",
2670
+ budgetExempt: true,
2671
+ execute(task, signal) {
2672
+ return streamCliLeaf({
2673
+ task,
2674
+ signal,
2675
+ seam,
2676
+ controller,
2677
+ onProc: (p) => {
2678
+ proc = p;
2679
+ },
2680
+ onArtifact: (a) => {
2681
+ artifact = a;
2682
+ }
2683
+ });
2684
+ },
2685
+ async teardown(grace) {
2686
+ controller.abort();
2687
+ if (!proc || proc.exitCode !== null || proc.killed) return { destroyed: true };
2688
+ return killWithGrace(proc, grace);
2689
+ },
2690
+ resultArtifact() {
2691
+ if (!artifact) {
2692
+ throw new ValidationError("cliExecutor: resultArtifact() read before stream drained");
2693
+ }
2694
+ return artifact;
2695
+ }
2696
+ };
2697
+ };
2698
+ async function* streamCliLeaf(args) {
2699
+ const prompt = taskToPrompt(args.task);
2700
+ const proc = spawn(args.seam.bin, args.seam.args ?? [], {
2701
+ ...args.seam.cwd ? { cwd: args.seam.cwd } : {},
2702
+ env: { ...process.env, ...args.seam.env ?? {} },
2703
+ stdio: ["pipe", "pipe", "pipe"]
2704
+ });
2705
+ args.onProc(proc);
2706
+ const onAbort = () => killWithGrace(proc, "brutalKill");
2707
+ if (args.signal.aborted || args.controller.signal.aborted) onAbort();
2708
+ else {
2709
+ args.signal.addEventListener("abort", onAbort, { once: true });
2710
+ args.controller.signal.addEventListener("abort", onAbort, { once: true });
2711
+ }
2712
+ if (proc.stdin) {
2713
+ proc.stdin.write(prompt);
2714
+ proc.stdin.end();
2715
+ }
2716
+ const chunks = [];
2717
+ const errChunks = [];
2718
+ if (proc.stdout) proc.stdout.on("data", (d) => chunks.push(d.toString("utf8")));
2719
+ if (proc.stderr) proc.stderr.on("data", (d) => errChunks.push(d.toString("utf8")));
2720
+ const exit = await new Promise((resolve) => {
2721
+ proc.once("error", (err) => resolve({ code: null, error: err }));
2722
+ proc.once("close", (code) => resolve({ code }));
2723
+ });
2724
+ args.signal.removeEventListener("abort", onAbort);
2725
+ args.controller.signal.removeEventListener("abort", onAbort);
2726
+ if (exit.error) {
2727
+ throw new ValidationError(`cliExecutor: spawn failed: ${exit.error.message}`, {
2728
+ cause: exit.error
2729
+ });
2730
+ }
2731
+ if (exit.code !== 0) {
2732
+ throw new ValidationError(
2733
+ `cliExecutor: ${args.seam.bin} exited ${exit.code}: ${errChunks.join("").slice(0, 200)}`
2734
+ );
2735
+ }
2736
+ const out = { content: chunks.join("") };
2737
+ args.onArtifact({ outRef: contentRef("cli", out), out, spent: zeroSpend3() });
2738
+ yield { kind: "iteration" };
2739
+ }
2740
+ function killWithGrace(proc, grace) {
2741
+ if (proc.exitCode !== null || proc.killed) return Promise.resolve({ destroyed: true });
2742
+ return new Promise((resolve) => {
2743
+ let timer;
2744
+ proc.once("close", () => {
2745
+ if (timer) clearTimeout(timer);
2746
+ resolve({ destroyed: true });
2747
+ });
2748
+ if (grace === "brutalKill") {
2749
+ proc.kill("SIGKILL");
2750
+ return;
2751
+ }
2752
+ proc.kill("SIGTERM");
2753
+ if (grace === "infinity") return;
2754
+ timer = setTimeout(() => {
2755
+ if (proc.exitCode === null && !proc.killed) proc.kill("SIGKILL");
2756
+ }, grace);
2757
+ });
2758
+ }
2759
+ function createExecutorRegistry() {
2760
+ const factories = /* @__PURE__ */ new Map();
2761
+ factories.set("router", routerInlineExecutor);
2762
+ factories.set("inline", routerInlineExecutor);
2763
+ factories.set("sandbox", sandboxExecutor);
2764
+ factories.set("cli", cliExecutor);
2765
+ return {
2766
+ register(runtime, factory) {
2767
+ if (factories.has(runtime)) {
2768
+ throw new ValidationError(`executor registry: runtime "${runtime}" already registered`);
2769
+ }
2770
+ factories.set(runtime, factory);
2771
+ },
2772
+ resolve(spec) {
2773
+ if (spec.executor) {
2774
+ const byo = spec.executor;
2775
+ return { succeeded: true, value: (() => byo) };
2776
+ }
2777
+ if (spec.harness === null) {
2778
+ const f2 = factories.get("router");
2779
+ if (!f2) return { succeeded: false, error: 'executor registry: no "router" factory' };
2780
+ return { succeeded: true, value: f2 };
2781
+ }
2782
+ const runtimeTag = "sandbox";
2783
+ const f = factories.get(runtimeTag);
2784
+ if (!f) {
2785
+ return {
2786
+ succeeded: false,
2787
+ error: `executor registry: no factory for runtime "${runtimeTag}" (harness "${spec.harness}") and no BYO executor`
2788
+ };
2789
+ }
2790
+ return { succeeded: true, value: f };
2791
+ }
2792
+ };
2793
+ }
2794
+ function readSeam(ctx, key, who) {
2795
+ const seam = ctx.seams[key];
2796
+ if (seam === void 0 || seam === null) {
2797
+ throw new ValidationError(`${who} executor: missing required seam "${key}" on ExecutorContext`);
2798
+ }
2799
+ return seam;
2800
+ }
2801
+ function taskToPrompt(task) {
2802
+ if (typeof task === "string") return task;
2803
+ if (task && typeof task === "object") {
2804
+ const obj = task;
2805
+ for (const k of ["prompt", "content", "task", "message"]) {
2806
+ if (typeof obj[k] === "string") return obj[k];
2807
+ }
2808
+ }
2809
+ return JSON.stringify(task);
2810
+ }
2811
+ function taskToMessages(task, spec) {
2812
+ const messages = [];
2813
+ const system = spec.profile.prompt?.systemPrompt;
2814
+ if (typeof system === "string" && system.length > 0) {
2815
+ messages.push({ role: "system", content: system });
2816
+ }
2817
+ messages.push({ role: "user", content: taskToPrompt(task) });
2818
+ return messages;
2819
+ }
2820
+ function singleShotDriver(maxIterations) {
2821
+ return {
2822
+ name: "leaf",
2823
+ plan(task, history) {
2824
+ return Promise.resolve(history.length >= maxIterations ? [] : [task]);
2825
+ },
2826
+ decide(history) {
2827
+ return history.length >= maxIterations ? "stop" : "continue";
2828
+ }
2829
+ };
2830
+ }
2831
+ function linkSignals(a, b) {
2832
+ if (a.aborted || b.aborted) {
2833
+ const c2 = new AbortController();
2834
+ c2.abort();
2835
+ return c2.signal;
2836
+ }
2837
+ const c = new AbortController();
2838
+ const onAbort = () => c.abort();
2839
+ a.addEventListener("abort", onAbort, { once: true });
2840
+ b.addEventListener("abort", onAbort, { once: true });
2841
+ return c.signal;
2842
+ }
2843
+
2844
+ // src/runtime/supervise/budget.ts
2845
+ function spendFromUsageEvents(events) {
2846
+ const tokens = zeroTokenUsage();
2847
+ let usd = 0;
2848
+ let iterations = 0;
2849
+ for (const ev of events) {
2850
+ if (ev.kind === "tokens") {
2851
+ addTokenUsage(tokens, { input: ev.input, output: ev.output });
2852
+ } else if (ev.kind === "cost") {
2853
+ usd += ev.usd;
2854
+ } else {
2855
+ iterations += 1;
2856
+ }
2857
+ }
2858
+ return { iterations, tokens, usd, ms: 0 };
2859
+ }
2860
+ async function foldUsage(events) {
2861
+ if (Array.isArray(events)) return spendFromUsageEvents(events);
2862
+ const tokens = zeroTokenUsage();
2863
+ let usd = 0;
2864
+ let iterations = 0;
2865
+ for await (const ev of events) {
2866
+ if (ev.kind === "tokens") {
2867
+ addTokenUsage(tokens, { input: ev.input, output: ev.output });
2868
+ } else if (ev.kind === "cost") {
2869
+ usd += ev.usd;
2870
+ } else {
2871
+ iterations += 1;
2872
+ }
2873
+ }
2874
+ return { iterations, tokens, usd, ms: 0 };
2875
+ }
2876
+ function totalTokens(usage) {
2877
+ return usage.input + usage.output;
2878
+ }
2879
+ function createBudgetPool(root, now = Date.now) {
2880
+ let freeTokens = root.maxTokens;
2881
+ let reservedTokens = 0;
2882
+ let committedTokens = 0;
2883
+ const usdCapped = root.maxUsd !== void 0;
2884
+ let freeUsd = root.maxUsd ?? 0;
2885
+ let reservedUsd = 0;
2886
+ let committedUsd = 0;
2887
+ let freeIterations = root.maxIterations;
2888
+ let reservedIterations = 0;
2889
+ let committedIterations = 0;
2890
+ const absoluteDeadlineMs = root.deadlineMs !== void 0 ? now() + root.deadlineMs : 0;
2891
+ let nextTicketId = 0;
2892
+ const open = /* @__PURE__ */ new Set();
2893
+ function reserve(b) {
2894
+ const wantTokens = b.maxTokens;
2895
+ const wantUsd = b.maxUsd ?? 0;
2896
+ const wantIterations = b.maxIterations;
2897
+ if (wantTokens > freeTokens) return { ok: false, reason: "budget-exhausted" };
2898
+ if (wantIterations > freeIterations) return { ok: false, reason: "budget-exhausted" };
2899
+ if (wantUsd > 0 && (!usdCapped || wantUsd > freeUsd)) {
2900
+ return { ok: false, reason: "budget-exhausted" };
2901
+ }
2902
+ freeTokens -= wantTokens;
2903
+ reservedTokens += wantTokens;
2904
+ freeIterations -= wantIterations;
2905
+ reservedIterations += wantIterations;
2906
+ if (wantUsd > 0) {
2907
+ freeUsd -= wantUsd;
2908
+ reservedUsd += wantUsd;
2909
+ }
2910
+ const id = nextTicketId++;
2911
+ open.add(id);
2912
+ return {
2913
+ ok: true,
2914
+ ticket: { id, reserved: { tokens: wantTokens, usd: wantUsd, iterations: wantIterations } }
2915
+ };
2916
+ }
2917
+ function reconcile(ticket, spent) {
2918
+ if (!open.has(ticket.id)) {
2919
+ throw new Error(`budget pool: reconcile of unknown or already-settled ticket ${ticket.id}`);
2920
+ }
2921
+ open.delete(ticket.id);
2922
+ const { tokens: rTokens, usd: rUsd, iterations: rIterations } = ticket.reserved;
2923
+ const spentTokens = totalTokens(spent.tokens);
2924
+ if (spentTokens > rTokens) {
2925
+ throw new Error(
2926
+ `budget pool: ticket ${ticket.id} spent ${spentTokens} tokens > reserved ${rTokens}`
2927
+ );
2928
+ }
2929
+ if (spent.iterations > rIterations) {
2930
+ throw new Error(
2931
+ `budget pool: ticket ${ticket.id} spent ${spent.iterations} iterations > reserved ${rIterations}`
2932
+ );
2933
+ }
2934
+ if (usdCapped && spent.usd > rUsd) {
2935
+ throw new Error(`budget pool: ticket ${ticket.id} spent $${spent.usd} > reserved $${rUsd}`);
2936
+ }
2937
+ reservedTokens -= rTokens;
2938
+ committedTokens += spentTokens;
2939
+ freeTokens += rTokens - spentTokens;
2940
+ reservedIterations -= rIterations;
2941
+ committedIterations += spent.iterations;
2942
+ freeIterations += rIterations - spent.iterations;
2943
+ if (usdCapped && rUsd > 0) {
2944
+ reservedUsd -= rUsd;
2945
+ committedUsd += spent.usd;
2946
+ freeUsd += rUsd - spent.usd;
2947
+ } else {
2948
+ committedUsd += spent.usd;
2949
+ }
2950
+ }
2951
+ function readout() {
2952
+ return {
2953
+ tokensLeft: freeTokens,
2954
+ usdLeft: usdCapped ? freeUsd : 0,
2955
+ deadlineMs: absoluteDeadlineMs,
2956
+ reservedTokens
2957
+ };
2958
+ }
2959
+ function assertNoOpenTickets() {
2960
+ if (open.size > 0) {
2961
+ throw new Error(
2962
+ `budget pool: ${open.size} reservation(s) still open at join barrier (leaked ticket ids: ${[...open].join(", ")}) \u2014 conserved-pool invariant violated`
2963
+ );
2964
+ }
2965
+ }
2966
+ return {
2967
+ reserve,
2968
+ reconcile,
2969
+ spendFrom: foldUsage,
2970
+ readout,
2971
+ assertNoOpenTickets
2972
+ };
2973
+ }
2974
+
2975
+ // src/runtime/supervise/supervisor.ts
2976
+ var defaultMaxDepth = 4;
2977
+ function createSupervisor() {
2978
+ let attached;
2979
+ async function run(root, task, opts) {
2980
+ const now = opts.now ?? Date.now;
2981
+ const pool = createBudgetPool(opts.budget, now);
2982
+ await opts.journal.beginTree(opts.runId, new Date(now()).toISOString());
2983
+ await opts.journal.appendEvent(opts.runId, {
2984
+ kind: "spawned",
2985
+ id: opts.runId,
2986
+ label: "root",
2987
+ budget: opts.budget,
2988
+ runtime: "inline",
2989
+ seq: 0,
2990
+ at: new Date(now()).toISOString()
2991
+ });
2992
+ const controller = new AbortController();
2993
+ const cascadeAbort = (reason) => {
2994
+ if (controller.signal.aborted) return;
2995
+ controller.abort(reason);
2996
+ };
2997
+ const onCallerAbort = () => cascadeAbort("caller signal aborted");
2998
+ if (opts.signal) {
2999
+ if (opts.signal.aborted) cascadeAbort("caller signal aborted");
3000
+ else opts.signal.addEventListener("abort", onCallerAbort, { once: true });
3001
+ }
3002
+ const breaker = createIntensityBreaker(opts, () => cascadeAbort("intensity breaker tripped"));
3003
+ const journal = wrapJournalForBreaker(opts.journal, breaker);
3004
+ const scope = createScope({
3005
+ parentId: opts.runId,
3006
+ root: opts.runId,
3007
+ pool,
3008
+ journal,
3009
+ blobs: opts.blobs,
3010
+ executors: opts.executors,
3011
+ seams: {},
3012
+ depth: 0,
3013
+ maxDepth: opts.maxDepth ?? defaultMaxDepth,
3014
+ signal: controller.signal,
3015
+ now,
3016
+ hooks: opts.hooks
3017
+ });
3018
+ const openScope = scope;
3019
+ if (attached) {
3020
+ attached.bind({ scope: openScope, cascadeAbort, signal: pushRootSignal(cascadeAbort) });
3021
+ }
3022
+ let actOutcome;
3023
+ try {
3024
+ const out = await root.act(task, scope);
3025
+ actOutcome = { ok: true, out };
3026
+ } catch (error) {
3027
+ actOutcome = { ok: false, error };
3028
+ } finally {
3029
+ await drainLiveChildren(openScope, controller);
3030
+ if (opts.signal) opts.signal.removeEventListener("abort", onCallerAbort);
3031
+ if (attached) attached.unbind();
3032
+ }
3033
+ const tree = scope.view;
3034
+ if (actOutcome.ok) {
3035
+ pool.assertNoOpenTickets();
3036
+ const out = actOutcome.out;
3037
+ const outRef = contentAddress(out);
3038
+ await opts.blobs.put(outRef, out);
3039
+ return {
3040
+ kind: "winner",
3041
+ out,
3042
+ outRef,
3043
+ tree,
3044
+ spentTotal: await spentTotalFromJournal(journal, opts.runId)
3045
+ };
3046
+ }
3047
+ return {
3048
+ kind: "no-winner",
3049
+ reason: classifyNoWinner(controller, pool, opts, breaker),
3050
+ tree,
3051
+ downCount: breaker.downCount()
3052
+ };
3053
+ }
3054
+ function attach(h) {
3055
+ const control = rootControls.get(h);
3056
+ if (!control) {
3057
+ throw new RuntimeRunStateError(
3058
+ "supervisor.attach: handle was not minted by createRootHandle (no control channel)"
3059
+ );
3060
+ }
3061
+ attached = control;
3062
+ }
3063
+ return { run, attach };
3064
+ }
3065
+ var rootControls = /* @__PURE__ */ new WeakMap();
3066
+ function createRootHandle() {
3067
+ let binding;
3068
+ const handle = {
3069
+ view() {
3070
+ if (!binding) {
3071
+ throw new RuntimeRunStateError(
3072
+ "RootHandle.view: handle is not bound to a live run (attach it before run, read after run starts)"
3073
+ );
3074
+ }
3075
+ return binding.scope.view;
3076
+ },
3077
+ signal(msg) {
3078
+ if (!binding) {
3079
+ throw new RuntimeRunStateError("RootHandle.signal: handle is not bound to a live run");
3080
+ }
3081
+ binding.signal(msg);
3082
+ },
3083
+ abort(reason) {
3084
+ if (!binding) {
3085
+ throw new RuntimeRunStateError("RootHandle.abort: handle is not bound to a live run");
3086
+ }
3087
+ binding.cascadeAbort(reason ?? "root handle aborted");
3088
+ }
3089
+ };
3090
+ rootControls.set(handle, {
3091
+ bind(b) {
3092
+ binding = b;
3093
+ },
3094
+ unbind() {
3095
+ binding = void 0;
3096
+ }
3097
+ });
3098
+ return handle;
3099
+ }
3100
+ function pushRootSignal(cascadeAbort) {
3101
+ return (msg) => {
3102
+ if (msg.kind === "cancel") cascadeAbort(msg.reason ?? "root signal: cancel");
3103
+ };
3104
+ }
3105
+ function createIntensityBreaker(opts, trip) {
3106
+ const max = opts.maxRestarts;
3107
+ const within = opts.withinMs;
3108
+ const armed = max !== void 0 && within !== void 0;
3109
+ const recent = [];
3110
+ let total = 0;
3111
+ let isTripped = false;
3112
+ return {
3113
+ recordDown(at) {
3114
+ total += 1;
3115
+ if (!armed || isTripped) return;
3116
+ recent.push(at);
3117
+ const cutoff = at - within;
3118
+ while (recent.length > 0 && recent[0] < cutoff) recent.shift();
3119
+ if (recent.length > max) {
3120
+ isTripped = true;
3121
+ trip();
3122
+ }
3123
+ },
3124
+ tripped() {
3125
+ return isTripped;
3126
+ },
3127
+ downCount() {
3128
+ return total;
3129
+ }
3130
+ };
3131
+ }
3132
+ function wrapJournalForBreaker(journal, breaker) {
3133
+ return {
3134
+ loadTree: (root) => journal.loadTree(root),
3135
+ beginTree: (root, at) => journal.beginTree(root, at),
3136
+ appendEvent: (root, ev) => {
3137
+ if (ev.kind === "settled" && ev.status === "down") breaker.recordDown(Date.parse(ev.at));
3138
+ return journal.appendEvent(root, ev);
3139
+ }
3140
+ };
3141
+ }
3142
+ async function drainLiveChildren(scope, controller) {
3143
+ const hasLive = scope.view.inFlight > 0;
3144
+ if (!hasLive) return;
3145
+ if (!controller.signal.aborted) controller.abort();
3146
+ await Promise.allSettled([drainCursor(scope)]);
3147
+ }
3148
+ async function drainCursor(scope) {
3149
+ for (; ; ) {
3150
+ const settled = await scope.next();
3151
+ if (settled === null) return;
3152
+ }
3153
+ }
3154
+ function classifyNoWinner(controller, pool, opts, breaker) {
3155
+ if (breaker.tripped()) return "all-children-down";
3156
+ if (controller.signal.aborted) return "aborted";
3157
+ if (poolExhausted(pool, opts)) return "budget-exhausted";
3158
+ return "all-children-down";
3159
+ }
3160
+ function poolExhausted(pool, opts) {
3161
+ const r = pool.readout();
3162
+ if (r.tokensLeft <= 0) return true;
3163
+ if (opts.budget.maxUsd !== void 0 && r.usdLeft <= 0) return true;
3164
+ if (opts.budget.deadlineMs !== void 0 && r.deadlineMs > 0 && (opts.now ?? Date.now)() >= r.deadlineMs) {
3165
+ return true;
3166
+ }
3167
+ return false;
3168
+ }
3169
+ async function spentTotalFromJournal(journal, root) {
3170
+ const events = await journal.loadTree(root);
3171
+ if (events === void 0) {
3172
+ throw new RuntimeRunStateError(
3173
+ `supervisor: spawn tree '${root}' is missing from the journal after run (corrupted log)`
3174
+ );
3175
+ }
3176
+ const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
3177
+ for (const ev of events) {
3178
+ if (ev.kind !== "settled") continue;
3179
+ total.iterations += ev.spent.iterations;
3180
+ total.tokens.input += ev.spent.tokens.input;
3181
+ total.tokens.output += ev.spent.tokens.output;
3182
+ total.usd += ev.spent.usd;
3183
+ total.ms += ev.spent.ms;
3184
+ }
3185
+ return total;
3186
+ }
3187
+
3188
+ // src/runtime/personify/registry.ts
3189
+ function createShapeRegistry() {
3190
+ const shapes = /* @__PURE__ */ new Map();
3191
+ return {
3192
+ register(name, factory) {
3193
+ if (shapes.has(name)) {
3194
+ throw new ValidationError(`shape registry: shape "${name}" already registered`);
3195
+ }
3196
+ shapes.set(name, factory);
3197
+ },
3198
+ resolve(name) {
3199
+ const factory = shapes.get(name);
3200
+ if (!factory) {
3201
+ return { succeeded: false, error: `shape registry: unknown shape "${name}"` };
3202
+ }
3203
+ return { succeeded: true, value: factory };
3204
+ },
3205
+ names() {
3206
+ return [...shapes.keys()];
3207
+ }
3208
+ };
3209
+ }
3210
+ var builtinShapes = createShapeRegistry();
3211
+ function registerShape(name, factory) {
3212
+ builtinShapes.register(name, factory);
3213
+ }
3214
+
3215
+ // src/runtime/personify/persona.ts
3216
+ function definePersona(input) {
3217
+ if (!input.executors.registry && !input.executors.seams) {
3218
+ throw new ValidationError(
3219
+ `definePersona("${input.name}"): executors must supply a registry or a seams bag (built-in runtimes read their seams off ExecutorContext; neither was provided)`
3220
+ );
3221
+ }
3222
+ if (!input.root || typeof input.root !== "object" || !("harness" in input.root)) {
3223
+ throw new ValidationError(`definePersona("${input.name}"): root must be an AgentSpec`);
3224
+ }
3225
+ return Object.freeze({
3226
+ name: input.name,
3227
+ root: input.root,
3228
+ directive: input.directive,
3229
+ context: input.context,
3230
+ executors: input.executors,
3231
+ ...input.extensions ? { extensions: input.extensions } : {}
3232
+ });
3233
+ }
3234
+ function createShapeContext(persona, budget) {
3235
+ return {
3236
+ persona,
3237
+ budget,
3238
+ spawnChild(name, spec) {
3239
+ const agent = {
3240
+ name,
3241
+ executorSpec: spec,
3242
+ act() {
3243
+ throw new ValidationError(
3244
+ `personify: spawned child "${name}" was run as a driver; its executorSpec drives a leaf`
3245
+ );
3246
+ }
3247
+ };
3248
+ return agent;
3249
+ },
3250
+ childSpec(profile, harness) {
3251
+ return {
3252
+ profile,
3253
+ harness: harness === void 0 ? persona.root.harness : harness,
3254
+ ...persona.root.executor ? { executor: persona.root.executor } : {}
3255
+ };
3256
+ }
3257
+ };
3258
+ }
3259
+ async function runPersonified(options) {
3260
+ const { persona } = options;
3261
+ const shape = resolveShape(options.shape);
3262
+ const shapeBudget = resolveShapeBudget(options.budget, options.shapeBudget);
3263
+ const ctx = createShapeContext(persona, shapeBudget);
3264
+ const rootAgent = shape(ctx);
3265
+ const executors = personaRegistry(persona);
3266
+ const supervisor = createSupervisor();
3267
+ if (options.handle) supervisor.attach(options.handle);
3268
+ const supervisorOpts = {
3269
+ budget: options.budget,
3270
+ runId: options.runId ?? `${persona.name}:${shapeName(options.shape, shape)}`,
3271
+ journal: options.journal ?? new InMemorySpawnJournal(),
3272
+ blobs: options.blobs ?? new InMemoryResultBlobStore(),
3273
+ executors,
3274
+ ...options.maxDepth !== void 0 ? { maxDepth: options.maxDepth } : {},
3275
+ ...options.maxRestarts !== void 0 ? { maxRestarts: options.maxRestarts } : {},
3276
+ ...options.withinMs !== void 0 ? { withinMs: options.withinMs } : {},
3277
+ ...options.now ? { now: options.now } : {},
3278
+ ...options.signal ? { signal: options.signal } : {}
3279
+ };
3280
+ return supervisor.run(rootAgent, options.task, supervisorOpts);
3281
+ }
3282
+ function resolveShape(shape) {
3283
+ if (typeof shape !== "string") return shape;
3284
+ const resolved = builtinShapes.resolve(shape);
3285
+ if (!resolved.succeeded) {
3286
+ throw new ValidationError(
3287
+ `runPersonified: ${resolved.error} (registered: ${builtinShapes.names().join(", ")})`
3288
+ );
3289
+ }
3290
+ return resolved.value;
3291
+ }
3292
+ function shapeName(shape, _resolved) {
3293
+ return typeof shape === "string" ? shape : shape.name || "shape";
3294
+ }
3295
+ function resolveShapeBudget(root, over) {
3296
+ const fanout2 = over?.fanout ?? defaultFanout;
3297
+ const perChild = over?.perChild ?? {
3298
+ maxIterations: Math.max(1, Math.floor(root.maxIterations / fanout2)),
3299
+ maxTokens: Math.max(1, Math.floor(root.maxTokens / fanout2)),
3300
+ ...root.maxUsd !== void 0 ? { maxUsd: root.maxUsd / fanout2 } : {},
3301
+ ...root.deadlineMs !== void 0 ? { deadlineMs: root.deadlineMs } : {}
3302
+ };
3303
+ return { perChild, fanout: fanout2 };
3304
+ }
3305
+ var defaultFanout = 3;
3306
+ function personaRegistry(persona) {
3307
+ const { registry, seams } = persona.executors;
3308
+ if (registry) return registry;
3309
+ if (!seams) {
3310
+ throw new ValidationError(
3311
+ `personify: persona "${persona.name}" supplies neither a registry nor seams`
3312
+ );
3313
+ }
3314
+ return withSeams(createExecutorRegistry(), seams);
3315
+ }
3316
+ function withSeams(base, seams) {
3317
+ return {
3318
+ register(runtime, factory) {
3319
+ base.register(runtime, factory);
3320
+ },
3321
+ resolve(spec) {
3322
+ const resolved = base.resolve(spec);
3323
+ if (!resolved.succeeded) return resolved;
3324
+ const inner = resolved.value;
3325
+ const wrapped = (s, ctx) => inner(s, mergeSeams(ctx, seams));
3326
+ return { succeeded: true, value: wrapped };
3327
+ }
3328
+ };
3329
+ }
3330
+ function mergeSeams(ctx, seams) {
3331
+ return { signal: ctx.signal, seams: { ...seams, ...ctx.seams } };
3332
+ }
3333
+
3334
+ // src/runtime/personify/trajectory.ts
3335
+ var defaultEqualKTolerance = 0.05;
3336
+ async function trajectoryReport(journal, blobs, root, options = {}) {
3337
+ const events = await journal.loadTree(root);
3338
+ if (events === void 0) {
3339
+ throw new Error(`trajectoryReport: no journaled tree for root '${root}'`);
3340
+ }
3341
+ const spawns = events.filter(isSpawned).sort(bySeq);
3342
+ const closes = events.filter((ev) => ev.kind !== "spawned").sort(bySeq);
3343
+ const nodes = /* @__PURE__ */ new Map();
3344
+ for (const ev of spawns) {
3345
+ nodes.set(ev.id, {
3346
+ id: ev.id,
3347
+ parent: ev.parent,
3348
+ label: ev.label,
3349
+ runtime: ev.runtime,
3350
+ status: "pending",
3351
+ ownSpend: zeroSpend4(),
3352
+ children: []
3353
+ });
3354
+ }
3355
+ for (const ev of closes) {
3356
+ const node = requireNode2(nodes, ev.id, root);
3357
+ if (ev.kind === "cancelled") {
3358
+ node.status = "cancelled";
3359
+ continue;
3360
+ }
3361
+ node.status = ev.status === "done" ? "done" : "failed";
3362
+ node.ownSpend = ev.spent;
3363
+ node.verdict = ev.verdict;
3364
+ node.outRef = ev.outRef;
3365
+ }
3366
+ if (!nodes.has(root)) {
3367
+ throw new Error(
3368
+ `trajectoryReport: root '${root}' has no spawned event in its journaled tree (corrupted log)`
3369
+ );
3370
+ }
3371
+ for (const ev of spawns) {
3372
+ if (ev.parent === void 0) continue;
3373
+ requireNode2(nodes, ev.parent, root).children.push(ev.id);
3374
+ }
3375
+ const rolledUp = rollUpSpend(nodes, root);
3376
+ if (options.withOutputs) {
3377
+ await attachOutputs(nodes, blobs);
3378
+ }
3379
+ const ordered = spawns.map((ev) => nodes.get(ev.id)).filter(isNode);
3380
+ const reported = ordered.map(
3381
+ (node) => freezeNode(node, requireSpend(rolledUp, node.id, root))
3382
+ );
3383
+ return {
3384
+ root,
3385
+ nodes: reported,
3386
+ total: requireSpend(rolledUp, root, root),
3387
+ statusCounts: countStatuses(reported)
3388
+ };
3389
+ }
3390
+ function equalKOnCost(arms, options = {}) {
3391
+ if (arms.length === 0) {
3392
+ throw new Error("equalKOnCost: no arms to compare");
3393
+ }
3394
+ const tolerance = options.tolerance ?? defaultEqualKTolerance;
3395
+ const armCosts = arms.map((arm) => ({
3396
+ label: arm.label,
3397
+ tokens: arm.report.total.tokens.input + arm.report.total.tokens.output,
3398
+ usd: arm.report.total.usd,
3399
+ iterations: arm.report.total.iterations
3400
+ }));
3401
+ const tokenValues = armCosts.map((a) => a.tokens);
3402
+ const usdValues = armCosts.map((a) => a.usd);
3403
+ const spread = {
3404
+ tokens: spreadOf(tokenValues),
3405
+ usd: spreadOf(usdValues)
3406
+ };
3407
+ const withinTolerance = fractionalSpread(tokenValues) <= tolerance && fractionalSpread(usdValues) <= tolerance;
3408
+ return {
3409
+ withinTolerance,
3410
+ arms: armCosts,
3411
+ spread,
3412
+ tolerance
3413
+ };
3414
+ }
3415
+ function rollUpSpend(nodes, root) {
3416
+ const rolled = /* @__PURE__ */ new Map();
3417
+ const stack = [{ id: root, expanded: false }];
3418
+ while (stack.length > 0) {
3419
+ const frame = stack.pop();
3420
+ if (frame === void 0) continue;
3421
+ const node = requireNode2(nodes, frame.id, root);
3422
+ if (!frame.expanded) {
3423
+ stack.push({ id: frame.id, expanded: true });
3424
+ for (const child of node.children) stack.push({ id: child, expanded: false });
3425
+ continue;
3426
+ }
3427
+ const sum = cloneSpend(node.ownSpend);
3428
+ for (const child of node.children) addSpend(sum, requireSpend(rolled, child, root));
3429
+ rolled.set(frame.id, sum);
3430
+ }
3431
+ return rolled;
3432
+ }
3433
+ async function attachOutputs(nodes, blobs) {
3434
+ for (const node of nodes.values()) {
3435
+ if (node.status !== "done" || node.outRef === void 0) continue;
3436
+ const out = await blobs.get(node.outRef);
3437
+ if (out === void 0) {
3438
+ throw new Error(
3439
+ `trajectoryReport: blob store has no artifact for outRef '${node.outRef}' (node '${node.id}')`
3440
+ );
3441
+ }
3442
+ node.output = out;
3443
+ }
3444
+ }
3445
+ function freezeNode(node, rolledUpSpend) {
3446
+ return {
3447
+ id: node.id,
3448
+ parent: node.parent,
3449
+ children: [...node.children],
3450
+ label: node.label,
3451
+ runtime: node.runtime,
3452
+ status: node.status,
3453
+ ownSpend: node.ownSpend,
3454
+ rolledUpSpend,
3455
+ verdict: node.verdict,
3456
+ output: node.output,
3457
+ outRef: node.outRef
3458
+ };
3459
+ }
3460
+ function countStatuses(reported) {
3461
+ const counts = {
3462
+ done: 0,
3463
+ failed: 0,
3464
+ cancelled: 0,
3465
+ pending: 0
3466
+ };
3467
+ for (const node of reported) counts[node.status] += 1;
3468
+ return counts;
3469
+ }
3470
+ function zeroSpend4() {
3471
+ return { iterations: 0, tokens: zeroTokenUsage(), usd: 0, ms: 0 };
3472
+ }
3473
+ function cloneSpend(spend) {
3474
+ return {
3475
+ iterations: spend.iterations,
3476
+ tokens: { input: spend.tokens.input, output: spend.tokens.output },
3477
+ usd: spend.usd,
3478
+ ms: spend.ms
3479
+ };
3480
+ }
3481
+ function addSpend(acc, delta) {
3482
+ acc.iterations += delta.iterations;
3483
+ addTokenUsage(acc.tokens, delta.tokens);
3484
+ acc.usd += delta.usd;
3485
+ acc.ms += delta.ms;
3486
+ }
3487
+ function spreadOf(values) {
3488
+ if (values.length === 0) return 0;
3489
+ return Math.max(...values) - Math.min(...values);
3490
+ }
3491
+ function fractionalSpread(values) {
3492
+ const spread = spreadOf(values);
3493
+ if (spread === 0) return 0;
3494
+ const median = medianOf(values);
3495
+ if (median === 0) {
3496
+ throw new Error(
3497
+ "equalKOnCost: arms have a non-zero cost spread on a zero-median channel; cannot express it as a fraction"
3498
+ );
3499
+ }
3500
+ return spread / median;
3501
+ }
3502
+ function medianOf(values) {
3503
+ if (values.length === 0) {
3504
+ throw new Error("equalKOnCost: cannot take the median of an empty channel");
3505
+ }
3506
+ const sorted = [...values].sort((a, b) => a - b);
3507
+ const mid = Math.floor(sorted.length / 2);
3508
+ const hi = sorted[mid];
3509
+ if (sorted.length % 2 !== 0) return hi;
3510
+ const lo = sorted[mid - 1];
3511
+ return (lo + hi) / 2;
3512
+ }
3513
+ function isSpawned(ev) {
3514
+ return ev.kind === "spawned";
3515
+ }
3516
+ function isNode(node) {
3517
+ return node !== void 0;
3518
+ }
3519
+ function bySeq(a, b) {
3520
+ return a.seq - b.seq;
3521
+ }
3522
+ function requireNode2(nodes, id, root) {
3523
+ const node = nodes.get(id);
3524
+ if (!node) {
3525
+ throw new Error(
3526
+ `trajectoryReport: tree '${root}' references node '${id}' with no prior spawn (corrupted log)`
3527
+ );
3528
+ }
3529
+ return node;
3530
+ }
3531
+ function requireSpend(rolled, id, root) {
3532
+ const spend = rolled.get(id);
3533
+ if (!spend) {
3534
+ throw new Error(
3535
+ `trajectoryReport: node '${id}' was never rolled up in tree '${root}' (unreachable from root)`
3536
+ );
3537
+ }
3538
+ return spend;
3539
+ }
3540
+
3541
+ export {
3542
+ contentAddress,
3543
+ InMemoryResultBlobStore,
3544
+ FileResultBlobStore,
3545
+ InMemorySpawnJournal,
3546
+ FileSpawnJournal,
3547
+ replaySpawnTree,
3548
+ materializeTreeView,
3549
+ completionAuthorizes,
3550
+ stopSentinel,
3551
+ sentinelCompletion,
3552
+ deterministicCompletion,
3553
+ createDynamicDriver,
3554
+ renderAnalyses,
3555
+ reportLoopUsage,
3556
+ defineRuntimeHooks,
3557
+ composeRuntimeHooks,
3558
+ notifyRuntimeHookEvent,
3559
+ notifyRuntimeDecisionPoint,
3560
+ acquireSandbox,
3561
+ probeSandboxCapabilities,
3562
+ createSandboxLineage,
3563
+ runLoop,
3564
+ describeSandboxPlacement,
3565
+ createSandboxForSpec,
3566
+ defaultSelectWinner,
3567
+ loopDispatch,
3568
+ assertTraceDerivedFindings2 as assertTraceDerivedFindings,
3569
+ createScopeAnalyst,
3570
+ buildSteerContext,
3571
+ createScope,
3572
+ settledToIteration,
3573
+ pipeline,
3574
+ fanout,
3575
+ loopUntil,
3576
+ panel,
3577
+ verify,
3578
+ widen,
3579
+ flatWidenGate,
3580
+ InMemoryCorpus,
3581
+ FileCorpus,
3582
+ renderCorpusToInstructions,
3583
+ routerInlineExecutor,
3584
+ sandboxExecutor,
3585
+ cliExecutor,
3586
+ createExecutorRegistry,
3587
+ spendFromUsageEvents,
3588
+ createBudgetPool,
3589
+ createSupervisor,
3590
+ createRootHandle,
3591
+ createShapeRegistry,
3592
+ builtinShapes,
3593
+ registerShape,
3594
+ definePersona,
3595
+ runPersonified,
3596
+ trajectoryReport,
3597
+ equalKOnCost
3598
+ };
3599
+ //# sourceMappingURL=chunk-KEWO4KI6.js.map