@tangle-network/agent-runtime 0.56.1 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/analyst-loop.d.ts +1 -1
- package/dist/{chunk-EXIV2C72.js → chunk-E2L6PG5F.js} +59 -79
- package/dist/chunk-E2L6PG5F.js.map +1 -0
- package/dist/{chunk-G4NIVG34.js → chunk-ISVIQDIX.js} +4 -3
- package/dist/chunk-ISVIQDIX.js.map +1 -0
- package/dist/{chunk-4H2FML7G.js → chunk-MDFZSPHA.js} +950 -230
- package/dist/chunk-MDFZSPHA.js.map +1 -0
- package/dist/{chunk-F5XQA43K.js → chunk-VAGARXI5.js} +2 -2
- package/dist/{chunk-6XKXWA7H.js → chunk-ZGPQY6Z3.js} +2 -2
- package/dist/{coder-COuOK8h8.d.ts → coder-CybltHEm.d.ts} +1 -1
- package/dist/{coordination-DWNGqygr.d.ts → coordination-BydGBQCZ.d.ts} +164 -4
- package/dist/{delegates-D9o5_VFj.d.ts → delegates-C94qchkz.d.ts} +8 -2
- package/dist/index.d.ts +6 -6
- package/dist/index.js +4 -4
- package/dist/intelligence.d.ts +1 -1
- package/dist/{loop-runner-bin-CN2Se3jB.d.ts → loop-runner-bin-Noz7P-mS.d.ts} +3 -3
- package/dist/loop-runner-bin.d.ts +4 -4
- package/dist/loop-runner-bin.js +3 -3
- package/dist/loops.d.ts +7 -6
- package/dist/loops.js +31 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +20 -82
- package/dist/mcp/index.js +4 -4
- package/dist/{openai-tools-CoeLQ7Uo.d.ts → openai-tools-d4GKwgya.d.ts} +1 -1
- package/dist/profiles.d.ts +2 -2
- package/dist/{run-loop-DluzfJ2h.d.ts → run-loop-CcqfR_gy.d.ts} +1 -1
- package/dist/runtime.d.ts +336 -67
- package/dist/runtime.js +31 -1
- package/dist/{types-C8rNlxfV.d.ts → types-CUzjRFZ3.d.ts} +1 -1
- package/dist/workflow.d.ts +2 -2
- package/dist/workflow.js +1 -1
- package/package.json +2 -2
- package/skills/loop-writer/SKILL.md +1 -1
- package/skills/supervise/SKILL.md +1 -1
- package/dist/chunk-4H2FML7G.js.map +0 -1
- package/dist/chunk-EXIV2C72.js.map +0 -1
- package/dist/chunk-G4NIVG34.js.map +0 -1
- /package/dist/{chunk-F5XQA43K.js.map → chunk-VAGARXI5.js.map} +0 -0
- /package/dist/{chunk-6XKXWA7H.js.map → chunk-ZGPQY6Z3.js.map} +0 -0
|
@@ -305,8 +305,8 @@ var FileSpawnJournal = class {
|
|
|
305
305
|
}
|
|
306
306
|
};
|
|
307
307
|
function assertSeqUnique(root, events, ev) {
|
|
308
|
-
if (ev.kind === "spawned") return;
|
|
309
|
-
if (events.some((e) => e.kind !== "spawned" && e.seq === ev.seq)) {
|
|
308
|
+
if (ev.kind === "spawned" || ev.kind === "metered") return;
|
|
309
|
+
if (events.some((e) => e.kind !== "spawned" && e.kind !== "metered" && e.seq === ev.seq)) {
|
|
310
310
|
throw new Error(
|
|
311
311
|
`spawn journal corrupted: duplicate cursor seq ${ev.seq} in tree '${root}'; the cursor order replay relies on is not unique`
|
|
312
312
|
);
|
|
@@ -325,6 +325,7 @@ async function replaySpawnTree(journal, blobs, root) {
|
|
|
325
325
|
const settled = [];
|
|
326
326
|
for (const ev of ordered) {
|
|
327
327
|
if (ev.kind === "spawned") continue;
|
|
328
|
+
if (ev.kind === "metered") continue;
|
|
328
329
|
if (ev.kind === "cancelled") {
|
|
329
330
|
settled.push({
|
|
330
331
|
kind: "down",
|
|
@@ -384,7 +385,7 @@ function materializeTreeView(events) {
|
|
|
384
385
|
const nodes = /* @__PURE__ */ new Map();
|
|
385
386
|
let root;
|
|
386
387
|
const spawns = events.filter((ev) => ev.kind === "spawned").sort((a, b) => a.seq - b.seq);
|
|
387
|
-
const settlements = events.filter((ev) => ev.kind !== "spawned").sort((a, b) => a.seq - b.seq);
|
|
388
|
+
const settlements = events.filter((ev) => ev.kind !== "spawned" && ev.kind !== "metered").sort((a, b) => a.seq - b.seq);
|
|
388
389
|
for (const ev of spawns) {
|
|
389
390
|
if (ev.parent === void 0 && root === void 0) root = ev.id;
|
|
390
391
|
nodes.set(ev.id, {
|
|
@@ -408,6 +409,11 @@ function materializeTreeView(events) {
|
|
|
408
409
|
node.status = "cancelled";
|
|
409
410
|
}
|
|
410
411
|
}
|
|
412
|
+
for (const ev of events) {
|
|
413
|
+
if (ev.kind !== "metered") continue;
|
|
414
|
+
const node = requireNode(nodes, ev.id);
|
|
415
|
+
node.spent = addJournalSpend(node.spent, ev.spend);
|
|
416
|
+
}
|
|
411
417
|
const snapshots = [...nodes.values()].map(freezeSnapshot);
|
|
412
418
|
return {
|
|
413
419
|
root: root ?? snapshots[0]?.id ?? "",
|
|
@@ -418,6 +424,14 @@ function materializeTreeView(events) {
|
|
|
418
424
|
function zeroSpend() {
|
|
419
425
|
return { iterations: 0, tokens: zeroTokenUsage(), usd: 0, ms: 0 };
|
|
420
426
|
}
|
|
427
|
+
function addJournalSpend(a, b) {
|
|
428
|
+
return {
|
|
429
|
+
iterations: a.iterations + b.iterations,
|
|
430
|
+
tokens: { input: a.tokens.input + b.tokens.input, output: a.tokens.output + b.tokens.output },
|
|
431
|
+
usd: a.usd + b.usd,
|
|
432
|
+
ms: a.ms + b.ms
|
|
433
|
+
};
|
|
434
|
+
}
|
|
421
435
|
function requireNode(nodes, id) {
|
|
422
436
|
const node = nodes.get(id);
|
|
423
437
|
if (!node) {
|
|
@@ -795,14 +809,14 @@ ${traceSummary}`
|
|
|
795
809
|
return { findings, learned, report: renderReport(findings) };
|
|
796
810
|
}
|
|
797
811
|
function parseFindings(content) {
|
|
798
|
-
let
|
|
812
|
+
let obj2;
|
|
799
813
|
try {
|
|
800
|
-
|
|
814
|
+
obj2 = JSON.parse(content);
|
|
801
815
|
} catch {
|
|
802
816
|
const m = content.match(/\{[\s\S]*\}/);
|
|
803
|
-
|
|
817
|
+
obj2 = m ? JSON.parse(m[0]) : { findings: [] };
|
|
804
818
|
}
|
|
805
|
-
const arr =
|
|
819
|
+
const arr = obj2.findings;
|
|
806
820
|
return Array.isArray(arr) ? arr : [];
|
|
807
821
|
}
|
|
808
822
|
function renderReport(findings) {
|
|
@@ -1874,15 +1888,15 @@ async function emitTrace(emitter, event) {
|
|
|
1874
1888
|
await emitter.emit(event);
|
|
1875
1889
|
}
|
|
1876
1890
|
function hashJson(value) {
|
|
1877
|
-
let
|
|
1891
|
+
let str2;
|
|
1878
1892
|
try {
|
|
1879
|
-
|
|
1893
|
+
str2 = JSON.stringify(value) ?? String(value);
|
|
1880
1894
|
} catch {
|
|
1881
|
-
|
|
1895
|
+
str2 = String(value);
|
|
1882
1896
|
}
|
|
1883
1897
|
let h = 2166136261;
|
|
1884
|
-
for (let i = 0; i <
|
|
1885
|
-
h ^=
|
|
1898
|
+
for (let i = 0; i < str2.length; i += 1) {
|
|
1899
|
+
h ^= str2.charCodeAt(i);
|
|
1886
1900
|
h = Math.imul(h, 16777619);
|
|
1887
1901
|
}
|
|
1888
1902
|
return (h >>> 0).toString(16).padStart(8, "0");
|
|
@@ -2130,6 +2144,7 @@ function createScope(args) {
|
|
|
2130
2144
|
const children = /* @__PURE__ */ new Map();
|
|
2131
2145
|
let spawnOrdinal = 0;
|
|
2132
2146
|
let cursorSeq = 0;
|
|
2147
|
+
let meterSeq = 0;
|
|
2133
2148
|
const now = args.now ?? Date.now;
|
|
2134
2149
|
function spawn4(agent, task, opts) {
|
|
2135
2150
|
if (args.maxDepth !== void 0 && args.depth >= args.maxDepth) {
|
|
@@ -2257,10 +2272,36 @@ function createScope(args) {
|
|
|
2257
2272
|
child.deliver(msg);
|
|
2258
2273
|
return true;
|
|
2259
2274
|
}
|
|
2275
|
+
async function meter(spend, detail) {
|
|
2276
|
+
const seq = meterSeq++;
|
|
2277
|
+
args.pool.observe(spend);
|
|
2278
|
+
await args.journal.appendEvent(args.root, {
|
|
2279
|
+
kind: "metered",
|
|
2280
|
+
id: args.parentId,
|
|
2281
|
+
spend,
|
|
2282
|
+
seq,
|
|
2283
|
+
at: new Date(now()).toISOString()
|
|
2284
|
+
});
|
|
2285
|
+
notifyRuntimeHookEvent(
|
|
2286
|
+
args.hooks,
|
|
2287
|
+
{
|
|
2288
|
+
id: `${args.parentId}:meter:${seq}`,
|
|
2289
|
+
runId: args.root,
|
|
2290
|
+
target: "agent.turn",
|
|
2291
|
+
phase: "after",
|
|
2292
|
+
timestamp: now(),
|
|
2293
|
+
parentId: args.parentId,
|
|
2294
|
+
payload: { spend, ...detail ?? {} }
|
|
2295
|
+
},
|
|
2296
|
+
{ signal: args.signal }
|
|
2297
|
+
);
|
|
2298
|
+
}
|
|
2260
2299
|
return {
|
|
2261
2300
|
spawn: spawn4,
|
|
2262
2301
|
next,
|
|
2263
2302
|
send,
|
|
2303
|
+
signal: args.signal,
|
|
2304
|
+
meter,
|
|
2264
2305
|
get view() {
|
|
2265
2306
|
return makeTreeView(args.parentId, children);
|
|
2266
2307
|
},
|
|
@@ -2285,6 +2326,15 @@ async function finalizeSettlement(child, settlement, seq, args, now) {
|
|
|
2285
2326
|
seq,
|
|
2286
2327
|
at: new Date(now()).toISOString()
|
|
2287
2328
|
});
|
|
2329
|
+
if (settlement.metered) {
|
|
2330
|
+
await args.journal.appendEvent(args.root, {
|
|
2331
|
+
kind: "metered",
|
|
2332
|
+
id: child.id,
|
|
2333
|
+
spend: settlement.metered,
|
|
2334
|
+
seq,
|
|
2335
|
+
at: new Date(now()).toISOString()
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
2288
2338
|
notifyRuntimeHookEvent(
|
|
2289
2339
|
args.hooks,
|
|
2290
2340
|
{
|
|
@@ -2327,6 +2377,15 @@ async function finalizeSettlement(child, settlement, seq, args, now) {
|
|
|
2327
2377
|
seq,
|
|
2328
2378
|
at: new Date(now()).toISOString()
|
|
2329
2379
|
});
|
|
2380
|
+
if (settlement.metered) {
|
|
2381
|
+
await args.journal.appendEvent(args.root, {
|
|
2382
|
+
kind: "metered",
|
|
2383
|
+
id: child.id,
|
|
2384
|
+
spend: settlement.metered,
|
|
2385
|
+
seq,
|
|
2386
|
+
at: new Date(now()).toISOString()
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2330
2389
|
notifyRuntimeHookEvent(
|
|
2331
2390
|
args.hooks,
|
|
2332
2391
|
{
|
|
@@ -2380,9 +2439,10 @@ async function runChild(live, executor, childAbort, task, opts, pool2, ticket, b
|
|
|
2380
2439
|
artifact = terminal;
|
|
2381
2440
|
reconcileOnce(terminal.spent);
|
|
2382
2441
|
}
|
|
2442
|
+
const ownMetered = executor.metered?.();
|
|
2383
2443
|
if (childAbort.signal.aborted) {
|
|
2384
2444
|
await teardownSafe(executor, opts.shutdown ?? "brutalKill");
|
|
2385
|
-
return downRecord("aborted before settle", true);
|
|
2445
|
+
return downRecord("aborted before settle", true, ownMetered);
|
|
2386
2446
|
}
|
|
2387
2447
|
const outRef = contentAddress(artifact.out);
|
|
2388
2448
|
await blobs.put(outRef, artifact.out);
|
|
@@ -2392,13 +2452,14 @@ async function runChild(live, executor, childAbort, task, opts, pool2, ticket, b
|
|
|
2392
2452
|
out: artifact.out,
|
|
2393
2453
|
outRef,
|
|
2394
2454
|
...artifact.verdict ? { verdict: artifact.verdict } : {},
|
|
2395
|
-
spent: live.spent
|
|
2455
|
+
spent: live.spent,
|
|
2456
|
+
...ownMetered ? { metered: ownMetered } : {}
|
|
2396
2457
|
};
|
|
2397
2458
|
} catch (err) {
|
|
2398
2459
|
reconcileOnce(live.spent);
|
|
2399
2460
|
await teardownSafe(executor, "brutalKill");
|
|
2400
2461
|
const aborted = childAbort.signal.aborted || isAbortError2(err);
|
|
2401
|
-
return downRecord(errMessage(err), aborted || isInfraError(err));
|
|
2462
|
+
return downRecord(errMessage(err), aborted || isInfraError(err), executor.metered?.());
|
|
2402
2463
|
}
|
|
2403
2464
|
}
|
|
2404
2465
|
function settledToIteration(settled) {
|
|
@@ -2485,8 +2546,8 @@ async function teardownSafe(executor, grace) {
|
|
|
2485
2546
|
} catch {
|
|
2486
2547
|
}
|
|
2487
2548
|
}
|
|
2488
|
-
function downRecord(reason, infra) {
|
|
2489
|
-
return { kind: "down", reason, infra, restartCount: 0 };
|
|
2549
|
+
function downRecord(reason, infra, metered) {
|
|
2550
|
+
return { kind: "down", reason, infra, restartCount: 0, ...metered ? { metered } : {} };
|
|
2490
2551
|
}
|
|
2491
2552
|
function zeroSpend2() {
|
|
2492
2553
|
return { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
@@ -3078,23 +3139,33 @@ var driverExecutorFactory = (spec, ctx) => {
|
|
|
3078
3139
|
const journal = spec.journal;
|
|
3079
3140
|
const seam = readNestedScopeSeam(ctx);
|
|
3080
3141
|
let artifact;
|
|
3142
|
+
let meteredSpend;
|
|
3081
3143
|
return {
|
|
3082
3144
|
runtime: driverRuntime,
|
|
3083
3145
|
async execute(task, signal) {
|
|
3084
3146
|
const nestedRoot = nestedTreeKey(seam, journal);
|
|
3085
3147
|
await journal.beginTree(nestedRoot, (/* @__PURE__ */ new Date(0)).toISOString());
|
|
3086
3148
|
const nestedScope = seam.mount(nestedRoot, signal);
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3149
|
+
try {
|
|
3150
|
+
const out = await driver.act(task, nestedScope);
|
|
3151
|
+
const events = await loadTreeEvents(journal, nestedRoot);
|
|
3152
|
+
const settled = events.filter(isSettled);
|
|
3153
|
+
meteredSpend = nonZeroOrUndef(sumMetered(events));
|
|
3154
|
+
const verdict = deriveDeliveryVerdict(settled);
|
|
3155
|
+
artifact = {
|
|
3156
|
+
outRef: `${driverRuntime}:${nestedRoot}`,
|
|
3157
|
+
out,
|
|
3158
|
+
spent: sumSpend(settled),
|
|
3159
|
+
...verdict ? { verdict } : {}
|
|
3160
|
+
};
|
|
3161
|
+
return artifact;
|
|
3162
|
+
} catch (err) {
|
|
3163
|
+
meteredSpend = await safeSumMetered(journal, nestedRoot);
|
|
3164
|
+
throw err;
|
|
3165
|
+
}
|
|
3166
|
+
},
|
|
3167
|
+
metered() {
|
|
3168
|
+
return meteredSpend;
|
|
3098
3169
|
},
|
|
3099
3170
|
teardown() {
|
|
3100
3171
|
return Promise.resolve({ destroyed: true });
|
|
@@ -3131,16 +3202,17 @@ function nextNestOrdinal(journal) {
|
|
|
3131
3202
|
}
|
|
3132
3203
|
return c.n++;
|
|
3133
3204
|
}
|
|
3134
|
-
async function
|
|
3205
|
+
async function loadTreeEvents(journal, nestedRoot) {
|
|
3135
3206
|
const events = await journal.loadTree(nestedRoot);
|
|
3136
3207
|
if (events === void 0) {
|
|
3137
3208
|
throw new ValidationError(
|
|
3138
3209
|
`driverExecutor: nested tree '${nestedRoot}' missing from the journal after run (corrupted log)`
|
|
3139
3210
|
);
|
|
3140
3211
|
}
|
|
3141
|
-
return events
|
|
3142
|
-
|
|
3143
|
-
|
|
3212
|
+
return events;
|
|
3213
|
+
}
|
|
3214
|
+
function isSettled(ev) {
|
|
3215
|
+
return ev.kind === "settled";
|
|
3144
3216
|
}
|
|
3145
3217
|
function sumSpend(settled) {
|
|
3146
3218
|
const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
@@ -3153,6 +3225,31 @@ function sumSpend(settled) {
|
|
|
3153
3225
|
}
|
|
3154
3226
|
return total;
|
|
3155
3227
|
}
|
|
3228
|
+
function sumMetered(events) {
|
|
3229
|
+
const total = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
3230
|
+
for (const ev of events) {
|
|
3231
|
+
if (ev.kind !== "metered") continue;
|
|
3232
|
+
total.iterations += ev.spend.iterations;
|
|
3233
|
+
total.tokens.input += ev.spend.tokens.input;
|
|
3234
|
+
total.tokens.output += ev.spend.tokens.output;
|
|
3235
|
+
total.usd += ev.spend.usd;
|
|
3236
|
+
total.ms += ev.spend.ms;
|
|
3237
|
+
}
|
|
3238
|
+
return total;
|
|
3239
|
+
}
|
|
3240
|
+
function isNonZeroSpend(s) {
|
|
3241
|
+
return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
|
|
3242
|
+
}
|
|
3243
|
+
function nonZeroOrUndef(s) {
|
|
3244
|
+
return isNonZeroSpend(s) ? s : void 0;
|
|
3245
|
+
}
|
|
3246
|
+
async function safeSumMetered(journal, nestedRoot) {
|
|
3247
|
+
try {
|
|
3248
|
+
return nonZeroOrUndef(sumMetered(await loadTreeEvents(journal, nestedRoot)));
|
|
3249
|
+
} catch {
|
|
3250
|
+
return void 0;
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3156
3253
|
function deriveDeliveryVerdict(settled) {
|
|
3157
3254
|
let sawChild = false;
|
|
3158
3255
|
let anyValid = false;
|
|
@@ -3326,10 +3423,57 @@ async function routerToolLoop(cfg, system, user, tools, execute, opts) {
|
|
|
3326
3423
|
return { final: lastText, turns: maxTurns, toolCalls, toolTrace, usage, messages };
|
|
3327
3424
|
}
|
|
3328
3425
|
|
|
3426
|
+
// src/runtime/supervise/inbox.ts
|
|
3427
|
+
function parseDown(msg) {
|
|
3428
|
+
if (!msg || typeof msg !== "object") return void 0;
|
|
3429
|
+
const m = msg;
|
|
3430
|
+
const interrupt = m.interrupt === true;
|
|
3431
|
+
if (typeof m.steer === "string") return { kind: "steer", text: m.steer, interrupt };
|
|
3432
|
+
if (typeof m.answer === "string")
|
|
3433
|
+
return {
|
|
3434
|
+
kind: "answer",
|
|
3435
|
+
text: m.answer,
|
|
3436
|
+
interrupt,
|
|
3437
|
+
...typeof m.questionId === "string" ? { questionId: m.questionId } : {}
|
|
3438
|
+
};
|
|
3439
|
+
return void 0;
|
|
3440
|
+
}
|
|
3441
|
+
function createInbox() {
|
|
3442
|
+
const pending = [];
|
|
3443
|
+
let live = null;
|
|
3444
|
+
return {
|
|
3445
|
+
deliver(msg) {
|
|
3446
|
+
const m = parseDown(msg);
|
|
3447
|
+
if (!m) return;
|
|
3448
|
+
pending.push(m);
|
|
3449
|
+
if (m.interrupt && live && !live.signal.aborted) live.abort();
|
|
3450
|
+
},
|
|
3451
|
+
drain() {
|
|
3452
|
+
return pending.splice(0, pending.length);
|
|
3453
|
+
},
|
|
3454
|
+
pending: () => pending.length,
|
|
3455
|
+
freshInterrupt() {
|
|
3456
|
+
live = new AbortController();
|
|
3457
|
+
return live.signal;
|
|
3458
|
+
},
|
|
3459
|
+
fold(messages) {
|
|
3460
|
+
const lines = messages.map((m) => {
|
|
3461
|
+
if (m.kind === "answer")
|
|
3462
|
+
return `- Answer to your question${m.questionId ? ` (${m.questionId})` : ""}: ${m.text}`;
|
|
3463
|
+
return `- New instruction from your supervisor: ${m.text}`;
|
|
3464
|
+
});
|
|
3465
|
+
return `[SUPERVISOR] Out-of-band message(s) \u2014 address these before continuing:
|
|
3466
|
+
${lines.join("\n")}`;
|
|
3467
|
+
}
|
|
3468
|
+
};
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3329
3471
|
// src/runtime/supervise/worktree-cli-executor.ts
|
|
3330
|
-
import { spawn as spawn2 } from "child_process";
|
|
3331
3472
|
import { randomUUID } from "crypto";
|
|
3332
3473
|
|
|
3474
|
+
// src/mcp/worktree-harness.ts
|
|
3475
|
+
import { spawn as spawn2 } from "child_process";
|
|
3476
|
+
|
|
3333
3477
|
// src/mcp/worktree.ts
|
|
3334
3478
|
import { spawn } from "child_process";
|
|
3335
3479
|
async function runGitAsync(args, cwd, runner) {
|
|
@@ -3411,8 +3555,115 @@ async function removeWorktree(options) {
|
|
|
3411
3555
|
).catch(() => void 0);
|
|
3412
3556
|
}
|
|
3413
3557
|
|
|
3558
|
+
// src/mcp/worktree-harness.ts
|
|
3559
|
+
var defaultCheckOutputCap = 16e3;
|
|
3560
|
+
async function runWorktreeHarness(opts) {
|
|
3561
|
+
const runHarness = opts.runHarness ?? runLocalHarness;
|
|
3562
|
+
const runCommand = opts.runCommand ?? defaultRunCommand;
|
|
3563
|
+
const checkTimeoutMs = opts.checkTimeoutMs ?? opts.harnessTimeoutMs ?? 5 * 60 * 1e3;
|
|
3564
|
+
const cap = opts.checkOutputCap ?? defaultCheckOutputCap;
|
|
3565
|
+
const worktree = await createWorktree({
|
|
3566
|
+
repoRoot: opts.repoRoot,
|
|
3567
|
+
runId: opts.runId,
|
|
3568
|
+
...opts.baseRef ? { baseRef: opts.baseRef } : {},
|
|
3569
|
+
...opts.runGit ? { runGit: opts.runGit } : {}
|
|
3570
|
+
});
|
|
3571
|
+
const cleanup = () => removeWorktree({
|
|
3572
|
+
worktree,
|
|
3573
|
+
repoRoot: opts.repoRoot,
|
|
3574
|
+
...opts.runGit ? { runGit: opts.runGit } : {}
|
|
3575
|
+
}).catch(() => void 0);
|
|
3576
|
+
try {
|
|
3577
|
+
const { command, args } = harnessInvocation(opts.harness, opts.profile, opts.taskPrompt);
|
|
3578
|
+
const harnessResult = await runHarness({
|
|
3579
|
+
harness: opts.harness,
|
|
3580
|
+
cwd: worktree.path,
|
|
3581
|
+
taskPrompt: opts.taskPrompt,
|
|
3582
|
+
invocation: { command, args },
|
|
3583
|
+
...opts.harnessTimeoutMs !== void 0 ? { timeoutMs: opts.harnessTimeoutMs } : {},
|
|
3584
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
3585
|
+
});
|
|
3586
|
+
const diff = await captureWorktreeDiff({
|
|
3587
|
+
worktree,
|
|
3588
|
+
...opts.runGit ? { runGit: opts.runGit } : {}
|
|
3589
|
+
});
|
|
3590
|
+
const checks = await runChecks({
|
|
3591
|
+
worktreePath: worktree.path,
|
|
3592
|
+
...opts.testCmd !== void 0 ? { testCmd: opts.testCmd } : {},
|
|
3593
|
+
...opts.typecheckCmd !== void 0 ? { typecheckCmd: opts.typecheckCmd } : {},
|
|
3594
|
+
timeoutMs: checkTimeoutMs,
|
|
3595
|
+
cap,
|
|
3596
|
+
runCommand,
|
|
3597
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
3598
|
+
});
|
|
3599
|
+
const result = {
|
|
3600
|
+
branch: worktree.branch,
|
|
3601
|
+
patch: diff.patch,
|
|
3602
|
+
stats: diff.stats,
|
|
3603
|
+
harness: {
|
|
3604
|
+
name: opts.harness,
|
|
3605
|
+
exitCode: harnessResult.exitCode,
|
|
3606
|
+
timedOut: harnessResult.timedOut,
|
|
3607
|
+
killedBySignal: harnessResult.killedBySignal,
|
|
3608
|
+
durationMs: harnessResult.durationMs,
|
|
3609
|
+
stdout: harnessResult.stdout,
|
|
3610
|
+
stderr: harnessResult.stderr
|
|
3611
|
+
},
|
|
3612
|
+
...checks ? { checks } : {}
|
|
3613
|
+
};
|
|
3614
|
+
return { worktree, result, cleanup };
|
|
3615
|
+
} catch (err) {
|
|
3616
|
+
await cleanup();
|
|
3617
|
+
throw err;
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
async function runChecks(opts) {
|
|
3621
|
+
if (opts.testCmd === void 0 && opts.typecheckCmd === void 0) return void 0;
|
|
3622
|
+
const run = async (command) => {
|
|
3623
|
+
const res = await opts.runCommand({
|
|
3624
|
+
command,
|
|
3625
|
+
cwd: opts.worktreePath,
|
|
3626
|
+
timeoutMs: opts.timeoutMs,
|
|
3627
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
3628
|
+
});
|
|
3629
|
+
return {
|
|
3630
|
+
command,
|
|
3631
|
+
passed: res.exitCode === 0,
|
|
3632
|
+
exitCode: res.exitCode,
|
|
3633
|
+
output: res.output.length > opts.cap ? res.output.slice(-opts.cap) : res.output
|
|
3634
|
+
};
|
|
3635
|
+
};
|
|
3636
|
+
const checks = {};
|
|
3637
|
+
if (opts.testCmd !== void 0) checks.tests = await run(opts.testCmd);
|
|
3638
|
+
if (opts.typecheckCmd !== void 0) checks.typecheck = await run(opts.typecheckCmd);
|
|
3639
|
+
return checks;
|
|
3640
|
+
}
|
|
3641
|
+
function defaultRunCommand(opts) {
|
|
3642
|
+
return new Promise((resolve, reject) => {
|
|
3643
|
+
const child = spawn2("/bin/sh", ["-c", opts.command], {
|
|
3644
|
+
cwd: opts.cwd,
|
|
3645
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3646
|
+
});
|
|
3647
|
+
const chunks = [];
|
|
3648
|
+
let settled = false;
|
|
3649
|
+
const timer = setTimeout(() => child.kill("SIGTERM"), opts.timeoutMs);
|
|
3650
|
+
const onAbort = () => child.kill("SIGTERM");
|
|
3651
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
3652
|
+
child.stdout?.on("data", (d) => chunks.push(String(d)));
|
|
3653
|
+
child.stderr?.on("data", (d) => chunks.push(String(d)));
|
|
3654
|
+
const finish = (fn) => {
|
|
3655
|
+
if (settled) return;
|
|
3656
|
+
settled = true;
|
|
3657
|
+
clearTimeout(timer);
|
|
3658
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
3659
|
+
fn();
|
|
3660
|
+
};
|
|
3661
|
+
child.on("error", (err) => finish(() => reject(err)));
|
|
3662
|
+
child.on("close", (code) => finish(() => resolve({ exitCode: code, output: chunks.join("") })));
|
|
3663
|
+
});
|
|
3664
|
+
}
|
|
3665
|
+
|
|
3414
3666
|
// src/runtime/supervise/worktree-cli-executor.ts
|
|
3415
|
-
var checkOutputCap = 16e3;
|
|
3416
3667
|
function createWorktreeCliExecutor(options) {
|
|
3417
3668
|
if (!options.repoRoot) {
|
|
3418
3669
|
throw new ValidationError("createWorktreeCliExecutor: repoRoot required");
|
|
@@ -3424,22 +3675,9 @@ function createWorktreeCliExecutor(options) {
|
|
|
3424
3675
|
throw new ValidationError("createWorktreeCliExecutor: taskPrompt required");
|
|
3425
3676
|
}
|
|
3426
3677
|
const runId = options.runId ?? randomUUID();
|
|
3427
|
-
const runHarness = options.runHarness ?? runLocalHarness;
|
|
3428
|
-
const runCommand = options.runCommand ?? defaultRunCommand;
|
|
3429
|
-
const checkTimeoutMs = options.checkTimeoutMs ?? options.harnessTimeoutMs ?? 5 * 60 * 1e3;
|
|
3430
3678
|
const controller = new AbortController();
|
|
3431
|
-
let
|
|
3679
|
+
let run;
|
|
3432
3680
|
let artifact;
|
|
3433
|
-
const removeIfPresent = async () => {
|
|
3434
|
-
if (!worktree) return;
|
|
3435
|
-
const wt = worktree;
|
|
3436
|
-
worktree = void 0;
|
|
3437
|
-
await removeWorktree({
|
|
3438
|
-
worktree: wt,
|
|
3439
|
-
repoRoot: options.repoRoot,
|
|
3440
|
-
...options.runGit ? { runGit: options.runGit } : {}
|
|
3441
|
-
}).catch(() => void 0);
|
|
3442
|
-
};
|
|
3443
3681
|
return {
|
|
3444
3682
|
runtime: "cli",
|
|
3445
3683
|
// A harness CLI cannot account tokens — exclude it from the conserved pool + equal-k.
|
|
@@ -3447,52 +3685,22 @@ function createWorktreeCliExecutor(options) {
|
|
|
3447
3685
|
async execute(_task, signal) {
|
|
3448
3686
|
const linked = linkSignals(signal, controller.signal);
|
|
3449
3687
|
const started = Date.now();
|
|
3450
|
-
|
|
3688
|
+
run = await runWorktreeHarness({
|
|
3451
3689
|
repoRoot: options.repoRoot,
|
|
3452
|
-
|
|
3453
|
-
...options.baseRef ? { baseRef: options.baseRef } : {},
|
|
3454
|
-
...options.runGit ? { runGit: options.runGit } : {}
|
|
3455
|
-
});
|
|
3456
|
-
const { command, args } = harnessInvocation(
|
|
3457
|
-
options.harness,
|
|
3458
|
-
options.profile,
|
|
3459
|
-
options.taskPrompt
|
|
3460
|
-
);
|
|
3461
|
-
const harnessResult = await runHarness({
|
|
3690
|
+
profile: options.profile,
|
|
3462
3691
|
harness: options.harness,
|
|
3463
|
-
cwd: worktree.path,
|
|
3464
3692
|
taskPrompt: options.taskPrompt,
|
|
3465
|
-
|
|
3466
|
-
...options.
|
|
3467
|
-
...linked ? { signal: linked } : {}
|
|
3468
|
-
});
|
|
3469
|
-
const checks = await runWorktreeChecks({
|
|
3470
|
-
worktreePath: worktree.path,
|
|
3693
|
+
runId,
|
|
3694
|
+
...options.baseRef ? { baseRef: options.baseRef } : {},
|
|
3471
3695
|
...options.testCmd !== void 0 ? { testCmd: options.testCmd } : {},
|
|
3472
3696
|
...options.typecheckCmd !== void 0 ? { typecheckCmd: options.typecheckCmd } : {},
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
...linked ? { signal: linked } : {}
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
...options.runGit ? { runGit: options.runGit } : {}
|
|
3697
|
+
...options.harnessTimeoutMs !== void 0 ? { harnessTimeoutMs: options.harnessTimeoutMs } : {},
|
|
3698
|
+
...options.checkTimeoutMs !== void 0 ? { checkTimeoutMs: options.checkTimeoutMs } : {},
|
|
3699
|
+
...linked ? { signal: linked } : {},
|
|
3700
|
+
...options.runGit ? { runGit: options.runGit } : {},
|
|
3701
|
+
...options.runHarness ? { runHarness: options.runHarness } : {},
|
|
3702
|
+
...options.runCommand ? { runCommand: options.runCommand } : {}
|
|
3480
3703
|
});
|
|
3481
|
-
const out = {
|
|
3482
|
-
branch: worktree.branch,
|
|
3483
|
-
patch: diff.patch,
|
|
3484
|
-
stats: diff.stats,
|
|
3485
|
-
harness: {
|
|
3486
|
-
name: options.harness,
|
|
3487
|
-
exitCode: harnessResult.exitCode,
|
|
3488
|
-
timedOut: harnessResult.timedOut,
|
|
3489
|
-
killedBySignal: harnessResult.killedBySignal,
|
|
3490
|
-
durationMs: harnessResult.durationMs,
|
|
3491
|
-
stdout: harnessResult.stdout,
|
|
3492
|
-
stderr: harnessResult.stderr
|
|
3493
|
-
},
|
|
3494
|
-
...checks ? { checks } : {}
|
|
3495
|
-
};
|
|
3496
3704
|
const spent = {
|
|
3497
3705
|
iterations: 1,
|
|
3498
3706
|
// budgetExempt: spend is recorded zero (not metered), never a fabricated cost.
|
|
@@ -3500,12 +3708,16 @@ function createWorktreeCliExecutor(options) {
|
|
|
3500
3708
|
usd: 0,
|
|
3501
3709
|
ms: Date.now() - started
|
|
3502
3710
|
};
|
|
3503
|
-
artifact = { outRef: contentAddress(
|
|
3711
|
+
artifact = { outRef: contentAddress(run.result), out: run.result, spent };
|
|
3504
3712
|
return artifact;
|
|
3505
3713
|
},
|
|
3506
3714
|
async teardown(_grace) {
|
|
3507
3715
|
controller.abort();
|
|
3508
|
-
|
|
3716
|
+
if (run) {
|
|
3717
|
+
const r = run;
|
|
3718
|
+
run = void 0;
|
|
3719
|
+
await r.cleanup();
|
|
3720
|
+
}
|
|
3509
3721
|
return { destroyed: true };
|
|
3510
3722
|
},
|
|
3511
3723
|
resultArtifact() {
|
|
@@ -3518,51 +3730,6 @@ function createWorktreeCliExecutor(options) {
|
|
|
3518
3730
|
}
|
|
3519
3731
|
};
|
|
3520
3732
|
}
|
|
3521
|
-
async function runWorktreeChecks(opts) {
|
|
3522
|
-
if (opts.testCmd === void 0 && opts.typecheckCmd === void 0) return void 0;
|
|
3523
|
-
const run = async (command) => {
|
|
3524
|
-
const res = await opts.runCommand({
|
|
3525
|
-
command,
|
|
3526
|
-
cwd: opts.worktreePath,
|
|
3527
|
-
timeoutMs: opts.timeoutMs,
|
|
3528
|
-
...opts.signal ? { signal: opts.signal } : {}
|
|
3529
|
-
});
|
|
3530
|
-
return {
|
|
3531
|
-
command,
|
|
3532
|
-
passed: res.exitCode === 0,
|
|
3533
|
-
exitCode: res.exitCode,
|
|
3534
|
-
output: res.output.length > checkOutputCap ? res.output.slice(-checkOutputCap) : res.output
|
|
3535
|
-
};
|
|
3536
|
-
};
|
|
3537
|
-
const checks = {};
|
|
3538
|
-
if (opts.testCmd !== void 0) checks.tests = await run(opts.testCmd);
|
|
3539
|
-
if (opts.typecheckCmd !== void 0) checks.typecheck = await run(opts.typecheckCmd);
|
|
3540
|
-
return checks;
|
|
3541
|
-
}
|
|
3542
|
-
function defaultRunCommand(opts) {
|
|
3543
|
-
return new Promise((resolve, reject) => {
|
|
3544
|
-
const child = spawn2("/bin/sh", ["-c", opts.command], {
|
|
3545
|
-
cwd: opts.cwd,
|
|
3546
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3547
|
-
});
|
|
3548
|
-
const chunks = [];
|
|
3549
|
-
let settled = false;
|
|
3550
|
-
const timer = setTimeout(() => child.kill("SIGTERM"), opts.timeoutMs);
|
|
3551
|
-
const onAbort = () => child.kill("SIGTERM");
|
|
3552
|
-
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
3553
|
-
child.stdout?.on("data", (d) => chunks.push(String(d)));
|
|
3554
|
-
child.stderr?.on("data", (d) => chunks.push(String(d)));
|
|
3555
|
-
const finish = (fn) => {
|
|
3556
|
-
if (settled) return;
|
|
3557
|
-
settled = true;
|
|
3558
|
-
clearTimeout(timer);
|
|
3559
|
-
opts.signal?.removeEventListener("abort", onAbort);
|
|
3560
|
-
fn();
|
|
3561
|
-
};
|
|
3562
|
-
child.on("error", (err) => finish(() => reject(err)));
|
|
3563
|
-
child.on("close", (code) => finish(() => resolve({ exitCode: code, output: chunks.join("") })));
|
|
3564
|
-
});
|
|
3565
|
-
}
|
|
3566
3733
|
function linkSignals(a, b) {
|
|
3567
3734
|
if (a.aborted || b.aborted) {
|
|
3568
3735
|
const c2 = new AbortController();
|
|
@@ -3583,15 +3750,15 @@ var cliSeamKey = "cli";
|
|
|
3583
3750
|
var bridgeSeamKey = "bridge";
|
|
3584
3751
|
var cliWorktreeSeamKey = "cli-worktree";
|
|
3585
3752
|
function contentRef(prefix, value) {
|
|
3586
|
-
let
|
|
3753
|
+
let str2;
|
|
3587
3754
|
try {
|
|
3588
|
-
|
|
3755
|
+
str2 = JSON.stringify(value) ?? String(value);
|
|
3589
3756
|
} catch {
|
|
3590
|
-
|
|
3757
|
+
str2 = String(value);
|
|
3591
3758
|
}
|
|
3592
3759
|
let h = 2166136261;
|
|
3593
|
-
for (let i = 0; i <
|
|
3594
|
-
h ^=
|
|
3760
|
+
for (let i = 0; i < str2.length; i += 1) {
|
|
3761
|
+
h ^= str2.charCodeAt(i);
|
|
3595
3762
|
h = Math.imul(h, 16777619);
|
|
3596
3763
|
}
|
|
3597
3764
|
return `${prefix}:${(h >>> 0).toString(16).padStart(8, "0")}`;
|
|
@@ -3671,35 +3838,59 @@ var routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
3671
3838
|
};
|
|
3672
3839
|
abortIfSignalled();
|
|
3673
3840
|
if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
|
|
3841
|
+
const inbox = createInbox();
|
|
3674
3842
|
let artifact;
|
|
3675
3843
|
return {
|
|
3676
3844
|
runtime: "router",
|
|
3845
|
+
deliver: (m) => inbox.deliver(m),
|
|
3677
3846
|
async execute(task, signal) {
|
|
3678
3847
|
const started = Date.now();
|
|
3679
|
-
const linked = linkSignals2(signal, controller.signal);
|
|
3680
3848
|
const messages = [
|
|
3681
3849
|
...taskToMessages(task, spec)
|
|
3682
3850
|
];
|
|
3683
3851
|
const tokens = zeroTokenUsage();
|
|
3684
3852
|
let turns = 0;
|
|
3685
3853
|
let lastText = "";
|
|
3854
|
+
const flush = () => {
|
|
3855
|
+
const pending = inbox.drain();
|
|
3856
|
+
if (pending.length) messages.push({ role: "user", content: inbox.fold(pending) });
|
|
3857
|
+
return pending.length > 0;
|
|
3858
|
+
};
|
|
3859
|
+
const external = mergeAbortSignals(signal, controller.signal);
|
|
3686
3860
|
for (let t = 0; t < maxTurns; t += 1) {
|
|
3861
|
+
flush();
|
|
3862
|
+
const interruptSig = inbox.freshInterrupt();
|
|
3863
|
+
const turnController = new AbortController();
|
|
3864
|
+
const abortTurn = () => turnController.abort();
|
|
3865
|
+
if (external.aborted) turnController.abort();
|
|
3866
|
+
else external.addEventListener("abort", abortTurn);
|
|
3867
|
+
interruptSig.addEventListener("abort", abortTurn, { once: true });
|
|
3868
|
+
const cleanup = () => external.removeEventListener("abort", abortTurn);
|
|
3869
|
+
let res;
|
|
3870
|
+
try {
|
|
3871
|
+
res = await fetch(`${seam.routerBaseUrl.replace(/\/$/, "")}/chat/completions`, {
|
|
3872
|
+
method: "POST",
|
|
3873
|
+
headers: {
|
|
3874
|
+
"content-type": "application/json",
|
|
3875
|
+
authorization: `Bearer ${seam.routerKey}`
|
|
3876
|
+
},
|
|
3877
|
+
body: JSON.stringify({
|
|
3878
|
+
model,
|
|
3879
|
+
messages,
|
|
3880
|
+
tools: seam.tools,
|
|
3881
|
+
tool_choice: "auto",
|
|
3882
|
+
temperature: 0.2
|
|
3883
|
+
}),
|
|
3884
|
+
signal: turnController.signal
|
|
3885
|
+
});
|
|
3886
|
+
} catch (e) {
|
|
3887
|
+
cleanup();
|
|
3888
|
+
const interruptAbort = e instanceof DOMException && e.name === "AbortError" && interruptSig.aborted && !signal.aborted && !controller.signal.aborted;
|
|
3889
|
+
if (interruptAbort) continue;
|
|
3890
|
+
throw e;
|
|
3891
|
+
}
|
|
3892
|
+
cleanup();
|
|
3687
3893
|
turns += 1;
|
|
3688
|
-
const res = await fetch(`${seam.routerBaseUrl.replace(/\/$/, "")}/chat/completions`, {
|
|
3689
|
-
method: "POST",
|
|
3690
|
-
headers: {
|
|
3691
|
-
"content-type": "application/json",
|
|
3692
|
-
authorization: `Bearer ${seam.routerKey}`
|
|
3693
|
-
},
|
|
3694
|
-
body: JSON.stringify({
|
|
3695
|
-
model,
|
|
3696
|
-
messages,
|
|
3697
|
-
tools: seam.tools,
|
|
3698
|
-
tool_choice: "auto",
|
|
3699
|
-
temperature: 0.2
|
|
3700
|
-
}),
|
|
3701
|
-
...linked ? { signal: linked } : {}
|
|
3702
|
-
});
|
|
3703
3894
|
if (!res.ok) {
|
|
3704
3895
|
throw new ValidationError(
|
|
3705
3896
|
`routerToolsInlineExecutor: router ${res.status}: ${(await res.text()).slice(0, 200)}`
|
|
@@ -3714,7 +3905,10 @@ var routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
3714
3905
|
const msg = data.choices?.[0]?.message;
|
|
3715
3906
|
if (msg?.content) lastText = msg.content;
|
|
3716
3907
|
const toolCalls = msg?.tool_calls ?? [];
|
|
3717
|
-
if (toolCalls.length === 0)
|
|
3908
|
+
if (toolCalls.length === 0) {
|
|
3909
|
+
if (flush()) continue;
|
|
3910
|
+
break;
|
|
3911
|
+
}
|
|
3718
3912
|
messages.push({
|
|
3719
3913
|
role: "assistant",
|
|
3720
3914
|
content: msg?.content ?? "",
|
|
@@ -3738,8 +3932,20 @@ var routerToolsInlineExecutor = (spec, ctx) => {
|
|
|
3738
3932
|
});
|
|
3739
3933
|
continue;
|
|
3740
3934
|
}
|
|
3741
|
-
const
|
|
3935
|
+
const toolName = tc?.function?.name ?? "";
|
|
3936
|
+
let result;
|
|
3937
|
+
let status = "ok";
|
|
3938
|
+
try {
|
|
3939
|
+
result = await seam.executeToolCall(toolName, args, task);
|
|
3940
|
+
} catch (e) {
|
|
3941
|
+
status = "error";
|
|
3942
|
+
result = `error: ${e instanceof Error ? e.message : String(e)}`;
|
|
3943
|
+
}
|
|
3742
3944
|
messages.push({ role: "tool", tool_call_id: id, content: result });
|
|
3945
|
+
try {
|
|
3946
|
+
seam.onToolStep?.({ toolName, args, status });
|
|
3947
|
+
} catch {
|
|
3948
|
+
}
|
|
3743
3949
|
}
|
|
3744
3950
|
}
|
|
3745
3951
|
const usd = isModelPriced2(model) ? estimateCost2(tokens.input, tokens.output, model) : 0;
|
|
@@ -4132,9 +4338,9 @@ function readSeam(ctx, key, who) {
|
|
|
4132
4338
|
function taskToPrompt(task) {
|
|
4133
4339
|
if (typeof task === "string") return task;
|
|
4134
4340
|
if (task && typeof task === "object") {
|
|
4135
|
-
const
|
|
4341
|
+
const obj2 = task;
|
|
4136
4342
|
for (const k of ["prompt", "content", "task", "message"]) {
|
|
4137
|
-
if (typeof
|
|
4343
|
+
if (typeof obj2[k] === "string") return obj2[k];
|
|
4138
4344
|
}
|
|
4139
4345
|
}
|
|
4140
4346
|
return JSON.stringify(task);
|
|
@@ -4171,6 +4377,18 @@ function linkSignals2(a, b) {
|
|
|
4171
4377
|
b.addEventListener("abort", onAbort, { once: true });
|
|
4172
4378
|
return c.signal;
|
|
4173
4379
|
}
|
|
4380
|
+
function mergeAbortSignals(...signals) {
|
|
4381
|
+
const c = new AbortController();
|
|
4382
|
+
const onAbort = () => c.abort();
|
|
4383
|
+
for (const s of signals) {
|
|
4384
|
+
if (s.aborted) {
|
|
4385
|
+
c.abort();
|
|
4386
|
+
break;
|
|
4387
|
+
}
|
|
4388
|
+
s.addEventListener("abort", onAbort, { once: true });
|
|
4389
|
+
}
|
|
4390
|
+
return c.signal;
|
|
4391
|
+
}
|
|
4174
4392
|
|
|
4175
4393
|
// src/runtime/supervise/budget.ts
|
|
4176
4394
|
function spendFromUsageEvents(events) {
|
|
@@ -4279,10 +4497,20 @@ function createBudgetPool(root, now = Date.now) {
|
|
|
4279
4497
|
committedUsd += spent.usd;
|
|
4280
4498
|
}
|
|
4281
4499
|
}
|
|
4500
|
+
function observe2(spend) {
|
|
4501
|
+
const tokens = totalTokens(spend.tokens);
|
|
4502
|
+
freeTokens -= tokens;
|
|
4503
|
+
committedTokens += tokens;
|
|
4504
|
+
freeIterations -= spend.iterations;
|
|
4505
|
+
committedIterations += spend.iterations;
|
|
4506
|
+
committedUsd += spend.usd;
|
|
4507
|
+
if (usdCapped) freeUsd -= spend.usd;
|
|
4508
|
+
}
|
|
4282
4509
|
function readout() {
|
|
4283
4510
|
return {
|
|
4284
4511
|
tokensLeft: freeTokens,
|
|
4285
4512
|
usdLeft: usdCapped ? freeUsd : 0,
|
|
4513
|
+
usdCapped,
|
|
4286
4514
|
deadlineMs: absoluteDeadlineMs,
|
|
4287
4515
|
reservedTokens
|
|
4288
4516
|
};
|
|
@@ -4299,6 +4527,7 @@ function createBudgetPool(root, now = Date.now) {
|
|
|
4299
4527
|
reconcile,
|
|
4300
4528
|
spendFrom: foldUsage,
|
|
4301
4529
|
readout,
|
|
4530
|
+
observe: observe2,
|
|
4302
4531
|
assertNoOpenTickets
|
|
4303
4532
|
};
|
|
4304
4533
|
}
|
|
@@ -4368,12 +4597,14 @@ function createSupervisor() {
|
|
|
4368
4597
|
if (out !== void 0) {
|
|
4369
4598
|
const outRef = contentAddress(out);
|
|
4370
4599
|
await opts.blobs.put(outRef, out);
|
|
4600
|
+
const { childWork, driverInference } = await spentFromJournal(journal, opts.runId);
|
|
4371
4601
|
return {
|
|
4372
4602
|
kind: "winner",
|
|
4373
4603
|
out,
|
|
4374
4604
|
outRef,
|
|
4375
4605
|
tree,
|
|
4376
|
-
spentTotal:
|
|
4606
|
+
spentTotal: addSpend(childWork, driverInference),
|
|
4607
|
+
...isNonEmptySpend(driverInference) ? { spentBreakdown: { driverInference, childWork } } : {}
|
|
4377
4608
|
};
|
|
4378
4609
|
}
|
|
4379
4610
|
return {
|
|
@@ -4505,23 +4736,38 @@ function poolExhausted(pool2, opts) {
|
|
|
4505
4736
|
}
|
|
4506
4737
|
return false;
|
|
4507
4738
|
}
|
|
4508
|
-
async function
|
|
4739
|
+
async function spentFromJournal(journal, root) {
|
|
4509
4740
|
const events = await journal.loadTree(root);
|
|
4510
4741
|
if (events === void 0) {
|
|
4511
4742
|
throw new RuntimeRunStateError(
|
|
4512
4743
|
`supervisor: spawn tree '${root}' is missing from the journal after run (corrupted log)`
|
|
4513
4744
|
);
|
|
4514
4745
|
}
|
|
4515
|
-
const
|
|
4746
|
+
const childWork = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
4747
|
+
const driverInference = { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: 0 };
|
|
4516
4748
|
for (const ev of events) {
|
|
4517
|
-
if (ev.kind
|
|
4518
|
-
|
|
4519
|
-
total.tokens.input += ev.spent.tokens.input;
|
|
4520
|
-
total.tokens.output += ev.spent.tokens.output;
|
|
4521
|
-
total.usd += ev.spent.usd;
|
|
4522
|
-
total.ms += ev.spent.ms;
|
|
4749
|
+
if (ev.kind === "settled") accumulate(childWork, ev.spent);
|
|
4750
|
+
else if (ev.kind === "metered") accumulate(driverInference, ev.spend);
|
|
4523
4751
|
}
|
|
4524
|
-
return
|
|
4752
|
+
return { childWork, driverInference };
|
|
4753
|
+
}
|
|
4754
|
+
function accumulate(a, b) {
|
|
4755
|
+
a.iterations += b.iterations;
|
|
4756
|
+
a.tokens.input += b.tokens.input;
|
|
4757
|
+
a.tokens.output += b.tokens.output;
|
|
4758
|
+
a.usd += b.usd;
|
|
4759
|
+
a.ms += b.ms;
|
|
4760
|
+
}
|
|
4761
|
+
function addSpend(a, b) {
|
|
4762
|
+
return {
|
|
4763
|
+
iterations: a.iterations + b.iterations,
|
|
4764
|
+
tokens: { input: a.tokens.input + b.tokens.input, output: a.tokens.output + b.tokens.output },
|
|
4765
|
+
usd: a.usd + b.usd,
|
|
4766
|
+
ms: a.ms + b.ms
|
|
4767
|
+
};
|
|
4768
|
+
}
|
|
4769
|
+
function isNonEmptySpend(s) {
|
|
4770
|
+
return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0;
|
|
4525
4771
|
}
|
|
4526
4772
|
|
|
4527
4773
|
// src/runtime/personify/registry.ts
|
|
@@ -4680,7 +4926,7 @@ async function trajectoryReport(journal, blobs, root, options = {}) {
|
|
|
4680
4926
|
throw new Error(`trajectoryReport: no journaled tree for root '${root}'`);
|
|
4681
4927
|
}
|
|
4682
4928
|
const spawns = events.filter(isSpawned).sort(bySeq);
|
|
4683
|
-
const closes = events.filter((ev) => ev.kind !== "spawned").sort(bySeq);
|
|
4929
|
+
const closes = events.filter((ev) => ev.kind !== "spawned" && ev.kind !== "metered").sort(bySeq);
|
|
4684
4930
|
const nodes = /* @__PURE__ */ new Map();
|
|
4685
4931
|
for (const ev of spawns) {
|
|
4686
4932
|
nodes.set(ev.id, {
|
|
@@ -4704,6 +4950,11 @@ async function trajectoryReport(journal, blobs, root, options = {}) {
|
|
|
4704
4950
|
node.verdict = ev.verdict;
|
|
4705
4951
|
node.outRef = ev.outRef;
|
|
4706
4952
|
}
|
|
4953
|
+
for (const ev of events) {
|
|
4954
|
+
if (ev.kind !== "metered") continue;
|
|
4955
|
+
const node = requireNode2(nodes, ev.id, root);
|
|
4956
|
+
node.ownSpend = addNodeSpend(node.ownSpend, ev.spend);
|
|
4957
|
+
}
|
|
4707
4958
|
if (!nodes.has(root)) {
|
|
4708
4959
|
throw new Error(
|
|
4709
4960
|
`trajectoryReport: root '${root}' has no spawned event in its journaled tree (corrupted log)`
|
|
@@ -4766,7 +5017,7 @@ function rollUpSpend(nodes, root) {
|
|
|
4766
5017
|
continue;
|
|
4767
5018
|
}
|
|
4768
5019
|
const sum = cloneSpend(node.ownSpend);
|
|
4769
|
-
for (const child of node.children)
|
|
5020
|
+
for (const child of node.children) addSpend2(sum, requireSpend(rolled, child, root));
|
|
4770
5021
|
rolled.set(frame.id, sum);
|
|
4771
5022
|
}
|
|
4772
5023
|
return rolled;
|
|
@@ -4811,6 +5062,14 @@ function countStatuses(reported) {
|
|
|
4811
5062
|
function zeroSpend4() {
|
|
4812
5063
|
return { iterations: 0, tokens: zeroTokenUsage(), usd: 0, ms: 0 };
|
|
4813
5064
|
}
|
|
5065
|
+
function addNodeSpend(a, b) {
|
|
5066
|
+
return {
|
|
5067
|
+
iterations: a.iterations + b.iterations,
|
|
5068
|
+
tokens: { input: a.tokens.input + b.tokens.input, output: a.tokens.output + b.tokens.output },
|
|
5069
|
+
usd: a.usd + b.usd,
|
|
5070
|
+
ms: a.ms + b.ms
|
|
5071
|
+
};
|
|
5072
|
+
}
|
|
4814
5073
|
function cloneSpend(spend) {
|
|
4815
5074
|
return {
|
|
4816
5075
|
iterations: spend.iterations,
|
|
@@ -4819,7 +5078,7 @@ function cloneSpend(spend) {
|
|
|
4819
5078
|
ms: spend.ms
|
|
4820
5079
|
};
|
|
4821
5080
|
}
|
|
4822
|
-
function
|
|
5081
|
+
function addSpend2(acc, delta) {
|
|
4823
5082
|
acc.iterations += delta.iterations;
|
|
4824
5083
|
addTokenUsage(acc.tokens, delta.tokens);
|
|
4825
5084
|
acc.usd += delta.usd;
|
|
@@ -6569,7 +6828,7 @@ function supervisorSkill(opts) {
|
|
|
6569
6828
|
' \u2022 systemPrompt: rich, specific instructions for THIS sub-task \u2014 tell the worker exactly what to produce, how to use its tools fully, and what "done" means. Never a one-liner; write the prompt a power-user would write.',
|
|
6570
6829
|
" \u2022 model: the model best suited to this sub-task (omit to use the default).",
|
|
6571
6830
|
" NEVER spawn a worker with an empty profile. The quality of the worker IS the quality of the profile you write.",
|
|
6572
|
-
"3.
|
|
6831
|
+
"3. await_event (kinds:['settled']) to collect each worker. Its result says valid:true only if the deployable check passed.",
|
|
6573
6832
|
"4. If a worker did NOT deliver, AUTHOR A NEW worker whose systemPrompt names the SPECIFIC failure and how to fix it \u2014 never just retry the same prompt.",
|
|
6574
6833
|
"5. Stop (reply with no tool call) once the work is delivered. You cannot declare done yourself \u2014 only a delivered (valid:true) worker counts.",
|
|
6575
6834
|
...opts?.goal ? ["", `The goal: ${opts.goal}`] : []
|
|
@@ -6708,6 +6967,62 @@ function isArtifact(value) {
|
|
|
6708
6967
|
return value !== null && typeof value === "object" && typeof value.patch === "string" && typeof value.stats === "object";
|
|
6709
6968
|
}
|
|
6710
6969
|
|
|
6970
|
+
// src/runtime/supervise/event-bus.ts
|
|
6971
|
+
function createEventBus(now = Date.now) {
|
|
6972
|
+
const queue = [];
|
|
6973
|
+
const log = [];
|
|
6974
|
+
const subscribers = [];
|
|
6975
|
+
const byKind = {};
|
|
6976
|
+
let seq = 0;
|
|
6977
|
+
let pulled = 0;
|
|
6978
|
+
const matches = (r, kinds) => !kinds || kinds.includes(r.event.type);
|
|
6979
|
+
const bestIndex = (kinds) => {
|
|
6980
|
+
let best = -1;
|
|
6981
|
+
let bestPriority = Number.NEGATIVE_INFINITY;
|
|
6982
|
+
for (let i = 0; i < queue.length; i++) {
|
|
6983
|
+
const r = queue[i];
|
|
6984
|
+
if (!r || !matches(r, kinds)) continue;
|
|
6985
|
+
if (r.priority > bestPriority) {
|
|
6986
|
+
best = i;
|
|
6987
|
+
bestPriority = r.priority;
|
|
6988
|
+
}
|
|
6989
|
+
}
|
|
6990
|
+
return best;
|
|
6991
|
+
};
|
|
6992
|
+
return {
|
|
6993
|
+
async publish(event, opts) {
|
|
6994
|
+
const record = { seq: seq++, at: now(), priority: opts?.priority ?? 0, event };
|
|
6995
|
+
if (opts?.queue !== false) queue.push(record);
|
|
6996
|
+
log.push(record);
|
|
6997
|
+
byKind[event.type] = (byKind[event.type] ?? 0) + 1;
|
|
6998
|
+
for (const handler of subscribers) await handler(record);
|
|
6999
|
+
return record;
|
|
7000
|
+
},
|
|
7001
|
+
pull(kinds) {
|
|
7002
|
+
const i = bestIndex(kinds);
|
|
7003
|
+
if (i < 0) return void 0;
|
|
7004
|
+
pulled++;
|
|
7005
|
+
return queue.splice(i, 1)[0]?.event;
|
|
7006
|
+
},
|
|
7007
|
+
subscribe(handler) {
|
|
7008
|
+
subscribers.push(handler);
|
|
7009
|
+
return () => {
|
|
7010
|
+
const i = subscribers.indexOf(handler);
|
|
7011
|
+
if (i >= 0) subscribers.splice(i, 1);
|
|
7012
|
+
};
|
|
7013
|
+
},
|
|
7014
|
+
pending(kinds) {
|
|
7015
|
+
return kinds ? queue.filter((r) => matches(r, kinds)).length : queue.length;
|
|
7016
|
+
},
|
|
7017
|
+
history() {
|
|
7018
|
+
return log;
|
|
7019
|
+
},
|
|
7020
|
+
stats() {
|
|
7021
|
+
return { published: seq, pulled, byKind: { ...byKind } };
|
|
7022
|
+
}
|
|
7023
|
+
};
|
|
7024
|
+
}
|
|
7025
|
+
|
|
6711
7026
|
// src/mcp/tools/coordination.ts
|
|
6712
7027
|
var idArg = { type: "string", description: "The workerId returned by spawn_worker." };
|
|
6713
7028
|
function createCoordinationTools(opts) {
|
|
@@ -6717,12 +7032,18 @@ function createCoordinationTools(opts) {
|
|
|
6717
7032
|
const ledger = [];
|
|
6718
7033
|
const questions = [];
|
|
6719
7034
|
const questionPolicy = opts.questionPolicy ?? "auto";
|
|
6720
|
-
const
|
|
7035
|
+
const bus = createEventBus();
|
|
7036
|
+
if (opts.onEvent) {
|
|
7037
|
+
const cb = opts.onEvent;
|
|
7038
|
+
bus.subscribe((rec) => cb(rec.event));
|
|
7039
|
+
}
|
|
7040
|
+
const urgencyPriority = (u) => u === "blocks-run" ? 20 : u === "blocks-step" ? 10 : 0;
|
|
7041
|
+
const str2 = (v, field) => {
|
|
6721
7042
|
if (typeof v !== "string" || v.length === 0)
|
|
6722
7043
|
throw new Error(`coordination tools: "${field}" must be a non-empty string`);
|
|
6723
7044
|
return v;
|
|
6724
7045
|
};
|
|
6725
|
-
const
|
|
7046
|
+
const obj2 = (raw) => {
|
|
6726
7047
|
if (!raw || typeof raw !== "object")
|
|
6727
7048
|
throw new Error("coordination tools: arguments must be an object");
|
|
6728
7049
|
return raw;
|
|
@@ -6748,15 +7069,52 @@ function createCoordinationTools(opts) {
|
|
|
6748
7069
|
ledger.push(w);
|
|
6749
7070
|
return w;
|
|
6750
7071
|
};
|
|
7072
|
+
const drainSettlement = async () => {
|
|
7073
|
+
const s = await opts.scope.next();
|
|
7074
|
+
if (!s) return false;
|
|
7075
|
+
const w = recordSettled(s);
|
|
7076
|
+
await bus.publish({ type: "settled", worker: w });
|
|
7077
|
+
if (w.status === "done" && w.outRef && opts.analysts && opts.analyzeOnSettle?.length) {
|
|
7078
|
+
const trace = await opts.blobs.get(w.outRef);
|
|
7079
|
+
for (const analyst of opts.analyzeOnSettle) {
|
|
7080
|
+
const findings = await opts.analysts.run(analyst, trace);
|
|
7081
|
+
await bus.publish({ type: "finding", finding: { fromWorker: w.id, analyst, findings } });
|
|
7082
|
+
}
|
|
7083
|
+
}
|
|
7084
|
+
return true;
|
|
7085
|
+
};
|
|
7086
|
+
async function sendDown(type, down, questionId) {
|
|
7087
|
+
await bus.publish(
|
|
7088
|
+
type === "answer" ? { type, down, questionId: str2(questionId, "questionId") } : { type, down },
|
|
7089
|
+
{ queue: false }
|
|
7090
|
+
);
|
|
7091
|
+
}
|
|
7092
|
+
const projectEvent = (ev) => {
|
|
7093
|
+
if (ev.type === "settled") {
|
|
7094
|
+
const w = ev.worker;
|
|
7095
|
+
return w.status === "done" ? {
|
|
7096
|
+
type: "settled",
|
|
7097
|
+
settled: w.id,
|
|
7098
|
+
status: "done",
|
|
7099
|
+
score: w.score,
|
|
7100
|
+
valid: w.valid,
|
|
7101
|
+
outRef: w.outRef
|
|
7102
|
+
} : { type: "settled", settled: w.id, status: "down", reason: w.reason };
|
|
7103
|
+
}
|
|
7104
|
+
if (ev.type === "question") return { type: "question", question: ev.question };
|
|
7105
|
+
if (ev.type === "finding") return { type: "finding", ...ev.finding };
|
|
7106
|
+
if (ev.type === "answer") return { type: "answer", ...ev.down, questionId: ev.questionId };
|
|
7107
|
+
return { type: ev.type, ...ev.down };
|
|
7108
|
+
};
|
|
6751
7109
|
const nextQuestionId = (from) => `${from}:q${questionSeq++}`;
|
|
6752
7110
|
const normalizeQuestion = (q, fallbackFrom) => {
|
|
6753
|
-
const from =
|
|
7111
|
+
const from = str2(q.from ?? fallbackFrom, "from");
|
|
6754
7112
|
return {
|
|
6755
7113
|
id: typeof q.id === "string" && q.id.length > 0 ? q.id : nextQuestionId(from),
|
|
6756
7114
|
from,
|
|
6757
7115
|
level: level(q.level),
|
|
6758
|
-
question:
|
|
6759
|
-
reason:
|
|
7116
|
+
question: str2(q.question, "question"),
|
|
7117
|
+
reason: str2(q.reason, "reason"),
|
|
6760
7118
|
...q.options ? { options: q.options } : {},
|
|
6761
7119
|
urgency: urgency(q.urgency)
|
|
6762
7120
|
};
|
|
@@ -6781,7 +7139,11 @@ function createCoordinationTools(opts) {
|
|
|
6781
7139
|
return { question: record, added: true };
|
|
6782
7140
|
};
|
|
6783
7141
|
const emitNewQuestion = async (record) => {
|
|
6784
|
-
if (record.added)
|
|
7142
|
+
if (record.added)
|
|
7143
|
+
await bus.publish(
|
|
7144
|
+
{ type: "question", question: record.question },
|
|
7145
|
+
{ priority: urgencyPriority(record.question.urgency) }
|
|
7146
|
+
);
|
|
6785
7147
|
return record.question;
|
|
6786
7148
|
};
|
|
6787
7149
|
const decideQuestion = (questionId, decision) => {
|
|
@@ -6816,7 +7178,7 @@ function createCoordinationTools(opts) {
|
|
|
6816
7178
|
required: ["profile", "task"]
|
|
6817
7179
|
},
|
|
6818
7180
|
handler: (raw) => {
|
|
6819
|
-
const a =
|
|
7181
|
+
const a = obj2(raw);
|
|
6820
7182
|
const agent = opts.makeWorkerAgent(a.profile);
|
|
6821
7183
|
const res = opts.scope.spawn(agent, a.task, {
|
|
6822
7184
|
budget: opts.perWorker,
|
|
@@ -6830,7 +7192,7 @@ function createCoordinationTools(opts) {
|
|
|
6830
7192
|
description: "Inspect a worker status, spend, and settled output artifact when available.",
|
|
6831
7193
|
inputSchema: { type: "object", properties: { workerId: idArg }, required: ["workerId"] },
|
|
6832
7194
|
handler: async (raw) => {
|
|
6833
|
-
const id =
|
|
7195
|
+
const id = str2(obj2(raw).workerId, "workerId");
|
|
6834
7196
|
const node = opts.scope.view.nodes.find((n) => n.id === id);
|
|
6835
7197
|
if (!node) return { error: `unknown workerId ${JSON.stringify(id)}` };
|
|
6836
7198
|
const output = node.outRef ? await opts.blobs.get(node.outRef) : void 0;
|
|
@@ -6844,38 +7206,52 @@ function createCoordinationTools(opts) {
|
|
|
6844
7206
|
},
|
|
6845
7207
|
{
|
|
6846
7208
|
name: "steer_worker",
|
|
6847
|
-
description: "
|
|
7209
|
+
description: "Send a message DOWN to a still-LIVE worker (parent\u2192child): a new instruction, a course correction, or a continuation. The worker drains it at its next step boundary \u2014 and before it may settle, so it cannot finish while a message it never read is pending. A worker that already settled is gone (returns delivered:false) \u2014 spawn a fresh one instead.",
|
|
6848
7210
|
inputSchema: {
|
|
6849
7211
|
type: "object",
|
|
6850
7212
|
properties: {
|
|
6851
7213
|
workerId: idArg,
|
|
6852
|
-
instruction: { type: "string", description: "What the worker should do next." }
|
|
7214
|
+
instruction: { type: "string", description: "What the worker should do next." },
|
|
7215
|
+
interrupt: {
|
|
7216
|
+
type: "boolean",
|
|
7217
|
+
description: "true = forceful: abort the worker\u2019s in-flight inference so it re-plans on the NEXT turn (a tool already mid-execution finishes first; only the owned tool-loop honors this). false/omitted = queued: it flushes at the next step boundary (and before it may settle)."
|
|
7218
|
+
}
|
|
6853
7219
|
},
|
|
6854
7220
|
required: ["workerId", "instruction"]
|
|
6855
7221
|
},
|
|
6856
|
-
handler: (raw) => {
|
|
6857
|
-
const a =
|
|
6858
|
-
const
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
7222
|
+
handler: async (raw) => {
|
|
7223
|
+
const a = obj2(raw);
|
|
7224
|
+
const workerId = str2(a.workerId, "workerId");
|
|
7225
|
+
const instruction = str2(a.instruction, "instruction");
|
|
7226
|
+
const interrupt = a.interrupt === true;
|
|
7227
|
+
const delivered = opts.scope.send(workerId, { steer: instruction, interrupt });
|
|
7228
|
+
await sendDown("steer", { toWorker: workerId, instruction, delivered });
|
|
7229
|
+
return { delivered };
|
|
6862
7230
|
}
|
|
6863
7231
|
},
|
|
6864
7232
|
{
|
|
6865
|
-
name: "
|
|
6866
|
-
description: "Wait for the next
|
|
6867
|
-
inputSchema: {
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
7233
|
+
name: "await_event",
|
|
7234
|
+
description: "Wait for and pull the next message a worker, sub-driver, or analyst sent up \u2014 the unified inbox. An event is one of: a settled worker output ('settled'), a question needing your answer ('question', from ask_parent / the worker's ask-user), or a trace-analyst finding ('finding', from analyze-on-settle). Pass kinds:['settled'] for just the next finished worker; omit `kinds` to also receive questions and findings. Returns { idle: true } when nothing is queued and no workers are live.",
|
|
7235
|
+
inputSchema: {
|
|
7236
|
+
type: "object",
|
|
7237
|
+
properties: {
|
|
7238
|
+
kinds: {
|
|
7239
|
+
type: "array",
|
|
7240
|
+
items: { type: "string", enum: ["settled", "question", "finding"] },
|
|
7241
|
+
description: "Restrict to these event kinds (any if omitted)."
|
|
7242
|
+
}
|
|
7243
|
+
}
|
|
7244
|
+
},
|
|
7245
|
+
handler: async (raw) => {
|
|
7246
|
+
const k = obj2(raw).kinds;
|
|
7247
|
+
const kinds = Array.isArray(k) ? k.filter((x) => x === "settled" || x === "question" || x === "finding") : void 0;
|
|
7248
|
+
let ev = bus.pull(kinds);
|
|
7249
|
+
if (!ev) {
|
|
7250
|
+
const drained = await drainSettlement();
|
|
7251
|
+
ev = bus.pull(kinds);
|
|
7252
|
+
if (!ev) return { idle: !drained };
|
|
7253
|
+
}
|
|
7254
|
+
return projectEvent(ev);
|
|
6879
7255
|
}
|
|
6880
7256
|
},
|
|
6881
7257
|
{
|
|
@@ -6899,17 +7275,24 @@ function createCoordinationTools(opts) {
|
|
|
6899
7275
|
},
|
|
6900
7276
|
required: ["questionId"]
|
|
6901
7277
|
},
|
|
6902
|
-
handler: (raw) => {
|
|
6903
|
-
const a =
|
|
6904
|
-
const questionId =
|
|
7278
|
+
handler: async (raw) => {
|
|
7279
|
+
const a = obj2(raw);
|
|
7280
|
+
const questionId = str2(a.questionId, "questionId");
|
|
6905
7281
|
if (typeof a.answer === "string" && a.answer.length > 0) {
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
})
|
|
7282
|
+
const answer = a.answer;
|
|
7283
|
+
const question = decideQuestion(questionId, {
|
|
7284
|
+
kind: "answer",
|
|
7285
|
+
answer,
|
|
7286
|
+
by: typeof a.by === "string" && a.by.length > 0 ? a.by : "user"
|
|
6912
7287
|
});
|
|
7288
|
+
const interrupt = question.urgency === "blocks-run" || question.urgency === "blocks-step";
|
|
7289
|
+
const delivered = opts.scope.send(question.from, { answer, questionId, interrupt });
|
|
7290
|
+
await sendDown(
|
|
7291
|
+
"answer",
|
|
7292
|
+
{ toWorker: question.from, instruction: answer, delivered },
|
|
7293
|
+
questionId
|
|
7294
|
+
);
|
|
7295
|
+
return { question, delivered };
|
|
6913
7296
|
}
|
|
6914
7297
|
if (typeof a.deferReason === "string" && a.deferReason.length > 0) {
|
|
6915
7298
|
return Promise.resolve({
|
|
@@ -6947,15 +7330,15 @@ function createCoordinationTools(opts) {
|
|
|
6947
7330
|
required: ["from", "level", "question", "reason", "urgency"]
|
|
6948
7331
|
},
|
|
6949
7332
|
handler: async (raw) => {
|
|
6950
|
-
const a =
|
|
6951
|
-
const from =
|
|
7333
|
+
const a = obj2(raw);
|
|
7334
|
+
const from = str2(a.from, "from");
|
|
6952
7335
|
const q = await emitNewQuestion(
|
|
6953
7336
|
addQuestion(
|
|
6954
7337
|
{
|
|
6955
7338
|
from,
|
|
6956
7339
|
level: level(a.level),
|
|
6957
|
-
question:
|
|
6958
|
-
reason:
|
|
7340
|
+
question: str2(a.question, "question"),
|
|
7341
|
+
reason: str2(a.reason, "reason"),
|
|
6959
7342
|
urgency: urgency(a.urgency)
|
|
6960
7343
|
},
|
|
6961
7344
|
from,
|
|
@@ -6982,7 +7365,7 @@ function createCoordinationTools(opts) {
|
|
|
6982
7365
|
});
|
|
6983
7366
|
}
|
|
6984
7367
|
stopped = true;
|
|
6985
|
-
const r =
|
|
7368
|
+
const r = obj2(raw).reason;
|
|
6986
7369
|
reason = typeof r === "string" ? r : void 0;
|
|
6987
7370
|
return Promise.resolve({ stopped: true });
|
|
6988
7371
|
}
|
|
@@ -7007,19 +7390,22 @@ function createCoordinationTools(opts) {
|
|
|
7007
7390
|
required: ["kind", "workerId"]
|
|
7008
7391
|
},
|
|
7009
7392
|
handler: async (raw) => {
|
|
7010
|
-
const a =
|
|
7011
|
-
const id =
|
|
7393
|
+
const a = obj2(raw);
|
|
7394
|
+
const id = str2(a.workerId, "workerId");
|
|
7012
7395
|
const node = opts.scope.view.nodes.find((n) => n.id === id);
|
|
7013
7396
|
if (!node) return { error: `unknown workerId ${JSON.stringify(id)}` };
|
|
7014
7397
|
if (!node.outRef)
|
|
7015
7398
|
return { error: `worker ${JSON.stringify(id)} has not settled \u2014 no trace to analyze yet` };
|
|
7016
7399
|
const trace = await opts.blobs.get(node.outRef);
|
|
7017
|
-
return { findings: await opts.analysts?.run(
|
|
7400
|
+
return { findings: await opts.analysts?.run(str2(a.kind, "kind"), trace) };
|
|
7018
7401
|
}
|
|
7019
7402
|
});
|
|
7020
7403
|
}
|
|
7021
7404
|
return {
|
|
7022
7405
|
tools,
|
|
7406
|
+
history: () => bus.history(),
|
|
7407
|
+
raiseFinding: (finding) => bus.publish({ type: "finding", finding }).then(() => void 0),
|
|
7408
|
+
stats: () => bus.stats(),
|
|
7023
7409
|
isStopped: () => stopped,
|
|
7024
7410
|
stopReason: () => reason,
|
|
7025
7411
|
settled: () => ledger,
|
|
@@ -7028,11 +7414,29 @@ function createCoordinationTools(opts) {
|
|
|
7028
7414
|
}
|
|
7029
7415
|
|
|
7030
7416
|
// src/runtime/supervise/coordination-driver.ts
|
|
7417
|
+
var runawayTripwireTurns = 2e3;
|
|
7418
|
+
function poolStarved(scope, perWorker) {
|
|
7419
|
+
const b = scope.budget;
|
|
7420
|
+
if (b.reservedTokens > 0) return false;
|
|
7421
|
+
const tokenStarved = b.tokensLeft < perWorker.maxTokens;
|
|
7422
|
+
const usdStarved = b.usdCapped && b.usdLeft <= 0;
|
|
7423
|
+
return tokenStarved || usdStarved;
|
|
7424
|
+
}
|
|
7425
|
+
function deadlinePassed(scope, now) {
|
|
7426
|
+
const b = scope.budget;
|
|
7427
|
+
return b.deadlineMs > 0 && now() >= b.deadlineMs;
|
|
7428
|
+
}
|
|
7031
7429
|
function coordinationDriverAgent(opts) {
|
|
7032
7430
|
if (typeof opts.chat?.next !== "function") {
|
|
7033
7431
|
throw new ValidationError("coordinationDriverAgent: opts.chat.next must be a function");
|
|
7034
7432
|
}
|
|
7035
|
-
|
|
7433
|
+
if (opts.maxTurns !== void 0 && opts.maxTurns < 0) {
|
|
7434
|
+
throw new ValidationError(
|
|
7435
|
+
"coordinationDriverAgent: maxTurns must be >= 0 (0 lifts the turn cap; bounds become the conserved pool + deadline + abort)"
|
|
7436
|
+
);
|
|
7437
|
+
}
|
|
7438
|
+
const maxTurns = opts.maxTurns === 0 ? runawayTripwireTurns : opts.maxTurns ?? 16;
|
|
7439
|
+
const now = opts.now ?? Date.now;
|
|
7036
7440
|
return {
|
|
7037
7441
|
name: opts.name,
|
|
7038
7442
|
async act(task, scope) {
|
|
@@ -7051,9 +7455,28 @@ function coordinationDriverAgent(opts) {
|
|
|
7051
7455
|
const system = typeof opts.systemPrompt === "function" ? opts.systemPrompt(task) : opts.systemPrompt;
|
|
7052
7456
|
const messages = [{ role: "user", content: stringifyTask(task) }];
|
|
7053
7457
|
for (let turn = 0; turn < maxTurns; turn += 1) {
|
|
7054
|
-
if (coord.isStopped()) break;
|
|
7458
|
+
if (coord.isStopped() || scope.signal.aborted) break;
|
|
7459
|
+
if (poolStarved(scope, opts.perWorker) || deadlinePassed(scope, now)) break;
|
|
7055
7460
|
const res = await opts.chat.next({ system, messages, tools: toolSpecs });
|
|
7056
7461
|
const calls = res.toolCalls ?? [];
|
|
7462
|
+
if (res.usage || res.costUsd !== void 0) {
|
|
7463
|
+
const turnSpend = {
|
|
7464
|
+
// iterations:0 — the conserved iteration channel (`maxIterations`) budgets CHILD rounds,
|
|
7465
|
+
// not driver turns; counting turns there would conflate the two AND make a driver arm's
|
|
7466
|
+
// iteration count diverge from a blind arm's. The driver is bounded by maxTurns + the
|
|
7467
|
+
// token/usd pool; its turn COUNT stays observable via the per-turn `agent.turn` events.
|
|
7468
|
+
iterations: 0,
|
|
7469
|
+
tokens: { input: res.usage?.input ?? 0, output: res.usage?.output ?? 0 },
|
|
7470
|
+
usd: res.costUsd ?? 0,
|
|
7471
|
+
ms: 0
|
|
7472
|
+
};
|
|
7473
|
+
await scope.meter(turnSpend, {
|
|
7474
|
+
kind: "driver-inference",
|
|
7475
|
+
driver: opts.name,
|
|
7476
|
+
turn,
|
|
7477
|
+
toolCalls: calls.map((c) => c.name)
|
|
7478
|
+
});
|
|
7479
|
+
}
|
|
7057
7480
|
if (calls.length === 0) {
|
|
7058
7481
|
return finalize2(coord, opts.blobs);
|
|
7059
7482
|
}
|
|
@@ -7833,15 +8256,15 @@ function randomTaskId() {
|
|
|
7833
8256
|
return `dlg-${t}-${r}`;
|
|
7834
8257
|
}
|
|
7835
8258
|
function hashIdempotencyInput(value) {
|
|
7836
|
-
let
|
|
8259
|
+
let str2;
|
|
7837
8260
|
try {
|
|
7838
|
-
|
|
8261
|
+
str2 = JSON.stringify(canonicalize(value));
|
|
7839
8262
|
} catch {
|
|
7840
|
-
|
|
8263
|
+
str2 = String(value);
|
|
7841
8264
|
}
|
|
7842
8265
|
let h = 2166136261;
|
|
7843
|
-
for (let i = 0; i <
|
|
7844
|
-
h ^=
|
|
8266
|
+
for (let i = 0; i < str2.length; i += 1) {
|
|
8267
|
+
h ^= str2.charCodeAt(i);
|
|
7845
8268
|
h = Math.imul(h, 16777619);
|
|
7846
8269
|
}
|
|
7847
8270
|
return (h >>> 0).toString(16).padStart(8, "0");
|
|
@@ -9215,7 +9638,11 @@ async function serveCoordinationMcp(opts) {
|
|
|
9215
9638
|
scope: opts.scope,
|
|
9216
9639
|
blobs: opts.blobs,
|
|
9217
9640
|
makeWorkerAgent: opts.makeWorkerAgent,
|
|
9218
|
-
perWorker: opts.perWorker
|
|
9641
|
+
perWorker: opts.perWorker,
|
|
9642
|
+
...opts.analysts ? { analysts: opts.analysts } : {},
|
|
9643
|
+
...opts.analyzeOnSettle ? { analyzeOnSettle: opts.analyzeOnSettle } : {},
|
|
9644
|
+
...opts.onEvent ? { onEvent: opts.onEvent } : {},
|
|
9645
|
+
...opts.questionPolicy ? { questionPolicy: opts.questionPolicy } : {}
|
|
9219
9646
|
});
|
|
9220
9647
|
const mcp = createMcpServer({ extraTools: coord.tools, serverName: "coordination" });
|
|
9221
9648
|
const host = opts.host ?? "127.0.0.1";
|
|
@@ -9265,12 +9692,289 @@ async function serveCoordinationMcp(opts) {
|
|
|
9265
9692
|
port,
|
|
9266
9693
|
settled: () => coord.settled(),
|
|
9267
9694
|
isStopped: () => coord.isStopped(),
|
|
9695
|
+
history: () => coord.history(),
|
|
9696
|
+
stats: () => coord.stats(),
|
|
9697
|
+
raiseFinding: (finding) => coord.raiseFinding(finding),
|
|
9268
9698
|
close: () => new Promise((resolve) => {
|
|
9269
9699
|
server.close(() => resolve());
|
|
9270
9700
|
})
|
|
9271
9701
|
};
|
|
9272
9702
|
}
|
|
9273
9703
|
|
|
9704
|
+
// src/runtime/supervise/detector-monitor.ts
|
|
9705
|
+
import {
|
|
9706
|
+
argHash,
|
|
9707
|
+
errorStreakDetector,
|
|
9708
|
+
observeAll,
|
|
9709
|
+
repeatedActionDetector
|
|
9710
|
+
} from "@tangle-network/agent-eval";
|
|
9711
|
+
function defaultToolDetectors() {
|
|
9712
|
+
return [repeatedActionDetector({ maxRepeated: 3 }), errorStreakDetector({ maxErrors: 3 })];
|
|
9713
|
+
}
|
|
9714
|
+
function watchTrace(source, opts = {}) {
|
|
9715
|
+
const detectors = opts.detectors ?? defaultToolDetectors();
|
|
9716
|
+
return source.onSpan((span) => {
|
|
9717
|
+
let fingerprint;
|
|
9718
|
+
try {
|
|
9719
|
+
fingerprint = `${span.toolName}|${argHash(span.args)}`;
|
|
9720
|
+
} catch {
|
|
9721
|
+
fingerprint = `${span.toolName}|<unhashable>`;
|
|
9722
|
+
}
|
|
9723
|
+
const signals = observeAll(detectors, {
|
|
9724
|
+
actionFingerprint: fingerprint,
|
|
9725
|
+
...span.status ? { status: span.status } : {},
|
|
9726
|
+
label: span.toolName
|
|
9727
|
+
});
|
|
9728
|
+
for (const s of signals) void opts.onSignal?.(s, span);
|
|
9729
|
+
});
|
|
9730
|
+
}
|
|
9731
|
+
|
|
9732
|
+
// src/runtime/supervise/router-driver-chat.ts
|
|
9733
|
+
function routerDriverChat(c, opts = {}) {
|
|
9734
|
+
const temperature = opts.temperature ?? 0.4;
|
|
9735
|
+
return {
|
|
9736
|
+
next: async ({ system, messages, tools }) => {
|
|
9737
|
+
const oa = [
|
|
9738
|
+
{ role: "system", content: system },
|
|
9739
|
+
...messages.map(toOpenAI)
|
|
9740
|
+
];
|
|
9741
|
+
const oaTools = tools.map((t) => ({
|
|
9742
|
+
type: "function",
|
|
9743
|
+
function: { name: t.name, description: t.description, parameters: t.parameters }
|
|
9744
|
+
}));
|
|
9745
|
+
const r = await routerChatWithTools(c, oa, oaTools, { temperature, toolChoice: "auto" });
|
|
9746
|
+
return {
|
|
9747
|
+
...r.content ? { content: r.content } : {},
|
|
9748
|
+
// Forward the router's REAL usage + cost so the driver meters its own inference against the
|
|
9749
|
+
// conserved pool (the integrity hole: these were dropped on the floor before).
|
|
9750
|
+
...r.usage ? { usage: r.usage } : {},
|
|
9751
|
+
...typeof r.costUsd === "number" ? { costUsd: r.costUsd } : {},
|
|
9752
|
+
toolCalls: r.toolCalls.map((tc) => ({
|
|
9753
|
+
id: tc.id,
|
|
9754
|
+
name: tc.name,
|
|
9755
|
+
arguments: safeParse(tc.arguments)
|
|
9756
|
+
}))
|
|
9757
|
+
};
|
|
9758
|
+
}
|
|
9759
|
+
};
|
|
9760
|
+
}
|
|
9761
|
+
function toOpenAI(m) {
|
|
9762
|
+
if (m.role === "assistant" && m.toolCalls?.length) {
|
|
9763
|
+
return {
|
|
9764
|
+
role: "assistant",
|
|
9765
|
+
content: m.content ?? "",
|
|
9766
|
+
tool_calls: m.toolCalls.map((tc) => ({
|
|
9767
|
+
id: tc.id ?? tc.name,
|
|
9768
|
+
type: "function",
|
|
9769
|
+
function: { name: tc.name, arguments: JSON.stringify(tc.arguments) }
|
|
9770
|
+
}))
|
|
9771
|
+
};
|
|
9772
|
+
}
|
|
9773
|
+
if (m.role === "tool") {
|
|
9774
|
+
return { role: "tool", tool_call_id: m.toolCallId ?? m.name ?? "call", content: m.content };
|
|
9775
|
+
}
|
|
9776
|
+
return { role: m.role, content: m.content };
|
|
9777
|
+
}
|
|
9778
|
+
function safeParse(s) {
|
|
9779
|
+
try {
|
|
9780
|
+
return JSON.parse(s);
|
|
9781
|
+
} catch {
|
|
9782
|
+
return {};
|
|
9783
|
+
}
|
|
9784
|
+
}
|
|
9785
|
+
|
|
9786
|
+
// src/runtime/supervise/trace-source.ts
|
|
9787
|
+
function toToolSpan(input, runId, seq, at) {
|
|
9788
|
+
return {
|
|
9789
|
+
spanId: `${runId}-t${seq}`,
|
|
9790
|
+
runId,
|
|
9791
|
+
kind: "tool",
|
|
9792
|
+
name: input.toolName,
|
|
9793
|
+
toolName: input.toolName,
|
|
9794
|
+
args: input.args,
|
|
9795
|
+
status: input.status ?? "ok",
|
|
9796
|
+
startedAt: at,
|
|
9797
|
+
endedAt: at,
|
|
9798
|
+
...input.result !== void 0 ? { result: input.result } : {}
|
|
9799
|
+
};
|
|
9800
|
+
}
|
|
9801
|
+
var obj = (v) => v && typeof v === "object" ? v : void 0;
|
|
9802
|
+
var str = (v) => typeof v === "string" && v ? v : void 0;
|
|
9803
|
+
var decodeOpencodePart = (p) => {
|
|
9804
|
+
if (str(p.type)?.toLowerCase() !== "tool" || !str(p.tool)) return void 0;
|
|
9805
|
+
const state = obj(p.state);
|
|
9806
|
+
const status = str(state?.status) ?? "";
|
|
9807
|
+
if (status === "pending" || status === "running") return void 0;
|
|
9808
|
+
return {
|
|
9809
|
+
toolName: p.tool,
|
|
9810
|
+
args: state?.input ?? {},
|
|
9811
|
+
...status === "error" ? { status: "error" } : status ? { status: "ok" } : {},
|
|
9812
|
+
...str(p.callID) ? { callId: p.callID } : {}
|
|
9813
|
+
};
|
|
9814
|
+
};
|
|
9815
|
+
var decodeAnthropicPart = (p) => {
|
|
9816
|
+
if (str(p.type)?.toLowerCase() !== "tool_use") return void 0;
|
|
9817
|
+
const name = str(p.name) ?? str(p.tool);
|
|
9818
|
+
if (!name) return void 0;
|
|
9819
|
+
const id = str(p.id) ?? str(p.tool_use_id);
|
|
9820
|
+
return {
|
|
9821
|
+
toolName: name,
|
|
9822
|
+
args: p.input ?? {},
|
|
9823
|
+
...id ? { callId: id } : {}
|
|
9824
|
+
};
|
|
9825
|
+
};
|
|
9826
|
+
var decodeOpenAiPart = (p) => {
|
|
9827
|
+
const type = str(p.type)?.toLowerCase();
|
|
9828
|
+
const fn = obj(p.function);
|
|
9829
|
+
if (type !== "function" && type !== "tool_call") return void 0;
|
|
9830
|
+
const name = str(fn?.name) ?? str(p.name);
|
|
9831
|
+
if (!name) return void 0;
|
|
9832
|
+
const rawArgs = fn?.arguments ?? p.arguments;
|
|
9833
|
+
return {
|
|
9834
|
+
toolName: name,
|
|
9835
|
+
args: typeof rawArgs === "string" ? safeParse2(rawArgs) : rawArgs ?? {},
|
|
9836
|
+
...str(p.id) ? { callId: p.id } : str(fn?.id) ? { callId: fn?.id } : {}
|
|
9837
|
+
};
|
|
9838
|
+
};
|
|
9839
|
+
var toolPartDecoders = {
|
|
9840
|
+
opencode: decodeOpencodePart,
|
|
9841
|
+
"claude-code": decodeAnthropicPart,
|
|
9842
|
+
anthropic: decodeAnthropicPart,
|
|
9843
|
+
codex: decodeOpenAiPart,
|
|
9844
|
+
openai: decodeOpenAiPart,
|
|
9845
|
+
router: decodeOpenAiPart,
|
|
9846
|
+
kimi: decodeOpenAiPart
|
|
9847
|
+
};
|
|
9848
|
+
function decodeToolPart(part, harness) {
|
|
9849
|
+
const p = obj(part);
|
|
9850
|
+
if (!p) return void 0;
|
|
9851
|
+
const specific = harness ? toolPartDecoders[harness] : void 0;
|
|
9852
|
+
if (specific) return specific(p);
|
|
9853
|
+
for (const decode of new Set(Object.values(toolPartDecoders))) {
|
|
9854
|
+
const step = decode(p);
|
|
9855
|
+
if (step) return step;
|
|
9856
|
+
}
|
|
9857
|
+
return void 0;
|
|
9858
|
+
}
|
|
9859
|
+
function safeParse2(s) {
|
|
9860
|
+
try {
|
|
9861
|
+
return JSON.parse(s);
|
|
9862
|
+
} catch {
|
|
9863
|
+
return s;
|
|
9864
|
+
}
|
|
9865
|
+
}
|
|
9866
|
+
var runSeq = 0;
|
|
9867
|
+
function createPushTraceSource(opts = {}) {
|
|
9868
|
+
const runId = opts.runId ?? `push-${runSeq++}`;
|
|
9869
|
+
const now = opts.now ?? Date.now;
|
|
9870
|
+
const spans = [];
|
|
9871
|
+
const subs = /* @__PURE__ */ new Set();
|
|
9872
|
+
return {
|
|
9873
|
+
record(input) {
|
|
9874
|
+
const span = toToolSpan(input, runId, spans.length, now());
|
|
9875
|
+
spans.push(span);
|
|
9876
|
+
for (const fn of subs) {
|
|
9877
|
+
try {
|
|
9878
|
+
fn(span);
|
|
9879
|
+
} catch {
|
|
9880
|
+
}
|
|
9881
|
+
}
|
|
9882
|
+
return span;
|
|
9883
|
+
},
|
|
9884
|
+
source: {
|
|
9885
|
+
onSpan(handler) {
|
|
9886
|
+
subs.add(handler);
|
|
9887
|
+
return () => subs.delete(handler);
|
|
9888
|
+
},
|
|
9889
|
+
collect: () => Promise.resolve([...spans])
|
|
9890
|
+
}
|
|
9891
|
+
};
|
|
9892
|
+
}
|
|
9893
|
+
function createPartsTraceSource(opts) {
|
|
9894
|
+
const runId = opts.runId ?? `parts-${runSeq++}`;
|
|
9895
|
+
const now = opts.now ?? Date.now;
|
|
9896
|
+
const subs = /* @__PURE__ */ new Set();
|
|
9897
|
+
const seenLive = /* @__PURE__ */ new Set();
|
|
9898
|
+
let liveSeq = 0;
|
|
9899
|
+
let unsub;
|
|
9900
|
+
const startLive = () => {
|
|
9901
|
+
if (unsub || !opts.subscribeParts) return;
|
|
9902
|
+
unsub = opts.subscribeParts((part) => {
|
|
9903
|
+
const step = decodeToolPart(part, opts.harness);
|
|
9904
|
+
if (!step) return;
|
|
9905
|
+
if (step.callId) {
|
|
9906
|
+
if (seenLive.has(step.callId)) return;
|
|
9907
|
+
seenLive.add(step.callId);
|
|
9908
|
+
}
|
|
9909
|
+
const span = toToolSpan(step, runId, liveSeq++, now());
|
|
9910
|
+
for (const fn of subs) {
|
|
9911
|
+
try {
|
|
9912
|
+
fn(span);
|
|
9913
|
+
} catch {
|
|
9914
|
+
}
|
|
9915
|
+
}
|
|
9916
|
+
});
|
|
9917
|
+
};
|
|
9918
|
+
return {
|
|
9919
|
+
onSpan(handler) {
|
|
9920
|
+
subs.add(handler);
|
|
9921
|
+
startLive();
|
|
9922
|
+
return () => {
|
|
9923
|
+
subs.delete(handler);
|
|
9924
|
+
if (subs.size === 0 && unsub) {
|
|
9925
|
+
unsub();
|
|
9926
|
+
unsub = void 0;
|
|
9927
|
+
}
|
|
9928
|
+
};
|
|
9929
|
+
},
|
|
9930
|
+
async collect() {
|
|
9931
|
+
const parts = await opts.collectParts();
|
|
9932
|
+
const spans = [];
|
|
9933
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9934
|
+
for (const part of parts) {
|
|
9935
|
+
const step = decodeToolPart(part, opts.harness);
|
|
9936
|
+
if (!step) continue;
|
|
9937
|
+
if (step.callId) {
|
|
9938
|
+
if (seen.has(step.callId)) continue;
|
|
9939
|
+
seen.add(step.callId);
|
|
9940
|
+
}
|
|
9941
|
+
spans.push(toToolSpan(step, runId, spans.length, now()));
|
|
9942
|
+
}
|
|
9943
|
+
return spans;
|
|
9944
|
+
}
|
|
9945
|
+
};
|
|
9946
|
+
}
|
|
9947
|
+
function sandboxSessionTraceSource(box, sessionId, opts = {}) {
|
|
9948
|
+
return createPartsTraceSource({
|
|
9949
|
+
collectParts: async () => {
|
|
9950
|
+
const msgs = await box.messages({ sessionId });
|
|
9951
|
+
return msgs.flatMap((m) => m.parts ? [...m.parts] : []);
|
|
9952
|
+
},
|
|
9953
|
+
...opts.harness ? { harness: opts.harness } : {},
|
|
9954
|
+
...opts.subscribeParts ? { subscribeParts: opts.subscribeParts } : {},
|
|
9955
|
+
runId: opts.runId ?? `box-${sessionId}`,
|
|
9956
|
+
...opts.now ? { now: opts.now } : {}
|
|
9957
|
+
});
|
|
9958
|
+
}
|
|
9959
|
+
|
|
9960
|
+
// src/runtime/supervise/trajectory-recorder.ts
|
|
9961
|
+
import { buildTrajectory, InMemoryTraceStore } from "@tangle-network/agent-eval";
|
|
9962
|
+
import { stuckLoopView, toolWasteView } from "@tangle-network/agent-eval/pipelines";
|
|
9963
|
+
async function analyzeTrace(source, runId = "worker") {
|
|
9964
|
+
const spans = await source.collect();
|
|
9965
|
+
const store = new InMemoryTraceStore();
|
|
9966
|
+
for (let i = 0; i < spans.length; i += 1) {
|
|
9967
|
+
const s = spans[i];
|
|
9968
|
+
if (s) await store.appendSpan({ ...s, runId, spanId: `${runId}-t${i}` });
|
|
9969
|
+
}
|
|
9970
|
+
const [trajectory, stuckLoop, toolWaste] = await Promise.all([
|
|
9971
|
+
buildTrajectory(store, runId),
|
|
9972
|
+
stuckLoopView(store, { runId }),
|
|
9973
|
+
toolWasteView(store, { runId })
|
|
9974
|
+
]);
|
|
9975
|
+
return { trajectory, stuckLoop, toolWaste };
|
|
9976
|
+
}
|
|
9977
|
+
|
|
9274
9978
|
// src/runtime/verifier-environment.ts
|
|
9275
9979
|
var submitTool = {
|
|
9276
9980
|
type: "function",
|
|
@@ -9618,9 +10322,11 @@ export {
|
|
|
9618
10322
|
routerChatWithUsage,
|
|
9619
10323
|
routerChatWithTools,
|
|
9620
10324
|
routerToolLoop,
|
|
10325
|
+
createInbox,
|
|
9621
10326
|
createWorktree,
|
|
9622
10327
|
captureWorktreeDiff,
|
|
9623
10328
|
removeWorktree,
|
|
10329
|
+
runWorktreeHarness,
|
|
9624
10330
|
createWorktreeCliExecutor,
|
|
9625
10331
|
cliWorktreeExecutor,
|
|
9626
10332
|
createExecutor,
|
|
@@ -9661,6 +10367,7 @@ export {
|
|
|
9661
10367
|
authoredWorker,
|
|
9662
10368
|
coderDeliverable,
|
|
9663
10369
|
worktreeCoderFanout,
|
|
10370
|
+
createEventBus,
|
|
9664
10371
|
createCoordinationTools,
|
|
9665
10372
|
coordinationDriverAgent,
|
|
9666
10373
|
InMemoryFeedbackStore,
|
|
@@ -9715,6 +10422,19 @@ export {
|
|
|
9715
10422
|
createMcpServer,
|
|
9716
10423
|
createInProcessTransport,
|
|
9717
10424
|
serveCoordinationMcp,
|
|
10425
|
+
defaultToolDetectors,
|
|
10426
|
+
watchTrace,
|
|
10427
|
+
routerDriverChat,
|
|
10428
|
+
toToolSpan,
|
|
10429
|
+
decodeOpencodePart,
|
|
10430
|
+
decodeAnthropicPart,
|
|
10431
|
+
decodeOpenAiPart,
|
|
10432
|
+
toolPartDecoders,
|
|
10433
|
+
decodeToolPart,
|
|
10434
|
+
createPushTraceSource,
|
|
10435
|
+
createPartsTraceSource,
|
|
10436
|
+
sandboxSessionTraceSource,
|
|
10437
|
+
analyzeTrace,
|
|
9718
10438
|
createVerifierEnvironment,
|
|
9719
10439
|
createWaterfallCollector,
|
|
9720
10440
|
localShell,
|
|
@@ -9722,4 +10442,4 @@ export {
|
|
|
9722
10442
|
jjWorkspace,
|
|
9723
10443
|
runInWorkspace
|
|
9724
10444
|
};
|
|
9725
|
-
//# sourceMappingURL=chunk-
|
|
10445
|
+
//# sourceMappingURL=chunk-MDFZSPHA.js.map
|