@tangle-network/agent-runtime 0.101.1 → 0.102.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +5 -3
- package/dist/agent.js.map +1 -1
- package/dist/analyst-loop.d.ts +1 -1
- package/dist/candidate-execution/index.d.ts +13 -2
- package/dist/candidate-execution/index.js +8 -3
- package/dist/chunk-377GOJHJ.js +578 -0
- package/dist/chunk-377GOJHJ.js.map +1 -0
- package/dist/{chunk-DI677IPX.js → chunk-3DW5DEKQ.js} +3 -3
- package/dist/{chunk-KVJQV342.js → chunk-6MR4IJRW.js} +25 -1777
- package/dist/chunk-6MR4IJRW.js.map +1 -0
- package/dist/{chunk-RJPQLUDA.js → chunk-7FJJUC73.js} +336 -43
- package/dist/chunk-7FJJUC73.js.map +1 -0
- package/dist/{chunk-TA4RBJLE.js → chunk-CI7WS6S2.js} +5 -5
- package/dist/{chunk-U4PRAATQ.js → chunk-CK76OZPM.js} +3 -3
- package/dist/{chunk-KSBUSEUA.js → chunk-KJ2Q4T7F.js} +2 -2
- package/dist/{chunk-MAONWP4R.js → chunk-KPHTCHQ4.js} +611 -1419
- package/dist/chunk-KPHTCHQ4.js.map +1 -0
- package/dist/{chunk-BXZ7GPL4.js → chunk-KSM6GFHP.js} +2 -2
- package/dist/chunk-L5DST3QC.js +351 -0
- package/dist/chunk-L5DST3QC.js.map +1 -0
- package/dist/{chunk-HGSHPVJ6.js → chunk-LJLG734E.js} +200 -441
- package/dist/chunk-LJLG734E.js.map +1 -0
- package/dist/chunk-ROILMKDL.js +1338 -0
- package/dist/chunk-ROILMKDL.js.map +1 -0
- package/dist/{chunk-N2CEE5Q3.js → chunk-RTB3TC73.js} +3 -3
- package/dist/chunk-SQ3ZJH3Y.js +3082 -0
- package/dist/chunk-SQ3ZJH3Y.js.map +1 -0
- package/dist/chunk-UPWGXKXB.js +254 -0
- package/dist/chunk-UPWGXKXB.js.map +1 -0
- package/dist/{completion-gate-C80jiRfN.d.ts → completion-gate-C6dhVN-N.d.ts} +2 -2
- package/dist/{coordination-BFE3Den7.d.ts → coordination-D9l_YmwW.d.ts} +6 -6
- package/dist/environment-provider.d.ts +2 -2
- package/dist/{improve-g75IE2Cx.d.ts → improve-B40dRu0X.d.ts} +47 -19
- package/dist/index.d.ts +331 -23
- package/dist/index.js +544 -136
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +63 -62
- package/dist/intelligence.js +62 -471
- package/dist/intelligence.js.map +1 -1
- package/dist/knowledge.d.ts +8 -8
- package/dist/knowledge.js +9 -7
- package/dist/{local-harness-ZqCx51u7.d.ts → local-harness-CtK9dSny.d.ts} +14 -2
- package/dist/{loop-runner-bin-Cn1N2rRo.d.ts → loop-runner-bin-PnWslIw4.d.ts} +2 -2
- package/dist/loop-runner-bin.d.ts +6 -6
- package/dist/loop-runner-bin.js +6 -4
- package/dist/loops.d.ts +231 -460
- package/dist/loops.js +58 -37
- package/dist/mcp/bin.js +2 -2
- package/dist/mcp/index.d.ts +184 -12
- package/dist/mcp/index.js +31 -9
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/memory-bin.d.ts +1 -0
- package/dist/mcp/memory-bin.js +22 -0
- package/dist/mcp/memory-bin.js.map +1 -0
- package/dist/profiles.d.ts +3 -3
- package/dist/structural-rollout-D7xik9XR.d.ts +446 -0
- package/dist/{substrate-DO2GHNg2.d.ts → substrate-Dyiqmd3o.d.ts} +1 -1
- package/dist/{supervise-BLPI50-w.d.ts → supervise-BQdNAXtK.d.ts} +4 -4
- package/dist/testing.js +255 -211
- package/dist/testing.js.map +1 -1
- package/dist/{types-B3vAW0Oq.d.ts → types-CLat4bT9.d.ts} +1 -1
- package/dist/{types-CmnA2iL3.d.ts → types-D870JlH0.d.ts} +2 -2
- package/dist/{worktree-fanout-DCA3G4bO.d.ts → worktree-fanout-DsIR4d6w.d.ts} +234 -234
- package/package.json +6 -5
- package/dist/chunk-HGSHPVJ6.js.map +0 -1
- package/dist/chunk-IKGCKPDX.js +0 -1115
- package/dist/chunk-IKGCKPDX.js.map +0 -1
- package/dist/chunk-KVJQV342.js.map +0 -1
- package/dist/chunk-MAONWP4R.js.map +0 -1
- package/dist/chunk-RJPQLUDA.js.map +0 -1
- package/dist/{chunk-DI677IPX.js.map → chunk-3DW5DEKQ.js.map} +0 -0
- package/dist/{chunk-TA4RBJLE.js.map → chunk-CI7WS6S2.js.map} +0 -0
- package/dist/{chunk-U4PRAATQ.js.map → chunk-CK76OZPM.js.map} +0 -0
- package/dist/{chunk-KSBUSEUA.js.map → chunk-KJ2Q4T7F.js.map} +0 -0
- package/dist/{chunk-BXZ7GPL4.js.map → chunk-KSM6GFHP.js.map} +0 -0
- package/dist/{chunk-N2CEE5Q3.js.map → chunk-RTB3TC73.js.map} +0 -0
- package/dist/{sanitize-BTSsdBXw.d.ts → sanitize-DEbPNtyI.d.ts} +58 -58
|
@@ -6,9 +6,18 @@ import {
|
|
|
6
6
|
} from "./chunk-2VZYRB55.js";
|
|
7
7
|
import {
|
|
8
8
|
assertModelAllowed,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
captureWorktreeDiff,
|
|
10
|
+
createSupervisor,
|
|
11
|
+
createWorktree,
|
|
12
|
+
notifyRuntimeHookEvent,
|
|
13
|
+
removeWorktree,
|
|
14
|
+
routerBrain,
|
|
15
|
+
routerChatWithUsage,
|
|
16
|
+
runBrainLoop,
|
|
17
|
+
runWorktreeChecks,
|
|
18
|
+
runWorktreeHarness,
|
|
19
|
+
withDriverExecutor
|
|
20
|
+
} from "./chunk-SQ3ZJH3Y.js";
|
|
12
21
|
import {
|
|
13
22
|
InMemoryResultBlobStore,
|
|
14
23
|
InMemorySpawnJournal,
|
|
@@ -38,7 +47,6 @@ import {
|
|
|
38
47
|
AgentEvalError,
|
|
39
48
|
ConfigError,
|
|
40
49
|
NotFoundError,
|
|
41
|
-
RuntimeRunStateError,
|
|
42
50
|
ValidationError
|
|
43
51
|
} from "./chunk-YEJR7IXO.js";
|
|
44
52
|
|
|
@@ -84,919 +92,6 @@ function isAsyncIterable(v) {
|
|
|
84
92
|
return v != null && typeof v[Symbol.asyncIterator] === "function";
|
|
85
93
|
}
|
|
86
94
|
|
|
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
|
-
|
|
658
|
-
// src/mcp/worktree.ts
|
|
659
|
-
import { spawn } from "child_process";
|
|
660
|
-
import { isAbsolute, win32 } from "path";
|
|
661
|
-
async function runGitAsync(args, cwd, runner) {
|
|
662
|
-
if (runner) return runner(args, { cwd });
|
|
663
|
-
return new Promise((resolve, reject) => {
|
|
664
|
-
const proc = spawn("git", args, { cwd, stdio: "pipe" });
|
|
665
|
-
let stdout = "";
|
|
666
|
-
let stderr = "";
|
|
667
|
-
proc.stdout?.on("data", (c) => {
|
|
668
|
-
stdout += String(c);
|
|
669
|
-
});
|
|
670
|
-
proc.stderr?.on("data", (c) => {
|
|
671
|
-
stderr += String(c);
|
|
672
|
-
});
|
|
673
|
-
proc.on("error", reject);
|
|
674
|
-
proc.on("close", (code) => resolve({ stdout, stderr, exitCode: code ?? -1 }));
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
function ensureGitOk(step, result) {
|
|
678
|
-
if (result.exitCode !== 0) {
|
|
679
|
-
throw gitFailure(step, result);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
function gitFailure(step, result) {
|
|
683
|
-
return new Error(
|
|
684
|
-
`worktree: git ${step} failed (exit ${result.exitCode}): ${result.stderr.slice(0, 400)}`
|
|
685
|
-
);
|
|
686
|
-
}
|
|
687
|
-
async function createWorktree(options) {
|
|
688
|
-
const variants = options.variantsDir ?? ".agent-worktrees";
|
|
689
|
-
const baseRef = options.baseRef ?? "HEAD";
|
|
690
|
-
const branch = `delegate/${options.runId}`;
|
|
691
|
-
const path2 = `${options.repoRoot.replace(/\/+$/, "")}/${variants}/${options.runId}`;
|
|
692
|
-
const headSha = await runGitAsync(["rev-parse", baseRef], options.repoRoot, options.runGit);
|
|
693
|
-
ensureGitOk(`rev-parse ${baseRef}`, headSha);
|
|
694
|
-
const add = await runGitAsync(
|
|
695
|
-
["worktree", "add", "-b", branch, path2, baseRef],
|
|
696
|
-
options.repoRoot,
|
|
697
|
-
options.runGit
|
|
698
|
-
);
|
|
699
|
-
ensureGitOk(`worktree add ${path2}`, add);
|
|
700
|
-
return { path: path2, baseSha: headSha.stdout.trim(), branch };
|
|
701
|
-
}
|
|
702
|
-
async function captureWorktreeDiff(options) {
|
|
703
|
-
const baseRef = options.baseRef ?? options.worktree.baseSha;
|
|
704
|
-
const pathspecs = worktreeDiffPathspecs(options.excludePaths ?? []);
|
|
705
|
-
const staged = await runGitAsync(
|
|
706
|
-
["add", "-A", ...pathspecs.length > 0 ? ["--", ".", ...pathspecs] : []],
|
|
707
|
-
options.worktree.path,
|
|
708
|
-
options.runGit
|
|
709
|
-
);
|
|
710
|
-
ensureGitOk("add -A", staged);
|
|
711
|
-
const patch = await runGitAsync(
|
|
712
|
-
["diff", "--cached", baseRef, ...pathspecs.length > 0 ? ["--", ".", ...pathspecs] : []],
|
|
713
|
-
options.worktree.path,
|
|
714
|
-
options.runGit
|
|
715
|
-
);
|
|
716
|
-
ensureGitOk(`diff --cached ${baseRef}`, patch);
|
|
717
|
-
const shortstat = await runGitAsync(
|
|
718
|
-
[
|
|
719
|
-
"diff",
|
|
720
|
-
"--cached",
|
|
721
|
-
"--shortstat",
|
|
722
|
-
baseRef,
|
|
723
|
-
...pathspecs.length > 0 ? ["--", ".", ...pathspecs] : []
|
|
724
|
-
],
|
|
725
|
-
options.worktree.path,
|
|
726
|
-
options.runGit
|
|
727
|
-
);
|
|
728
|
-
ensureGitOk(`diff --cached --shortstat ${baseRef}`, shortstat);
|
|
729
|
-
const stats = parseShortstat(shortstat.stdout);
|
|
730
|
-
return { patch: patch.stdout, stats };
|
|
731
|
-
}
|
|
732
|
-
function worktreeDiffPathspecs(paths) {
|
|
733
|
-
const normalized = /* @__PURE__ */ new Set();
|
|
734
|
-
for (const path2 of paths) {
|
|
735
|
-
if (typeof path2 !== "string" || path2.length === 0 || path2.includes("\\") || hasControlCharacter(path2) || isAbsolute(path2) || win32.isAbsolute(path2)) {
|
|
736
|
-
throw new Error(
|
|
737
|
-
`worktree: excluded path must be a canonical repository-relative path: ${path2}`
|
|
738
|
-
);
|
|
739
|
-
}
|
|
740
|
-
const segments = path2.split("/");
|
|
741
|
-
if (segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
|
|
742
|
-
throw new Error(
|
|
743
|
-
`worktree: excluded path must be a canonical repository-relative path: ${path2}`
|
|
744
|
-
);
|
|
745
|
-
}
|
|
746
|
-
normalized.add(path2);
|
|
747
|
-
}
|
|
748
|
-
return [...normalized].sort((left, right) => left.localeCompare(right)).map((path2) => `:(top,exclude,literal)${path2}`);
|
|
749
|
-
}
|
|
750
|
-
function hasControlCharacter(value) {
|
|
751
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
752
|
-
const code = value.charCodeAt(index);
|
|
753
|
-
if (code < 32 || code === 127) return true;
|
|
754
|
-
}
|
|
755
|
-
return false;
|
|
756
|
-
}
|
|
757
|
-
function parseShortstat(text) {
|
|
758
|
-
const out = { filesChanged: 0, insertions: 0, deletions: 0 };
|
|
759
|
-
const filesMatch = text.match(/(\d+)\s+files?\s+changed/);
|
|
760
|
-
if (filesMatch?.[1]) out.filesChanged = Number(filesMatch[1]);
|
|
761
|
-
const insertMatch = text.match(/(\d+)\s+insertions?/);
|
|
762
|
-
if (insertMatch?.[1]) out.insertions = Number(insertMatch[1]);
|
|
763
|
-
const deleteMatch = text.match(/(\d+)\s+deletions?/);
|
|
764
|
-
if (deleteMatch?.[1]) out.deletions = Number(deleteMatch[1]);
|
|
765
|
-
return out;
|
|
766
|
-
}
|
|
767
|
-
async function removeWorktree(options) {
|
|
768
|
-
const force = options.force ?? true;
|
|
769
|
-
const args = ["worktree", "remove"];
|
|
770
|
-
if (force) args.push("--force");
|
|
771
|
-
args.push(options.worktree.path);
|
|
772
|
-
const removal = await runGitAsync(args, options.repoRoot, options.runGit);
|
|
773
|
-
const removalError = removal.exitCode !== 0 && !/not a working tree/iu.test(removal.stderr) ? gitFailure(`worktree remove ${options.worktree.path}`, removal) : void 0;
|
|
774
|
-
const branchRemoval = await runGitAsync(
|
|
775
|
-
["branch", "-D", options.worktree.branch],
|
|
776
|
-
options.repoRoot,
|
|
777
|
-
options.runGit
|
|
778
|
-
);
|
|
779
|
-
const branchError = branchRemoval.exitCode !== 0 && !/branch .+ not found/iu.test(branchRemoval.stderr) ? gitFailure(`branch -D ${options.worktree.branch}`, branchRemoval) : void 0;
|
|
780
|
-
if (removalError && branchError) {
|
|
781
|
-
throw new AggregateError(
|
|
782
|
-
[removalError, branchError],
|
|
783
|
-
"worktree: failed to remove worktree and branch"
|
|
784
|
-
);
|
|
785
|
-
}
|
|
786
|
-
if (removalError) throw removalError;
|
|
787
|
-
if (branchError) throw branchError;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
// src/runtime/router-client.ts
|
|
791
|
-
import { estimateCost, isModelPriced } from "@tangle-network/agent-eval";
|
|
792
|
-
|
|
793
|
-
// src/runtime/tool-loop.ts
|
|
794
|
-
function estimateConversationTokens(messages) {
|
|
795
|
-
let chars = 0;
|
|
796
|
-
for (const m of messages) {
|
|
797
|
-
const content = m.content;
|
|
798
|
-
chars += typeof content === "string" ? content.length : safeJsonLength(content);
|
|
799
|
-
const calls = m.tool_calls;
|
|
800
|
-
if (calls) for (const c of calls) chars += c.function?.arguments?.length ?? 0;
|
|
801
|
-
}
|
|
802
|
-
return Math.ceil(chars / 4);
|
|
803
|
-
}
|
|
804
|
-
function safeJsonLength(value) {
|
|
805
|
-
if (value === void 0 || value === null) return 0;
|
|
806
|
-
try {
|
|
807
|
-
return JSON.stringify(value)?.length ?? 0;
|
|
808
|
-
} catch {
|
|
809
|
-
return String(value).length;
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
async function maybeCompact(messages, c, turn) {
|
|
813
|
-
const head = c.preserveHead !== void 0 && c.preserveHead > 0 ? c.preserveHead : 2;
|
|
814
|
-
if (messages.length <= head + 1) return false;
|
|
815
|
-
const estimate = c.estimateTokens ?? estimateConversationTokens;
|
|
816
|
-
const before = estimate(messages);
|
|
817
|
-
if (before <= c.thresholdTokens) return false;
|
|
818
|
-
const digest = await c.distill([...messages]);
|
|
819
|
-
messages.splice(head, messages.length - head, {
|
|
820
|
-
role: "user",
|
|
821
|
-
content: `[earlier work compacted to save context \u2014 progress so far]
|
|
822
|
-
${digest}`
|
|
823
|
-
});
|
|
824
|
-
c.onCompact?.({ turn, beforeTokens: before, afterTokens: estimate(messages) });
|
|
825
|
-
return true;
|
|
826
|
-
}
|
|
827
|
-
async function runBrainLoop(opts) {
|
|
828
|
-
const maxTurns = opts.maxTurns ?? 4;
|
|
829
|
-
const messages = [...opts.initialMessages];
|
|
830
|
-
let toolCalls = 0;
|
|
831
|
-
let lastText = "";
|
|
832
|
-
const usage = { input: 0, output: 0 };
|
|
833
|
-
const toolTrace = [];
|
|
834
|
-
for (let turn = 1; turn <= maxTurns; turn += 1) {
|
|
835
|
-
if (opts.hooks?.stopBefore?.(turn)) break;
|
|
836
|
-
await opts.hooks?.beforeTurn?.(turn, messages);
|
|
837
|
-
if (opts.compaction) await maybeCompact(messages, opts.compaction, turn);
|
|
838
|
-
const r = await opts.chat(messages, opts.tools);
|
|
839
|
-
if (r.usage) {
|
|
840
|
-
usage.input += r.usage.input;
|
|
841
|
-
usage.output += r.usage.output;
|
|
842
|
-
opts.hooks?.onUsage?.(r.usage);
|
|
843
|
-
}
|
|
844
|
-
if (r.content) lastText = r.content;
|
|
845
|
-
if (r.toolCalls.length === 0) {
|
|
846
|
-
if (r.content) messages.push({ role: "assistant", content: r.content });
|
|
847
|
-
return { final: lastText, turns: turn, toolCalls, toolTrace, usage, messages };
|
|
848
|
-
}
|
|
849
|
-
messages.push({
|
|
850
|
-
role: "assistant",
|
|
851
|
-
content: r.content ?? "",
|
|
852
|
-
tool_calls: r.toolCalls.map((tc) => ({
|
|
853
|
-
id: tc.id,
|
|
854
|
-
type: "function",
|
|
855
|
-
function: { name: tc.name, arguments: tc.arguments }
|
|
856
|
-
}))
|
|
857
|
-
});
|
|
858
|
-
for (const tc of r.toolCalls) {
|
|
859
|
-
toolCalls += 1;
|
|
860
|
-
let args = {};
|
|
861
|
-
try {
|
|
862
|
-
args = JSON.parse(tc.arguments);
|
|
863
|
-
} catch {
|
|
864
|
-
messages.push({
|
|
865
|
-
role: "tool",
|
|
866
|
-
tool_call_id: tc.id,
|
|
867
|
-
content: `error: arguments were not valid JSON: ${tc.arguments.slice(0, 200)}`
|
|
868
|
-
});
|
|
869
|
-
continue;
|
|
870
|
-
}
|
|
871
|
-
const out = await opts.execute(tc.name, args);
|
|
872
|
-
messages.push({ role: "tool", tool_call_id: tc.id, content: out });
|
|
873
|
-
toolTrace.push({ name: tc.name, args: tc.arguments, result: out });
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
return { final: lastText, turns: maxTurns, toolCalls, toolTrace, usage, messages };
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
// src/runtime/router-client.ts
|
|
880
|
-
async function routerChatWithUsage(cfg, messages, opts) {
|
|
881
|
-
const url = `${cfg.routerBaseUrl.replace(/\/$/, "")}/chat/completions`;
|
|
882
|
-
const headers = { "content-type": "application/json", authorization: `Bearer ${cfg.routerKey}` };
|
|
883
|
-
let temperature = opts?.temperature ?? 0.2;
|
|
884
|
-
const body = () => ({
|
|
885
|
-
model: cfg.model,
|
|
886
|
-
messages,
|
|
887
|
-
temperature,
|
|
888
|
-
max_tokens: opts?.maxTokens ?? 8192,
|
|
889
|
-
...opts?.reasoningEffort ? { reasoning_effort: opts.reasoningEffort } : {}
|
|
890
|
-
});
|
|
891
|
-
if (cfg.complete) return parseChatResult(await cfg.complete(body()), cfg.model);
|
|
892
|
-
let lastErr = "";
|
|
893
|
-
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
894
|
-
const res = await fetch(url, {
|
|
895
|
-
method: "POST",
|
|
896
|
-
headers,
|
|
897
|
-
body: JSON.stringify(body()),
|
|
898
|
-
...opts?.signal ? { signal: opts.signal } : {}
|
|
899
|
-
});
|
|
900
|
-
if (res.ok) return parseChatResult(await res.json(), cfg.model);
|
|
901
|
-
const status = res.status;
|
|
902
|
-
const text = (await res.text()).slice(0, 200);
|
|
903
|
-
lastErr = `router ${status}: ${text}`;
|
|
904
|
-
if (status === 400 && /temperature/i.test(text) && temperature !== 1) {
|
|
905
|
-
temperature = 1;
|
|
906
|
-
continue;
|
|
907
|
-
}
|
|
908
|
-
if (![408, 425, 429, 500, 502, 503, 504, 520, 522, 524].includes(status))
|
|
909
|
-
throw new Error(lastErr);
|
|
910
|
-
if (attempt < 4) await new Promise((r) => setTimeout(r, 800 * 2 ** attempt));
|
|
911
|
-
}
|
|
912
|
-
throw new Error(`${lastErr} (exhausted retries)`);
|
|
913
|
-
}
|
|
914
|
-
function parseChatResult(json, model) {
|
|
915
|
-
const data = json;
|
|
916
|
-
const u = data.usage;
|
|
917
|
-
const usage = u && typeof u.prompt_tokens === "number" && typeof u.completion_tokens === "number" ? { input: u.prompt_tokens, output: u.completion_tokens } : void 0;
|
|
918
|
-
const costUsd = usage && isModelPriced(model) ? estimateCost(usage.input, usage.output, model) : void 0;
|
|
919
|
-
const msg = data.choices?.[0]?.message;
|
|
920
|
-
const { content, reasoning } = splitReasoning(
|
|
921
|
-
msg?.content ?? "",
|
|
922
|
-
msg?.reasoning ?? msg?.reasoning_content
|
|
923
|
-
);
|
|
924
|
-
return {
|
|
925
|
-
content,
|
|
926
|
-
...reasoning ? { reasoning } : {},
|
|
927
|
-
...usage ? { usage } : {},
|
|
928
|
-
...costUsd !== void 0 ? { costUsd } : {}
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
function splitReasoning(rawContent, fieldReasoning) {
|
|
932
|
-
const open = rawContent.indexOf("<think>");
|
|
933
|
-
if (open !== -1) {
|
|
934
|
-
const close = rawContent.indexOf("</think>", open);
|
|
935
|
-
const inline = close !== -1 ? rawContent.slice(open + 7, close) : rawContent.slice(open + 7);
|
|
936
|
-
const rest = close !== -1 ? rawContent.slice(0, open) + rawContent.slice(close + 8) : rawContent.slice(0, open);
|
|
937
|
-
const reasoning = [fieldReasoning, inline.trim()].filter(Boolean).join("\n");
|
|
938
|
-
return { content: rest.trim(), ...reasoning ? { reasoning } : {} };
|
|
939
|
-
}
|
|
940
|
-
return { content: rawContent, ...fieldReasoning ? { reasoning: fieldReasoning } : {} };
|
|
941
|
-
}
|
|
942
|
-
async function routerChatWithTools(cfg, messages, tools, opts) {
|
|
943
|
-
const body = {
|
|
944
|
-
model: cfg.model,
|
|
945
|
-
messages,
|
|
946
|
-
tools,
|
|
947
|
-
tool_choice: opts?.toolChoice ?? "auto",
|
|
948
|
-
temperature: opts?.temperature ?? 0.3,
|
|
949
|
-
...opts?.maxTokens ? { max_tokens: opts.maxTokens } : {}
|
|
950
|
-
};
|
|
951
|
-
const raw = cfg.complete ? await cfg.complete(body) : await (async () => {
|
|
952
|
-
const res = await fetch(`${cfg.routerBaseUrl.replace(/\/$/, "")}/chat/completions`, {
|
|
953
|
-
method: "POST",
|
|
954
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${cfg.routerKey}` },
|
|
955
|
-
body: JSON.stringify(body),
|
|
956
|
-
...opts?.signal ? { signal: opts.signal } : {}
|
|
957
|
-
});
|
|
958
|
-
if (!res.ok) throw new Error(`router ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
959
|
-
return res.json();
|
|
960
|
-
})();
|
|
961
|
-
const data = raw;
|
|
962
|
-
const msg = data.choices?.[0]?.message;
|
|
963
|
-
const toolCalls = (msg?.tool_calls ?? []).map((tc, i) => ({
|
|
964
|
-
id: tc.id ?? `call_${i}`,
|
|
965
|
-
name: tc.function?.name ?? "",
|
|
966
|
-
arguments: tc.function?.arguments ?? "{}"
|
|
967
|
-
}));
|
|
968
|
-
const u = data.usage;
|
|
969
|
-
const usage = u && typeof u.prompt_tokens === "number" && typeof u.completion_tokens === "number" ? { input: u.prompt_tokens, output: u.completion_tokens } : void 0;
|
|
970
|
-
const costUsd = usage && isModelPriced(cfg.model) ? estimateCost(usage.input, usage.output, cfg.model) : void 0;
|
|
971
|
-
return {
|
|
972
|
-
content: msg?.content ?? null,
|
|
973
|
-
toolCalls,
|
|
974
|
-
...usage ? { usage } : {},
|
|
975
|
-
...costUsd !== void 0 ? { costUsd } : {}
|
|
976
|
-
};
|
|
977
|
-
}
|
|
978
|
-
async function routerToolLoop(cfg, system, user, tools, execute, opts) {
|
|
979
|
-
const initialMessages = opts?.initialMessages ?? [
|
|
980
|
-
{ role: "system", content: system },
|
|
981
|
-
{ role: "user", content: user }
|
|
982
|
-
];
|
|
983
|
-
return runBrainLoop({
|
|
984
|
-
chat: (messages, toolSpecs) => routerChatWithTools(cfg, messages, toolSpecs, {
|
|
985
|
-
...opts?.temperature !== void 0 ? { temperature: opts.temperature } : {},
|
|
986
|
-
...opts?.maxTokens ? { maxTokens: opts.maxTokens } : {},
|
|
987
|
-
...opts?.signal ? { signal: opts.signal } : {}
|
|
988
|
-
}),
|
|
989
|
-
tools,
|
|
990
|
-
execute,
|
|
991
|
-
initialMessages,
|
|
992
|
-
maxTurns: opts?.maxTurns ?? 4
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
function routerBrain(cfg, opts = {}) {
|
|
996
|
-
const temperature = opts.temperature ?? 0.4;
|
|
997
|
-
return (messages, tools) => routerChatWithTools(cfg, messages, tools, { temperature, toolChoice: "auto" });
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
95
|
// src/runtime/sandbox-acquire.ts
|
|
1001
96
|
var RETRYABLE_HTTP = /* @__PURE__ */ new Set([502, 503, 504, 522, 524, 408, 425, 429]);
|
|
1002
97
|
var TERMINAL_STATUS = /* @__PURE__ */ new Set(["failed", "expired", "stopped"]);
|
|
@@ -1687,13 +782,13 @@ async function executeIteration(args) {
|
|
|
1687
782
|
await destroySandboxSafe(box, args.ctx.traceEmitter, args.runId, args.now);
|
|
1688
783
|
}
|
|
1689
784
|
}
|
|
1690
|
-
if (
|
|
785
|
+
if (isAbortError(slot.error) || args.signal.aborted) {
|
|
1691
786
|
if (slot.error) throw slot.error;
|
|
1692
787
|
throwAbort();
|
|
1693
788
|
}
|
|
1694
789
|
if (slot.error instanceof ValidationError) throw slot.error;
|
|
1695
790
|
}
|
|
1696
|
-
function
|
|
791
|
+
function isAbortError(err) {
|
|
1697
792
|
return err instanceof Error && err.name === "AbortError";
|
|
1698
793
|
}
|
|
1699
794
|
var TEARDOWN_TIMEOUT_MS2 = 15e3;
|
|
@@ -2003,302 +1098,6 @@ ${lines.join("\n")}`;
|
|
|
2003
1098
|
|
|
2004
1099
|
// src/runtime/supervise/worktree-cli-executor.ts
|
|
2005
1100
|
import { randomUUID } from "crypto";
|
|
2006
|
-
|
|
2007
|
-
// src/mcp/worktree-harness.ts
|
|
2008
|
-
import { spawn as spawn2 } from "child_process";
|
|
2009
|
-
import { createHash } from "crypto";
|
|
2010
|
-
import {
|
|
2011
|
-
applyWorkspacePlan,
|
|
2012
|
-
materializeProfile
|
|
2013
|
-
} from "@tangle-network/agent-profile-materialize";
|
|
2014
|
-
var defaultCheckOutputCap = 16e3;
|
|
2015
|
-
async function runWorktreeHarness(opts) {
|
|
2016
|
-
const runHarness = opts.runHarness ?? runLocalHarness;
|
|
2017
|
-
const runCommand = opts.runCommand ?? defaultRunCommand;
|
|
2018
|
-
const checkTimeoutMs = opts.checkTimeoutMs ?? opts.harnessTimeoutMs ?? 5 * 60 * 1e3;
|
|
2019
|
-
const cap = opts.checkOutputCap ?? defaultCheckOutputCap;
|
|
2020
|
-
const worktree = await createWorktree({
|
|
2021
|
-
repoRoot: opts.repoRoot,
|
|
2022
|
-
runId: opts.runId,
|
|
2023
|
-
...opts.baseRef ? { baseRef: opts.baseRef } : {},
|
|
2024
|
-
...opts.runGit ? { runGit: opts.runGit } : {}
|
|
2025
|
-
});
|
|
2026
|
-
const cleanup = () => removeWorktree({
|
|
2027
|
-
worktree,
|
|
2028
|
-
repoRoot: opts.repoRoot,
|
|
2029
|
-
...opts.runGit ? { runGit: opts.runGit } : {}
|
|
2030
|
-
});
|
|
2031
|
-
try {
|
|
2032
|
-
assertSupportedWorktreeProfile(opts.profile, opts.harness);
|
|
2033
|
-
assertSafeProfileResourcePaths(opts.profile);
|
|
2034
|
-
const resourceInstructions = resolveResourceInstructions(opts.profile);
|
|
2035
|
-
const workspaceProfile = materializationOnlyProfile(opts.profile);
|
|
2036
|
-
const plan = materializeProfile(workspaceProfile, opts.harness);
|
|
2037
|
-
if (plan.unsupported.length > 0) {
|
|
2038
|
-
throw new Error(
|
|
2039
|
-
`runWorktreeHarness: profile cannot be materialized for ${opts.harness}: ${plan.unsupported.map(({ dimension, reason }) => `${dimension}: ${reason}`).join("; ")}`
|
|
2040
|
-
);
|
|
2041
|
-
}
|
|
2042
|
-
assertSafeMaterializedPaths(plan);
|
|
2043
|
-
const applied = applyWorkspacePlan(plan, worktree.path, { existingFiles: "reject" });
|
|
2044
|
-
if (applied.unsupported.length > 0) {
|
|
2045
|
-
throw new Error("runWorktreeHarness: applied profile unexpectedly retained unsupported rows");
|
|
2046
|
-
}
|
|
2047
|
-
const invocationProfile = profileWithResourceInstructions(opts.profile, resourceInstructions);
|
|
2048
|
-
const { command, args } = harnessInvocation(opts.harness, invocationProfile, opts.taskPrompt, {
|
|
2049
|
-
// This helper created the candidate worktree above; autonomous Claude
|
|
2050
|
-
// edits are permitted only inside that isolated checkout.
|
|
2051
|
-
dangerouslySkipPermissions: opts.harness === "claude",
|
|
2052
|
-
...opts.codexReproducible ? { codexReproducible: true } : {}
|
|
2053
|
-
});
|
|
2054
|
-
const harnessResult = await runHarness({
|
|
2055
|
-
harness: opts.harness,
|
|
2056
|
-
cwd: worktree.path,
|
|
2057
|
-
taskPrompt: opts.taskPrompt,
|
|
2058
|
-
invocation: { command, args: [...args, ...applied.flags] },
|
|
2059
|
-
env: { ...process.env, ...applied.env },
|
|
2060
|
-
...opts.codexReproducible ? { codexReproducible: true } : {},
|
|
2061
|
-
...opts.codexReadDeniedPaths ? { codexReadDeniedPaths: opts.codexReadDeniedPaths } : {},
|
|
2062
|
-
...opts.harnessTimeoutMs !== void 0 ? { timeoutMs: opts.harnessTimeoutMs } : {},
|
|
2063
|
-
...opts.signal ? { signal: opts.signal } : {}
|
|
2064
|
-
});
|
|
2065
|
-
const diff = await captureWorktreeDiff({
|
|
2066
|
-
worktree,
|
|
2067
|
-
excludePaths: applied.written,
|
|
2068
|
-
...opts.runGit ? { runGit: opts.runGit } : {}
|
|
2069
|
-
});
|
|
2070
|
-
const checks = await runWorktreeChecks({
|
|
2071
|
-
worktreePath: worktree.path,
|
|
2072
|
-
...opts.testCmd !== void 0 ? { testCmd: opts.testCmd } : {},
|
|
2073
|
-
...opts.typecheckCmd !== void 0 ? { typecheckCmd: opts.typecheckCmd } : {},
|
|
2074
|
-
timeoutMs: checkTimeoutMs,
|
|
2075
|
-
cap,
|
|
2076
|
-
runCommand,
|
|
2077
|
-
...opts.signal ? { signal: opts.signal } : {}
|
|
2078
|
-
});
|
|
2079
|
-
const result = {
|
|
2080
|
-
branch: worktree.branch,
|
|
2081
|
-
patch: diff.patch,
|
|
2082
|
-
stats: diff.stats,
|
|
2083
|
-
profileMaterialization: profileMaterializationReceipt(applied, resourceInstructions),
|
|
2084
|
-
harness: {
|
|
2085
|
-
name: opts.harness,
|
|
2086
|
-
exitCode: harnessResult.exitCode,
|
|
2087
|
-
timedOut: harnessResult.timedOut,
|
|
2088
|
-
killedBySignal: harnessResult.killedBySignal,
|
|
2089
|
-
durationMs: harnessResult.durationMs,
|
|
2090
|
-
stdout: harnessResult.stdout,
|
|
2091
|
-
stderr: harnessResult.stderr,
|
|
2092
|
-
...harnessResult.usage ? { usage: harnessResult.usage } : {},
|
|
2093
|
-
...harnessResult.evidence ? {
|
|
2094
|
-
cliVersion: harnessResult.evidence.cliVersion,
|
|
2095
|
-
executableSha256: harnessResult.evidence.executableSha256,
|
|
2096
|
-
requestedPromptSha256: harnessResult.evidence.requestedPromptSha256,
|
|
2097
|
-
effectivePromptSha256: harnessResult.evidence.effectivePromptSha256,
|
|
2098
|
-
nonPromptArgsSha256: harnessResult.evidence.nonPromptArgsSha256,
|
|
2099
|
-
controlledConfigSha256: harnessResult.evidence.controlledConfigSha256,
|
|
2100
|
-
readDeniedPaths: [...harnessResult.evidence.readDeniedPaths],
|
|
2101
|
-
readDeniedPathsSha256: harnessResult.evidence.readDeniedPathsSha256,
|
|
2102
|
-
readDeniedPathCount: harnessResult.evidence.readDeniedPathCount,
|
|
2103
|
-
executionPolicy: harnessResult.evidence.policy
|
|
2104
|
-
} : {}
|
|
2105
|
-
},
|
|
2106
|
-
...checks ? { checks } : {}
|
|
2107
|
-
};
|
|
2108
|
-
return { worktree, result, cleanup };
|
|
2109
|
-
} catch (err) {
|
|
2110
|
-
try {
|
|
2111
|
-
await cleanup();
|
|
2112
|
-
} catch (cleanupError) {
|
|
2113
|
-
throw new AggregateError(
|
|
2114
|
-
[err, cleanupError],
|
|
2115
|
-
"runWorktreeHarness: run failed and worktree cleanup also failed"
|
|
2116
|
-
);
|
|
2117
|
-
}
|
|
2118
|
-
throw err;
|
|
2119
|
-
}
|
|
2120
|
-
}
|
|
2121
|
-
function resolveResourceInstructions(profile) {
|
|
2122
|
-
const instructions = profile.resources?.instructions;
|
|
2123
|
-
if (instructions === void 0) return void 0;
|
|
2124
|
-
if (typeof instructions === "string")
|
|
2125
|
-
return instructions.trim().length > 0 ? instructions : void 0;
|
|
2126
|
-
if (instructions.kind === "inline" && typeof instructions.content === "string") {
|
|
2127
|
-
return instructions.content.trim().length > 0 ? instructions.content : void 0;
|
|
2128
|
-
}
|
|
2129
|
-
throw new Error(
|
|
2130
|
-
"runWorktreeHarness: resources.instructions must be a string or inline resource; remote refs require pre-resolution"
|
|
2131
|
-
);
|
|
2132
|
-
}
|
|
2133
|
-
function profileWithResourceInstructions(profile, resourceInstructions) {
|
|
2134
|
-
let resources;
|
|
2135
|
-
if (profile.resources) {
|
|
2136
|
-
const { instructions: _instructions, ...structuralResources } = profile.resources;
|
|
2137
|
-
resources = structuralResources;
|
|
2138
|
-
}
|
|
2139
|
-
return {
|
|
2140
|
-
...profile,
|
|
2141
|
-
...resources ? { resources } : {},
|
|
2142
|
-
...resourceInstructions === void 0 ? {} : {
|
|
2143
|
-
prompt: {
|
|
2144
|
-
...profile.prompt,
|
|
2145
|
-
instructions: [...profile.prompt?.instructions ?? [], resourceInstructions]
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
};
|
|
2149
|
-
}
|
|
2150
|
-
function materializationOnlyProfile(profile) {
|
|
2151
|
-
const {
|
|
2152
|
-
prompt: _prompt,
|
|
2153
|
-
model: _model,
|
|
2154
|
-
resources: profileResources,
|
|
2155
|
-
...structuralProfile
|
|
2156
|
-
} = profile;
|
|
2157
|
-
let resources;
|
|
2158
|
-
if (profileResources) {
|
|
2159
|
-
const { instructions: _instructions, ...fileBackedResources } = profileResources;
|
|
2160
|
-
resources = fileBackedResources;
|
|
2161
|
-
}
|
|
2162
|
-
return {
|
|
2163
|
-
...structuralProfile,
|
|
2164
|
-
...resources ? { resources } : {}
|
|
2165
|
-
};
|
|
2166
|
-
}
|
|
2167
|
-
function assertSupportedWorktreeProfile(profile, harness) {
|
|
2168
|
-
const unsupportedAxes = [
|
|
2169
|
-
hasEntries(profile.tools) ? "tools" : null,
|
|
2170
|
-
hasEntries(profile.permissions) ? "permissions" : null,
|
|
2171
|
-
profile.connections && profile.connections.length > 0 ? "connections" : null,
|
|
2172
|
-
hasEntries(profile.confidential) ? "confidential" : null,
|
|
2173
|
-
hasEntries(profile.modes) ? "modes" : null,
|
|
2174
|
-
hasEntries(profile.extensions) ? "extensions" : null
|
|
2175
|
-
].filter((axis) => axis !== null);
|
|
2176
|
-
if (profile.model?.reasoningEffort !== void 0 && harness !== "codex") {
|
|
2177
|
-
unsupportedAxes.push("model.reasoningEffort");
|
|
2178
|
-
}
|
|
2179
|
-
for (const [name, server] of Object.entries(profile.mcp ?? {})) {
|
|
2180
|
-
const path2 = `mcp[${JSON.stringify(name)}]`;
|
|
2181
|
-
if (server.enabled === false && harness !== "opencode") {
|
|
2182
|
-
unsupportedAxes.push(`${path2}.enabled`);
|
|
2183
|
-
}
|
|
2184
|
-
if (hasEntries(server.headers) && harness === "codex") {
|
|
2185
|
-
unsupportedAxes.push(`${path2}.headers`);
|
|
2186
|
-
}
|
|
2187
|
-
if (server.cwd !== void 0 && harness === "opencode") {
|
|
2188
|
-
unsupportedAxes.push(`${path2}.cwd`);
|
|
2189
|
-
}
|
|
2190
|
-
}
|
|
2191
|
-
if (harness === "claude") {
|
|
2192
|
-
for (const [event, commands] of Object.entries(profile.hooks ?? {})) {
|
|
2193
|
-
for (const [index, command] of commands.entries()) {
|
|
2194
|
-
const path2 = `hooks[${JSON.stringify(event)}][${index}]`;
|
|
2195
|
-
if (hasEntries(command.env)) unsupportedAxes.push(`${path2}.env`);
|
|
2196
|
-
if (command.blocking !== void 0) unsupportedAxes.push(`${path2}.blocking`);
|
|
2197
|
-
}
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2200
|
-
for (const [name, subagent] of Object.entries(profile.subagents ?? {})) {
|
|
2201
|
-
const path2 = `subagents[${JSON.stringify(name)}]`;
|
|
2202
|
-
if (hasEntries(subagent.permissions)) unsupportedAxes.push(`${path2}.permissions`);
|
|
2203
|
-
if (subagent.maxSteps !== void 0) unsupportedAxes.push(`${path2}.maxSteps`);
|
|
2204
|
-
if (hasEntries(subagent.tools) && harness !== "claude") {
|
|
2205
|
-
unsupportedAxes.push(`${path2}.tools`);
|
|
2206
|
-
}
|
|
2207
|
-
}
|
|
2208
|
-
if (unsupportedAxes.length > 0) {
|
|
2209
|
-
throw new Error(
|
|
2210
|
-
`runWorktreeHarness: profile requests unsupported worktree behavior: ${unsupportedAxes.join(", ")}`
|
|
2211
|
-
);
|
|
2212
|
-
}
|
|
2213
|
-
}
|
|
2214
|
-
function hasEntries(value) {
|
|
2215
|
-
return value !== void 0 && Object.keys(value).length > 0;
|
|
2216
|
-
}
|
|
2217
|
-
function assertSafeMaterializedPaths(plan) {
|
|
2218
|
-
for (const file of plan.files) {
|
|
2219
|
-
if (file.relPath.split("/").some(isGitMetadataSegment)) {
|
|
2220
|
-
throw new Error(
|
|
2221
|
-
`runWorktreeHarness: profile file cannot target reserved Git metadata: ${file.relPath}`
|
|
2222
|
-
);
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
function assertSafeProfileResourcePaths(profile) {
|
|
2227
|
-
for (const file of profile.resources?.files ?? []) {
|
|
2228
|
-
if (file.path.split("/").some(isGitMetadataSegment)) {
|
|
2229
|
-
throw new Error(
|
|
2230
|
-
`runWorktreeHarness: profile file cannot target reserved Git metadata: ${file.path}`
|
|
2231
|
-
);
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
function isGitMetadataSegment(segment) {
|
|
2236
|
-
const windowsCanonical = segment.replace(/[ .]+$/u, "").toLowerCase();
|
|
2237
|
-
return windowsCanonical === ".git" || windowsCanonical.startsWith(".git:");
|
|
2238
|
-
}
|
|
2239
|
-
function profileMaterializationReceipt(applied, resourceInstructions) {
|
|
2240
|
-
const instructionBytes = resourceInstructions === void 0 ? null : Buffer.from(resourceInstructions, "utf8");
|
|
2241
|
-
return {
|
|
2242
|
-
workspacePlanDigest: applied.workspacePlanDigest,
|
|
2243
|
-
writtenPaths: [...applied.written],
|
|
2244
|
-
unsupported: [...applied.unsupported],
|
|
2245
|
-
environmentNames: Object.keys(applied.env).sort(),
|
|
2246
|
-
flags: [...applied.flags],
|
|
2247
|
-
resourceInstructions: instructionBytes ? {
|
|
2248
|
-
delivery: "invocation-prompt",
|
|
2249
|
-
sha256: `sha256:${createHash("sha256").update(instructionBytes).digest("hex")}`,
|
|
2250
|
-
byteLength: instructionBytes.byteLength
|
|
2251
|
-
} : { delivery: "none", sha256: null, byteLength: 0 }
|
|
2252
|
-
};
|
|
2253
|
-
}
|
|
2254
|
-
async function runWorktreeChecks(opts) {
|
|
2255
|
-
if (opts.testCmd === void 0 && opts.typecheckCmd === void 0) return void 0;
|
|
2256
|
-
const runCommand = opts.runCommand ?? defaultRunCommand;
|
|
2257
|
-
const run = async (command) => {
|
|
2258
|
-
const res = await runCommand({
|
|
2259
|
-
command,
|
|
2260
|
-
cwd: opts.worktreePath,
|
|
2261
|
-
timeoutMs: opts.timeoutMs,
|
|
2262
|
-
...opts.signal ? { signal: opts.signal } : {}
|
|
2263
|
-
});
|
|
2264
|
-
return {
|
|
2265
|
-
command,
|
|
2266
|
-
passed: res.exitCode === 0,
|
|
2267
|
-
exitCode: res.exitCode,
|
|
2268
|
-
output: res.output.length > opts.cap ? res.output.slice(-opts.cap) : res.output
|
|
2269
|
-
};
|
|
2270
|
-
};
|
|
2271
|
-
const checks = {};
|
|
2272
|
-
if (opts.testCmd !== void 0) checks.tests = await run(opts.testCmd);
|
|
2273
|
-
if (opts.typecheckCmd !== void 0) checks.typecheck = await run(opts.typecheckCmd);
|
|
2274
|
-
return checks;
|
|
2275
|
-
}
|
|
2276
|
-
function defaultRunCommand(opts) {
|
|
2277
|
-
return new Promise((resolve, reject) => {
|
|
2278
|
-
const child = spawn2("/bin/sh", ["-c", opts.command], {
|
|
2279
|
-
cwd: opts.cwd,
|
|
2280
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2281
|
-
});
|
|
2282
|
-
const chunks = [];
|
|
2283
|
-
let settled = false;
|
|
2284
|
-
const timer = setTimeout(() => child.kill("SIGTERM"), opts.timeoutMs);
|
|
2285
|
-
const onAbort = () => child.kill("SIGTERM");
|
|
2286
|
-
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2287
|
-
child.stdout?.on("data", (d) => chunks.push(String(d)));
|
|
2288
|
-
child.stderr?.on("data", (d) => chunks.push(String(d)));
|
|
2289
|
-
const finish = (fn) => {
|
|
2290
|
-
if (settled) return;
|
|
2291
|
-
settled = true;
|
|
2292
|
-
clearTimeout(timer);
|
|
2293
|
-
opts.signal?.removeEventListener("abort", onAbort);
|
|
2294
|
-
fn();
|
|
2295
|
-
};
|
|
2296
|
-
child.on("error", (err) => finish(() => reject(err)));
|
|
2297
|
-
child.on("close", (code) => finish(() => resolve({ exitCode: code, output: chunks.join("") })));
|
|
2298
|
-
});
|
|
2299
|
-
}
|
|
2300
|
-
|
|
2301
|
-
// src/runtime/supervise/worktree-cli-executor.ts
|
|
2302
1101
|
function createWorktreeCliExecutor(options) {
|
|
2303
1102
|
if (!options.repoRoot) {
|
|
2304
1103
|
throw new ValidationError("createWorktreeCliExecutor: repoRoot required");
|
|
@@ -2404,12 +1203,12 @@ function linkSignals(a, b) {
|
|
|
2404
1203
|
}
|
|
2405
1204
|
|
|
2406
1205
|
// src/runtime/supervise/runtime.ts
|
|
2407
|
-
import { spawn
|
|
1206
|
+
import { spawn } from "child_process";
|
|
2408
1207
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2409
1208
|
import { request as httpRequest } from "http";
|
|
2410
1209
|
import { request as httpsRequest } from "https";
|
|
2411
1210
|
import { Readable } from "stream";
|
|
2412
|
-
import { estimateCost
|
|
1211
|
+
import { estimateCost, isModelPriced } from "@tangle-network/agent-eval";
|
|
2413
1212
|
var routerSeamKey = "router";
|
|
2414
1213
|
var sandboxSeamKey = "sandbox";
|
|
2415
1214
|
var cliSeamKey = "cli";
|
|
@@ -2430,7 +1229,7 @@ function contentRef(prefix, value) {
|
|
|
2430
1229
|
}
|
|
2431
1230
|
return `${prefix}:${(h >>> 0).toString(16).padStart(8, "0")}`;
|
|
2432
1231
|
}
|
|
2433
|
-
function
|
|
1232
|
+
function zeroSpend() {
|
|
2434
1233
|
return { iterations: 0, tokens: zeroTokenUsage(), usd: 0, ms: 0 };
|
|
2435
1234
|
}
|
|
2436
1235
|
var routerInlineExecutor = (spec, ctx) => {
|
|
@@ -2624,7 +1423,7 @@ var routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
2624
1423
|
}
|
|
2625
1424
|
}
|
|
2626
1425
|
}
|
|
2627
|
-
const usd =
|
|
1426
|
+
const usd = isModelPriced(model) ? estimateCost(tokens.input, tokens.output, model) : 0;
|
|
2628
1427
|
const spent = { iterations: turns, tokens, usd, ms: Date.now() - started };
|
|
2629
1428
|
const out = { content: lastText };
|
|
2630
1429
|
artifact = { outRef: contentRef("router-tools", { model, content: lastText }), out, spent };
|
|
@@ -2799,7 +1598,7 @@ var cliExecutor = (_spec, ctx) => {
|
|
|
2799
1598
|
};
|
|
2800
1599
|
async function* streamCliLeaf(args) {
|
|
2801
1600
|
const prompt = taskToPrompt(args.task);
|
|
2802
|
-
const proc =
|
|
1601
|
+
const proc = spawn(args.seam.bin, args.seam.args ?? [], {
|
|
2803
1602
|
...args.seam.cwd ? { cwd: args.seam.cwd } : {},
|
|
2804
1603
|
env: { ...process.env, ...args.seam.env ?? {} },
|
|
2805
1604
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -2836,7 +1635,7 @@ async function* streamCliLeaf(args) {
|
|
|
2836
1635
|
);
|
|
2837
1636
|
}
|
|
2838
1637
|
const out = { content: chunks.join("") };
|
|
2839
|
-
args.onArtifact({ outRef: contentRef("cli", out), out, spent:
|
|
1638
|
+
args.onArtifact({ outRef: contentRef("cli", out), out, spent: zeroSpend() });
|
|
2840
1639
|
yield { kind: "iteration" };
|
|
2841
1640
|
}
|
|
2842
1641
|
function killWithGrace(proc, grace) {
|
|
@@ -3224,7 +2023,7 @@ function bridgeWorktreeExecutor(spec, ctx, seam) {
|
|
|
3224
2023
|
inner = bridgeExecutor(spec, bridgeCtx);
|
|
3225
2024
|
for (const msg of pending.splice(0)) inner.deliver?.(msg);
|
|
3226
2025
|
const run = inner.execute(seam.taskPrompt, linked);
|
|
3227
|
-
if (
|
|
2026
|
+
if (isAsyncIterable2(run)) {
|
|
3228
2027
|
for await (const event of run) yield event;
|
|
3229
2028
|
bridgeArtifact = inner.resultArtifact();
|
|
3230
2029
|
} else {
|
|
@@ -3317,7 +2116,7 @@ function bridgeOutputText(out) {
|
|
|
3317
2116
|
return String(out);
|
|
3318
2117
|
}
|
|
3319
2118
|
}
|
|
3320
|
-
function
|
|
2119
|
+
function isAsyncIterable2(value) {
|
|
3321
2120
|
return value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function";
|
|
3322
2121
|
}
|
|
3323
2122
|
var cliWorktreeExecutor = (spec, ctx) => {
|
|
@@ -3477,181 +2276,6 @@ function mergeAbortSignals(...signals) {
|
|
|
3477
2276
|
return c.signal;
|
|
3478
2277
|
}
|
|
3479
2278
|
|
|
3480
|
-
// src/runtime/supervise/driver-executor.ts
|
|
3481
|
-
var driverRuntime = "driver";
|
|
3482
|
-
var driverRole = "driver";
|
|
3483
|
-
function isDriverSpec(spec) {
|
|
3484
|
-
const role = spec.profile.metadata?.role;
|
|
3485
|
-
if (role !== driverRole) return false;
|
|
3486
|
-
const driver = spec.driver;
|
|
3487
|
-
if (!isAgent(driver)) {
|
|
3488
|
-
throw new ValidationError(
|
|
3489
|
-
"driverExecutor: a driver-role spec must carry a `driver` Agent to run inside its nested scope"
|
|
3490
|
-
);
|
|
3491
|
-
}
|
|
3492
|
-
return true;
|
|
3493
|
-
}
|
|
3494
|
-
var driverExecutorFactory = (spec, ctx) => {
|
|
3495
|
-
if (!isDriverSpec(spec)) {
|
|
3496
|
-
throw new ValidationError(
|
|
3497
|
-
'driverExecutorFactory: spec is not a driver child (no role:"driver" marker)'
|
|
3498
|
-
);
|
|
3499
|
-
}
|
|
3500
|
-
const driver = spec.driver;
|
|
3501
|
-
const journal = spec.journal;
|
|
3502
|
-
const seam = readNestedScopeSeam(ctx);
|
|
3503
|
-
let artifact;
|
|
3504
|
-
let meteredSpend;
|
|
3505
|
-
return {
|
|
3506
|
-
runtime: driverRuntime,
|
|
3507
|
-
async execute(task, signal) {
|
|
3508
|
-
const nestedRoot = nestedTreeKey(seam, journal);
|
|
3509
|
-
await journal.beginTree(nestedRoot, (/* @__PURE__ */ new Date(0)).toISOString());
|
|
3510
|
-
const nestedScope = seam.mount(nestedRoot, signal);
|
|
3511
|
-
try {
|
|
3512
|
-
const out = await driver.act(task, nestedScope);
|
|
3513
|
-
const events = await loadTreeEvents(journal, nestedRoot);
|
|
3514
|
-
const settled = events.filter(isSettled);
|
|
3515
|
-
meteredSpend = nonZeroOrUndef(sumMetered(events));
|
|
3516
|
-
const verdict = deriveDeliveryVerdict(settled);
|
|
3517
|
-
artifact = {
|
|
3518
|
-
outRef: `${driverRuntime}:${nestedRoot}`,
|
|
3519
|
-
out,
|
|
3520
|
-
spent: sumSpend(settled),
|
|
3521
|
-
...verdict ? { verdict } : {}
|
|
3522
|
-
};
|
|
3523
|
-
return artifact;
|
|
3524
|
-
} catch (err) {
|
|
3525
|
-
meteredSpend = await safeSumMetered(journal, nestedRoot);
|
|
3526
|
-
throw err;
|
|
3527
|
-
}
|
|
3528
|
-
},
|
|
3529
|
-
metered() {
|
|
3530
|
-
return meteredSpend;
|
|
3531
|
-
},
|
|
3532
|
-
teardown() {
|
|
3533
|
-
return Promise.resolve({ destroyed: true });
|
|
3534
|
-
},
|
|
3535
|
-
resultArtifact() {
|
|
3536
|
-
if (!artifact) {
|
|
3537
|
-
throw new ValidationError("driverExecutor: resultArtifact() read before execute()");
|
|
3538
|
-
}
|
|
3539
|
-
return artifact;
|
|
3540
|
-
}
|
|
3541
|
-
};
|
|
3542
|
-
};
|
|
3543
|
-
function withDriverExecutor(base) {
|
|
3544
|
-
return {
|
|
3545
|
-
register: base.register.bind(base),
|
|
3546
|
-
resolve(spec) {
|
|
3547
|
-
const role = spec.profile.metadata?.role;
|
|
3548
|
-
if (role === driverRole && !spec.executor) {
|
|
3549
|
-
return { succeeded: true, value: driverExecutorFactory };
|
|
3550
|
-
}
|
|
3551
|
-
return base.resolve(spec);
|
|
3552
|
-
}
|
|
3553
|
-
};
|
|
3554
|
-
}
|
|
3555
|
-
function nestedTreeKey(seam, journal) {
|
|
3556
|
-
return `${seam.journalRoot}/d${nextNestOrdinal(journal)}`;
|
|
3557
|
-
}
|
|
3558
|
-
var nestCounters = /* @__PURE__ */ new WeakMap();
|
|
3559
|
-
function nextNestOrdinal(journal) {
|
|
3560
|
-
let c = nestCounters.get(journal);
|
|
3561
|
-
if (!c) {
|
|
3562
|
-
c = { n: 0 };
|
|
3563
|
-
nestCounters.set(journal, c);
|
|
3564
|
-
}
|
|
3565
|
-
return c.n++;
|
|
3566
|
-
}
|
|
3567
|
-
async function loadTreeEvents(journal, nestedRoot) {
|
|
3568
|
-
const events = await journal.loadTree(nestedRoot);
|
|
3569
|
-
if (events === void 0) {
|
|
3570
|
-
throw new ValidationError(
|
|
3571
|
-
`driverExecutor: nested tree '${nestedRoot}' missing from the journal after run (corrupted log)`
|
|
3572
|
-
);
|
|
3573
|
-
}
|
|
3574
|
-
return events;
|
|
3575
|
-
}
|
|
3576
|
-
function isSettled(ev) {
|
|
3577
|
-
return ev.kind === "settled";
|
|
3578
|
-
}
|
|
3579
|
-
function sumSpend(settled) {
|
|
3580
|
-
const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
3581
|
-
for (const ev of settled) {
|
|
3582
|
-
total.iterations += ev.spent.iterations;
|
|
3583
|
-
total.tokens.input += ev.spent.tokens.input;
|
|
3584
|
-
total.tokens.output += ev.spent.tokens.output;
|
|
3585
|
-
total.usd += ev.spent.usd;
|
|
3586
|
-
if (ev.spent.usdKnown === false) total.usdKnown = false;
|
|
3587
|
-
total.ms += ev.spent.ms;
|
|
3588
|
-
}
|
|
3589
|
-
return total;
|
|
3590
|
-
}
|
|
3591
|
-
function sumMetered(events) {
|
|
3592
|
-
const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
3593
|
-
for (const ev of events) {
|
|
3594
|
-
if (ev.kind !== "metered") continue;
|
|
3595
|
-
total.iterations += ev.spend.iterations;
|
|
3596
|
-
total.tokens.input += ev.spend.tokens.input;
|
|
3597
|
-
total.tokens.output += ev.spend.tokens.output;
|
|
3598
|
-
total.usd += ev.spend.usd;
|
|
3599
|
-
if (ev.spend.usdKnown === false) total.usdKnown = false;
|
|
3600
|
-
total.ms += ev.spend.ms;
|
|
3601
|
-
}
|
|
3602
|
-
return total;
|
|
3603
|
-
}
|
|
3604
|
-
function isNonZeroSpend(s) {
|
|
3605
|
-
return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
|
|
3606
|
-
}
|
|
3607
|
-
function nonZeroOrUndef(s) {
|
|
3608
|
-
return isNonZeroSpend(s) ? s : void 0;
|
|
3609
|
-
}
|
|
3610
|
-
async function safeSumMetered(journal, nestedRoot) {
|
|
3611
|
-
try {
|
|
3612
|
-
return nonZeroOrUndef(sumMetered(await loadTreeEvents(journal, nestedRoot)));
|
|
3613
|
-
} catch {
|
|
3614
|
-
return void 0;
|
|
3615
|
-
}
|
|
3616
|
-
}
|
|
3617
|
-
function deriveDeliveryVerdict(settled) {
|
|
3618
|
-
let sawChild = false;
|
|
3619
|
-
let anyValid = false;
|
|
3620
|
-
let bestValidScore;
|
|
3621
|
-
let bestDoneScore;
|
|
3622
|
-
for (const ev of settled) {
|
|
3623
|
-
sawChild = true;
|
|
3624
|
-
if (ev.status !== "done") continue;
|
|
3625
|
-
const score = ev.verdict?.score;
|
|
3626
|
-
if (score !== void 0 && (bestDoneScore === void 0 || score > bestDoneScore)) {
|
|
3627
|
-
bestDoneScore = score;
|
|
3628
|
-
}
|
|
3629
|
-
if (ev.verdict?.valid === true) {
|
|
3630
|
-
anyValid = true;
|
|
3631
|
-
if (score !== void 0 && (bestValidScore === void 0 || score > bestValidScore)) {
|
|
3632
|
-
bestValidScore = score;
|
|
3633
|
-
}
|
|
3634
|
-
}
|
|
3635
|
-
}
|
|
3636
|
-
if (!sawChild) return void 0;
|
|
3637
|
-
return {
|
|
3638
|
-
valid: anyValid,
|
|
3639
|
-
score: anyValid ? bestValidScore ?? 1 : bestDoneScore ?? 0
|
|
3640
|
-
};
|
|
3641
|
-
}
|
|
3642
|
-
function readNestedScopeSeam(ctx) {
|
|
3643
|
-
const seam = ctx.seams[nestedScopeSeamKey];
|
|
3644
|
-
if (!seam || typeof seam.mount !== "function") {
|
|
3645
|
-
throw new ValidationError(
|
|
3646
|
-
`driverExecutor: missing required seam "${nestedScopeSeamKey}" \u2014 a driver child must be spawned through a Scope that seeds it (the keystone scope does)`
|
|
3647
|
-
);
|
|
3648
|
-
}
|
|
3649
|
-
return seam;
|
|
3650
|
-
}
|
|
3651
|
-
function isAgent(value) {
|
|
3652
|
-
return typeof value === "object" && value !== null && typeof value.act === "function" && typeof value.name === "string";
|
|
3653
|
-
}
|
|
3654
|
-
|
|
3655
2279
|
// src/runtime/supervise/run-context.ts
|
|
3656
2280
|
function createInMemoryRunContext(opts = {}) {
|
|
3657
2281
|
const base = createExecutorRegistry();
|
|
@@ -3662,364 +2286,6 @@ function createInMemoryRunContext(opts = {}) {
|
|
|
3662
2286
|
};
|
|
3663
2287
|
}
|
|
3664
2288
|
|
|
3665
|
-
// src/runtime/supervise/budget.ts
|
|
3666
|
-
function spendFromUsageEvents(events) {
|
|
3667
|
-
const tokens = zeroTokenUsage();
|
|
3668
|
-
let usd = 0;
|
|
3669
|
-
let iterations = 0;
|
|
3670
|
-
for (const ev of events) {
|
|
3671
|
-
if (ev.kind === "tokens") {
|
|
3672
|
-
addTokenUsage(tokens, { input: ev.input, output: ev.output });
|
|
3673
|
-
} else if (ev.kind === "cost") {
|
|
3674
|
-
usd += ev.usd;
|
|
3675
|
-
} else {
|
|
3676
|
-
iterations += 1;
|
|
3677
|
-
}
|
|
3678
|
-
}
|
|
3679
|
-
return { iterations, tokens, usd, ms: 0 };
|
|
3680
|
-
}
|
|
3681
|
-
async function foldUsage(events) {
|
|
3682
|
-
if (Array.isArray(events)) return spendFromUsageEvents(events);
|
|
3683
|
-
const tokens = zeroTokenUsage();
|
|
3684
|
-
let usd = 0;
|
|
3685
|
-
let iterations = 0;
|
|
3686
|
-
for await (const ev of events) {
|
|
3687
|
-
if (ev.kind === "tokens") {
|
|
3688
|
-
addTokenUsage(tokens, { input: ev.input, output: ev.output });
|
|
3689
|
-
} else if (ev.kind === "cost") {
|
|
3690
|
-
usd += ev.usd;
|
|
3691
|
-
} else {
|
|
3692
|
-
iterations += 1;
|
|
3693
|
-
}
|
|
3694
|
-
}
|
|
3695
|
-
return { iterations, tokens, usd, ms: 0 };
|
|
3696
|
-
}
|
|
3697
|
-
function totalTokens(usage) {
|
|
3698
|
-
return usage.input + usage.output;
|
|
3699
|
-
}
|
|
3700
|
-
function createBudgetPool(root, now = Date.now) {
|
|
3701
|
-
let freeTokens = root.maxTokens;
|
|
3702
|
-
let reservedTokens = 0;
|
|
3703
|
-
let committedTokens = 0;
|
|
3704
|
-
const usdCapped = root.maxUsd !== void 0;
|
|
3705
|
-
let freeUsd = root.maxUsd ?? 0;
|
|
3706
|
-
let reservedUsd = 0;
|
|
3707
|
-
let committedUsd = 0;
|
|
3708
|
-
let freeIterations = root.maxIterations;
|
|
3709
|
-
let reservedIterations = 0;
|
|
3710
|
-
let committedIterations = 0;
|
|
3711
|
-
const absoluteDeadlineMs = root.deadlineMs !== void 0 ? now() + root.deadlineMs : 0;
|
|
3712
|
-
let nextTicketId = 0;
|
|
3713
|
-
const open = /* @__PURE__ */ new Set();
|
|
3714
|
-
function reserve(b) {
|
|
3715
|
-
const wantTokens = b.maxTokens;
|
|
3716
|
-
const wantUsd = b.maxUsd ?? 0;
|
|
3717
|
-
const wantIterations = b.maxIterations;
|
|
3718
|
-
if (wantTokens > freeTokens) return { ok: false, reason: "budget-exhausted" };
|
|
3719
|
-
if (wantIterations > freeIterations) return { ok: false, reason: "budget-exhausted" };
|
|
3720
|
-
if (wantUsd > 0 && (!usdCapped || wantUsd > freeUsd)) {
|
|
3721
|
-
return { ok: false, reason: "budget-exhausted" };
|
|
3722
|
-
}
|
|
3723
|
-
freeTokens -= wantTokens;
|
|
3724
|
-
reservedTokens += wantTokens;
|
|
3725
|
-
freeIterations -= wantIterations;
|
|
3726
|
-
reservedIterations += wantIterations;
|
|
3727
|
-
if (wantUsd > 0) {
|
|
3728
|
-
freeUsd -= wantUsd;
|
|
3729
|
-
reservedUsd += wantUsd;
|
|
3730
|
-
}
|
|
3731
|
-
const id = nextTicketId++;
|
|
3732
|
-
open.add(id);
|
|
3733
|
-
return {
|
|
3734
|
-
ok: true,
|
|
3735
|
-
ticket: { id, reserved: { tokens: wantTokens, usd: wantUsd, iterations: wantIterations } }
|
|
3736
|
-
};
|
|
3737
|
-
}
|
|
3738
|
-
function reconcile(ticket, spent) {
|
|
3739
|
-
if (!open.has(ticket.id)) {
|
|
3740
|
-
throw new Error(`budget pool: reconcile of unknown or already-settled ticket ${ticket.id}`);
|
|
3741
|
-
}
|
|
3742
|
-
open.delete(ticket.id);
|
|
3743
|
-
const { tokens: rTokens, usd: rUsd, iterations: rIterations } = ticket.reserved;
|
|
3744
|
-
if (usdCapped && spent.usdKnown === false) {
|
|
3745
|
-
throw new Error(
|
|
3746
|
-
`budget pool: ticket ${ticket.id} reported unknown dollar cost under a dollar-capped budget`
|
|
3747
|
-
);
|
|
3748
|
-
}
|
|
3749
|
-
const spentTokens = totalTokens(spent.tokens);
|
|
3750
|
-
if (spentTokens > rTokens) {
|
|
3751
|
-
throw new Error(
|
|
3752
|
-
`budget pool: ticket ${ticket.id} spent ${spentTokens} tokens > reserved ${rTokens}`
|
|
3753
|
-
);
|
|
3754
|
-
}
|
|
3755
|
-
if (spent.iterations > rIterations) {
|
|
3756
|
-
throw new Error(
|
|
3757
|
-
`budget pool: ticket ${ticket.id} spent ${spent.iterations} iterations > reserved ${rIterations}`
|
|
3758
|
-
);
|
|
3759
|
-
}
|
|
3760
|
-
if (usdCapped && spent.usd > rUsd) {
|
|
3761
|
-
throw new Error(`budget pool: ticket ${ticket.id} spent $${spent.usd} > reserved $${rUsd}`);
|
|
3762
|
-
}
|
|
3763
|
-
reservedTokens -= rTokens;
|
|
3764
|
-
committedTokens += spentTokens;
|
|
3765
|
-
freeTokens += rTokens - spentTokens;
|
|
3766
|
-
reservedIterations -= rIterations;
|
|
3767
|
-
committedIterations += spent.iterations;
|
|
3768
|
-
freeIterations += rIterations - spent.iterations;
|
|
3769
|
-
if (usdCapped && rUsd > 0) {
|
|
3770
|
-
reservedUsd -= rUsd;
|
|
3771
|
-
committedUsd += spent.usd;
|
|
3772
|
-
freeUsd += rUsd - spent.usd;
|
|
3773
|
-
} else {
|
|
3774
|
-
committedUsd += spent.usd;
|
|
3775
|
-
}
|
|
3776
|
-
}
|
|
3777
|
-
function observe(spend) {
|
|
3778
|
-
if (usdCapped && spend.usdKnown === false) {
|
|
3779
|
-
throw new Error(
|
|
3780
|
-
"budget pool: cannot observe unknown dollar cost under a dollar-capped budget"
|
|
3781
|
-
);
|
|
3782
|
-
}
|
|
3783
|
-
const tokens = totalTokens(spend.tokens);
|
|
3784
|
-
freeTokens -= tokens;
|
|
3785
|
-
committedTokens += tokens;
|
|
3786
|
-
freeIterations -= spend.iterations;
|
|
3787
|
-
committedIterations += spend.iterations;
|
|
3788
|
-
committedUsd += spend.usd;
|
|
3789
|
-
if (usdCapped) freeUsd -= spend.usd;
|
|
3790
|
-
}
|
|
3791
|
-
function readout() {
|
|
3792
|
-
return {
|
|
3793
|
-
tokensLeft: freeTokens,
|
|
3794
|
-
usdLeft: usdCapped ? freeUsd : 0,
|
|
3795
|
-
usdCapped,
|
|
3796
|
-
deadlineMs: absoluteDeadlineMs,
|
|
3797
|
-
reservedTokens
|
|
3798
|
-
};
|
|
3799
|
-
}
|
|
3800
|
-
function assertNoOpenTickets() {
|
|
3801
|
-
if (open.size > 0) {
|
|
3802
|
-
throw new Error(
|
|
3803
|
-
`budget pool: ${open.size} reservation(s) still open at join barrier (leaked ticket ids: ${[...open].join(", ")}) \u2014 conserved-pool invariant violated`
|
|
3804
|
-
);
|
|
3805
|
-
}
|
|
3806
|
-
}
|
|
3807
|
-
return {
|
|
3808
|
-
reserve,
|
|
3809
|
-
reconcile,
|
|
3810
|
-
spendFrom: foldUsage,
|
|
3811
|
-
readout,
|
|
3812
|
-
observe,
|
|
3813
|
-
assertNoOpenTickets
|
|
3814
|
-
};
|
|
3815
|
-
}
|
|
3816
|
-
|
|
3817
|
-
// src/runtime/supervise/supervisor.ts
|
|
3818
|
-
var defaultMaxDepth = 4;
|
|
3819
|
-
function createSupervisor() {
|
|
3820
|
-
let attached;
|
|
3821
|
-
async function run(root, task, opts) {
|
|
3822
|
-
const now = opts.now ?? Date.now;
|
|
3823
|
-
const pool = createBudgetPool(opts.budget, now);
|
|
3824
|
-
await opts.journal.beginTree(opts.runId, new Date(now()).toISOString());
|
|
3825
|
-
await opts.journal.appendEvent(opts.runId, {
|
|
3826
|
-
kind: "spawned",
|
|
3827
|
-
id: opts.runId,
|
|
3828
|
-
label: "root",
|
|
3829
|
-
budget: opts.budget,
|
|
3830
|
-
runtime: "inline",
|
|
3831
|
-
seq: 0,
|
|
3832
|
-
at: new Date(now()).toISOString()
|
|
3833
|
-
});
|
|
3834
|
-
const controller = new AbortController();
|
|
3835
|
-
const cascadeAbort = (reason) => {
|
|
3836
|
-
if (controller.signal.aborted) return;
|
|
3837
|
-
controller.abort(reason);
|
|
3838
|
-
};
|
|
3839
|
-
const onCallerAbort = () => cascadeAbort("caller signal aborted");
|
|
3840
|
-
if (opts.signal) {
|
|
3841
|
-
if (opts.signal.aborted) cascadeAbort("caller signal aborted");
|
|
3842
|
-
else opts.signal.addEventListener("abort", onCallerAbort, { once: true });
|
|
3843
|
-
}
|
|
3844
|
-
const breaker = createIntensityBreaker(opts, () => cascadeAbort("intensity breaker tripped"));
|
|
3845
|
-
const journal = wrapJournalForBreaker(opts.journal, breaker);
|
|
3846
|
-
const scope = createScope({
|
|
3847
|
-
parentId: opts.runId,
|
|
3848
|
-
root: opts.runId,
|
|
3849
|
-
pool,
|
|
3850
|
-
journal,
|
|
3851
|
-
blobs: opts.blobs,
|
|
3852
|
-
executors: opts.executors,
|
|
3853
|
-
seams: {},
|
|
3854
|
-
depth: 0,
|
|
3855
|
-
maxDepth: opts.maxDepth ?? defaultMaxDepth,
|
|
3856
|
-
signal: controller.signal,
|
|
3857
|
-
now,
|
|
3858
|
-
hooks: opts.hooks
|
|
3859
|
-
});
|
|
3860
|
-
const openScope = scope;
|
|
3861
|
-
if (attached) {
|
|
3862
|
-
attached.bind({ scope: openScope, cascadeAbort, signal: pushRootSignal(cascadeAbort) });
|
|
3863
|
-
}
|
|
3864
|
-
let actOutcome;
|
|
3865
|
-
try {
|
|
3866
|
-
const out = await root.act(task, scope);
|
|
3867
|
-
actOutcome = { ok: true, out };
|
|
3868
|
-
} catch (error) {
|
|
3869
|
-
actOutcome = { ok: false, error };
|
|
3870
|
-
} finally {
|
|
3871
|
-
await drainLiveChildren(openScope, controller);
|
|
3872
|
-
if (opts.signal) opts.signal.removeEventListener("abort", onCallerAbort);
|
|
3873
|
-
if (attached) attached.unbind();
|
|
3874
|
-
}
|
|
3875
|
-
const tree = scope.view;
|
|
3876
|
-
if (actOutcome.ok) {
|
|
3877
|
-
pool.assertNoOpenTickets();
|
|
3878
|
-
const out = actOutcome.out;
|
|
3879
|
-
if (out !== void 0) {
|
|
3880
|
-
const outRef = contentAddress(out);
|
|
3881
|
-
await opts.blobs.put(outRef, out);
|
|
3882
|
-
const { childWork, driverInference } = await spentFromJournal(journal, opts.runId);
|
|
3883
|
-
return {
|
|
3884
|
-
kind: "winner",
|
|
3885
|
-
out,
|
|
3886
|
-
outRef,
|
|
3887
|
-
tree,
|
|
3888
|
-
spentTotal: addSpend(childWork, driverInference),
|
|
3889
|
-
...isNonEmptySpend(driverInference) ? { spentBreakdown: { driverInference, childWork } } : {}
|
|
3890
|
-
};
|
|
3891
|
-
}
|
|
3892
|
-
return noWinner();
|
|
3893
|
-
}
|
|
3894
|
-
return noWinner();
|
|
3895
|
-
async function noWinner() {
|
|
3896
|
-
const { childWork, driverInference } = await spentFromJournal(journal, opts.runId);
|
|
3897
|
-
return {
|
|
3898
|
-
kind: "no-winner",
|
|
3899
|
-
reason: classifyNoWinner(controller, pool, opts, breaker),
|
|
3900
|
-
tree,
|
|
3901
|
-
downCount: breaker.downCount(),
|
|
3902
|
-
spentTotal: addSpend(childWork, driverInference)
|
|
3903
|
-
};
|
|
3904
|
-
}
|
|
3905
|
-
}
|
|
3906
|
-
function attach(h) {
|
|
3907
|
-
const control = rootControls.get(h);
|
|
3908
|
-
if (!control) {
|
|
3909
|
-
throw new RuntimeRunStateError(
|
|
3910
|
-
"supervisor.attach: handle was not minted by createRootHandle (no control channel)"
|
|
3911
|
-
);
|
|
3912
|
-
}
|
|
3913
|
-
attached = control;
|
|
3914
|
-
}
|
|
3915
|
-
return { run, attach };
|
|
3916
|
-
}
|
|
3917
|
-
var rootControls = /* @__PURE__ */ new WeakMap();
|
|
3918
|
-
function pushRootSignal(cascadeAbort) {
|
|
3919
|
-
return (msg) => {
|
|
3920
|
-
if (msg.kind === "cancel") cascadeAbort(msg.reason ?? "root signal: cancel");
|
|
3921
|
-
};
|
|
3922
|
-
}
|
|
3923
|
-
function createIntensityBreaker(opts, trip) {
|
|
3924
|
-
const max = opts.maxRestarts;
|
|
3925
|
-
const within = opts.withinMs;
|
|
3926
|
-
const armed = max !== void 0 && within !== void 0;
|
|
3927
|
-
const recent = [];
|
|
3928
|
-
let total = 0;
|
|
3929
|
-
let isTripped = false;
|
|
3930
|
-
return {
|
|
3931
|
-
recordDown(at) {
|
|
3932
|
-
total += 1;
|
|
3933
|
-
if (!armed || isTripped) return;
|
|
3934
|
-
recent.push(at);
|
|
3935
|
-
const cutoff = at - within;
|
|
3936
|
-
while (recent.length > 0 && recent[0] < cutoff) recent.shift();
|
|
3937
|
-
if (recent.length > max) {
|
|
3938
|
-
isTripped = true;
|
|
3939
|
-
trip();
|
|
3940
|
-
}
|
|
3941
|
-
},
|
|
3942
|
-
tripped() {
|
|
3943
|
-
return isTripped;
|
|
3944
|
-
},
|
|
3945
|
-
downCount() {
|
|
3946
|
-
return total;
|
|
3947
|
-
}
|
|
3948
|
-
};
|
|
3949
|
-
}
|
|
3950
|
-
function wrapJournalForBreaker(journal, breaker) {
|
|
3951
|
-
return {
|
|
3952
|
-
loadTree: (root) => journal.loadTree(root),
|
|
3953
|
-
beginTree: (root, at) => journal.beginTree(root, at),
|
|
3954
|
-
appendEvent: (root, ev) => {
|
|
3955
|
-
if (ev.kind === "settled" && ev.status === "down") breaker.recordDown(Date.parse(ev.at));
|
|
3956
|
-
return journal.appendEvent(root, ev);
|
|
3957
|
-
}
|
|
3958
|
-
};
|
|
3959
|
-
}
|
|
3960
|
-
async function drainLiveChildren(scope, controller) {
|
|
3961
|
-
const hasLive = scope.view.inFlight > 0;
|
|
3962
|
-
if (!hasLive) return;
|
|
3963
|
-
if (!controller.signal.aborted) controller.abort();
|
|
3964
|
-
await Promise.allSettled([drainCursor(scope)]);
|
|
3965
|
-
}
|
|
3966
|
-
async function drainCursor(scope) {
|
|
3967
|
-
for (; ; ) {
|
|
3968
|
-
const settled = await scope.next();
|
|
3969
|
-
if (settled === null) return;
|
|
3970
|
-
}
|
|
3971
|
-
}
|
|
3972
|
-
function classifyNoWinner(controller, pool, opts, breaker) {
|
|
3973
|
-
if (breaker.tripped()) return "all-children-down";
|
|
3974
|
-
if (controller.signal.aborted) return "aborted";
|
|
3975
|
-
if (poolExhausted(pool, opts)) return "budget-exhausted";
|
|
3976
|
-
return "all-children-down";
|
|
3977
|
-
}
|
|
3978
|
-
function poolExhausted(pool, opts) {
|
|
3979
|
-
const r = pool.readout();
|
|
3980
|
-
if (r.tokensLeft <= 0) return true;
|
|
3981
|
-
if (opts.budget.maxUsd !== void 0 && r.usdLeft <= 0) return true;
|
|
3982
|
-
if (opts.budget.deadlineMs !== void 0 && r.deadlineMs > 0 && (opts.now ?? Date.now)() >= r.deadlineMs) {
|
|
3983
|
-
return true;
|
|
3984
|
-
}
|
|
3985
|
-
return false;
|
|
3986
|
-
}
|
|
3987
|
-
async function spentFromJournal(journal, root) {
|
|
3988
|
-
const events = await journal.loadTree(root);
|
|
3989
|
-
if (events === void 0) {
|
|
3990
|
-
throw new RuntimeRunStateError(
|
|
3991
|
-
`supervisor: spawn tree '${root}' is missing from the journal after run (corrupted log)`
|
|
3992
|
-
);
|
|
3993
|
-
}
|
|
3994
|
-
const childWork = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
3995
|
-
const driverInference = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
3996
|
-
for (const ev of events) {
|
|
3997
|
-
if (ev.kind === "settled") accumulate(childWork, ev.spent);
|
|
3998
|
-
else if (ev.kind === "metered") accumulate(driverInference, ev.spend);
|
|
3999
|
-
}
|
|
4000
|
-
return { childWork, driverInference };
|
|
4001
|
-
}
|
|
4002
|
-
function accumulate(a, b) {
|
|
4003
|
-
a.iterations += b.iterations;
|
|
4004
|
-
a.tokens.input += b.tokens.input;
|
|
4005
|
-
a.tokens.output += b.tokens.output;
|
|
4006
|
-
a.usd += b.usd;
|
|
4007
|
-
if (b.usdKnown === false) a.usdKnown = false;
|
|
4008
|
-
a.ms += b.ms;
|
|
4009
|
-
}
|
|
4010
|
-
function addSpend(a, b) {
|
|
4011
|
-
return {
|
|
4012
|
-
iterations: a.iterations + b.iterations,
|
|
4013
|
-
tokens: { input: a.tokens.input + b.tokens.input, output: a.tokens.output + b.tokens.output },
|
|
4014
|
-
usd: a.usd + b.usd,
|
|
4015
|
-
...a.usdKnown === false || b.usdKnown === false ? { usdKnown: false } : {},
|
|
4016
|
-
ms: a.ms + b.ms
|
|
4017
|
-
};
|
|
4018
|
-
}
|
|
4019
|
-
function isNonEmptySpend(s) {
|
|
4020
|
-
return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
|
|
4021
|
-
}
|
|
4022
|
-
|
|
4023
2289
|
// src/runtime/supervise/event-bus.ts
|
|
4024
2290
|
function createEventBus(now = Date.now) {
|
|
4025
2291
|
const queue = [];
|
|
@@ -4676,7 +2942,7 @@ ${narrative}` : roster;
|
|
|
4676
2942
|
return `${roster}
|
|
4677
2943
|
|
|
4678
2944
|
## Progress notes
|
|
4679
|
-
Summary unavailable: ${
|
|
2945
|
+
Summary unavailable: ${errMessage(e)}`;
|
|
4680
2946
|
}
|
|
4681
2947
|
}),
|
|
4682
2948
|
...opts.compaction.onCompact ? { onCompact: opts.compaction.onCompact } : {},
|
|
@@ -4747,7 +3013,7 @@ function safeJson(v) {
|
|
|
4747
3013
|
return String(v);
|
|
4748
3014
|
}
|
|
4749
3015
|
}
|
|
4750
|
-
function
|
|
3016
|
+
function errMessage(e) {
|
|
4751
3017
|
return e instanceof Error ? e.message : String(e);
|
|
4752
3018
|
}
|
|
4753
3019
|
|
|
@@ -6748,21 +5014,6 @@ async function delegate(intent, opts = {}) {
|
|
|
6748
5014
|
|
|
6749
5015
|
export {
|
|
6750
5016
|
gateOnDeliverable,
|
|
6751
|
-
defineRuntimeHooks,
|
|
6752
|
-
composeRuntimeHooks,
|
|
6753
|
-
notifyRuntimeHookEvent,
|
|
6754
|
-
notifyRuntimeDecisionPoint,
|
|
6755
|
-
createScope,
|
|
6756
|
-
settledToIteration,
|
|
6757
|
-
withDriverExecutor,
|
|
6758
|
-
createWorktree,
|
|
6759
|
-
captureWorktreeDiff,
|
|
6760
|
-
removeWorktree,
|
|
6761
|
-
runWorktreeHarness,
|
|
6762
|
-
routerChatWithUsage,
|
|
6763
|
-
routerChatWithTools,
|
|
6764
|
-
routerToolLoop,
|
|
6765
|
-
routerBrain,
|
|
6766
5017
|
acquireSandbox,
|
|
6767
5018
|
probeSandboxCapabilities,
|
|
6768
5019
|
createSandboxLineage,
|
|
@@ -6775,9 +5026,6 @@ export {
|
|
|
6775
5026
|
createExecutor,
|
|
6776
5027
|
createExecutorRegistry,
|
|
6777
5028
|
createInMemoryRunContext,
|
|
6778
|
-
spendFromUsageEvents,
|
|
6779
|
-
createBudgetPool,
|
|
6780
|
-
createSupervisor,
|
|
6781
5029
|
createEventBus,
|
|
6782
5030
|
createCoordinationTools,
|
|
6783
5031
|
driverAgent,
|
|
@@ -6836,4 +5084,4 @@ export {
|
|
|
6836
5084
|
workerFromBackend,
|
|
6837
5085
|
supervise
|
|
6838
5086
|
};
|
|
6839
|
-
//# sourceMappingURL=chunk-
|
|
5087
|
+
//# sourceMappingURL=chunk-6MR4IJRW.js.map
|