@tangle-network/agent-runtime 0.90.0 → 0.91.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-4IBAMGBE.js → chunk-AD7JW4QG.js} +420 -23
- package/dist/chunk-AD7JW4QG.js.map +1 -0
- package/dist/{chunk-7LO5GMAO.js → chunk-CMYMTRGA.js} +14 -5
- package/dist/chunk-CMYMTRGA.js.map +1 -0
- package/dist/{chunk-XQEISTK2.js → chunk-DWWII6N2.js} +2 -2
- package/dist/{chunk-XMOU4WEY.js → chunk-FF77IBQM.js} +15 -3
- package/dist/chunk-FF77IBQM.js.map +1 -0
- package/dist/{chunk-MHK62APK.js → chunk-IOUUITQA.js} +3 -3
- 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-74SBMDTO.js → chunk-JRS3YSRZ.js} +2 -2
- package/dist/{chunk-QSNSMJSZ.js → chunk-NC66AM3S.js} +1487 -1481
- package/dist/chunk-NC66AM3S.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 +915 -18
- package/dist/index.js +5742 -33
- 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 +6 -7
- 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-DWWII6N2.js.map} +0 -0
- /package/dist/{chunk-MHK62APK.js.map → chunk-IOUUITQA.js.map} +0 -0
- /package/dist/{chunk-74SBMDTO.js.map → chunk-JRS3YSRZ.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,
|
|
@@ -34,58 +34,6 @@ import {
|
|
|
34
34
|
ValidationError
|
|
35
35
|
} from "./chunk-YEJR7IXO.js";
|
|
36
36
|
|
|
37
|
-
// src/runtime/supervise/model-policy.ts
|
|
38
|
-
function assertModelAllowed(model, allowed) {
|
|
39
|
-
if (!allowed || model === void 0) return;
|
|
40
|
-
if (!allowed.includes(model)) {
|
|
41
|
-
throw new ConfigError(
|
|
42
|
-
`model ${JSON.stringify(model)} is not in the allowed set ${JSON.stringify([...allowed])}`
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
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
37
|
// src/durable/spawn-journal.ts
|
|
90
38
|
import { createHash } from "crypto";
|
|
91
39
|
function contentAddress(artifact) {
|
|
@@ -153,574 +101,1182 @@ function assertSeqUnique(root, events, ev) {
|
|
|
153
101
|
}
|
|
154
102
|
}
|
|
155
103
|
|
|
156
|
-
// src/runtime-
|
|
157
|
-
function
|
|
158
|
-
return
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
onEvent: hooks.some((hook) => hook.onEvent) ? (event, context) => {
|
|
164
|
-
const pending = [];
|
|
165
|
-
for (const hook of hooks) {
|
|
166
|
-
const result = hook.onEvent?.(event, context);
|
|
167
|
-
if (isThenable(result)) pending.push(Promise.resolve(result));
|
|
168
|
-
}
|
|
169
|
-
if (pending.length > 0) return Promise.all(pending).then(() => void 0);
|
|
170
|
-
return void 0;
|
|
171
|
-
} : void 0,
|
|
172
|
-
onDecisionPoint: hooks.some((hook) => hook.onDecisionPoint) ? (point, context) => {
|
|
173
|
-
const pending = [];
|
|
174
|
-
for (const hook of hooks) {
|
|
175
|
-
const result = hook.onDecisionPoint?.(point, context);
|
|
176
|
-
if (isThenable(result)) pending.push(Promise.resolve(result));
|
|
177
|
-
}
|
|
178
|
-
if (pending.length > 0) return Promise.all(pending).then(() => void 0);
|
|
179
|
-
return void 0;
|
|
180
|
-
} : void 0,
|
|
181
|
-
onHookError: hooks.some((hook) => hook.onHookError) ? (error, context) => {
|
|
182
|
-
const pending = [];
|
|
183
|
-
for (const hook of hooks) {
|
|
184
|
-
const result = hook.onHookError?.(error, context);
|
|
185
|
-
if (isThenable(result)) pending.push(Promise.resolve(result));
|
|
186
|
-
}
|
|
187
|
-
if (pending.length > 0) return Promise.all(pending).then(() => void 0);
|
|
188
|
-
return void 0;
|
|
189
|
-
} : void 0
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
function notifyRuntimeHookEvent(hooks, event, context = {}) {
|
|
193
|
-
const onEvent = hooks?.onEvent;
|
|
194
|
-
if (!onEvent) return;
|
|
195
|
-
try {
|
|
196
|
-
const result = onEvent(event, context);
|
|
197
|
-
if (isThenable(result)) {
|
|
198
|
-
void result.catch((error) => {
|
|
199
|
-
notifyRuntimeHookError(hooks, toError(error), {
|
|
200
|
-
hook: "onEvent",
|
|
201
|
-
eventId: event.id,
|
|
202
|
-
target: event.target,
|
|
203
|
-
phase: event.phase
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
} catch (error) {
|
|
208
|
-
notifyRuntimeHookError(hooks, toError(error), {
|
|
209
|
-
hook: "onEvent",
|
|
210
|
-
eventId: event.id,
|
|
211
|
-
target: event.target,
|
|
212
|
-
phase: event.phase
|
|
213
|
-
});
|
|
104
|
+
// src/runtime/supervise/model-policy.ts
|
|
105
|
+
function assertModelAllowed(model, allowed) {
|
|
106
|
+
if (!allowed || model === void 0) return;
|
|
107
|
+
if (!allowed.includes(model)) {
|
|
108
|
+
throw new ConfigError(
|
|
109
|
+
`model ${JSON.stringify(model)} is not in the allowed set ${JSON.stringify([...allowed])}`
|
|
110
|
+
);
|
|
214
111
|
}
|
|
215
112
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
} catch (error) {
|
|
231
|
-
notifyRuntimeHookError(hooks, toError(error), {
|
|
232
|
-
hook: "onDecisionPoint",
|
|
233
|
-
decisionId: point.id,
|
|
234
|
-
decisionKind: point.kind
|
|
235
|
-
});
|
|
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;
|
|
236
125
|
}
|
|
126
|
+
return Math.ceil(chars / 4);
|
|
237
127
|
}
|
|
238
|
-
function
|
|
128
|
+
function safeJsonLength(value) {
|
|
129
|
+
if (value === void 0 || value === null) return 0;
|
|
239
130
|
try {
|
|
240
|
-
|
|
241
|
-
if (isThenable(result)) void result.catch(() => void 0);
|
|
131
|
+
return JSON.stringify(value)?.length ?? 0;
|
|
242
132
|
} catch {
|
|
133
|
+
return String(value).length;
|
|
243
134
|
}
|
|
244
135
|
}
|
|
245
|
-
function
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
mount(nestedRoot, signal) {
|
|
260
|
-
return createScope({
|
|
261
|
-
parentId: childNodeId,
|
|
262
|
-
root: nestedRoot,
|
|
263
|
-
pool: args.pool,
|
|
264
|
-
journal: args.journal,
|
|
265
|
-
blobs: args.blobs,
|
|
266
|
-
executors: args.executors,
|
|
267
|
-
// Re-seed the parent's NON-recursion seams (sandbox/router for leaf grandchildren);
|
|
268
|
-
// the nested scope adds its OWN nested-scope seam per child in `spawn`.
|
|
269
|
-
seams: args.seams,
|
|
270
|
-
depth: args.depth + 1,
|
|
271
|
-
...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
|
|
272
|
-
signal,
|
|
273
|
-
...args.now ? { now: args.now } : {},
|
|
274
|
-
...args.hooks ? { hooks: args.hooks } : {}
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
};
|
|
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;
|
|
278
150
|
}
|
|
279
|
-
function
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
let
|
|
283
|
-
let
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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);
|
|
288
167
|
}
|
|
289
|
-
|
|
290
|
-
if (
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
);
|
|
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 };
|
|
294
172
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
};
|
|
321
|
-
const live = {
|
|
322
|
-
id,
|
|
323
|
-
status: "acquiring",
|
|
324
|
-
runtime: executor.runtime,
|
|
325
|
-
budget: opts.budget,
|
|
326
|
-
label: opts.label,
|
|
327
|
-
spent: zeroSpend(),
|
|
328
|
-
settled: void 0,
|
|
329
|
-
delivered: false,
|
|
330
|
-
executorDone: false,
|
|
331
|
-
...executor.deliver ? { deliver: executor.deliver.bind(executor) } : {}
|
|
332
|
-
};
|
|
333
|
-
children.set(id, live);
|
|
334
|
-
void args.journal.appendEvent(args.root, {
|
|
335
|
-
kind: "spawned",
|
|
336
|
-
id,
|
|
337
|
-
parent: args.parentId,
|
|
338
|
-
label: opts.label,
|
|
339
|
-
budget: opts.budget,
|
|
340
|
-
runtime: executor.runtime,
|
|
341
|
-
seq: ordinal,
|
|
342
|
-
at: new Date(now()).toISOString()
|
|
343
|
-
});
|
|
344
|
-
notifyRuntimeHookEvent(
|
|
345
|
-
args.hooks,
|
|
346
|
-
{
|
|
347
|
-
id: `${id}:spawn`,
|
|
348
|
-
runId: args.root,
|
|
349
|
-
target: "agent.spawn",
|
|
350
|
-
phase: "after",
|
|
351
|
-
timestamp: now(),
|
|
352
|
-
stepIndex: ordinal,
|
|
353
|
-
parentId: args.parentId,
|
|
354
|
-
payload: {
|
|
355
|
-
childId: id,
|
|
356
|
-
label: opts.label,
|
|
357
|
-
runtime: executor.runtime,
|
|
358
|
-
budget: opts.budget,
|
|
359
|
-
depth: args.depth
|
|
360
|
-
}
|
|
361
|
-
},
|
|
362
|
-
{ signal: args.signal }
|
|
363
|
-
);
|
|
364
|
-
const settled = runChild(
|
|
365
|
-
live,
|
|
366
|
-
executor,
|
|
367
|
-
childAbort,
|
|
368
|
-
task,
|
|
369
|
-
opts,
|
|
370
|
-
args.pool,
|
|
371
|
-
reservation.ticket,
|
|
372
|
-
args.blobs
|
|
373
|
-
).then((s) => {
|
|
374
|
-
live.resolved = s;
|
|
375
|
-
return s;
|
|
376
|
-
}).finally(() => {
|
|
377
|
-
args.signal.removeEventListener("abort", cascadeAbort);
|
|
378
|
-
});
|
|
379
|
-
live.settled = settled;
|
|
380
|
-
return { ok: true, handle };
|
|
381
|
-
} catch (err) {
|
|
382
|
-
args.pool.reconcile(reservation.ticket, zeroSpend());
|
|
383
|
-
throw err;
|
|
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 });
|
|
384
198
|
}
|
|
385
199
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
+
|
|
324
|
+
// src/runtime-hooks.ts
|
|
325
|
+
function defineRuntimeHooks(hooks) {
|
|
326
|
+
return hooks;
|
|
327
|
+
}
|
|
328
|
+
function composeRuntimeHooks(...entries) {
|
|
329
|
+
const hooks = entries.filter((entry) => !!entry);
|
|
330
|
+
return {
|
|
331
|
+
onEvent: hooks.some((hook) => hook.onEvent) ? (event, context) => {
|
|
332
|
+
const pending = [];
|
|
333
|
+
for (const hook of hooks) {
|
|
334
|
+
const result = hook.onEvent?.(event, context);
|
|
335
|
+
if (isThenable(result)) pending.push(Promise.resolve(result));
|
|
402
336
|
}
|
|
403
|
-
|
|
337
|
+
if (pending.length > 0) return Promise.all(pending).then(() => void 0);
|
|
338
|
+
return void 0;
|
|
339
|
+
} : void 0,
|
|
340
|
+
onDecisionPoint: hooks.some((hook) => hook.onDecisionPoint) ? (point, context) => {
|
|
341
|
+
const pending = [];
|
|
342
|
+
for (const hook of hooks) {
|
|
343
|
+
const result = hook.onDecisionPoint?.(point, context);
|
|
344
|
+
if (isThenable(result)) pending.push(Promise.resolve(result));
|
|
345
|
+
}
|
|
346
|
+
if (pending.length > 0) return Promise.all(pending).then(() => void 0);
|
|
347
|
+
return void 0;
|
|
348
|
+
} : void 0,
|
|
349
|
+
onHookError: hooks.some((hook) => hook.onHookError) ? (error, context) => {
|
|
350
|
+
const pending = [];
|
|
351
|
+
for (const hook of hooks) {
|
|
352
|
+
const result = hook.onHookError?.(error, context);
|
|
353
|
+
if (isThenable(result)) pending.push(Promise.resolve(result));
|
|
354
|
+
}
|
|
355
|
+
if (pending.length > 0) return Promise.all(pending).then(() => void 0);
|
|
356
|
+
return void 0;
|
|
357
|
+
} : void 0
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function notifyRuntimeHookEvent(hooks, event, context = {}) {
|
|
361
|
+
const onEvent = hooks?.onEvent;
|
|
362
|
+
if (!onEvent) return;
|
|
363
|
+
try {
|
|
364
|
+
const result = onEvent(event, context);
|
|
365
|
+
if (isThenable(result)) {
|
|
366
|
+
void result.catch((error) => {
|
|
367
|
+
notifyRuntimeHookError(hooks, toError(error), {
|
|
368
|
+
hook: "onEvent",
|
|
369
|
+
eventId: event.id,
|
|
370
|
+
target: event.target,
|
|
371
|
+
phase: event.phase
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
} catch (error) {
|
|
376
|
+
notifyRuntimeHookError(hooks, toError(error), {
|
|
377
|
+
hook: "onEvent",
|
|
378
|
+
eventId: event.id,
|
|
379
|
+
target: event.target,
|
|
380
|
+
phase: event.phase
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function notifyRuntimeDecisionPoint(hooks, point, context = {}) {
|
|
385
|
+
const onDecisionPoint = hooks?.onDecisionPoint;
|
|
386
|
+
if (!onDecisionPoint) return;
|
|
387
|
+
try {
|
|
388
|
+
const result = onDecisionPoint(point, context);
|
|
389
|
+
if (isThenable(result)) {
|
|
390
|
+
void result.catch((error) => {
|
|
391
|
+
notifyRuntimeHookError(hooks, toError(error), {
|
|
392
|
+
hook: "onDecisionPoint",
|
|
393
|
+
decisionId: point.id,
|
|
394
|
+
decisionKind: point.kind
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
} catch (error) {
|
|
399
|
+
notifyRuntimeHookError(hooks, toError(error), {
|
|
400
|
+
hook: "onDecisionPoint",
|
|
401
|
+
decisionId: point.id,
|
|
402
|
+
decisionKind: point.kind
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
function notifyRuntimeHookError(hooks, error, context) {
|
|
407
|
+
try {
|
|
408
|
+
const result = hooks?.onHookError?.(error, context);
|
|
409
|
+
if (isThenable(result)) void result.catch(() => void 0);
|
|
410
|
+
} catch {
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function isThenable(value) {
|
|
414
|
+
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
415
|
+
}
|
|
416
|
+
function toError(error) {
|
|
417
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/runtime/supervise/scope.ts
|
|
421
|
+
var nestedScopeSeamKey = "nested-scope";
|
|
422
|
+
function makeNestedScopeSeam(args, childNodeId) {
|
|
423
|
+
return {
|
|
424
|
+
depth: args.depth,
|
|
425
|
+
...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
|
|
426
|
+
journalRoot: args.root,
|
|
427
|
+
mount(nestedRoot, signal) {
|
|
428
|
+
return createScope({
|
|
429
|
+
parentId: childNodeId,
|
|
430
|
+
root: nestedRoot,
|
|
431
|
+
pool: args.pool,
|
|
432
|
+
journal: args.journal,
|
|
433
|
+
blobs: args.blobs,
|
|
434
|
+
executors: args.executors,
|
|
435
|
+
// Re-seed the parent's NON-recursion seams (sandbox/router for leaf grandchildren);
|
|
436
|
+
// the nested scope adds its OWN nested-scope seam per child in `spawn`.
|
|
437
|
+
seams: args.seams,
|
|
438
|
+
depth: args.depth + 1,
|
|
439
|
+
...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
|
|
440
|
+
signal,
|
|
441
|
+
...args.now ? { now: args.now } : {},
|
|
442
|
+
...args.hooks ? { hooks: args.hooks } : {}
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
function createScope(args) {
|
|
448
|
+
const children = /* @__PURE__ */ new Map();
|
|
449
|
+
let spawnOrdinal = 0;
|
|
450
|
+
let cursorSeq = 0;
|
|
451
|
+
let meterSeq = 0;
|
|
452
|
+
const now = args.now ?? Date.now;
|
|
453
|
+
function spawn4(agent, task, opts) {
|
|
454
|
+
if (args.maxDepth !== void 0 && args.depth >= args.maxDepth) {
|
|
455
|
+
return { ok: false, reason: "depth-exceeded" };
|
|
456
|
+
}
|
|
457
|
+
const spec = agent.executorSpec;
|
|
458
|
+
if (!isAgentSpec(spec)) {
|
|
459
|
+
throw new ValidationError(
|
|
460
|
+
`scope.spawn: agent "${agent.name}" exposes no \`executorSpec\` (AgentSpec) to resolve a Executor`
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
const resolved = args.executors.resolve(spec);
|
|
464
|
+
if (!resolved.succeeded) throw new ValidationError(`scope.spawn: ${resolved.error}`);
|
|
465
|
+
const reservation = args.pool.reserve(opts.budget);
|
|
466
|
+
if (!reservation.ok) return { ok: false, reason: reservation.reason };
|
|
467
|
+
try {
|
|
468
|
+
const ordinal = spawnOrdinal++;
|
|
469
|
+
const id = `${args.parentId}:s${ordinal}`;
|
|
470
|
+
const childAbort = new AbortController();
|
|
471
|
+
const cascadeAbort = () => childAbort.abort();
|
|
472
|
+
if (args.signal.aborted) childAbort.abort();
|
|
473
|
+
else args.signal.addEventListener("abort", cascadeAbort, { once: true });
|
|
474
|
+
const ctx = {
|
|
475
|
+
signal: childAbort.signal,
|
|
476
|
+
seams: { ...args.seams, [nestedScopeSeamKey]: makeNestedScopeSeam(args, id) }
|
|
477
|
+
};
|
|
478
|
+
const executor = resolved.value(spec, ctx);
|
|
479
|
+
const handle = {
|
|
480
|
+
id,
|
|
481
|
+
label: opts.label,
|
|
482
|
+
get status() {
|
|
483
|
+
return children.get(id)?.status ?? "cancelled";
|
|
484
|
+
},
|
|
485
|
+
abort(reason) {
|
|
486
|
+
childAbort.abort(reason);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
const live = {
|
|
490
|
+
id,
|
|
491
|
+
status: "acquiring",
|
|
492
|
+
runtime: executor.runtime,
|
|
493
|
+
budget: opts.budget,
|
|
494
|
+
label: opts.label,
|
|
495
|
+
spent: zeroSpend(),
|
|
496
|
+
settled: void 0,
|
|
497
|
+
delivered: false,
|
|
498
|
+
executorDone: false,
|
|
499
|
+
...executor.deliver ? { deliver: executor.deliver.bind(executor) } : {}
|
|
500
|
+
};
|
|
501
|
+
children.set(id, live);
|
|
502
|
+
void args.journal.appendEvent(args.root, {
|
|
503
|
+
kind: "spawned",
|
|
504
|
+
id,
|
|
505
|
+
parent: args.parentId,
|
|
506
|
+
label: opts.label,
|
|
507
|
+
budget: opts.budget,
|
|
508
|
+
runtime: executor.runtime,
|
|
509
|
+
seq: ordinal,
|
|
510
|
+
at: new Date(now()).toISOString()
|
|
511
|
+
});
|
|
512
|
+
notifyRuntimeHookEvent(
|
|
513
|
+
args.hooks,
|
|
514
|
+
{
|
|
515
|
+
id: `${id}:spawn`,
|
|
516
|
+
runId: args.root,
|
|
517
|
+
target: "agent.spawn",
|
|
518
|
+
phase: "after",
|
|
519
|
+
timestamp: now(),
|
|
520
|
+
stepIndex: ordinal,
|
|
521
|
+
parentId: args.parentId,
|
|
522
|
+
payload: {
|
|
523
|
+
childId: id,
|
|
524
|
+
label: opts.label,
|
|
525
|
+
runtime: executor.runtime,
|
|
526
|
+
budget: opts.budget,
|
|
527
|
+
depth: args.depth
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
{ signal: args.signal }
|
|
531
|
+
);
|
|
532
|
+
const settled = runChild(
|
|
533
|
+
live,
|
|
534
|
+
executor,
|
|
535
|
+
childAbort,
|
|
536
|
+
task,
|
|
537
|
+
opts,
|
|
538
|
+
args.pool,
|
|
539
|
+
reservation.ticket,
|
|
540
|
+
args.blobs
|
|
541
|
+
).then((s) => {
|
|
542
|
+
live.resolved = s;
|
|
543
|
+
return s;
|
|
544
|
+
}).finally(() => {
|
|
545
|
+
args.signal.removeEventListener("abort", cascadeAbort);
|
|
546
|
+
});
|
|
547
|
+
live.settled = settled;
|
|
548
|
+
return { ok: true, handle };
|
|
549
|
+
} catch (err) {
|
|
550
|
+
args.pool.reconcile(reservation.ticket, zeroSpend());
|
|
551
|
+
throw err;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
async function next() {
|
|
555
|
+
const undelivered = () => [...children.values()].filter((c) => !c.delivered);
|
|
556
|
+
if (undelivered().length === 0) return null;
|
|
557
|
+
for (; ; ) {
|
|
558
|
+
const pending = undelivered();
|
|
559
|
+
if (pending.length === 0) return null;
|
|
560
|
+
const ready = pending.find((c) => c.resolved !== void 0);
|
|
561
|
+
const chosen = ready ?? await raceFirstSettled(pending);
|
|
562
|
+
if (chosen.delivered) continue;
|
|
563
|
+
chosen.delivered = true;
|
|
564
|
+
const seq = cursorSeq++;
|
|
565
|
+
const settlement = chosen.resolved;
|
|
566
|
+
if (!settlement) {
|
|
567
|
+
throw new ValidationError(
|
|
568
|
+
`scope.next: child '${chosen.id}' won the settle race without a resolved value`
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
return finalizeSettlement(chosen, settlement, seq, args, now);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
async function nextResolved() {
|
|
575
|
+
for (; ; ) {
|
|
576
|
+
const candidates = [...children.values()].filter((c) => !c.delivered);
|
|
577
|
+
const pick = candidates.find((c) => c.resolved !== void 0) ?? candidates.find((c) => c.executorDone);
|
|
578
|
+
if (!pick) return null;
|
|
579
|
+
const settlement = await pick.settled;
|
|
580
|
+
if (pick.delivered) continue;
|
|
581
|
+
pick.delivered = true;
|
|
582
|
+
const seq = cursorSeq++;
|
|
583
|
+
return finalizeSettlement(pick, settlement, seq, args, now);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
function send(nodeId, msg) {
|
|
587
|
+
const child = children.get(nodeId);
|
|
588
|
+
if (!child || child.delivered || !child.deliver) return false;
|
|
589
|
+
child.deliver(msg);
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
async function meter(spend, detail) {
|
|
593
|
+
const seq = meterSeq++;
|
|
594
|
+
args.pool.observe(spend);
|
|
595
|
+
await args.journal.appendEvent(args.root, {
|
|
596
|
+
kind: "metered",
|
|
597
|
+
id: args.parentId,
|
|
598
|
+
spend,
|
|
599
|
+
seq,
|
|
600
|
+
at: new Date(now()).toISOString()
|
|
601
|
+
});
|
|
602
|
+
notifyRuntimeHookEvent(
|
|
603
|
+
args.hooks,
|
|
604
|
+
{
|
|
605
|
+
id: `${args.parentId}:meter:${seq}`,
|
|
606
|
+
runId: args.root,
|
|
607
|
+
target: "agent.turn",
|
|
608
|
+
phase: "after",
|
|
609
|
+
timestamp: now(),
|
|
610
|
+
parentId: args.parentId,
|
|
611
|
+
payload: { spend, ...detail ?? {} }
|
|
612
|
+
},
|
|
613
|
+
{ signal: args.signal }
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
return {
|
|
617
|
+
spawn: spawn4,
|
|
618
|
+
next,
|
|
619
|
+
nextResolved,
|
|
620
|
+
send,
|
|
621
|
+
signal: args.signal,
|
|
622
|
+
meter,
|
|
623
|
+
get view() {
|
|
624
|
+
return makeTreeView(args.parentId, children);
|
|
625
|
+
},
|
|
626
|
+
get budget() {
|
|
627
|
+
return args.pool.readout();
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
async function raceFirstSettled(pending) {
|
|
632
|
+
return Promise.race(pending.map((c) => c.settled.then(() => c)));
|
|
633
|
+
}
|
|
634
|
+
async function finalizeSettlement(child, settlement, seq, args, now) {
|
|
635
|
+
const handle = frozenHandle(child);
|
|
636
|
+
if (settlement.kind === "down") {
|
|
637
|
+
child.status = "failed";
|
|
638
|
+
await args.journal.appendEvent(args.root, {
|
|
639
|
+
kind: "settled",
|
|
640
|
+
id: child.id,
|
|
641
|
+
status: "down",
|
|
642
|
+
spent: child.spent,
|
|
643
|
+
infra: settlement.infra,
|
|
644
|
+
seq,
|
|
645
|
+
at: new Date(now()).toISOString()
|
|
646
|
+
});
|
|
647
|
+
if (settlement.metered) {
|
|
648
|
+
await args.journal.appendEvent(args.root, {
|
|
649
|
+
kind: "metered",
|
|
650
|
+
id: child.id,
|
|
651
|
+
spend: settlement.metered,
|
|
652
|
+
seq,
|
|
653
|
+
at: new Date(now()).toISOString()
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
notifyRuntimeHookEvent(
|
|
657
|
+
args.hooks,
|
|
658
|
+
{
|
|
659
|
+
id: `${child.id}:settled`,
|
|
660
|
+
runId: args.root,
|
|
661
|
+
target: "agent.child",
|
|
662
|
+
phase: "after",
|
|
663
|
+
timestamp: now(),
|
|
664
|
+
stepIndex: seq,
|
|
665
|
+
parentId: args.parentId,
|
|
666
|
+
payload: {
|
|
667
|
+
childId: child.id,
|
|
668
|
+
status: "down",
|
|
669
|
+
reason: settlement.reason,
|
|
670
|
+
infra: settlement.infra,
|
|
671
|
+
spent: child.spent
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
{ signal: args.signal }
|
|
675
|
+
);
|
|
676
|
+
return {
|
|
677
|
+
kind: "down",
|
|
678
|
+
handle,
|
|
679
|
+
reason: settlement.reason,
|
|
680
|
+
infra: settlement.infra,
|
|
681
|
+
restartCount: settlement.restartCount,
|
|
682
|
+
seq
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
child.status = "done";
|
|
686
|
+
child.outRef = settlement.outRef;
|
|
687
|
+
child.spent = settlement.spent;
|
|
688
|
+
await args.journal.appendEvent(args.root, {
|
|
689
|
+
kind: "settled",
|
|
690
|
+
id: child.id,
|
|
691
|
+
status: "done",
|
|
692
|
+
outRef: settlement.outRef,
|
|
693
|
+
...settlement.verdict ? { verdict: settlement.verdict } : {},
|
|
694
|
+
spent: settlement.spent,
|
|
695
|
+
seq,
|
|
696
|
+
at: new Date(now()).toISOString()
|
|
697
|
+
});
|
|
698
|
+
if (settlement.metered) {
|
|
699
|
+
await args.journal.appendEvent(args.root, {
|
|
700
|
+
kind: "metered",
|
|
701
|
+
id: child.id,
|
|
702
|
+
spend: settlement.metered,
|
|
703
|
+
seq,
|
|
704
|
+
at: new Date(now()).toISOString()
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
notifyRuntimeHookEvent(
|
|
708
|
+
args.hooks,
|
|
709
|
+
{
|
|
710
|
+
id: `${child.id}:settled`,
|
|
711
|
+
runId: args.root,
|
|
712
|
+
target: "agent.child",
|
|
713
|
+
phase: "after",
|
|
714
|
+
timestamp: now(),
|
|
715
|
+
stepIndex: seq,
|
|
716
|
+
parentId: args.parentId,
|
|
717
|
+
payload: {
|
|
718
|
+
childId: child.id,
|
|
719
|
+
status: "done",
|
|
720
|
+
outRef: settlement.outRef,
|
|
721
|
+
score: settlement.verdict?.score,
|
|
722
|
+
valid: settlement.verdict?.valid,
|
|
723
|
+
spent: settlement.spent
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
{ signal: args.signal }
|
|
727
|
+
);
|
|
728
|
+
return {
|
|
729
|
+
kind: "done",
|
|
730
|
+
handle,
|
|
731
|
+
out: settlement.out,
|
|
732
|
+
outRef: settlement.outRef,
|
|
733
|
+
...settlement.verdict ? { verdict: settlement.verdict } : {},
|
|
734
|
+
spent: settlement.spent,
|
|
735
|
+
seq
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
async function runChild(live, executor, childAbort, task, opts, pool, ticket, blobs) {
|
|
739
|
+
let reconciled = false;
|
|
740
|
+
const reconcileOnce = (spend) => {
|
|
741
|
+
if (reconciled) return;
|
|
742
|
+
reconciled = true;
|
|
743
|
+
pool.reconcile(ticket, clampSpend(spend, opts.budget));
|
|
744
|
+
};
|
|
745
|
+
try {
|
|
746
|
+
live.status = "running";
|
|
747
|
+
const ran = executor.execute(task, childAbort.signal);
|
|
748
|
+
let artifact;
|
|
749
|
+
if (isAsyncIterable(ran)) {
|
|
750
|
+
const spend = await foldStream(ran);
|
|
751
|
+
live.spent = spend;
|
|
752
|
+
artifact = executor.resultArtifact();
|
|
753
|
+
reconcileOnce(spend);
|
|
754
|
+
} else {
|
|
755
|
+
const terminal = await ran;
|
|
756
|
+
live.spent = terminal.spent;
|
|
757
|
+
artifact = terminal;
|
|
758
|
+
reconcileOnce(terminal.spent);
|
|
759
|
+
}
|
|
760
|
+
live.executorDone = true;
|
|
761
|
+
const ownMetered = executor.metered?.();
|
|
762
|
+
if (childAbort.signal.aborted) {
|
|
763
|
+
await teardownSafe(executor, opts.shutdown ?? "brutalKill");
|
|
764
|
+
return downRecord("aborted before settle", true, ownMetered);
|
|
765
|
+
}
|
|
766
|
+
const outRef = contentAddress(artifact.out);
|
|
767
|
+
await blobs.put(outRef, artifact.out);
|
|
768
|
+
await teardownSafe(executor, opts.shutdown ?? "infinity");
|
|
769
|
+
return {
|
|
770
|
+
kind: "done",
|
|
771
|
+
out: artifact.out,
|
|
772
|
+
outRef,
|
|
773
|
+
...artifact.verdict ? { verdict: artifact.verdict } : {},
|
|
774
|
+
spent: live.spent,
|
|
775
|
+
...ownMetered ? { metered: ownMetered } : {}
|
|
776
|
+
};
|
|
777
|
+
} catch (err) {
|
|
778
|
+
live.executorDone = true;
|
|
779
|
+
reconcileOnce(live.spent);
|
|
780
|
+
await teardownSafe(executor, "brutalKill");
|
|
781
|
+
const aborted = childAbort.signal.aborted || isAbortError(err);
|
|
782
|
+
return downRecord(errMessage(err), aborted || isInfraError(err), executor.metered?.());
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
function settledToIteration(settled) {
|
|
786
|
+
if (settled.kind === "down") {
|
|
787
|
+
throw new ValidationError(
|
|
788
|
+
`settledToIteration: cannot adapt a 'down' settlement (node '${settled.handle.id}', seq ${settled.seq}) to an Iteration`
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
return {
|
|
792
|
+
index: settled.seq,
|
|
793
|
+
task: void 0,
|
|
794
|
+
agentRunName: settled.handle.label,
|
|
795
|
+
output: settled.out,
|
|
796
|
+
...settled.verdict ? { verdict: settled.verdict } : {},
|
|
797
|
+
events: [],
|
|
798
|
+
startedAt: 0,
|
|
799
|
+
endedAt: settled.spent.ms,
|
|
800
|
+
costUsd: settled.spent.usd,
|
|
801
|
+
tokenUsage: { input: settled.spent.tokens.input, output: settled.spent.tokens.output }
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
function makeTreeView(root, children) {
|
|
805
|
+
const nodes = [...children.values()].map((c) => ({
|
|
806
|
+
id: c.id,
|
|
807
|
+
parent: root,
|
|
808
|
+
label: c.label,
|
|
809
|
+
status: c.status,
|
|
810
|
+
runtime: c.runtime,
|
|
811
|
+
budget: c.budget,
|
|
812
|
+
spent: c.spent,
|
|
813
|
+
...c.outRef ? { outRef: c.outRef } : {}
|
|
814
|
+
}));
|
|
815
|
+
return {
|
|
816
|
+
root,
|
|
817
|
+
nodes,
|
|
818
|
+
inFlight: nodes.filter((n) => n.status === "running" || n.status === "acquiring").length
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
function frozenHandle(child) {
|
|
822
|
+
return {
|
|
823
|
+
id: child.id,
|
|
824
|
+
label: child.label,
|
|
825
|
+
status: child.status,
|
|
826
|
+
abort() {
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
async function foldStream(stream) {
|
|
831
|
+
const tokens = { input: 0, output: 0 };
|
|
832
|
+
let usd = 0;
|
|
833
|
+
let iterations = 0;
|
|
834
|
+
for await (const ev of stream) {
|
|
835
|
+
if (ev.kind === "tokens") {
|
|
836
|
+
tokens.input += ev.input;
|
|
837
|
+
tokens.output += ev.output;
|
|
838
|
+
} else if (ev.kind === "cost") {
|
|
839
|
+
usd += ev.usd;
|
|
840
|
+
} else {
|
|
841
|
+
iterations += 1;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return { iterations, tokens, usd, ms: 0 };
|
|
845
|
+
}
|
|
846
|
+
function clampSpend(spend, budget) {
|
|
847
|
+
const totalTokens2 = spend.tokens.input + spend.tokens.output;
|
|
848
|
+
const tokensOk = totalTokens2 <= budget.maxTokens;
|
|
849
|
+
const itersOk = spend.iterations <= budget.maxIterations;
|
|
850
|
+
const usdOk = budget.maxUsd === void 0 || spend.usd <= budget.maxUsd;
|
|
851
|
+
if (tokensOk && itersOk && usdOk) return spend;
|
|
852
|
+
const ratio = !tokensOk && totalTokens2 > 0 ? budget.maxTokens / totalTokens2 : 1;
|
|
853
|
+
return {
|
|
854
|
+
iterations: Math.min(spend.iterations, budget.maxIterations),
|
|
855
|
+
tokens: ratio < 1 ? {
|
|
856
|
+
input: Math.floor(spend.tokens.input * ratio),
|
|
857
|
+
output: Math.floor(spend.tokens.output * ratio)
|
|
858
|
+
} : spend.tokens,
|
|
859
|
+
usd: budget.maxUsd === void 0 ? spend.usd : Math.min(spend.usd, budget.maxUsd),
|
|
860
|
+
ms: spend.ms
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
async function teardownSafe(executor, grace) {
|
|
864
|
+
try {
|
|
865
|
+
await executor.teardown(grace);
|
|
866
|
+
} catch {
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
function downRecord(reason, infra, metered) {
|
|
870
|
+
return { kind: "down", reason, infra, restartCount: 0, ...metered ? { metered } : {} };
|
|
871
|
+
}
|
|
872
|
+
function zeroSpend() {
|
|
873
|
+
return { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
874
|
+
}
|
|
875
|
+
function isAsyncIterable(value) {
|
|
876
|
+
return typeof value === "object" && value !== null && typeof value[Symbol.asyncIterator] === "function";
|
|
877
|
+
}
|
|
878
|
+
function isAgentSpec(value) {
|
|
879
|
+
if (typeof value !== "object" || value === null) return false;
|
|
880
|
+
const v = value;
|
|
881
|
+
return "profile" in v && "harness" in v;
|
|
882
|
+
}
|
|
883
|
+
function isAbortError(err) {
|
|
884
|
+
return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
|
|
885
|
+
}
|
|
886
|
+
function isInfraError(err) {
|
|
887
|
+
return err instanceof ValidationError;
|
|
888
|
+
}
|
|
889
|
+
function errMessage(err) {
|
|
890
|
+
if (err instanceof Error) return err.message;
|
|
891
|
+
return String(err);
|
|
892
|
+
}
|
|
893
|
+
|
|
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;
|
|
404
999
|
}
|
|
405
1000
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const seq = cursorSeq++;
|
|
415
|
-
return finalizeSettlement(pick, settlement, seq, args, now);
|
|
416
|
-
}
|
|
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;
|
|
417
1009
|
}
|
|
418
|
-
function
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
1010
|
+
function readout() {
|
|
1011
|
+
return {
|
|
1012
|
+
tokensLeft: freeTokens,
|
|
1013
|
+
usdLeft: usdCapped ? freeUsd : 0,
|
|
1014
|
+
usdCapped,
|
|
1015
|
+
deadlineMs: absoluteDeadlineMs,
|
|
1016
|
+
reservedTokens
|
|
1017
|
+
};
|
|
423
1018
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
spend,
|
|
431
|
-
seq,
|
|
432
|
-
at: new Date(now()).toISOString()
|
|
433
|
-
});
|
|
434
|
-
notifyRuntimeHookEvent(
|
|
435
|
-
args.hooks,
|
|
436
|
-
{
|
|
437
|
-
id: `${args.parentId}:meter:${seq}`,
|
|
438
|
-
runId: args.root,
|
|
439
|
-
target: "agent.turn",
|
|
440
|
-
phase: "after",
|
|
441
|
-
timestamp: now(),
|
|
442
|
-
parentId: args.parentId,
|
|
443
|
-
payload: { spend, ...detail ?? {} }
|
|
444
|
-
},
|
|
445
|
-
{ signal: args.signal }
|
|
446
|
-
);
|
|
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
|
+
}
|
|
447
1025
|
}
|
|
448
1026
|
return {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
get view() {
|
|
456
|
-
return makeTreeView(args.parentId, children);
|
|
457
|
-
},
|
|
458
|
-
get budget() {
|
|
459
|
-
return args.pool.readout();
|
|
460
|
-
}
|
|
1027
|
+
reserve,
|
|
1028
|
+
reconcile,
|
|
1029
|
+
spendFrom: foldUsage,
|
|
1030
|
+
readout,
|
|
1031
|
+
observe,
|
|
1032
|
+
assertNoOpenTickets
|
|
461
1033
|
};
|
|
462
1034
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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,
|
|
477
1051
|
at: new Date(now()).toISOString()
|
|
478
1052
|
});
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
spend: settlement.metered,
|
|
484
|
-
seq,
|
|
485
|
-
at: new Date(now()).toISOString()
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
notifyRuntimeHookEvent(
|
|
489
|
-
args.hooks,
|
|
490
|
-
{
|
|
491
|
-
id: `${child.id}:settled`,
|
|
492
|
-
runId: args.root,
|
|
493
|
-
target: "agent.child",
|
|
494
|
-
phase: "after",
|
|
495
|
-
timestamp: now(),
|
|
496
|
-
stepIndex: seq,
|
|
497
|
-
parentId: args.parentId,
|
|
498
|
-
payload: {
|
|
499
|
-
childId: child.id,
|
|
500
|
-
status: "down",
|
|
501
|
-
reason: settlement.reason,
|
|
502
|
-
infra: settlement.infra,
|
|
503
|
-
spent: child.spent
|
|
504
|
-
}
|
|
505
|
-
},
|
|
506
|
-
{ signal: args.signal }
|
|
507
|
-
);
|
|
508
|
-
return {
|
|
509
|
-
kind: "down",
|
|
510
|
-
handle,
|
|
511
|
-
reason: settlement.reason,
|
|
512
|
-
infra: settlement.infra,
|
|
513
|
-
restartCount: settlement.restartCount,
|
|
514
|
-
seq
|
|
1053
|
+
const controller = new AbortController();
|
|
1054
|
+
const cascadeAbort = (reason) => {
|
|
1055
|
+
if (controller.signal.aborted) return;
|
|
1056
|
+
controller.abort(reason);
|
|
515
1057
|
};
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
at: new Date(now()).toISOString()
|
|
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
|
|
537
1078
|
});
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
outRef: settlement.outRef,
|
|
553
|
-
score: settlement.verdict?.score,
|
|
554
|
-
valid: settlement.verdict?.valid,
|
|
555
|
-
spent: settlement.spent
|
|
556
|
-
}
|
|
557
|
-
},
|
|
558
|
-
{ signal: args.signal }
|
|
559
|
-
);
|
|
560
|
-
return {
|
|
561
|
-
kind: "done",
|
|
562
|
-
handle,
|
|
563
|
-
out: settlement.out,
|
|
564
|
-
outRef: settlement.outRef,
|
|
565
|
-
...settlement.verdict ? { verdict: settlement.verdict } : {},
|
|
566
|
-
spent: settlement.spent,
|
|
567
|
-
seq
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
async function runChild(live, executor, childAbort, task, opts, pool, ticket, blobs) {
|
|
571
|
-
let reconciled = false;
|
|
572
|
-
const reconcileOnce = (spend) => {
|
|
573
|
-
if (reconciled) return;
|
|
574
|
-
reconciled = true;
|
|
575
|
-
pool.reconcile(ticket, clampSpend(spend, opts.budget));
|
|
576
|
-
};
|
|
577
|
-
try {
|
|
578
|
-
live.status = "running";
|
|
579
|
-
const ran = executor.execute(task, childAbort.signal);
|
|
580
|
-
let artifact;
|
|
581
|
-
if (isAsyncIterable2(ran)) {
|
|
582
|
-
const spend = await foldStream(ran);
|
|
583
|
-
live.spent = spend;
|
|
584
|
-
artifact = executor.resultArtifact();
|
|
585
|
-
reconcileOnce(spend);
|
|
586
|
-
} else {
|
|
587
|
-
const terminal = await ran;
|
|
588
|
-
live.spent = terminal.spent;
|
|
589
|
-
artifact = terminal;
|
|
590
|
-
reconcileOnce(terminal.spent);
|
|
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();
|
|
591
1093
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
+
};
|
|
597
1123
|
}
|
|
598
|
-
const outRef = contentAddress(artifact.out);
|
|
599
|
-
await blobs.put(outRef, artifact.out);
|
|
600
|
-
await teardownSafe(executor, opts.shutdown ?? "infinity");
|
|
601
|
-
return {
|
|
602
|
-
kind: "done",
|
|
603
|
-
out: artifact.out,
|
|
604
|
-
outRef,
|
|
605
|
-
...artifact.verdict ? { verdict: artifact.verdict } : {},
|
|
606
|
-
spent: live.spent,
|
|
607
|
-
...ownMetered ? { metered: ownMetered } : {}
|
|
608
|
-
};
|
|
609
|
-
} catch (err) {
|
|
610
|
-
live.executorDone = true;
|
|
611
|
-
reconcileOnce(live.spent);
|
|
612
|
-
await teardownSafe(executor, "brutalKill");
|
|
613
|
-
const aborted = childAbort.signal.aborted || isAbortError(err);
|
|
614
|
-
return downRecord(errMessage(err), aborted || isInfraError(err), executor.metered?.());
|
|
615
1124
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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;
|
|
622
1133
|
}
|
|
623
|
-
return {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
events: [],
|
|
630
|
-
startedAt: 0,
|
|
631
|
-
endedAt: settled.spent.ms,
|
|
632
|
-
costUsd: settled.spent.usd,
|
|
633
|
-
tokenUsage: { input: settled.spent.tokens.input, output: settled.spent.tokens.output }
|
|
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");
|
|
634
1140
|
};
|
|
635
1141
|
}
|
|
636
|
-
function
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
budget: c.budget,
|
|
644
|
-
spent: c.spent,
|
|
645
|
-
...c.outRef ? { outRef: c.outRef } : {}
|
|
646
|
-
}));
|
|
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;
|
|
647
1149
|
return {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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
|
+
}
|
|
651
1167
|
};
|
|
652
1168
|
}
|
|
653
|
-
function
|
|
1169
|
+
function wrapJournalForBreaker(journal, breaker) {
|
|
654
1170
|
return {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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);
|
|
659
1176
|
}
|
|
660
1177
|
};
|
|
661
1178
|
}
|
|
662
|
-
async function
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
} else {
|
|
673
|
-
iterations += 1;
|
|
674
|
-
}
|
|
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;
|
|
675
1189
|
}
|
|
676
|
-
return { iterations, tokens, usd, ms: 0 };
|
|
677
1190
|
}
|
|
678
|
-
function
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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) {
|
|
685
1229
|
return {
|
|
686
|
-
iterations:
|
|
687
|
-
tokens:
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
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
|
+
}
|
|
693
1276
|
};
|
|
694
1277
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
await executor.teardown(grace);
|
|
698
|
-
} catch {
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
function downRecord(reason, infra, metered) {
|
|
702
|
-
return { kind: "down", reason, infra, restartCount: 0, ...metered ? { metered } : {} };
|
|
703
|
-
}
|
|
704
|
-
function zeroSpend() {
|
|
705
|
-
return { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
706
|
-
}
|
|
707
|
-
function isAsyncIterable2(value) {
|
|
708
|
-
return typeof value === "object" && value !== null && typeof value[Symbol.asyncIterator] === "function";
|
|
709
|
-
}
|
|
710
|
-
function isAgentSpec(value) {
|
|
711
|
-
if (typeof value !== "object" || value === null) return false;
|
|
712
|
-
const v = value;
|
|
713
|
-
return "profile" in v && "harness" in v;
|
|
714
|
-
}
|
|
715
|
-
function isAbortError(err) {
|
|
716
|
-
return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
|
|
717
|
-
}
|
|
718
|
-
function isInfraError(err) {
|
|
719
|
-
return err instanceof ValidationError;
|
|
720
|
-
}
|
|
721
|
-
function errMessage(err) {
|
|
722
|
-
if (err instanceof Error) return err.message;
|
|
723
|
-
return String(err);
|
|
1278
|
+
function isAsyncIterable2(v) {
|
|
1279
|
+
return v != null && typeof v[Symbol.asyncIterator] === "function";
|
|
724
1280
|
}
|
|
725
1281
|
|
|
726
1282
|
// src/mcp/worktree.ts
|
|
@@ -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
|
|
3481
|
+
return JSON.stringify(out) ?? String(out);
|
|
3423
3482
|
} 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;
|
|
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";
|
|
3712
3616
|
}
|
|
3713
|
-
|
|
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;
|
|
3638
|
+
}
|
|
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
|
|
@@ -6545,11 +6551,14 @@ async function delegate(intent, opts = {}) {
|
|
|
6545
6551
|
}
|
|
6546
6552
|
|
|
6547
6553
|
export {
|
|
6548
|
-
assertModelAllowed,
|
|
6549
|
-
gateOnDeliverable,
|
|
6550
6554
|
contentAddress,
|
|
6551
6555
|
InMemoryResultBlobStore,
|
|
6552
6556
|
InMemorySpawnJournal,
|
|
6557
|
+
assertModelAllowed,
|
|
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-NC66AM3S.js.map
|