@tangle-network/agent-runtime 0.93.2 → 0.94.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +5 -6
- package/dist/agent.js.map +1 -1
- package/dist/{agentic-generator-B8oeE2Yv.d.ts → agentic-generator-D9ZmAuiL.d.ts} +1 -1
- package/dist/analyst-loop.d.ts +1 -1
- package/dist/candidate-execution/index.d.ts +10 -3
- package/dist/candidate-execution/index.js +9 -3
- package/dist/{chunk-6K5CI33W.js → chunk-D336OZHK.js} +1232 -19
- package/dist/chunk-D336OZHK.js.map +1 -0
- package/dist/{chunk-TU73X3I6.js → chunk-HPCJBBLS.js} +2 -2
- package/dist/chunk-ISTDY47H.js +849 -0
- package/dist/chunk-ISTDY47H.js.map +1 -0
- package/dist/{chunk-RTVVH2KL.js → chunk-JEP2WM24.js} +2 -2
- package/dist/{chunk-Z7K5BPKZ.js → chunk-K7OPNHU4.js} +2 -2
- package/dist/{chunk-OX3QRJOA.js → chunk-KHLE3C5G.js} +281 -251
- package/dist/chunk-KHLE3C5G.js.map +1 -0
- package/dist/{chunk-XDSWWUKE.js → chunk-MZH4HORF.js} +6 -29
- package/dist/chunk-MZH4HORF.js.map +1 -0
- package/dist/chunk-PJNGPWTA.js +634 -0
- package/dist/chunk-PJNGPWTA.js.map +1 -0
- package/dist/{chunk-X5KJKS33.js → chunk-RTSZNYXM.js} +3 -3
- package/dist/{chunk-WGR6QJ5H.js → chunk-SJHFJOCY.js} +8 -161
- package/dist/chunk-SJHFJOCY.js.map +1 -0
- package/dist/{chunk-2BQMAZAB.js → chunk-URMJTSXB.js} +1378 -28
- package/dist/chunk-URMJTSXB.js.map +1 -0
- package/dist/{chunk-3D2RHC4K.js → chunk-WIPGQ4GT.js} +1 -1
- package/dist/{chunk-3D2RHC4K.js.map → chunk-WIPGQ4GT.js.map} +1 -1
- package/dist/{chunk-R5GWDTM3.js → chunk-ZEYAT33L.js} +2 -2
- package/dist/{completion-gate-BLaiN0-X.d.ts → completion-gate-BAj3dXeW.d.ts} +14 -2
- package/dist/{coordination-DxJ83oZA.d.ts → coordination-B3ZuApR_.d.ts} +7 -5
- package/dist/environment-provider.d.ts +2 -2
- package/dist/{improve-DDhQaaJT.d.ts → improve-r8SvIxYv.d.ts} +13 -16
- package/dist/index.d.ts +39 -110
- package/dist/index.js +92 -36
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +24 -7
- package/dist/intelligence.js +114 -20
- package/dist/intelligence.js.map +1 -1
- package/dist/knowledge.d.ts +7 -7
- package/dist/knowledge.js +5 -5
- package/dist/lifecycle.d.ts +3 -3
- package/dist/lifecycle.js +2 -2
- package/dist/{local-harness-dcD5WTTr.d.ts → local-harness-B7bNuNJw.d.ts} +49 -1
- package/dist/{loop-runner-bin-kKUNGLyV.d.ts → loop-runner-bin-D4bMIxmB.d.ts} +2 -2
- package/dist/loop-runner-bin.d.ts +6 -6
- package/dist/loop-runner-bin.js +6 -7
- package/dist/loops.d.ts +457 -18
- package/dist/loops.js +39 -41
- package/dist/mcp/bin.js +5 -5
- package/dist/mcp/index.d.ts +10 -10
- package/dist/mcp/index.js +9 -8
- package/dist/{mcp-serve-verifier-Bg4C3p5S.d.ts → mcp-serve-verifier-BuXjaZDE.d.ts} +1 -1
- package/dist/{openai-tools-E3woykz9.d.ts → openai-tools-D0ZSRCC6.d.ts} +1 -1
- package/dist/{prepare-DiVGKcwS.d.ts → prepare-BKxAiUcH.d.ts} +1 -1
- package/dist/profiles.d.ts +1 -1
- package/dist/{sanitize-C9go6tXj.d.ts → sanitize-C2jicjNf.d.ts} +1 -1
- package/dist/{supervise-T2pazU3G.d.ts → supervise-DTNcLDl2.d.ts} +4 -4
- package/dist/{types-DAdIm4AC.d.ts → types-Bb-CqxJH.d.ts} +5 -2
- package/dist/{types-B00NtbCs.d.ts → types-DWA64rbJ.d.ts} +1 -1
- package/dist/{worktree-fanout-BUb2Ag02.d.ts → worktree-fanout-IMKqdV_E.d.ts} +244 -244
- package/package.json +4 -4
- package/skills/build-with-agent-runtime/SKILL.md +1 -1
- package/dist/chunk-2BQMAZAB.js.map +0 -1
- package/dist/chunk-6K5CI33W.js.map +0 -1
- package/dist/chunk-FDJ7AHXG.js +0 -1229
- package/dist/chunk-FDJ7AHXG.js.map +0 -1
- package/dist/chunk-J2K6WIG6.js +0 -2172
- package/dist/chunk-J2K6WIG6.js.map +0 -1
- package/dist/chunk-OX3QRJOA.js.map +0 -1
- package/dist/chunk-PX6SXX3M.js +0 -146
- package/dist/chunk-PX6SXX3M.js.map +0 -1
- package/dist/chunk-WGR6QJ5H.js.map +0 -1
- package/dist/chunk-XDSWWUKE.js.map +0 -1
- package/dist/structural-rollout-DHGDbhvR.d.ts +0 -446
- /package/dist/{chunk-TU73X3I6.js.map → chunk-HPCJBBLS.js.map} +0 -0
- /package/dist/{chunk-RTVVH2KL.js.map → chunk-JEP2WM24.js.map} +0 -0
- /package/dist/{chunk-Z7K5BPKZ.js.map → chunk-K7OPNHU4.js.map} +0 -0
- /package/dist/{chunk-X5KJKS33.js.map → chunk-RTSZNYXM.js.map} +0 -0
- /package/dist/{chunk-R5GWDTM3.js.map → chunk-ZEYAT33L.js.map} +0 -0
|
@@ -3,19 +3,13 @@ import {
|
|
|
3
3
|
} from "./chunk-DPEUKJRO.js";
|
|
4
4
|
import {
|
|
5
5
|
assertModelAllowed,
|
|
6
|
-
buildLoopSpanNodes
|
|
7
|
-
|
|
8
|
-
notifyRuntimeHookEvent,
|
|
9
|
-
routerBrain,
|
|
10
|
-
routerChatWithUsage,
|
|
11
|
-
runBrainLoop,
|
|
12
|
-
withDriverExecutor
|
|
13
|
-
} from "./chunk-J2K6WIG6.js";
|
|
6
|
+
buildLoopSpanNodes
|
|
7
|
+
} from "./chunk-ISTDY47H.js";
|
|
14
8
|
import {
|
|
15
9
|
InMemoryResultBlobStore,
|
|
16
10
|
InMemorySpawnJournal,
|
|
17
11
|
contentAddress
|
|
18
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-WIPGQ4GT.js";
|
|
19
13
|
import {
|
|
20
14
|
providerAsExecutor,
|
|
21
15
|
resolveAgentEnvironmentProvider
|
|
@@ -39,11 +33,12 @@ import {
|
|
|
39
33
|
import {
|
|
40
34
|
harnessInvocation,
|
|
41
35
|
runLocalHarness
|
|
42
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-PJNGPWTA.js";
|
|
43
37
|
import {
|
|
44
38
|
AgentEvalError,
|
|
45
39
|
ConfigError,
|
|
46
40
|
NotFoundError,
|
|
41
|
+
RuntimeRunStateError,
|
|
47
42
|
ValidationError
|
|
48
43
|
} from "./chunk-YEJR7IXO.js";
|
|
49
44
|
|
|
@@ -89,6 +84,577 @@ function isAsyncIterable(v) {
|
|
|
89
84
|
return v != null && typeof v[Symbol.asyncIterator] === "function";
|
|
90
85
|
}
|
|
91
86
|
|
|
87
|
+
// src/runtime-hooks.ts
|
|
88
|
+
function defineRuntimeHooks(hooks) {
|
|
89
|
+
return hooks;
|
|
90
|
+
}
|
|
91
|
+
function composeRuntimeHooks(...entries) {
|
|
92
|
+
const hooks = entries.filter((entry) => !!entry);
|
|
93
|
+
return {
|
|
94
|
+
onEvent: hooks.some((hook) => hook.onEvent) ? (event, context) => {
|
|
95
|
+
const pending = [];
|
|
96
|
+
for (const hook of hooks) {
|
|
97
|
+
const result = hook.onEvent?.(event, context);
|
|
98
|
+
if (isThenable(result)) pending.push(Promise.resolve(result));
|
|
99
|
+
}
|
|
100
|
+
if (pending.length > 0) return Promise.all(pending).then(() => void 0);
|
|
101
|
+
return void 0;
|
|
102
|
+
} : void 0,
|
|
103
|
+
onDecisionPoint: hooks.some((hook) => hook.onDecisionPoint) ? (point, context) => {
|
|
104
|
+
const pending = [];
|
|
105
|
+
for (const hook of hooks) {
|
|
106
|
+
const result = hook.onDecisionPoint?.(point, context);
|
|
107
|
+
if (isThenable(result)) pending.push(Promise.resolve(result));
|
|
108
|
+
}
|
|
109
|
+
if (pending.length > 0) return Promise.all(pending).then(() => void 0);
|
|
110
|
+
return void 0;
|
|
111
|
+
} : void 0,
|
|
112
|
+
onHookError: hooks.some((hook) => hook.onHookError) ? (error, context) => {
|
|
113
|
+
const pending = [];
|
|
114
|
+
for (const hook of hooks) {
|
|
115
|
+
const result = hook.onHookError?.(error, context);
|
|
116
|
+
if (isThenable(result)) pending.push(Promise.resolve(result));
|
|
117
|
+
}
|
|
118
|
+
if (pending.length > 0) return Promise.all(pending).then(() => void 0);
|
|
119
|
+
return void 0;
|
|
120
|
+
} : void 0
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function notifyRuntimeHookEvent(hooks, event, context = {}) {
|
|
124
|
+
const onEvent = hooks?.onEvent;
|
|
125
|
+
if (!onEvent) return;
|
|
126
|
+
try {
|
|
127
|
+
const result = onEvent(event, context);
|
|
128
|
+
if (isThenable(result)) {
|
|
129
|
+
void result.catch((error) => {
|
|
130
|
+
notifyRuntimeHookError(hooks, toError(error), {
|
|
131
|
+
hook: "onEvent",
|
|
132
|
+
eventId: event.id,
|
|
133
|
+
target: event.target,
|
|
134
|
+
phase: event.phase
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
notifyRuntimeHookError(hooks, toError(error), {
|
|
140
|
+
hook: "onEvent",
|
|
141
|
+
eventId: event.id,
|
|
142
|
+
target: event.target,
|
|
143
|
+
phase: event.phase
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function notifyRuntimeDecisionPoint(hooks, point, context = {}) {
|
|
148
|
+
const onDecisionPoint = hooks?.onDecisionPoint;
|
|
149
|
+
if (!onDecisionPoint) return;
|
|
150
|
+
try {
|
|
151
|
+
const result = onDecisionPoint(point, context);
|
|
152
|
+
if (isThenable(result)) {
|
|
153
|
+
void result.catch((error) => {
|
|
154
|
+
notifyRuntimeHookError(hooks, toError(error), {
|
|
155
|
+
hook: "onDecisionPoint",
|
|
156
|
+
decisionId: point.id,
|
|
157
|
+
decisionKind: point.kind
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
notifyRuntimeHookError(hooks, toError(error), {
|
|
163
|
+
hook: "onDecisionPoint",
|
|
164
|
+
decisionId: point.id,
|
|
165
|
+
decisionKind: point.kind
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function notifyRuntimeHookError(hooks, error, context) {
|
|
170
|
+
try {
|
|
171
|
+
const result = hooks?.onHookError?.(error, context);
|
|
172
|
+
if (isThenable(result)) void result.catch(() => void 0);
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function isThenable(value) {
|
|
177
|
+
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
178
|
+
}
|
|
179
|
+
function toError(error) {
|
|
180
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/runtime/supervise/scope.ts
|
|
184
|
+
var nestedScopeSeamKey = "nested-scope";
|
|
185
|
+
function makeNestedScopeSeam(args, childNodeId) {
|
|
186
|
+
return {
|
|
187
|
+
depth: args.depth,
|
|
188
|
+
...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
|
|
189
|
+
journalRoot: args.root,
|
|
190
|
+
mount(nestedRoot, signal) {
|
|
191
|
+
return createScope({
|
|
192
|
+
parentId: childNodeId,
|
|
193
|
+
root: nestedRoot,
|
|
194
|
+
pool: args.pool,
|
|
195
|
+
journal: args.journal,
|
|
196
|
+
blobs: args.blobs,
|
|
197
|
+
executors: args.executors,
|
|
198
|
+
// Re-seed the parent's NON-recursion seams (sandbox/router for leaf grandchildren);
|
|
199
|
+
// the nested scope adds its OWN nested-scope seam per child in `spawn`.
|
|
200
|
+
seams: args.seams,
|
|
201
|
+
depth: args.depth + 1,
|
|
202
|
+
...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
|
|
203
|
+
signal,
|
|
204
|
+
...args.now ? { now: args.now } : {},
|
|
205
|
+
...args.hooks ? { hooks: args.hooks } : {}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function createScope(args) {
|
|
211
|
+
const children = /* @__PURE__ */ new Map();
|
|
212
|
+
let spawnOrdinal = 0;
|
|
213
|
+
let cursorSeq = 0;
|
|
214
|
+
let meterSeq = 0;
|
|
215
|
+
const now = args.now ?? Date.now;
|
|
216
|
+
function spawn4(agent, task, opts) {
|
|
217
|
+
if (args.maxDepth !== void 0 && args.depth >= args.maxDepth) {
|
|
218
|
+
return { ok: false, reason: "depth-exceeded" };
|
|
219
|
+
}
|
|
220
|
+
const spec = agent.executorSpec;
|
|
221
|
+
if (!isAgentSpec(spec)) {
|
|
222
|
+
throw new ValidationError(
|
|
223
|
+
`scope.spawn: agent "${agent.name}" exposes no \`executorSpec\` (AgentSpec) to resolve a Executor`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
const resolved = args.executors.resolve(spec);
|
|
227
|
+
if (!resolved.succeeded) throw new ValidationError(`scope.spawn: ${resolved.error}`);
|
|
228
|
+
const reservation = args.pool.reserve(opts.budget);
|
|
229
|
+
if (!reservation.ok) return { ok: false, reason: reservation.reason };
|
|
230
|
+
try {
|
|
231
|
+
const ordinal = spawnOrdinal++;
|
|
232
|
+
const id = `${args.parentId}:s${ordinal}`;
|
|
233
|
+
const childAbort = new AbortController();
|
|
234
|
+
const cascadeAbort = () => childAbort.abort();
|
|
235
|
+
if (args.signal.aborted) childAbort.abort();
|
|
236
|
+
else args.signal.addEventListener("abort", cascadeAbort, { once: true });
|
|
237
|
+
const ctx = {
|
|
238
|
+
signal: childAbort.signal,
|
|
239
|
+
seams: { ...args.seams, [nestedScopeSeamKey]: makeNestedScopeSeam(args, id) }
|
|
240
|
+
};
|
|
241
|
+
const executor = resolved.value(spec, ctx);
|
|
242
|
+
const handle = {
|
|
243
|
+
id,
|
|
244
|
+
label: opts.label,
|
|
245
|
+
get status() {
|
|
246
|
+
return children.get(id)?.status ?? "cancelled";
|
|
247
|
+
},
|
|
248
|
+
abort(reason) {
|
|
249
|
+
childAbort.abort(reason);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
const live = {
|
|
253
|
+
id,
|
|
254
|
+
status: "acquiring",
|
|
255
|
+
runtime: executor.runtime,
|
|
256
|
+
budget: opts.budget,
|
|
257
|
+
label: opts.label,
|
|
258
|
+
spent: zeroSpend(),
|
|
259
|
+
settled: void 0,
|
|
260
|
+
delivered: false,
|
|
261
|
+
executorDone: false,
|
|
262
|
+
...executor.deliver ? { deliver: executor.deliver.bind(executor) } : {}
|
|
263
|
+
};
|
|
264
|
+
children.set(id, live);
|
|
265
|
+
void args.journal.appendEvent(args.root, {
|
|
266
|
+
kind: "spawned",
|
|
267
|
+
id,
|
|
268
|
+
parent: args.parentId,
|
|
269
|
+
label: opts.label,
|
|
270
|
+
budget: opts.budget,
|
|
271
|
+
runtime: executor.runtime,
|
|
272
|
+
seq: ordinal,
|
|
273
|
+
at: new Date(now()).toISOString()
|
|
274
|
+
});
|
|
275
|
+
notifyRuntimeHookEvent(
|
|
276
|
+
args.hooks,
|
|
277
|
+
{
|
|
278
|
+
id: `${id}:spawn`,
|
|
279
|
+
runId: args.root,
|
|
280
|
+
target: "agent.spawn",
|
|
281
|
+
phase: "after",
|
|
282
|
+
timestamp: now(),
|
|
283
|
+
stepIndex: ordinal,
|
|
284
|
+
parentId: args.parentId,
|
|
285
|
+
payload: {
|
|
286
|
+
childId: id,
|
|
287
|
+
label: opts.label,
|
|
288
|
+
runtime: executor.runtime,
|
|
289
|
+
budget: opts.budget,
|
|
290
|
+
depth: args.depth
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
{ signal: args.signal }
|
|
294
|
+
);
|
|
295
|
+
const settled = runChild(
|
|
296
|
+
live,
|
|
297
|
+
executor,
|
|
298
|
+
childAbort,
|
|
299
|
+
task,
|
|
300
|
+
opts,
|
|
301
|
+
args.pool,
|
|
302
|
+
reservation.ticket,
|
|
303
|
+
args.blobs
|
|
304
|
+
).then((s) => {
|
|
305
|
+
live.resolved = s;
|
|
306
|
+
return s;
|
|
307
|
+
}).finally(() => {
|
|
308
|
+
args.signal.removeEventListener("abort", cascadeAbort);
|
|
309
|
+
});
|
|
310
|
+
live.settled = settled;
|
|
311
|
+
return { ok: true, handle };
|
|
312
|
+
} catch (err) {
|
|
313
|
+
args.pool.reconcile(reservation.ticket, zeroSpend());
|
|
314
|
+
throw err;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async function next() {
|
|
318
|
+
const undelivered = () => [...children.values()].filter((c) => !c.delivered);
|
|
319
|
+
if (undelivered().length === 0) return null;
|
|
320
|
+
for (; ; ) {
|
|
321
|
+
const pending = undelivered();
|
|
322
|
+
if (pending.length === 0) return null;
|
|
323
|
+
const ready = pending.find((c) => c.resolved !== void 0);
|
|
324
|
+
const chosen = ready ?? await raceFirstSettled(pending);
|
|
325
|
+
if (chosen.delivered) continue;
|
|
326
|
+
chosen.delivered = true;
|
|
327
|
+
const seq = cursorSeq++;
|
|
328
|
+
const settlement = chosen.resolved;
|
|
329
|
+
if (!settlement) {
|
|
330
|
+
throw new ValidationError(
|
|
331
|
+
`scope.next: child '${chosen.id}' won the settle race without a resolved value`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
return finalizeSettlement(chosen, settlement, seq, args, now);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async function nextResolved() {
|
|
338
|
+
for (; ; ) {
|
|
339
|
+
const candidates = [...children.values()].filter((c) => !c.delivered);
|
|
340
|
+
const pick = candidates.find((c) => c.resolved !== void 0) ?? candidates.find((c) => c.executorDone);
|
|
341
|
+
if (!pick) return null;
|
|
342
|
+
const settlement = await pick.settled;
|
|
343
|
+
if (pick.delivered) continue;
|
|
344
|
+
pick.delivered = true;
|
|
345
|
+
const seq = cursorSeq++;
|
|
346
|
+
return finalizeSettlement(pick, settlement, seq, args, now);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function send(nodeId, msg) {
|
|
350
|
+
const child = children.get(nodeId);
|
|
351
|
+
if (!child || child.delivered || !child.deliver) return false;
|
|
352
|
+
child.deliver(msg);
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
async function meter(spend, detail) {
|
|
356
|
+
const seq = meterSeq++;
|
|
357
|
+
args.pool.observe(spend);
|
|
358
|
+
await args.journal.appendEvent(args.root, {
|
|
359
|
+
kind: "metered",
|
|
360
|
+
id: args.parentId,
|
|
361
|
+
spend,
|
|
362
|
+
seq,
|
|
363
|
+
at: new Date(now()).toISOString()
|
|
364
|
+
});
|
|
365
|
+
notifyRuntimeHookEvent(
|
|
366
|
+
args.hooks,
|
|
367
|
+
{
|
|
368
|
+
id: `${args.parentId}:meter:${seq}`,
|
|
369
|
+
runId: args.root,
|
|
370
|
+
target: "agent.turn",
|
|
371
|
+
phase: "after",
|
|
372
|
+
timestamp: now(),
|
|
373
|
+
parentId: args.parentId,
|
|
374
|
+
payload: { spend, ...detail ?? {} }
|
|
375
|
+
},
|
|
376
|
+
{ signal: args.signal }
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
spawn: spawn4,
|
|
381
|
+
next,
|
|
382
|
+
nextResolved,
|
|
383
|
+
send,
|
|
384
|
+
signal: args.signal,
|
|
385
|
+
meter,
|
|
386
|
+
get view() {
|
|
387
|
+
return makeTreeView(args.parentId, children);
|
|
388
|
+
},
|
|
389
|
+
get budget() {
|
|
390
|
+
return args.pool.readout();
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
async function raceFirstSettled(pending) {
|
|
395
|
+
return Promise.race(pending.map((c) => c.settled.then(() => c)));
|
|
396
|
+
}
|
|
397
|
+
async function finalizeSettlement(child, settlement, seq, args, now) {
|
|
398
|
+
const handle = frozenHandle(child);
|
|
399
|
+
if (settlement.kind === "down") {
|
|
400
|
+
child.status = "failed";
|
|
401
|
+
await args.journal.appendEvent(args.root, {
|
|
402
|
+
kind: "settled",
|
|
403
|
+
id: child.id,
|
|
404
|
+
status: "down",
|
|
405
|
+
spent: child.spent,
|
|
406
|
+
infra: settlement.infra,
|
|
407
|
+
seq,
|
|
408
|
+
at: new Date(now()).toISOString()
|
|
409
|
+
});
|
|
410
|
+
if (settlement.metered) {
|
|
411
|
+
await args.journal.appendEvent(args.root, {
|
|
412
|
+
kind: "metered",
|
|
413
|
+
id: child.id,
|
|
414
|
+
spend: settlement.metered,
|
|
415
|
+
seq,
|
|
416
|
+
at: new Date(now()).toISOString()
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
notifyRuntimeHookEvent(
|
|
420
|
+
args.hooks,
|
|
421
|
+
{
|
|
422
|
+
id: `${child.id}:settled`,
|
|
423
|
+
runId: args.root,
|
|
424
|
+
target: "agent.child",
|
|
425
|
+
phase: "after",
|
|
426
|
+
timestamp: now(),
|
|
427
|
+
stepIndex: seq,
|
|
428
|
+
parentId: args.parentId,
|
|
429
|
+
payload: {
|
|
430
|
+
childId: child.id,
|
|
431
|
+
status: "down",
|
|
432
|
+
reason: settlement.reason,
|
|
433
|
+
infra: settlement.infra,
|
|
434
|
+
spent: child.spent
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
{ signal: args.signal }
|
|
438
|
+
);
|
|
439
|
+
return {
|
|
440
|
+
kind: "down",
|
|
441
|
+
handle,
|
|
442
|
+
reason: settlement.reason,
|
|
443
|
+
infra: settlement.infra,
|
|
444
|
+
restartCount: settlement.restartCount,
|
|
445
|
+
seq
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
child.status = "done";
|
|
449
|
+
child.outRef = settlement.outRef;
|
|
450
|
+
child.spent = settlement.spent;
|
|
451
|
+
await args.journal.appendEvent(args.root, {
|
|
452
|
+
kind: "settled",
|
|
453
|
+
id: child.id,
|
|
454
|
+
status: "done",
|
|
455
|
+
outRef: settlement.outRef,
|
|
456
|
+
...settlement.verdict ? { verdict: settlement.verdict } : {},
|
|
457
|
+
spent: settlement.spent,
|
|
458
|
+
seq,
|
|
459
|
+
at: new Date(now()).toISOString()
|
|
460
|
+
});
|
|
461
|
+
if (settlement.metered) {
|
|
462
|
+
await args.journal.appendEvent(args.root, {
|
|
463
|
+
kind: "metered",
|
|
464
|
+
id: child.id,
|
|
465
|
+
spend: settlement.metered,
|
|
466
|
+
seq,
|
|
467
|
+
at: new Date(now()).toISOString()
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
notifyRuntimeHookEvent(
|
|
471
|
+
args.hooks,
|
|
472
|
+
{
|
|
473
|
+
id: `${child.id}:settled`,
|
|
474
|
+
runId: args.root,
|
|
475
|
+
target: "agent.child",
|
|
476
|
+
phase: "after",
|
|
477
|
+
timestamp: now(),
|
|
478
|
+
stepIndex: seq,
|
|
479
|
+
parentId: args.parentId,
|
|
480
|
+
payload: {
|
|
481
|
+
childId: child.id,
|
|
482
|
+
status: "done",
|
|
483
|
+
outRef: settlement.outRef,
|
|
484
|
+
score: settlement.verdict?.score,
|
|
485
|
+
valid: settlement.verdict?.valid,
|
|
486
|
+
spent: settlement.spent
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
{ signal: args.signal }
|
|
490
|
+
);
|
|
491
|
+
return {
|
|
492
|
+
kind: "done",
|
|
493
|
+
handle,
|
|
494
|
+
out: settlement.out,
|
|
495
|
+
outRef: settlement.outRef,
|
|
496
|
+
...settlement.verdict ? { verdict: settlement.verdict } : {},
|
|
497
|
+
spent: settlement.spent,
|
|
498
|
+
seq
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
async function runChild(live, executor, childAbort, task, opts, pool, ticket, blobs) {
|
|
502
|
+
let reconciled = false;
|
|
503
|
+
const reconcileOnce = (spend) => {
|
|
504
|
+
if (reconciled) return;
|
|
505
|
+
reconciled = true;
|
|
506
|
+
pool.reconcile(ticket, clampSpend(spend, opts.budget));
|
|
507
|
+
};
|
|
508
|
+
try {
|
|
509
|
+
live.status = "running";
|
|
510
|
+
const ran = executor.execute(task, childAbort.signal);
|
|
511
|
+
let artifact;
|
|
512
|
+
if (isAsyncIterable2(ran)) {
|
|
513
|
+
const spend = await foldStream(ran);
|
|
514
|
+
live.spent = spend;
|
|
515
|
+
artifact = executor.resultArtifact();
|
|
516
|
+
reconcileOnce(spend);
|
|
517
|
+
} else {
|
|
518
|
+
const terminal = await ran;
|
|
519
|
+
live.spent = terminal.spent;
|
|
520
|
+
artifact = terminal;
|
|
521
|
+
reconcileOnce(terminal.spent);
|
|
522
|
+
}
|
|
523
|
+
live.executorDone = true;
|
|
524
|
+
const ownMetered = executor.metered?.();
|
|
525
|
+
if (childAbort.signal.aborted) {
|
|
526
|
+
await teardownSafe(executor, opts.shutdown ?? "brutalKill");
|
|
527
|
+
return downRecord("aborted before settle", true, ownMetered);
|
|
528
|
+
}
|
|
529
|
+
const outRef = contentAddress(artifact.out);
|
|
530
|
+
await blobs.put(outRef, artifact.out);
|
|
531
|
+
await teardownSafe(executor, opts.shutdown ?? "infinity");
|
|
532
|
+
return {
|
|
533
|
+
kind: "done",
|
|
534
|
+
out: artifact.out,
|
|
535
|
+
outRef,
|
|
536
|
+
...artifact.verdict ? { verdict: artifact.verdict } : {},
|
|
537
|
+
spent: live.spent,
|
|
538
|
+
...ownMetered ? { metered: ownMetered } : {}
|
|
539
|
+
};
|
|
540
|
+
} catch (err) {
|
|
541
|
+
live.executorDone = true;
|
|
542
|
+
reconcileOnce(live.spent);
|
|
543
|
+
await teardownSafe(executor, "brutalKill");
|
|
544
|
+
const aborted = childAbort.signal.aborted || isAbortError(err);
|
|
545
|
+
return downRecord(errMessage(err), aborted || isInfraError(err), executor.metered?.());
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function settledToIteration(settled) {
|
|
549
|
+
if (settled.kind === "down") {
|
|
550
|
+
throw new ValidationError(
|
|
551
|
+
`settledToIteration: cannot adapt a 'down' settlement (node '${settled.handle.id}', seq ${settled.seq}) to an Iteration`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
index: settled.seq,
|
|
556
|
+
task: void 0,
|
|
557
|
+
agentRunName: settled.handle.label,
|
|
558
|
+
output: settled.out,
|
|
559
|
+
...settled.verdict ? { verdict: settled.verdict } : {},
|
|
560
|
+
events: [],
|
|
561
|
+
startedAt: 0,
|
|
562
|
+
endedAt: settled.spent.ms,
|
|
563
|
+
costUsd: settled.spent.usd,
|
|
564
|
+
tokenUsage: { input: settled.spent.tokens.input, output: settled.spent.tokens.output }
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
function makeTreeView(root, children) {
|
|
568
|
+
const nodes = [...children.values()].map((c) => ({
|
|
569
|
+
id: c.id,
|
|
570
|
+
parent: root,
|
|
571
|
+
label: c.label,
|
|
572
|
+
status: c.status,
|
|
573
|
+
runtime: c.runtime,
|
|
574
|
+
budget: c.budget,
|
|
575
|
+
spent: c.spent,
|
|
576
|
+
...c.outRef ? { outRef: c.outRef } : {}
|
|
577
|
+
}));
|
|
578
|
+
return {
|
|
579
|
+
root,
|
|
580
|
+
nodes,
|
|
581
|
+
inFlight: nodes.filter((n) => n.status === "running" || n.status === "acquiring").length
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function frozenHandle(child) {
|
|
585
|
+
return {
|
|
586
|
+
id: child.id,
|
|
587
|
+
label: child.label,
|
|
588
|
+
status: child.status,
|
|
589
|
+
abort() {
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
async function foldStream(stream) {
|
|
594
|
+
const tokens = { input: 0, output: 0 };
|
|
595
|
+
let usd = 0;
|
|
596
|
+
let iterations = 0;
|
|
597
|
+
for await (const ev of stream) {
|
|
598
|
+
if (ev.kind === "tokens") {
|
|
599
|
+
tokens.input += ev.input;
|
|
600
|
+
tokens.output += ev.output;
|
|
601
|
+
} else if (ev.kind === "cost") {
|
|
602
|
+
usd += ev.usd;
|
|
603
|
+
} else {
|
|
604
|
+
iterations += 1;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return { iterations, tokens, usd, ms: 0 };
|
|
608
|
+
}
|
|
609
|
+
function clampSpend(spend, budget) {
|
|
610
|
+
const totalTokens2 = spend.tokens.input + spend.tokens.output;
|
|
611
|
+
const tokensOk = totalTokens2 <= budget.maxTokens;
|
|
612
|
+
const itersOk = spend.iterations <= budget.maxIterations;
|
|
613
|
+
const usdOk = budget.maxUsd === void 0 || spend.usd <= budget.maxUsd;
|
|
614
|
+
if (tokensOk && itersOk && usdOk) return spend;
|
|
615
|
+
const ratio = !tokensOk && totalTokens2 > 0 ? budget.maxTokens / totalTokens2 : 1;
|
|
616
|
+
return {
|
|
617
|
+
iterations: Math.min(spend.iterations, budget.maxIterations),
|
|
618
|
+
tokens: ratio < 1 ? {
|
|
619
|
+
input: Math.floor(spend.tokens.input * ratio),
|
|
620
|
+
output: Math.floor(spend.tokens.output * ratio)
|
|
621
|
+
} : spend.tokens,
|
|
622
|
+
usd: budget.maxUsd === void 0 ? spend.usd : Math.min(spend.usd, budget.maxUsd),
|
|
623
|
+
...spend.usdKnown === false ? { usdKnown: false } : {},
|
|
624
|
+
ms: spend.ms
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
async function teardownSafe(executor, grace) {
|
|
628
|
+
try {
|
|
629
|
+
await executor.teardown(grace);
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function downRecord(reason, infra, metered) {
|
|
634
|
+
return { kind: "down", reason, infra, restartCount: 0, ...metered ? { metered } : {} };
|
|
635
|
+
}
|
|
636
|
+
function zeroSpend() {
|
|
637
|
+
return { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
638
|
+
}
|
|
639
|
+
function isAsyncIterable2(value) {
|
|
640
|
+
return typeof value === "object" && value !== null && typeof value[Symbol.asyncIterator] === "function";
|
|
641
|
+
}
|
|
642
|
+
function isAgentSpec(value) {
|
|
643
|
+
if (typeof value !== "object" || value === null) return false;
|
|
644
|
+
const v = value;
|
|
645
|
+
return "profile" in v && "harness" in v;
|
|
646
|
+
}
|
|
647
|
+
function isAbortError(err) {
|
|
648
|
+
return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
|
|
649
|
+
}
|
|
650
|
+
function isInfraError(err) {
|
|
651
|
+
return err instanceof ValidationError;
|
|
652
|
+
}
|
|
653
|
+
function errMessage(err) {
|
|
654
|
+
if (err instanceof Error) return err.message;
|
|
655
|
+
return String(err);
|
|
656
|
+
}
|
|
657
|
+
|
|
92
658
|
// src/mcp/worktree.ts
|
|
93
659
|
import { spawn } from "child_process";
|
|
94
660
|
async function runGitAsync(args, cwd, runner) {
|
|
@@ -175,6 +741,216 @@ async function removeWorktree(options) {
|
|
|
175
741
|
).catch(() => void 0);
|
|
176
742
|
}
|
|
177
743
|
|
|
744
|
+
// src/runtime/router-client.ts
|
|
745
|
+
import { estimateCost, isModelPriced } from "@tangle-network/agent-eval";
|
|
746
|
+
|
|
747
|
+
// src/runtime/tool-loop.ts
|
|
748
|
+
function estimateConversationTokens(messages) {
|
|
749
|
+
let chars = 0;
|
|
750
|
+
for (const m of messages) {
|
|
751
|
+
const content = m.content;
|
|
752
|
+
chars += typeof content === "string" ? content.length : safeJsonLength(content);
|
|
753
|
+
const calls = m.tool_calls;
|
|
754
|
+
if (calls) for (const c of calls) chars += c.function?.arguments?.length ?? 0;
|
|
755
|
+
}
|
|
756
|
+
return Math.ceil(chars / 4);
|
|
757
|
+
}
|
|
758
|
+
function safeJsonLength(value) {
|
|
759
|
+
if (value === void 0 || value === null) return 0;
|
|
760
|
+
try {
|
|
761
|
+
return JSON.stringify(value)?.length ?? 0;
|
|
762
|
+
} catch {
|
|
763
|
+
return String(value).length;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
async function maybeCompact(messages, c, turn) {
|
|
767
|
+
const head = c.preserveHead !== void 0 && c.preserveHead > 0 ? c.preserveHead : 2;
|
|
768
|
+
if (messages.length <= head + 1) return false;
|
|
769
|
+
const estimate = c.estimateTokens ?? estimateConversationTokens;
|
|
770
|
+
const before = estimate(messages);
|
|
771
|
+
if (before <= c.thresholdTokens) return false;
|
|
772
|
+
const digest = await c.distill([...messages]);
|
|
773
|
+
messages.splice(head, messages.length - head, {
|
|
774
|
+
role: "user",
|
|
775
|
+
content: `[earlier work compacted to save context \u2014 progress so far]
|
|
776
|
+
${digest}`
|
|
777
|
+
});
|
|
778
|
+
c.onCompact?.({ turn, beforeTokens: before, afterTokens: estimate(messages) });
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
781
|
+
async function runBrainLoop(opts) {
|
|
782
|
+
const maxTurns = opts.maxTurns ?? 4;
|
|
783
|
+
const messages = [...opts.initialMessages];
|
|
784
|
+
let toolCalls = 0;
|
|
785
|
+
let lastText = "";
|
|
786
|
+
const usage = { input: 0, output: 0 };
|
|
787
|
+
const toolTrace = [];
|
|
788
|
+
for (let turn = 1; turn <= maxTurns; turn += 1) {
|
|
789
|
+
if (opts.hooks?.stopBefore?.(turn)) break;
|
|
790
|
+
await opts.hooks?.beforeTurn?.(turn, messages);
|
|
791
|
+
if (opts.compaction) await maybeCompact(messages, opts.compaction, turn);
|
|
792
|
+
const r = await opts.chat(messages, opts.tools);
|
|
793
|
+
if (r.usage) {
|
|
794
|
+
usage.input += r.usage.input;
|
|
795
|
+
usage.output += r.usage.output;
|
|
796
|
+
opts.hooks?.onUsage?.(r.usage);
|
|
797
|
+
}
|
|
798
|
+
if (r.content) lastText = r.content;
|
|
799
|
+
if (r.toolCalls.length === 0) {
|
|
800
|
+
if (r.content) messages.push({ role: "assistant", content: r.content });
|
|
801
|
+
return { final: lastText, turns: turn, toolCalls, toolTrace, usage, messages };
|
|
802
|
+
}
|
|
803
|
+
messages.push({
|
|
804
|
+
role: "assistant",
|
|
805
|
+
content: r.content ?? "",
|
|
806
|
+
tool_calls: r.toolCalls.map((tc) => ({
|
|
807
|
+
id: tc.id,
|
|
808
|
+
type: "function",
|
|
809
|
+
function: { name: tc.name, arguments: tc.arguments }
|
|
810
|
+
}))
|
|
811
|
+
});
|
|
812
|
+
for (const tc of r.toolCalls) {
|
|
813
|
+
toolCalls += 1;
|
|
814
|
+
let args = {};
|
|
815
|
+
try {
|
|
816
|
+
args = JSON.parse(tc.arguments);
|
|
817
|
+
} catch {
|
|
818
|
+
messages.push({
|
|
819
|
+
role: "tool",
|
|
820
|
+
tool_call_id: tc.id,
|
|
821
|
+
content: `error: arguments were not valid JSON: ${tc.arguments.slice(0, 200)}`
|
|
822
|
+
});
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
const out = await opts.execute(tc.name, args);
|
|
826
|
+
messages.push({ role: "tool", tool_call_id: tc.id, content: out });
|
|
827
|
+
toolTrace.push({ name: tc.name, args: tc.arguments, result: out });
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return { final: lastText, turns: maxTurns, toolCalls, toolTrace, usage, messages };
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// src/runtime/router-client.ts
|
|
834
|
+
async function routerChatWithUsage(cfg, messages, opts) {
|
|
835
|
+
const url = `${cfg.routerBaseUrl.replace(/\/$/, "")}/chat/completions`;
|
|
836
|
+
const headers = { "content-type": "application/json", authorization: `Bearer ${cfg.routerKey}` };
|
|
837
|
+
let temperature = opts?.temperature ?? 0.2;
|
|
838
|
+
const body = () => ({
|
|
839
|
+
model: cfg.model,
|
|
840
|
+
messages,
|
|
841
|
+
temperature,
|
|
842
|
+
max_tokens: opts?.maxTokens ?? 8192,
|
|
843
|
+
...opts?.reasoningEffort ? { reasoning_effort: opts.reasoningEffort } : {}
|
|
844
|
+
});
|
|
845
|
+
if (cfg.complete) return parseChatResult(await cfg.complete(body()), cfg.model);
|
|
846
|
+
let lastErr = "";
|
|
847
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
848
|
+
const res = await fetch(url, {
|
|
849
|
+
method: "POST",
|
|
850
|
+
headers,
|
|
851
|
+
body: JSON.stringify(body()),
|
|
852
|
+
...opts?.signal ? { signal: opts.signal } : {}
|
|
853
|
+
});
|
|
854
|
+
if (res.ok) return parseChatResult(await res.json(), cfg.model);
|
|
855
|
+
const status = res.status;
|
|
856
|
+
const text = (await res.text()).slice(0, 200);
|
|
857
|
+
lastErr = `router ${status}: ${text}`;
|
|
858
|
+
if (status === 400 && /temperature/i.test(text) && temperature !== 1) {
|
|
859
|
+
temperature = 1;
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
if (![408, 425, 429, 500, 502, 503, 504, 520, 522, 524].includes(status))
|
|
863
|
+
throw new Error(lastErr);
|
|
864
|
+
if (attempt < 4) await new Promise((r) => setTimeout(r, 800 * 2 ** attempt));
|
|
865
|
+
}
|
|
866
|
+
throw new Error(`${lastErr} (exhausted retries)`);
|
|
867
|
+
}
|
|
868
|
+
function parseChatResult(json, model) {
|
|
869
|
+
const data = json;
|
|
870
|
+
const u = data.usage;
|
|
871
|
+
const usage = u && typeof u.prompt_tokens === "number" && typeof u.completion_tokens === "number" ? { input: u.prompt_tokens, output: u.completion_tokens } : void 0;
|
|
872
|
+
const costUsd = usage && isModelPriced(model) ? estimateCost(usage.input, usage.output, model) : void 0;
|
|
873
|
+
const msg = data.choices?.[0]?.message;
|
|
874
|
+
const { content, reasoning } = splitReasoning(
|
|
875
|
+
msg?.content ?? "",
|
|
876
|
+
msg?.reasoning ?? msg?.reasoning_content
|
|
877
|
+
);
|
|
878
|
+
return {
|
|
879
|
+
content,
|
|
880
|
+
...reasoning ? { reasoning } : {},
|
|
881
|
+
...usage ? { usage } : {},
|
|
882
|
+
...costUsd !== void 0 ? { costUsd } : {}
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
function splitReasoning(rawContent, fieldReasoning) {
|
|
886
|
+
const open = rawContent.indexOf("<think>");
|
|
887
|
+
if (open !== -1) {
|
|
888
|
+
const close = rawContent.indexOf("</think>", open);
|
|
889
|
+
const inline = close !== -1 ? rawContent.slice(open + 7, close) : rawContent.slice(open + 7);
|
|
890
|
+
const rest = close !== -1 ? rawContent.slice(0, open) + rawContent.slice(close + 8) : rawContent.slice(0, open);
|
|
891
|
+
const reasoning = [fieldReasoning, inline.trim()].filter(Boolean).join("\n");
|
|
892
|
+
return { content: rest.trim(), ...reasoning ? { reasoning } : {} };
|
|
893
|
+
}
|
|
894
|
+
return { content: rawContent, ...fieldReasoning ? { reasoning: fieldReasoning } : {} };
|
|
895
|
+
}
|
|
896
|
+
async function routerChatWithTools(cfg, messages, tools, opts) {
|
|
897
|
+
const body = {
|
|
898
|
+
model: cfg.model,
|
|
899
|
+
messages,
|
|
900
|
+
tools,
|
|
901
|
+
tool_choice: opts?.toolChoice ?? "auto",
|
|
902
|
+
temperature: opts?.temperature ?? 0.3,
|
|
903
|
+
...opts?.maxTokens ? { max_tokens: opts.maxTokens } : {}
|
|
904
|
+
};
|
|
905
|
+
const raw = cfg.complete ? await cfg.complete(body) : await (async () => {
|
|
906
|
+
const res = await fetch(`${cfg.routerBaseUrl.replace(/\/$/, "")}/chat/completions`, {
|
|
907
|
+
method: "POST",
|
|
908
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${cfg.routerKey}` },
|
|
909
|
+
body: JSON.stringify(body),
|
|
910
|
+
...opts?.signal ? { signal: opts.signal } : {}
|
|
911
|
+
});
|
|
912
|
+
if (!res.ok) throw new Error(`router ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
913
|
+
return res.json();
|
|
914
|
+
})();
|
|
915
|
+
const data = raw;
|
|
916
|
+
const msg = data.choices?.[0]?.message;
|
|
917
|
+
const toolCalls = (msg?.tool_calls ?? []).map((tc, i) => ({
|
|
918
|
+
id: tc.id ?? `call_${i}`,
|
|
919
|
+
name: tc.function?.name ?? "",
|
|
920
|
+
arguments: tc.function?.arguments ?? "{}"
|
|
921
|
+
}));
|
|
922
|
+
const u = data.usage;
|
|
923
|
+
const usage = u && typeof u.prompt_tokens === "number" && typeof u.completion_tokens === "number" ? { input: u.prompt_tokens, output: u.completion_tokens } : void 0;
|
|
924
|
+
const costUsd = usage && isModelPriced(cfg.model) ? estimateCost(usage.input, usage.output, cfg.model) : void 0;
|
|
925
|
+
return {
|
|
926
|
+
content: msg?.content ?? null,
|
|
927
|
+
toolCalls,
|
|
928
|
+
...usage ? { usage } : {},
|
|
929
|
+
...costUsd !== void 0 ? { costUsd } : {}
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
async function routerToolLoop(cfg, system, user, tools, execute, opts) {
|
|
933
|
+
const initialMessages = opts?.initialMessages ?? [
|
|
934
|
+
{ role: "system", content: system },
|
|
935
|
+
{ role: "user", content: user }
|
|
936
|
+
];
|
|
937
|
+
return runBrainLoop({
|
|
938
|
+
chat: (messages, toolSpecs) => routerChatWithTools(cfg, messages, toolSpecs, {
|
|
939
|
+
...opts?.temperature !== void 0 ? { temperature: opts.temperature } : {},
|
|
940
|
+
...opts?.maxTokens ? { maxTokens: opts.maxTokens } : {},
|
|
941
|
+
...opts?.signal ? { signal: opts.signal } : {}
|
|
942
|
+
}),
|
|
943
|
+
tools,
|
|
944
|
+
execute,
|
|
945
|
+
initialMessages,
|
|
946
|
+
maxTurns: opts?.maxTurns ?? 4
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
function routerBrain(cfg, opts = {}) {
|
|
950
|
+
const temperature = opts.temperature ?? 0.4;
|
|
951
|
+
return (messages, tools) => routerChatWithTools(cfg, messages, tools, { temperature, toolChoice: "auto" });
|
|
952
|
+
}
|
|
953
|
+
|
|
178
954
|
// src/runtime/sandbox-acquire.ts
|
|
179
955
|
var RETRYABLE_HTTP = /* @__PURE__ */ new Set([502, 503, 504, 522, 524, 408, 425, 429]);
|
|
180
956
|
var TERMINAL_STATUS = /* @__PURE__ */ new Set(["failed", "expired", "stopped"]);
|
|
@@ -865,13 +1641,13 @@ async function executeIteration(args) {
|
|
|
865
1641
|
await destroySandboxSafe(box, args.ctx.traceEmitter, args.runId, args.now);
|
|
866
1642
|
}
|
|
867
1643
|
}
|
|
868
|
-
if (
|
|
1644
|
+
if (isAbortError2(slot.error) || args.signal.aborted) {
|
|
869
1645
|
if (slot.error) throw slot.error;
|
|
870
1646
|
throwAbort();
|
|
871
1647
|
}
|
|
872
1648
|
if (slot.error instanceof ValidationError) throw slot.error;
|
|
873
1649
|
}
|
|
874
|
-
function
|
|
1650
|
+
function isAbortError2(err) {
|
|
875
1651
|
return err instanceof Error && err.name === "AbortError";
|
|
876
1652
|
}
|
|
877
1653
|
var TEARDOWN_TIMEOUT_MS2 = 15e3;
|
|
@@ -1205,13 +1981,15 @@ async function runWorktreeHarness(opts) {
|
|
|
1205
1981
|
const { command, args } = harnessInvocation(opts.harness, opts.profile, opts.taskPrompt, {
|
|
1206
1982
|
// This helper created the candidate worktree above; autonomous Claude
|
|
1207
1983
|
// edits are permitted only inside that isolated checkout.
|
|
1208
|
-
dangerouslySkipPermissions: opts.harness === "claude"
|
|
1984
|
+
dangerouslySkipPermissions: opts.harness === "claude",
|
|
1985
|
+
...opts.codexReproducible ? { codexReproducible: true } : {}
|
|
1209
1986
|
});
|
|
1210
1987
|
const harnessResult = await runHarness({
|
|
1211
1988
|
harness: opts.harness,
|
|
1212
1989
|
cwd: worktree.path,
|
|
1213
1990
|
taskPrompt: opts.taskPrompt,
|
|
1214
1991
|
invocation: { command, args },
|
|
1992
|
+
...opts.codexReproducible ? { codexReproducible: true } : {},
|
|
1215
1993
|
...opts.harnessTimeoutMs !== void 0 ? { timeoutMs: opts.harnessTimeoutMs } : {},
|
|
1216
1994
|
...opts.signal ? { signal: opts.signal } : {}
|
|
1217
1995
|
});
|
|
@@ -1239,7 +2017,15 @@ async function runWorktreeHarness(opts) {
|
|
|
1239
2017
|
killedBySignal: harnessResult.killedBySignal,
|
|
1240
2018
|
durationMs: harnessResult.durationMs,
|
|
1241
2019
|
stdout: harnessResult.stdout,
|
|
1242
|
-
stderr: harnessResult.stderr
|
|
2020
|
+
stderr: harnessResult.stderr,
|
|
2021
|
+
...harnessResult.usage ? { usage: harnessResult.usage } : {},
|
|
2022
|
+
...harnessResult.evidence ? {
|
|
2023
|
+
cliVersion: harnessResult.evidence.cliVersion,
|
|
2024
|
+
effectivePromptSha256: harnessResult.evidence.effectivePromptSha256,
|
|
2025
|
+
nonPromptArgsSha256: harnessResult.evidence.nonPromptArgsSha256,
|
|
2026
|
+
controlledConfigSha256: harnessResult.evidence.controlledConfigSha256,
|
|
2027
|
+
executionPolicy: harnessResult.evidence.policy
|
|
2028
|
+
} : {}
|
|
1243
2029
|
},
|
|
1244
2030
|
...checks ? { checks } : {}
|
|
1245
2031
|
};
|
|
@@ -1307,9 +2093,17 @@ function createWorktreeCliExecutor(options) {
|
|
|
1307
2093
|
if (typeof options.taskPrompt !== "string" || options.taskPrompt.length === 0) {
|
|
1308
2094
|
throw new ValidationError("createWorktreeCliExecutor: taskPrompt required");
|
|
1309
2095
|
}
|
|
2096
|
+
if (options.codexReproducible && options.harness !== "codex") {
|
|
2097
|
+
throw new ValidationError(
|
|
2098
|
+
'createWorktreeCliExecutor: codexReproducible requires harness "codex"'
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
if (options.codexReproducible && options.budgetExempt === true) {
|
|
2102
|
+
throw new ValidationError("createWorktreeCliExecutor: codexReproducible cannot be budgetExempt");
|
|
2103
|
+
}
|
|
1310
2104
|
const runId = options.runId ?? randomUUID();
|
|
1311
2105
|
const controller = new AbortController();
|
|
1312
|
-
const budgetExempt = options.budgetExempt ??
|
|
2106
|
+
const budgetExempt = options.budgetExempt ?? !options.codexReproducible;
|
|
1313
2107
|
let run;
|
|
1314
2108
|
let artifact;
|
|
1315
2109
|
return {
|
|
@@ -1328,6 +2122,7 @@ function createWorktreeCliExecutor(options) {
|
|
|
1328
2122
|
...options.testCmd !== void 0 ? { testCmd: options.testCmd } : {},
|
|
1329
2123
|
...options.typecheckCmd !== void 0 ? { typecheckCmd: options.typecheckCmd } : {},
|
|
1330
2124
|
...options.harnessTimeoutMs !== void 0 ? { harnessTimeoutMs: options.harnessTimeoutMs } : {},
|
|
2125
|
+
...options.codexReproducible ? { codexReproducible: true } : {},
|
|
1331
2126
|
...options.checkTimeoutMs !== void 0 ? { checkTimeoutMs: options.checkTimeoutMs } : {},
|
|
1332
2127
|
...options.checkOutputCap !== void 0 ? { checkOutputCap: options.checkOutputCap } : {},
|
|
1333
2128
|
...linked ? { signal: linked } : {},
|
|
@@ -1335,13 +2130,20 @@ function createWorktreeCliExecutor(options) {
|
|
|
1335
2130
|
...options.runHarness ? { runHarness: options.runHarness } : {},
|
|
1336
2131
|
...options.runCommand ? { runCommand: options.runCommand } : {}
|
|
1337
2132
|
});
|
|
2133
|
+
const usage = run.result.harness.usage;
|
|
2134
|
+
if (!budgetExempt && !usage) {
|
|
2135
|
+
const completed = run;
|
|
2136
|
+
run = void 0;
|
|
2137
|
+
await completed.cleanup();
|
|
2138
|
+
throw new ValidationError(
|
|
2139
|
+
"createWorktreeCliExecutor: metered harness run returned no token usage"
|
|
2140
|
+
);
|
|
2141
|
+
}
|
|
1338
2142
|
const spent = {
|
|
1339
2143
|
iterations: 1,
|
|
1340
|
-
|
|
1341
|
-
// (NOT a fabricated cost). When budgetExempt is true the pool ignores this spend entirely;
|
|
1342
|
-
// when explicitly false the scope debits exactly this captured spend — the real iteration.
|
|
1343
|
-
tokens: zeroTokenUsage(),
|
|
2144
|
+
tokens: usage ? { input: usage.inputTokens, output: usage.outputTokens } : { input: 0, output: 0 },
|
|
1344
2145
|
usd: 0,
|
|
2146
|
+
...usage ? { usdKnown: false } : {},
|
|
1345
2147
|
ms: Date.now() - started
|
|
1346
2148
|
};
|
|
1347
2149
|
artifact = { outRef: contentAddress(run.result), out: run.result, spent };
|
|
@@ -1385,7 +2187,7 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
1385
2187
|
import { request as httpRequest } from "http";
|
|
1386
2188
|
import { request as httpsRequest } from "https";
|
|
1387
2189
|
import { Readable } from "stream";
|
|
1388
|
-
import { estimateCost, isModelPriced } from "@tangle-network/agent-eval";
|
|
2190
|
+
import { estimateCost as estimateCost2, isModelPriced as isModelPriced2 } from "@tangle-network/agent-eval";
|
|
1389
2191
|
var routerSeamKey = "router";
|
|
1390
2192
|
var sandboxSeamKey = "sandbox";
|
|
1391
2193
|
var cliSeamKey = "cli";
|
|
@@ -1406,7 +2208,7 @@ function contentRef(prefix, value) {
|
|
|
1406
2208
|
}
|
|
1407
2209
|
return `${prefix}:${(h >>> 0).toString(16).padStart(8, "0")}`;
|
|
1408
2210
|
}
|
|
1409
|
-
function
|
|
2211
|
+
function zeroSpend2() {
|
|
1410
2212
|
return { iterations: 0, tokens: zeroTokenUsage(), usd: 0, ms: 0 };
|
|
1411
2213
|
}
|
|
1412
2214
|
var routerInlineExecutor = (spec, ctx) => {
|
|
@@ -1600,7 +2402,7 @@ var routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
1600
2402
|
}
|
|
1601
2403
|
}
|
|
1602
2404
|
}
|
|
1603
|
-
const usd =
|
|
2405
|
+
const usd = isModelPriced2(model) ? estimateCost2(tokens.input, tokens.output, model) : 0;
|
|
1604
2406
|
const spent = { iterations: turns, tokens, usd, ms: Date.now() - started };
|
|
1605
2407
|
const out = { content: lastText };
|
|
1606
2408
|
artifact = { outRef: contentRef("router-tools", { model, content: lastText }), out, spent };
|
|
@@ -1812,7 +2614,7 @@ async function* streamCliLeaf(args) {
|
|
|
1812
2614
|
);
|
|
1813
2615
|
}
|
|
1814
2616
|
const out = { content: chunks.join("") };
|
|
1815
|
-
args.onArtifact({ outRef: contentRef("cli", out), out, spent:
|
|
2617
|
+
args.onArtifact({ outRef: contentRef("cli", out), out, spent: zeroSpend2() });
|
|
1816
2618
|
yield { kind: "iteration" };
|
|
1817
2619
|
}
|
|
1818
2620
|
function killWithGrace(proc, grace) {
|
|
@@ -2200,7 +3002,7 @@ function bridgeWorktreeExecutor(spec, ctx, seam) {
|
|
|
2200
3002
|
inner = bridgeExecutor(spec, bridgeCtx);
|
|
2201
3003
|
for (const msg of pending.splice(0)) inner.deliver?.(msg);
|
|
2202
3004
|
const run = inner.execute(seam.taskPrompt, linked);
|
|
2203
|
-
if (
|
|
3005
|
+
if (isAsyncIterable3(run)) {
|
|
2204
3006
|
for await (const event of run) yield event;
|
|
2205
3007
|
bridgeArtifact = inner.resultArtifact();
|
|
2206
3008
|
} else {
|
|
@@ -2293,7 +3095,7 @@ function bridgeOutputText(out) {
|
|
|
2293
3095
|
return String(out);
|
|
2294
3096
|
}
|
|
2295
3097
|
}
|
|
2296
|
-
function
|
|
3098
|
+
function isAsyncIterable3(value) {
|
|
2297
3099
|
return value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function";
|
|
2298
3100
|
}
|
|
2299
3101
|
var cliWorktreeExecutor = (spec, ctx) => {
|
|
@@ -2315,6 +3117,7 @@ var cliWorktreeExecutor = (spec, ctx) => {
|
|
|
2315
3117
|
...seam.runId ? { runId: seam.runId } : {},
|
|
2316
3118
|
...seam.baseRef ? { baseRef: seam.baseRef } : {},
|
|
2317
3119
|
...seam.harnessTimeoutMs !== void 0 ? { harnessTimeoutMs: seam.harnessTimeoutMs } : {},
|
|
3120
|
+
...seam.codexReproducible ? { codexReproducible: true } : {},
|
|
2318
3121
|
...seam.testCmd !== void 0 ? { testCmd: seam.testCmd } : {},
|
|
2319
3122
|
...seam.typecheckCmd !== void 0 ? { typecheckCmd: seam.typecheckCmd } : {},
|
|
2320
3123
|
...seam.checkTimeoutMs !== void 0 ? { checkTimeoutMs: seam.checkTimeoutMs } : {},
|
|
@@ -2451,6 +3254,181 @@ function mergeAbortSignals(...signals) {
|
|
|
2451
3254
|
return c.signal;
|
|
2452
3255
|
}
|
|
2453
3256
|
|
|
3257
|
+
// src/runtime/supervise/driver-executor.ts
|
|
3258
|
+
var driverRuntime = "driver";
|
|
3259
|
+
var driverRole = "driver";
|
|
3260
|
+
function isDriverSpec(spec) {
|
|
3261
|
+
const role = spec.profile.metadata?.role;
|
|
3262
|
+
if (role !== driverRole) return false;
|
|
3263
|
+
const driver = spec.driver;
|
|
3264
|
+
if (!isAgent(driver)) {
|
|
3265
|
+
throw new ValidationError(
|
|
3266
|
+
"driverExecutor: a driver-role spec must carry a `driver` Agent to run inside its nested scope"
|
|
3267
|
+
);
|
|
3268
|
+
}
|
|
3269
|
+
return true;
|
|
3270
|
+
}
|
|
3271
|
+
var driverExecutorFactory = (spec, ctx) => {
|
|
3272
|
+
if (!isDriverSpec(spec)) {
|
|
3273
|
+
throw new ValidationError(
|
|
3274
|
+
'driverExecutorFactory: spec is not a driver child (no role:"driver" marker)'
|
|
3275
|
+
);
|
|
3276
|
+
}
|
|
3277
|
+
const driver = spec.driver;
|
|
3278
|
+
const journal = spec.journal;
|
|
3279
|
+
const seam = readNestedScopeSeam(ctx);
|
|
3280
|
+
let artifact;
|
|
3281
|
+
let meteredSpend;
|
|
3282
|
+
return {
|
|
3283
|
+
runtime: driverRuntime,
|
|
3284
|
+
async execute(task, signal) {
|
|
3285
|
+
const nestedRoot = nestedTreeKey(seam, journal);
|
|
3286
|
+
await journal.beginTree(nestedRoot, (/* @__PURE__ */ new Date(0)).toISOString());
|
|
3287
|
+
const nestedScope = seam.mount(nestedRoot, signal);
|
|
3288
|
+
try {
|
|
3289
|
+
const out = await driver.act(task, nestedScope);
|
|
3290
|
+
const events = await loadTreeEvents(journal, nestedRoot);
|
|
3291
|
+
const settled = events.filter(isSettled);
|
|
3292
|
+
meteredSpend = nonZeroOrUndef(sumMetered(events));
|
|
3293
|
+
const verdict = deriveDeliveryVerdict(settled);
|
|
3294
|
+
artifact = {
|
|
3295
|
+
outRef: `${driverRuntime}:${nestedRoot}`,
|
|
3296
|
+
out,
|
|
3297
|
+
spent: sumSpend(settled),
|
|
3298
|
+
...verdict ? { verdict } : {}
|
|
3299
|
+
};
|
|
3300
|
+
return artifact;
|
|
3301
|
+
} catch (err) {
|
|
3302
|
+
meteredSpend = await safeSumMetered(journal, nestedRoot);
|
|
3303
|
+
throw err;
|
|
3304
|
+
}
|
|
3305
|
+
},
|
|
3306
|
+
metered() {
|
|
3307
|
+
return meteredSpend;
|
|
3308
|
+
},
|
|
3309
|
+
teardown() {
|
|
3310
|
+
return Promise.resolve({ destroyed: true });
|
|
3311
|
+
},
|
|
3312
|
+
resultArtifact() {
|
|
3313
|
+
if (!artifact) {
|
|
3314
|
+
throw new ValidationError("driverExecutor: resultArtifact() read before execute()");
|
|
3315
|
+
}
|
|
3316
|
+
return artifact;
|
|
3317
|
+
}
|
|
3318
|
+
};
|
|
3319
|
+
};
|
|
3320
|
+
function withDriverExecutor(base) {
|
|
3321
|
+
return {
|
|
3322
|
+
register: base.register.bind(base),
|
|
3323
|
+
resolve(spec) {
|
|
3324
|
+
const role = spec.profile.metadata?.role;
|
|
3325
|
+
if (role === driverRole && !spec.executor) {
|
|
3326
|
+
return { succeeded: true, value: driverExecutorFactory };
|
|
3327
|
+
}
|
|
3328
|
+
return base.resolve(spec);
|
|
3329
|
+
}
|
|
3330
|
+
};
|
|
3331
|
+
}
|
|
3332
|
+
function nestedTreeKey(seam, journal) {
|
|
3333
|
+
return `${seam.journalRoot}/d${nextNestOrdinal(journal)}`;
|
|
3334
|
+
}
|
|
3335
|
+
var nestCounters = /* @__PURE__ */ new WeakMap();
|
|
3336
|
+
function nextNestOrdinal(journal) {
|
|
3337
|
+
let c = nestCounters.get(journal);
|
|
3338
|
+
if (!c) {
|
|
3339
|
+
c = { n: 0 };
|
|
3340
|
+
nestCounters.set(journal, c);
|
|
3341
|
+
}
|
|
3342
|
+
return c.n++;
|
|
3343
|
+
}
|
|
3344
|
+
async function loadTreeEvents(journal, nestedRoot) {
|
|
3345
|
+
const events = await journal.loadTree(nestedRoot);
|
|
3346
|
+
if (events === void 0) {
|
|
3347
|
+
throw new ValidationError(
|
|
3348
|
+
`driverExecutor: nested tree '${nestedRoot}' missing from the journal after run (corrupted log)`
|
|
3349
|
+
);
|
|
3350
|
+
}
|
|
3351
|
+
return events;
|
|
3352
|
+
}
|
|
3353
|
+
function isSettled(ev) {
|
|
3354
|
+
return ev.kind === "settled";
|
|
3355
|
+
}
|
|
3356
|
+
function sumSpend(settled) {
|
|
3357
|
+
const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
3358
|
+
for (const ev of settled) {
|
|
3359
|
+
total.iterations += ev.spent.iterations;
|
|
3360
|
+
total.tokens.input += ev.spent.tokens.input;
|
|
3361
|
+
total.tokens.output += ev.spent.tokens.output;
|
|
3362
|
+
total.usd += ev.spent.usd;
|
|
3363
|
+
if (ev.spent.usdKnown === false) total.usdKnown = false;
|
|
3364
|
+
total.ms += ev.spent.ms;
|
|
3365
|
+
}
|
|
3366
|
+
return total;
|
|
3367
|
+
}
|
|
3368
|
+
function sumMetered(events) {
|
|
3369
|
+
const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
3370
|
+
for (const ev of events) {
|
|
3371
|
+
if (ev.kind !== "metered") continue;
|
|
3372
|
+
total.iterations += ev.spend.iterations;
|
|
3373
|
+
total.tokens.input += ev.spend.tokens.input;
|
|
3374
|
+
total.tokens.output += ev.spend.tokens.output;
|
|
3375
|
+
total.usd += ev.spend.usd;
|
|
3376
|
+
if (ev.spend.usdKnown === false) total.usdKnown = false;
|
|
3377
|
+
total.ms += ev.spend.ms;
|
|
3378
|
+
}
|
|
3379
|
+
return total;
|
|
3380
|
+
}
|
|
3381
|
+
function isNonZeroSpend(s) {
|
|
3382
|
+
return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
|
|
3383
|
+
}
|
|
3384
|
+
function nonZeroOrUndef(s) {
|
|
3385
|
+
return isNonZeroSpend(s) ? s : void 0;
|
|
3386
|
+
}
|
|
3387
|
+
async function safeSumMetered(journal, nestedRoot) {
|
|
3388
|
+
try {
|
|
3389
|
+
return nonZeroOrUndef(sumMetered(await loadTreeEvents(journal, nestedRoot)));
|
|
3390
|
+
} catch {
|
|
3391
|
+
return void 0;
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
function deriveDeliveryVerdict(settled) {
|
|
3395
|
+
let sawChild = false;
|
|
3396
|
+
let anyValid = false;
|
|
3397
|
+
let bestValidScore;
|
|
3398
|
+
let bestDoneScore;
|
|
3399
|
+
for (const ev of settled) {
|
|
3400
|
+
sawChild = true;
|
|
3401
|
+
if (ev.status !== "done") continue;
|
|
3402
|
+
const score = ev.verdict?.score;
|
|
3403
|
+
if (score !== void 0 && (bestDoneScore === void 0 || score > bestDoneScore)) {
|
|
3404
|
+
bestDoneScore = score;
|
|
3405
|
+
}
|
|
3406
|
+
if (ev.verdict?.valid === true) {
|
|
3407
|
+
anyValid = true;
|
|
3408
|
+
if (score !== void 0 && (bestValidScore === void 0 || score > bestValidScore)) {
|
|
3409
|
+
bestValidScore = score;
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
if (!sawChild) return void 0;
|
|
3414
|
+
return {
|
|
3415
|
+
valid: anyValid,
|
|
3416
|
+
score: anyValid ? bestValidScore ?? 1 : bestDoneScore ?? 0
|
|
3417
|
+
};
|
|
3418
|
+
}
|
|
3419
|
+
function readNestedScopeSeam(ctx) {
|
|
3420
|
+
const seam = ctx.seams[nestedScopeSeamKey];
|
|
3421
|
+
if (!seam || typeof seam.mount !== "function") {
|
|
3422
|
+
throw new ValidationError(
|
|
3423
|
+
`driverExecutor: missing required seam "${nestedScopeSeamKey}" \u2014 a driver child must be spawned through a Scope that seeds it (the keystone scope does)`
|
|
3424
|
+
);
|
|
3425
|
+
}
|
|
3426
|
+
return seam;
|
|
3427
|
+
}
|
|
3428
|
+
function isAgent(value) {
|
|
3429
|
+
return typeof value === "object" && value !== null && typeof value.act === "function" && typeof value.name === "string";
|
|
3430
|
+
}
|
|
3431
|
+
|
|
2454
3432
|
// src/runtime/supervise/run-context.ts
|
|
2455
3433
|
function createInMemoryRunContext(opts = {}) {
|
|
2456
3434
|
const base = createExecutorRegistry();
|
|
@@ -2461,6 +3439,364 @@ function createInMemoryRunContext(opts = {}) {
|
|
|
2461
3439
|
};
|
|
2462
3440
|
}
|
|
2463
3441
|
|
|
3442
|
+
// src/runtime/supervise/budget.ts
|
|
3443
|
+
function spendFromUsageEvents(events) {
|
|
3444
|
+
const tokens = zeroTokenUsage();
|
|
3445
|
+
let usd = 0;
|
|
3446
|
+
let iterations = 0;
|
|
3447
|
+
for (const ev of events) {
|
|
3448
|
+
if (ev.kind === "tokens") {
|
|
3449
|
+
addTokenUsage(tokens, { input: ev.input, output: ev.output });
|
|
3450
|
+
} else if (ev.kind === "cost") {
|
|
3451
|
+
usd += ev.usd;
|
|
3452
|
+
} else {
|
|
3453
|
+
iterations += 1;
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
return { iterations, tokens, usd, ms: 0 };
|
|
3457
|
+
}
|
|
3458
|
+
async function foldUsage(events) {
|
|
3459
|
+
if (Array.isArray(events)) return spendFromUsageEvents(events);
|
|
3460
|
+
const tokens = zeroTokenUsage();
|
|
3461
|
+
let usd = 0;
|
|
3462
|
+
let iterations = 0;
|
|
3463
|
+
for await (const ev of events) {
|
|
3464
|
+
if (ev.kind === "tokens") {
|
|
3465
|
+
addTokenUsage(tokens, { input: ev.input, output: ev.output });
|
|
3466
|
+
} else if (ev.kind === "cost") {
|
|
3467
|
+
usd += ev.usd;
|
|
3468
|
+
} else {
|
|
3469
|
+
iterations += 1;
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
return { iterations, tokens, usd, ms: 0 };
|
|
3473
|
+
}
|
|
3474
|
+
function totalTokens(usage) {
|
|
3475
|
+
return usage.input + usage.output;
|
|
3476
|
+
}
|
|
3477
|
+
function createBudgetPool(root, now = Date.now) {
|
|
3478
|
+
let freeTokens = root.maxTokens;
|
|
3479
|
+
let reservedTokens = 0;
|
|
3480
|
+
let committedTokens = 0;
|
|
3481
|
+
const usdCapped = root.maxUsd !== void 0;
|
|
3482
|
+
let freeUsd = root.maxUsd ?? 0;
|
|
3483
|
+
let reservedUsd = 0;
|
|
3484
|
+
let committedUsd = 0;
|
|
3485
|
+
let freeIterations = root.maxIterations;
|
|
3486
|
+
let reservedIterations = 0;
|
|
3487
|
+
let committedIterations = 0;
|
|
3488
|
+
const absoluteDeadlineMs = root.deadlineMs !== void 0 ? now() + root.deadlineMs : 0;
|
|
3489
|
+
let nextTicketId = 0;
|
|
3490
|
+
const open = /* @__PURE__ */ new Set();
|
|
3491
|
+
function reserve(b) {
|
|
3492
|
+
const wantTokens = b.maxTokens;
|
|
3493
|
+
const wantUsd = b.maxUsd ?? 0;
|
|
3494
|
+
const wantIterations = b.maxIterations;
|
|
3495
|
+
if (wantTokens > freeTokens) return { ok: false, reason: "budget-exhausted" };
|
|
3496
|
+
if (wantIterations > freeIterations) return { ok: false, reason: "budget-exhausted" };
|
|
3497
|
+
if (wantUsd > 0 && (!usdCapped || wantUsd > freeUsd)) {
|
|
3498
|
+
return { ok: false, reason: "budget-exhausted" };
|
|
3499
|
+
}
|
|
3500
|
+
freeTokens -= wantTokens;
|
|
3501
|
+
reservedTokens += wantTokens;
|
|
3502
|
+
freeIterations -= wantIterations;
|
|
3503
|
+
reservedIterations += wantIterations;
|
|
3504
|
+
if (wantUsd > 0) {
|
|
3505
|
+
freeUsd -= wantUsd;
|
|
3506
|
+
reservedUsd += wantUsd;
|
|
3507
|
+
}
|
|
3508
|
+
const id = nextTicketId++;
|
|
3509
|
+
open.add(id);
|
|
3510
|
+
return {
|
|
3511
|
+
ok: true,
|
|
3512
|
+
ticket: { id, reserved: { tokens: wantTokens, usd: wantUsd, iterations: wantIterations } }
|
|
3513
|
+
};
|
|
3514
|
+
}
|
|
3515
|
+
function reconcile(ticket, spent) {
|
|
3516
|
+
if (!open.has(ticket.id)) {
|
|
3517
|
+
throw new Error(`budget pool: reconcile of unknown or already-settled ticket ${ticket.id}`);
|
|
3518
|
+
}
|
|
3519
|
+
open.delete(ticket.id);
|
|
3520
|
+
const { tokens: rTokens, usd: rUsd, iterations: rIterations } = ticket.reserved;
|
|
3521
|
+
if (usdCapped && spent.usdKnown === false) {
|
|
3522
|
+
throw new Error(
|
|
3523
|
+
`budget pool: ticket ${ticket.id} reported unknown dollar cost under a dollar-capped budget`
|
|
3524
|
+
);
|
|
3525
|
+
}
|
|
3526
|
+
const spentTokens = totalTokens(spent.tokens);
|
|
3527
|
+
if (spentTokens > rTokens) {
|
|
3528
|
+
throw new Error(
|
|
3529
|
+
`budget pool: ticket ${ticket.id} spent ${spentTokens} tokens > reserved ${rTokens}`
|
|
3530
|
+
);
|
|
3531
|
+
}
|
|
3532
|
+
if (spent.iterations > rIterations) {
|
|
3533
|
+
throw new Error(
|
|
3534
|
+
`budget pool: ticket ${ticket.id} spent ${spent.iterations} iterations > reserved ${rIterations}`
|
|
3535
|
+
);
|
|
3536
|
+
}
|
|
3537
|
+
if (usdCapped && spent.usd > rUsd) {
|
|
3538
|
+
throw new Error(`budget pool: ticket ${ticket.id} spent $${spent.usd} > reserved $${rUsd}`);
|
|
3539
|
+
}
|
|
3540
|
+
reservedTokens -= rTokens;
|
|
3541
|
+
committedTokens += spentTokens;
|
|
3542
|
+
freeTokens += rTokens - spentTokens;
|
|
3543
|
+
reservedIterations -= rIterations;
|
|
3544
|
+
committedIterations += spent.iterations;
|
|
3545
|
+
freeIterations += rIterations - spent.iterations;
|
|
3546
|
+
if (usdCapped && rUsd > 0) {
|
|
3547
|
+
reservedUsd -= rUsd;
|
|
3548
|
+
committedUsd += spent.usd;
|
|
3549
|
+
freeUsd += rUsd - spent.usd;
|
|
3550
|
+
} else {
|
|
3551
|
+
committedUsd += spent.usd;
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
function observe(spend) {
|
|
3555
|
+
if (usdCapped && spend.usdKnown === false) {
|
|
3556
|
+
throw new Error(
|
|
3557
|
+
"budget pool: cannot observe unknown dollar cost under a dollar-capped budget"
|
|
3558
|
+
);
|
|
3559
|
+
}
|
|
3560
|
+
const tokens = totalTokens(spend.tokens);
|
|
3561
|
+
freeTokens -= tokens;
|
|
3562
|
+
committedTokens += tokens;
|
|
3563
|
+
freeIterations -= spend.iterations;
|
|
3564
|
+
committedIterations += spend.iterations;
|
|
3565
|
+
committedUsd += spend.usd;
|
|
3566
|
+
if (usdCapped) freeUsd -= spend.usd;
|
|
3567
|
+
}
|
|
3568
|
+
function readout() {
|
|
3569
|
+
return {
|
|
3570
|
+
tokensLeft: freeTokens,
|
|
3571
|
+
usdLeft: usdCapped ? freeUsd : 0,
|
|
3572
|
+
usdCapped,
|
|
3573
|
+
deadlineMs: absoluteDeadlineMs,
|
|
3574
|
+
reservedTokens
|
|
3575
|
+
};
|
|
3576
|
+
}
|
|
3577
|
+
function assertNoOpenTickets() {
|
|
3578
|
+
if (open.size > 0) {
|
|
3579
|
+
throw new Error(
|
|
3580
|
+
`budget pool: ${open.size} reservation(s) still open at join barrier (leaked ticket ids: ${[...open].join(", ")}) \u2014 conserved-pool invariant violated`
|
|
3581
|
+
);
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
return {
|
|
3585
|
+
reserve,
|
|
3586
|
+
reconcile,
|
|
3587
|
+
spendFrom: foldUsage,
|
|
3588
|
+
readout,
|
|
3589
|
+
observe,
|
|
3590
|
+
assertNoOpenTickets
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
// src/runtime/supervise/supervisor.ts
|
|
3595
|
+
var defaultMaxDepth = 4;
|
|
3596
|
+
function createSupervisor() {
|
|
3597
|
+
let attached;
|
|
3598
|
+
async function run(root, task, opts) {
|
|
3599
|
+
const now = opts.now ?? Date.now;
|
|
3600
|
+
const pool = createBudgetPool(opts.budget, now);
|
|
3601
|
+
await opts.journal.beginTree(opts.runId, new Date(now()).toISOString());
|
|
3602
|
+
await opts.journal.appendEvent(opts.runId, {
|
|
3603
|
+
kind: "spawned",
|
|
3604
|
+
id: opts.runId,
|
|
3605
|
+
label: "root",
|
|
3606
|
+
budget: opts.budget,
|
|
3607
|
+
runtime: "inline",
|
|
3608
|
+
seq: 0,
|
|
3609
|
+
at: new Date(now()).toISOString()
|
|
3610
|
+
});
|
|
3611
|
+
const controller = new AbortController();
|
|
3612
|
+
const cascadeAbort = (reason) => {
|
|
3613
|
+
if (controller.signal.aborted) return;
|
|
3614
|
+
controller.abort(reason);
|
|
3615
|
+
};
|
|
3616
|
+
const onCallerAbort = () => cascadeAbort("caller signal aborted");
|
|
3617
|
+
if (opts.signal) {
|
|
3618
|
+
if (opts.signal.aborted) cascadeAbort("caller signal aborted");
|
|
3619
|
+
else opts.signal.addEventListener("abort", onCallerAbort, { once: true });
|
|
3620
|
+
}
|
|
3621
|
+
const breaker = createIntensityBreaker(opts, () => cascadeAbort("intensity breaker tripped"));
|
|
3622
|
+
const journal = wrapJournalForBreaker(opts.journal, breaker);
|
|
3623
|
+
const scope = createScope({
|
|
3624
|
+
parentId: opts.runId,
|
|
3625
|
+
root: opts.runId,
|
|
3626
|
+
pool,
|
|
3627
|
+
journal,
|
|
3628
|
+
blobs: opts.blobs,
|
|
3629
|
+
executors: opts.executors,
|
|
3630
|
+
seams: {},
|
|
3631
|
+
depth: 0,
|
|
3632
|
+
maxDepth: opts.maxDepth ?? defaultMaxDepth,
|
|
3633
|
+
signal: controller.signal,
|
|
3634
|
+
now,
|
|
3635
|
+
hooks: opts.hooks
|
|
3636
|
+
});
|
|
3637
|
+
const openScope = scope;
|
|
3638
|
+
if (attached) {
|
|
3639
|
+
attached.bind({ scope: openScope, cascadeAbort, signal: pushRootSignal(cascadeAbort) });
|
|
3640
|
+
}
|
|
3641
|
+
let actOutcome;
|
|
3642
|
+
try {
|
|
3643
|
+
const out = await root.act(task, scope);
|
|
3644
|
+
actOutcome = { ok: true, out };
|
|
3645
|
+
} catch (error) {
|
|
3646
|
+
actOutcome = { ok: false, error };
|
|
3647
|
+
} finally {
|
|
3648
|
+
await drainLiveChildren(openScope, controller);
|
|
3649
|
+
if (opts.signal) opts.signal.removeEventListener("abort", onCallerAbort);
|
|
3650
|
+
if (attached) attached.unbind();
|
|
3651
|
+
}
|
|
3652
|
+
const tree = scope.view;
|
|
3653
|
+
if (actOutcome.ok) {
|
|
3654
|
+
pool.assertNoOpenTickets();
|
|
3655
|
+
const out = actOutcome.out;
|
|
3656
|
+
if (out !== void 0) {
|
|
3657
|
+
const outRef = contentAddress(out);
|
|
3658
|
+
await opts.blobs.put(outRef, out);
|
|
3659
|
+
const { childWork, driverInference } = await spentFromJournal(journal, opts.runId);
|
|
3660
|
+
return {
|
|
3661
|
+
kind: "winner",
|
|
3662
|
+
out,
|
|
3663
|
+
outRef,
|
|
3664
|
+
tree,
|
|
3665
|
+
spentTotal: addSpend(childWork, driverInference),
|
|
3666
|
+
...isNonEmptySpend(driverInference) ? { spentBreakdown: { driverInference, childWork } } : {}
|
|
3667
|
+
};
|
|
3668
|
+
}
|
|
3669
|
+
return noWinner();
|
|
3670
|
+
}
|
|
3671
|
+
return noWinner();
|
|
3672
|
+
async function noWinner() {
|
|
3673
|
+
const { childWork, driverInference } = await spentFromJournal(journal, opts.runId);
|
|
3674
|
+
return {
|
|
3675
|
+
kind: "no-winner",
|
|
3676
|
+
reason: classifyNoWinner(controller, pool, opts, breaker),
|
|
3677
|
+
tree,
|
|
3678
|
+
downCount: breaker.downCount(),
|
|
3679
|
+
spentTotal: addSpend(childWork, driverInference)
|
|
3680
|
+
};
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
function attach(h) {
|
|
3684
|
+
const control = rootControls.get(h);
|
|
3685
|
+
if (!control) {
|
|
3686
|
+
throw new RuntimeRunStateError(
|
|
3687
|
+
"supervisor.attach: handle was not minted by createRootHandle (no control channel)"
|
|
3688
|
+
);
|
|
3689
|
+
}
|
|
3690
|
+
attached = control;
|
|
3691
|
+
}
|
|
3692
|
+
return { run, attach };
|
|
3693
|
+
}
|
|
3694
|
+
var rootControls = /* @__PURE__ */ new WeakMap();
|
|
3695
|
+
function pushRootSignal(cascadeAbort) {
|
|
3696
|
+
return (msg) => {
|
|
3697
|
+
if (msg.kind === "cancel") cascadeAbort(msg.reason ?? "root signal: cancel");
|
|
3698
|
+
};
|
|
3699
|
+
}
|
|
3700
|
+
function createIntensityBreaker(opts, trip) {
|
|
3701
|
+
const max = opts.maxRestarts;
|
|
3702
|
+
const within = opts.withinMs;
|
|
3703
|
+
const armed = max !== void 0 && within !== void 0;
|
|
3704
|
+
const recent = [];
|
|
3705
|
+
let total = 0;
|
|
3706
|
+
let isTripped = false;
|
|
3707
|
+
return {
|
|
3708
|
+
recordDown(at) {
|
|
3709
|
+
total += 1;
|
|
3710
|
+
if (!armed || isTripped) return;
|
|
3711
|
+
recent.push(at);
|
|
3712
|
+
const cutoff = at - within;
|
|
3713
|
+
while (recent.length > 0 && recent[0] < cutoff) recent.shift();
|
|
3714
|
+
if (recent.length > max) {
|
|
3715
|
+
isTripped = true;
|
|
3716
|
+
trip();
|
|
3717
|
+
}
|
|
3718
|
+
},
|
|
3719
|
+
tripped() {
|
|
3720
|
+
return isTripped;
|
|
3721
|
+
},
|
|
3722
|
+
downCount() {
|
|
3723
|
+
return total;
|
|
3724
|
+
}
|
|
3725
|
+
};
|
|
3726
|
+
}
|
|
3727
|
+
function wrapJournalForBreaker(journal, breaker) {
|
|
3728
|
+
return {
|
|
3729
|
+
loadTree: (root) => journal.loadTree(root),
|
|
3730
|
+
beginTree: (root, at) => journal.beginTree(root, at),
|
|
3731
|
+
appendEvent: (root, ev) => {
|
|
3732
|
+
if (ev.kind === "settled" && ev.status === "down") breaker.recordDown(Date.parse(ev.at));
|
|
3733
|
+
return journal.appendEvent(root, ev);
|
|
3734
|
+
}
|
|
3735
|
+
};
|
|
3736
|
+
}
|
|
3737
|
+
async function drainLiveChildren(scope, controller) {
|
|
3738
|
+
const hasLive = scope.view.inFlight > 0;
|
|
3739
|
+
if (!hasLive) return;
|
|
3740
|
+
if (!controller.signal.aborted) controller.abort();
|
|
3741
|
+
await Promise.allSettled([drainCursor(scope)]);
|
|
3742
|
+
}
|
|
3743
|
+
async function drainCursor(scope) {
|
|
3744
|
+
for (; ; ) {
|
|
3745
|
+
const settled = await scope.next();
|
|
3746
|
+
if (settled === null) return;
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
function classifyNoWinner(controller, pool, opts, breaker) {
|
|
3750
|
+
if (breaker.tripped()) return "all-children-down";
|
|
3751
|
+
if (controller.signal.aborted) return "aborted";
|
|
3752
|
+
if (poolExhausted(pool, opts)) return "budget-exhausted";
|
|
3753
|
+
return "all-children-down";
|
|
3754
|
+
}
|
|
3755
|
+
function poolExhausted(pool, opts) {
|
|
3756
|
+
const r = pool.readout();
|
|
3757
|
+
if (r.tokensLeft <= 0) return true;
|
|
3758
|
+
if (opts.budget.maxUsd !== void 0 && r.usdLeft <= 0) return true;
|
|
3759
|
+
if (opts.budget.deadlineMs !== void 0 && r.deadlineMs > 0 && (opts.now ?? Date.now)() >= r.deadlineMs) {
|
|
3760
|
+
return true;
|
|
3761
|
+
}
|
|
3762
|
+
return false;
|
|
3763
|
+
}
|
|
3764
|
+
async function spentFromJournal(journal, root) {
|
|
3765
|
+
const events = await journal.loadTree(root);
|
|
3766
|
+
if (events === void 0) {
|
|
3767
|
+
throw new RuntimeRunStateError(
|
|
3768
|
+
`supervisor: spawn tree '${root}' is missing from the journal after run (corrupted log)`
|
|
3769
|
+
);
|
|
3770
|
+
}
|
|
3771
|
+
const childWork = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
3772
|
+
const driverInference = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
3773
|
+
for (const ev of events) {
|
|
3774
|
+
if (ev.kind === "settled") accumulate(childWork, ev.spent);
|
|
3775
|
+
else if (ev.kind === "metered") accumulate(driverInference, ev.spend);
|
|
3776
|
+
}
|
|
3777
|
+
return { childWork, driverInference };
|
|
3778
|
+
}
|
|
3779
|
+
function accumulate(a, b) {
|
|
3780
|
+
a.iterations += b.iterations;
|
|
3781
|
+
a.tokens.input += b.tokens.input;
|
|
3782
|
+
a.tokens.output += b.tokens.output;
|
|
3783
|
+
a.usd += b.usd;
|
|
3784
|
+
if (b.usdKnown === false) a.usdKnown = false;
|
|
3785
|
+
a.ms += b.ms;
|
|
3786
|
+
}
|
|
3787
|
+
function addSpend(a, b) {
|
|
3788
|
+
return {
|
|
3789
|
+
iterations: a.iterations + b.iterations,
|
|
3790
|
+
tokens: { input: a.tokens.input + b.tokens.input, output: a.tokens.output + b.tokens.output },
|
|
3791
|
+
usd: a.usd + b.usd,
|
|
3792
|
+
...a.usdKnown === false || b.usdKnown === false ? { usdKnown: false } : {},
|
|
3793
|
+
ms: a.ms + b.ms
|
|
3794
|
+
};
|
|
3795
|
+
}
|
|
3796
|
+
function isNonEmptySpend(s) {
|
|
3797
|
+
return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
|
|
3798
|
+
}
|
|
3799
|
+
|
|
2464
3800
|
// src/runtime/supervise/event-bus.ts
|
|
2465
3801
|
function createEventBus(now = Date.now) {
|
|
2466
3802
|
const queue = [];
|
|
@@ -3117,7 +4453,7 @@ ${narrative}` : roster;
|
|
|
3117
4453
|
return `${roster}
|
|
3118
4454
|
|
|
3119
4455
|
## Progress notes
|
|
3120
|
-
Summary unavailable: ${
|
|
4456
|
+
Summary unavailable: ${errMessage2(e)}`;
|
|
3121
4457
|
}
|
|
3122
4458
|
}),
|
|
3123
4459
|
...opts.compaction.onCompact ? { onCompact: opts.compaction.onCompact } : {},
|
|
@@ -3188,7 +4524,7 @@ function safeJson(v) {
|
|
|
3188
4524
|
return String(v);
|
|
3189
4525
|
}
|
|
3190
4526
|
}
|
|
3191
|
-
function
|
|
4527
|
+
function errMessage2(e) {
|
|
3192
4528
|
return e instanceof Error ? e.message : String(e);
|
|
3193
4529
|
}
|
|
3194
4530
|
|
|
@@ -5189,10 +6525,21 @@ async function delegate(intent, opts = {}) {
|
|
|
5189
6525
|
|
|
5190
6526
|
export {
|
|
5191
6527
|
gateOnDeliverable,
|
|
6528
|
+
defineRuntimeHooks,
|
|
6529
|
+
composeRuntimeHooks,
|
|
6530
|
+
notifyRuntimeHookEvent,
|
|
6531
|
+
notifyRuntimeDecisionPoint,
|
|
6532
|
+
createScope,
|
|
6533
|
+
settledToIteration,
|
|
6534
|
+
withDriverExecutor,
|
|
5192
6535
|
createWorktree,
|
|
5193
6536
|
captureWorktreeDiff,
|
|
5194
6537
|
removeWorktree,
|
|
5195
6538
|
runWorktreeHarness,
|
|
6539
|
+
routerChatWithUsage,
|
|
6540
|
+
routerChatWithTools,
|
|
6541
|
+
routerToolLoop,
|
|
6542
|
+
routerBrain,
|
|
5196
6543
|
acquireSandbox,
|
|
5197
6544
|
probeSandboxCapabilities,
|
|
5198
6545
|
createSandboxLineage,
|
|
@@ -5205,6 +6552,9 @@ export {
|
|
|
5205
6552
|
createExecutor,
|
|
5206
6553
|
createExecutorRegistry,
|
|
5207
6554
|
createInMemoryRunContext,
|
|
6555
|
+
spendFromUsageEvents,
|
|
6556
|
+
createBudgetPool,
|
|
6557
|
+
createSupervisor,
|
|
5208
6558
|
createEventBus,
|
|
5209
6559
|
createCoordinationTools,
|
|
5210
6560
|
driverAgent,
|
|
@@ -5263,4 +6613,4 @@ export {
|
|
|
5263
6613
|
workerFromBackend,
|
|
5264
6614
|
supervise
|
|
5265
6615
|
};
|
|
5266
|
-
//# sourceMappingURL=chunk-
|
|
6616
|
+
//# sourceMappingURL=chunk-URMJTSXB.js.map
|