@tangle-network/agent-runtime 0.90.0 → 0.90.1

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