@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.
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +4 -4
- package/dist/analyst-loop.d.ts +1 -1
- package/dist/{chunk-4WXGK6GV.js → chunk-7ON74BQO.js} +2 -2
- package/dist/{chunk-7LO5GMAO.js → chunk-CMYMTRGA.js} +14 -5
- package/dist/chunk-CMYMTRGA.js.map +1 -0
- package/dist/{chunk-UD4BHQMI.js → chunk-IVGYLCFH.js} +2 -2
- package/dist/{chunk-UD4BHQMI.js.map → chunk-IVGYLCFH.js.map} +1 -1
- package/dist/{chunk-XMOU4WEY.js → chunk-OOL3675H.js} +12 -2
- package/dist/chunk-OOL3675H.js.map +1 -0
- package/dist/{chunk-XQEISTK2.js → chunk-QK4DV5PR.js} +2 -2
- package/dist/{chunk-74SBMDTO.js → chunk-R2VAJGR3.js} +2 -2
- package/dist/{chunk-QSNSMJSZ.js → chunk-RYBVU4M3.js} +963 -957
- package/dist/chunk-RYBVU4M3.js.map +1 -0
- package/dist/{chunk-MHK62APK.js → chunk-WRUSWK4F.js} +3 -3
- package/dist/{chunk-4IBAMGBE.js → chunk-ZV4LXYCJ.js} +420 -23
- package/dist/chunk-ZV4LXYCJ.js.map +1 -0
- package/dist/{completion-gate-BDaBP8dd.d.ts → completion-gate-DkAnUmpb.d.ts} +2 -2
- package/dist/{coordination-DxHduZg7.d.ts → coordination-rRj5hjJK.d.ts} +5 -5
- package/dist/environment-provider.d.ts +2 -2
- package/dist/index.d.ts +112 -16
- package/dist/index.js +155 -10
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +227 -83
- package/dist/intelligence.js +259 -166
- package/dist/intelligence.js.map +1 -1
- package/dist/knowledge.d.ts +6 -6
- package/dist/knowledge.js +4 -4
- package/dist/lifecycle.d.ts +2 -2
- package/dist/lifecycle.js +2 -2
- package/dist/{local-harness-sI0S_XNA.d.ts → local-harness-dcD5WTTr.d.ts} +3 -0
- package/dist/{loop-runner-bin-D3GmB2Ru.d.ts → loop-runner-bin-DTbZVGfM.d.ts} +2 -2
- package/dist/loop-runner-bin.d.ts +6 -6
- package/dist/loop-runner-bin.js +5 -5
- package/dist/loops.d.ts +16 -256
- package/dist/loops.js +30 -4
- package/dist/mcp/bin.js +4 -4
- package/dist/mcp/index.d.ts +9 -9
- package/dist/mcp/index.js +6 -6
- package/dist/{mcp-serve-verifier-FL7-ZEb_.d.ts → mcp-serve-verifier-XsX8rkB9.d.ts} +12 -1
- package/dist/{openai-tools-D0xmzo0y.d.ts → openai-tools-C4ZfUD4L.d.ts} +1 -1
- package/dist/profiles.d.ts +1 -1
- package/dist/structural-rollout-MwlpgQ-6.d.ts +446 -0
- package/dist/{supervise-BpCdssu0.d.ts → supervise-DPmYPk0j.d.ts} +3 -3
- package/dist/{types-Dnk189QA.d.ts → types-SyuwunY_.d.ts} +1 -1
- package/dist/{types-DAJQRIUD.d.ts → types-eMNgWgFi.d.ts} +2 -2
- package/dist/{worktree-fanout-CfRXYmgV.d.ts → worktree-fanout-BDFQIO-Y.d.ts} +234 -234
- package/package.json +1 -1
- package/skills/build-with-agent-runtime/SKILL.md +20 -17
- package/dist/chunk-4IBAMGBE.js.map +0 -1
- package/dist/chunk-7LO5GMAO.js.map +0 -1
- package/dist/chunk-QSNSMJSZ.js.map +0 -1
- package/dist/chunk-XMOU4WEY.js.map +0 -1
- /package/dist/{chunk-4WXGK6GV.js.map → chunk-7ON74BQO.js.map} +0 -0
- /package/dist/{chunk-XQEISTK2.js.map → chunk-QK4DV5PR.js.map} +0 -0
- /package/dist/{chunk-74SBMDTO.js.map → chunk-R2VAJGR3.js.map} +0 -0
- /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-
|
|
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-
|
|
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 (
|
|
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
|
|
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/
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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(
|
|
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
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
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
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
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
|
|
3423
|
-
} catch {
|
|
3424
|
-
return
|
|
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
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
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
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
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
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
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
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
if (
|
|
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
|
-
|
|
3685
|
-
|
|
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
|
|
3578
|
+
return { succeeded: true, value: f };
|
|
3693
3579
|
}
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
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
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
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
|
-
|
|
3639
|
+
s.addEventListener("abort", onAbort, { once: true });
|
|
3714
3640
|
}
|
|
3715
|
-
return
|
|
3641
|
+
return c.signal;
|
|
3716
3642
|
}
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
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
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
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
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
const
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
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
|
-
|
|
3743
|
-
return
|
|
3693
|
+
metered() {
|
|
3694
|
+
return meteredSpend;
|
|
3744
3695
|
},
|
|
3745
|
-
|
|
3746
|
-
return
|
|
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
|
|
3706
|
+
};
|
|
3707
|
+
function withDriverExecutor(base) {
|
|
3751
3708
|
return {
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
if (
|
|
3756
|
-
|
|
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
|
-
|
|
3761
|
-
|
|
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
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
if (
|
|
3782
|
-
|
|
3783
|
-
|
|
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
|
|
3729
|
+
return c.n++;
|
|
3786
3730
|
}
|
|
3787
|
-
async function
|
|
3788
|
-
const events = await journal.loadTree(
|
|
3731
|
+
async function loadTreeEvents(journal, nestedRoot) {
|
|
3732
|
+
const events = await journal.loadTree(nestedRoot);
|
|
3789
3733
|
if (events === void 0) {
|
|
3790
|
-
throw new
|
|
3791
|
-
`
|
|
3734
|
+
throw new ValidationError(
|
|
3735
|
+
`driverExecutor: nested tree '${nestedRoot}' missing from the journal after run (corrupted log)`
|
|
3792
3736
|
);
|
|
3793
3737
|
}
|
|
3794
|
-
|
|
3795
|
-
|
|
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
|
|
3798
|
-
|
|
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
|
|
3764
|
+
return total;
|
|
3801
3765
|
}
|
|
3802
|
-
function
|
|
3803
|
-
|
|
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
|
|
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
|
-
|
|
3812
|
-
|
|
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
|
|
3818
|
-
|
|
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-
|
|
6647
|
+
//# sourceMappingURL=chunk-RYBVU4M3.js.map
|