@tangle-network/agent-runtime 0.102.2 → 0.103.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -10
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +5 -5
- package/dist/analyst-loop.d.ts +1 -1
- package/dist/candidate-execution/index.d.ts +5 -41
- package/dist/candidate-execution/index.js +8 -6
- package/dist/{chunk-QCXD7NHM.js → chunk-3LJF5XSE.js} +79 -334
- package/dist/chunk-3LJF5XSE.js.map +1 -0
- package/dist/{chunk-3DW5DEKQ.js → chunk-5AITUUHO.js} +3 -3
- package/dist/{chunk-SQ3ZJH3Y.js → chunk-6WZZXQV5.js} +467 -23
- package/dist/chunk-6WZZXQV5.js.map +1 -0
- package/dist/{chunk-SCOHHMKU.js → chunk-AAN2MB2X.js} +3 -3
- package/dist/{chunk-KSM6GFHP.js → chunk-B7K7V22Y.js} +2 -2
- package/dist/{chunk-ZUCUAFVF.js → chunk-EAQ5YRRY.js} +1382 -62
- package/dist/chunk-EAQ5YRRY.js.map +1 -0
- package/dist/chunk-ISPWRSEC.js +408 -0
- package/dist/chunk-ISPWRSEC.js.map +1 -0
- package/dist/{chunk-7FJJUC73.js → chunk-LFM4JBRW.js} +29 -6
- package/dist/{chunk-7FJJUC73.js.map → chunk-LFM4JBRW.js.map} +1 -1
- package/dist/{chunk-ROILMKDL.js → chunk-QYCKIV6C.js} +3 -3
- package/dist/{chunk-LJLG734E.js → chunk-SBTWKPVR.js} +10 -11
- package/dist/{chunk-LJLG734E.js.map → chunk-SBTWKPVR.js.map} +1 -1
- package/dist/{chunk-CK76OZPM.js → chunk-SNSMRT6H.js} +56 -386
- package/dist/chunk-SNSMRT6H.js.map +1 -0
- package/dist/{chunk-EKUMRQNR.js → chunk-TUSOOIDV.js} +2 -2
- package/dist/{chunk-J6V6DWWS.js → chunk-ZOYN3JR5.js} +5 -5
- package/dist/{chunk-377GOJHJ.js → chunk-ZXICDSAK.js} +333 -4
- package/dist/chunk-ZXICDSAK.js.map +1 -0
- package/dist/{completion-gate-C6dhVN-N.d.ts → completion-gate-DLINnrkM.d.ts} +1 -1
- package/dist/{coordination-D9l_YmwW.d.ts → coordination-DTehA977.d.ts} +250 -8
- package/dist/environment-provider.d.ts +2 -2
- package/dist/index.d.ts +12 -12
- package/dist/index.js +17 -15
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +26 -8
- package/dist/intelligence.js +24 -7
- package/dist/intelligence.js.map +1 -1
- package/dist/knowledge.d.ts +6 -6
- package/dist/knowledge.js +9 -9
- package/dist/{loop-runner-bin-PnWslIw4.d.ts → loop-runner-bin-DhIXsLAd.d.ts} +2 -2
- package/dist/loop-runner-bin.d.ts +5 -5
- package/dist/loop-runner-bin.js +6 -6
- package/dist/loops.d.ts +305 -148
- package/dist/loops.js +82 -14
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +6 -6
- package/dist/mcp/index.js +6 -6
- package/dist/profiles.d.ts +1 -1
- package/dist/{profile-DbfaMTdk.d.ts → protected-model-port-DzkUY3HQ.d.ts} +41 -3
- package/dist/{structural-rollout-D7xik9XR.d.ts → structural-rollout-BFPIy3iw.d.ts} +3 -3
- package/dist/supervise-Cp8bNcJi.d.ts +379 -0
- package/dist/testing.js +10 -10
- package/dist/testing.js.map +1 -1
- package/dist/{types-CWqfCO8s.d.ts → types-Cbp72qyu.d.ts} +1 -1
- package/dist/{types-CLat4bT9.d.ts → types-DK11_O4L.d.ts} +1 -1
- package/dist/types-lZTE_LBd.d.ts +957 -0
- package/dist/{worktree-fanout-DsIR4d6w.d.ts → worktree-fanout-DiiJWjJO.d.ts} +17 -5
- package/package.json +5 -4
- package/skills/build-with-agent-runtime/SKILL.md +7 -5
- package/skills/generate-eval/SKILL.md +45 -52
- package/skills/loop-writer/SKILL.md +79 -147
- package/skills/supervise/SKILL.md +33 -14
- package/dist/chunk-377GOJHJ.js.map +0 -1
- package/dist/chunk-CK76OZPM.js.map +0 -1
- package/dist/chunk-IMSNJSXH.js +0 -73
- package/dist/chunk-IMSNJSXH.js.map +0 -1
- package/dist/chunk-QCXD7NHM.js.map +0 -1
- package/dist/chunk-SQ3ZJH3Y.js.map +0 -1
- package/dist/chunk-ZUCUAFVF.js.map +0 -1
- package/dist/supervise-BQdNAXtK.d.ts +0 -131
- package/dist/types-D870JlH0.d.ts +0 -500
- /package/dist/{chunk-3DW5DEKQ.js.map → chunk-5AITUUHO.js.map} +0 -0
- /package/dist/{chunk-SCOHHMKU.js.map → chunk-AAN2MB2X.js.map} +0 -0
- /package/dist/{chunk-KSM6GFHP.js.map → chunk-B7K7V22Y.js.map} +0 -0
- /package/dist/{chunk-ROILMKDL.js.map → chunk-QYCKIV6C.js.map} +0 -0
- /package/dist/{chunk-EKUMRQNR.js.map → chunk-TUSOOIDV.js.map} +0 -0
- /package/dist/{chunk-J6V6DWWS.js.map → chunk-ZOYN3JR5.js.map} +0 -0
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
assertModelAllowed,
|
|
9
9
|
captureWorktreeDiff,
|
|
10
|
+
createActivityLog,
|
|
10
11
|
createSupervisor,
|
|
11
12
|
createWorktree,
|
|
12
13
|
notifyRuntimeHookEvent,
|
|
@@ -17,12 +18,14 @@ import {
|
|
|
17
18
|
runWorktreeChecks,
|
|
18
19
|
runWorktreeHarness,
|
|
19
20
|
withDriverExecutor
|
|
20
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-6WZZXQV5.js";
|
|
21
22
|
import {
|
|
23
|
+
FileResultBlobStore,
|
|
24
|
+
FileSpawnJournal,
|
|
22
25
|
InMemoryResultBlobStore,
|
|
23
26
|
InMemorySpawnJournal,
|
|
24
27
|
contentAddress
|
|
25
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-ISPWRSEC.js";
|
|
26
29
|
import {
|
|
27
30
|
providerAsExecutor,
|
|
28
31
|
resolveAgentEnvironmentProvider
|
|
@@ -1037,15 +1040,15 @@ function copyPlainSpine(value, seen) {
|
|
|
1037
1040
|
return copy;
|
|
1038
1041
|
}
|
|
1039
1042
|
function hashJson(value) {
|
|
1040
|
-
let
|
|
1043
|
+
let str2;
|
|
1041
1044
|
try {
|
|
1042
|
-
|
|
1045
|
+
str2 = JSON.stringify(value) ?? String(value);
|
|
1043
1046
|
} catch {
|
|
1044
|
-
|
|
1047
|
+
str2 = String(value);
|
|
1045
1048
|
}
|
|
1046
1049
|
let h = 2166136261;
|
|
1047
|
-
for (let i = 0; i <
|
|
1048
|
-
h ^=
|
|
1050
|
+
for (let i = 0; i < str2.length; i += 1) {
|
|
1051
|
+
h ^= str2.charCodeAt(i);
|
|
1049
1052
|
h = Math.imul(h, 16777619);
|
|
1050
1053
|
}
|
|
1051
1054
|
return (h >>> 0).toString(16).padStart(8, "0");
|
|
@@ -1096,6 +1099,735 @@ ${lines.join("\n")}`;
|
|
|
1096
1099
|
};
|
|
1097
1100
|
}
|
|
1098
1101
|
|
|
1102
|
+
// src/runtime/supervise/trace-source.ts
|
|
1103
|
+
function toToolSpan(input, runId, seq, at) {
|
|
1104
|
+
const startedAt = typeof input.startedAt === "number" ? input.startedAt : at;
|
|
1105
|
+
const endedAt = typeof input.endedAt === "number" ? input.endedAt : startedAt;
|
|
1106
|
+
return {
|
|
1107
|
+
spanId: `${runId}-t${seq}`,
|
|
1108
|
+
runId,
|
|
1109
|
+
kind: "tool",
|
|
1110
|
+
name: input.toolName,
|
|
1111
|
+
toolName: input.toolName,
|
|
1112
|
+
args: input.args,
|
|
1113
|
+
status: input.status ?? "ok",
|
|
1114
|
+
startedAt,
|
|
1115
|
+
endedAt,
|
|
1116
|
+
...input.result !== void 0 ? { result: input.result } : {}
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
var obj = (v) => v && typeof v === "object" ? v : void 0;
|
|
1120
|
+
var str = (v) => typeof v === "string" && v ? v : void 0;
|
|
1121
|
+
var decodeOpencodePart = (raw) => {
|
|
1122
|
+
if (str(raw.type)?.toLowerCase() !== "tool" || !str(raw.tool)) return void 0;
|
|
1123
|
+
const part = raw;
|
|
1124
|
+
const state = obj(part.state);
|
|
1125
|
+
const status = state?.status;
|
|
1126
|
+
if (!status || status === "pending" || status === "running") return void 0;
|
|
1127
|
+
return {
|
|
1128
|
+
toolName: part.tool,
|
|
1129
|
+
args: state?.input ?? {},
|
|
1130
|
+
// ToolStateError carries 'error' OR 'failed' — the reverse-engineered decoder missed 'failed'.
|
|
1131
|
+
status: status === "error" || status === "failed" ? "error" : "ok",
|
|
1132
|
+
...str(part.callID) ? { callId: part.callID } : {}
|
|
1133
|
+
};
|
|
1134
|
+
};
|
|
1135
|
+
var decodeAnthropicPart = (p) => {
|
|
1136
|
+
if (str(p.type)?.toLowerCase() !== "tool_use") return void 0;
|
|
1137
|
+
const name = str(p.name) ?? str(p.tool);
|
|
1138
|
+
if (!name) return void 0;
|
|
1139
|
+
const id = str(p.id) ?? str(p.tool_use_id);
|
|
1140
|
+
return {
|
|
1141
|
+
toolName: name,
|
|
1142
|
+
args: p.input ?? {},
|
|
1143
|
+
...id ? { callId: id } : {}
|
|
1144
|
+
};
|
|
1145
|
+
};
|
|
1146
|
+
var decodeOpenAiPart = (p) => {
|
|
1147
|
+
const type = str(p.type)?.toLowerCase();
|
|
1148
|
+
const fn = obj(p.function);
|
|
1149
|
+
if (type !== "function" && type !== "tool_call") return void 0;
|
|
1150
|
+
const name = str(fn?.name) ?? str(p.name);
|
|
1151
|
+
if (!name) return void 0;
|
|
1152
|
+
const rawArgs = fn?.arguments ?? p.arguments;
|
|
1153
|
+
return {
|
|
1154
|
+
toolName: name,
|
|
1155
|
+
args: typeof rawArgs === "string" ? safeParse(rawArgs) : rawArgs ?? {},
|
|
1156
|
+
...str(p.id) ? { callId: p.id } : str(fn?.id) ? { callId: fn?.id } : {}
|
|
1157
|
+
};
|
|
1158
|
+
};
|
|
1159
|
+
var toolPartDecoders = {
|
|
1160
|
+
opencode: decodeOpencodePart,
|
|
1161
|
+
"claude-code": decodeAnthropicPart,
|
|
1162
|
+
anthropic: decodeAnthropicPart,
|
|
1163
|
+
codex: decodeOpenAiPart,
|
|
1164
|
+
openai: decodeOpenAiPart,
|
|
1165
|
+
router: decodeOpenAiPart,
|
|
1166
|
+
kimi: decodeOpenAiPart
|
|
1167
|
+
};
|
|
1168
|
+
function decodeToolPart(part, harness) {
|
|
1169
|
+
const p = obj(part);
|
|
1170
|
+
if (!p) return void 0;
|
|
1171
|
+
const specific = harness ? toolPartDecoders[harness] : void 0;
|
|
1172
|
+
if (specific) return specific(p);
|
|
1173
|
+
for (const decode of new Set(Object.values(toolPartDecoders))) {
|
|
1174
|
+
const step = decode(p);
|
|
1175
|
+
if (step) return step;
|
|
1176
|
+
}
|
|
1177
|
+
return void 0;
|
|
1178
|
+
}
|
|
1179
|
+
function safeParse(s) {
|
|
1180
|
+
try {
|
|
1181
|
+
return JSON.parse(s);
|
|
1182
|
+
} catch {
|
|
1183
|
+
return s;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
var runSeq = 0;
|
|
1187
|
+
function createPushTraceSource(opts = {}) {
|
|
1188
|
+
const runId = opts.runId ?? `push-${runSeq++}`;
|
|
1189
|
+
const now = opts.now ?? Date.now;
|
|
1190
|
+
const spans = [];
|
|
1191
|
+
const subs = /* @__PURE__ */ new Set();
|
|
1192
|
+
return {
|
|
1193
|
+
record(input) {
|
|
1194
|
+
const span = toToolSpan(input, runId, spans.length, now());
|
|
1195
|
+
spans.push(span);
|
|
1196
|
+
for (const fn of subs) {
|
|
1197
|
+
try {
|
|
1198
|
+
fn(span);
|
|
1199
|
+
} catch {
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
return span;
|
|
1203
|
+
},
|
|
1204
|
+
source: {
|
|
1205
|
+
onSpan(handler) {
|
|
1206
|
+
subs.add(handler);
|
|
1207
|
+
return () => subs.delete(handler);
|
|
1208
|
+
},
|
|
1209
|
+
collect: () => Promise.resolve([...spans])
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
function createPartsTraceSource(opts) {
|
|
1214
|
+
const runId = opts.runId ?? `parts-${runSeq++}`;
|
|
1215
|
+
const now = opts.now ?? Date.now;
|
|
1216
|
+
const subs = /* @__PURE__ */ new Set();
|
|
1217
|
+
const seenLive = /* @__PURE__ */ new Set();
|
|
1218
|
+
let liveSeq = 0;
|
|
1219
|
+
let unsub;
|
|
1220
|
+
const startLive = () => {
|
|
1221
|
+
if (unsub || !opts.subscribeParts) return;
|
|
1222
|
+
unsub = opts.subscribeParts((part) => {
|
|
1223
|
+
const step = decodeToolPart(part, opts.harness);
|
|
1224
|
+
if (!step) return;
|
|
1225
|
+
if (step.callId) {
|
|
1226
|
+
if (seenLive.has(step.callId)) return;
|
|
1227
|
+
seenLive.add(step.callId);
|
|
1228
|
+
}
|
|
1229
|
+
const span = toToolSpan(step, runId, liveSeq++, now());
|
|
1230
|
+
for (const fn of subs) {
|
|
1231
|
+
try {
|
|
1232
|
+
fn(span);
|
|
1233
|
+
} catch {
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
};
|
|
1238
|
+
return {
|
|
1239
|
+
onSpan(handler) {
|
|
1240
|
+
subs.add(handler);
|
|
1241
|
+
startLive();
|
|
1242
|
+
return () => {
|
|
1243
|
+
subs.delete(handler);
|
|
1244
|
+
if (subs.size === 0 && unsub) {
|
|
1245
|
+
unsub();
|
|
1246
|
+
unsub = void 0;
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
},
|
|
1250
|
+
async collect() {
|
|
1251
|
+
const parts = await opts.collectParts();
|
|
1252
|
+
const spans = [];
|
|
1253
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1254
|
+
for (const part of parts) {
|
|
1255
|
+
const step = decodeToolPart(part, opts.harness);
|
|
1256
|
+
if (!step) continue;
|
|
1257
|
+
if (step.callId) {
|
|
1258
|
+
if (seen.has(step.callId)) continue;
|
|
1259
|
+
seen.add(step.callId);
|
|
1260
|
+
}
|
|
1261
|
+
spans.push(toToolSpan(step, runId, spans.length, now()));
|
|
1262
|
+
}
|
|
1263
|
+
return spans;
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
function sandboxSessionTraceSource(box, sessionId, opts = {}) {
|
|
1268
|
+
return createPartsTraceSource({
|
|
1269
|
+
collectParts: async () => {
|
|
1270
|
+
const msgs = await box.messages({ sessionId });
|
|
1271
|
+
return msgs.flatMap((m) => m.parts ? [...m.parts] : []);
|
|
1272
|
+
},
|
|
1273
|
+
...opts.harness ? { harness: opts.harness } : {},
|
|
1274
|
+
...opts.subscribeParts ? { subscribeParts: opts.subscribeParts } : {},
|
|
1275
|
+
runId: opts.runId ?? `box-${sessionId}`,
|
|
1276
|
+
...opts.now ? { now: opts.now } : {}
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// src/runtime/supervise/pi-executor.ts
|
|
1281
|
+
import { spawn } from "child_process";
|
|
1282
|
+
var PI_RUNTIME = "pi";
|
|
1283
|
+
var piSeamKey = "pi";
|
|
1284
|
+
var piExecutor = (spec, ctx) => {
|
|
1285
|
+
const seam = readPiSeam(ctx);
|
|
1286
|
+
const inbox = createInbox();
|
|
1287
|
+
const activity = createActivityLog(seam.activityWindow ?? 12);
|
|
1288
|
+
const trace = createPushTraceSource({
|
|
1289
|
+
runId: `pi-${spec.profile.name ?? "worker"}-${Date.now()}`
|
|
1290
|
+
});
|
|
1291
|
+
const controller = new AbortController();
|
|
1292
|
+
const cascade = () => controller.abort();
|
|
1293
|
+
if (ctx.signal.aborted) controller.abort();
|
|
1294
|
+
else ctx.signal.addEventListener("abort", cascade, { once: true });
|
|
1295
|
+
const state = {
|
|
1296
|
+
turns: 0,
|
|
1297
|
+
lastText: "",
|
|
1298
|
+
note: "starting",
|
|
1299
|
+
proc: void 0,
|
|
1300
|
+
artifact: void 0
|
|
1301
|
+
};
|
|
1302
|
+
return {
|
|
1303
|
+
runtime: PI_RUNTIME,
|
|
1304
|
+
// pi owns the queue; `deliver` only routes. A forceful message becomes pi's `steer` (which
|
|
1305
|
+
// pi delivers after the current tool batch and before the next model call); a queued one
|
|
1306
|
+
// becomes `follow_up` (after the turn finishes). Never throws — the inbox contract.
|
|
1307
|
+
deliver: (m) => inbox.deliver(m),
|
|
1308
|
+
progress: () => ({
|
|
1309
|
+
turns: state.turns,
|
|
1310
|
+
pendingMessages: inbox.pending(),
|
|
1311
|
+
recentActivity: activity.read(),
|
|
1312
|
+
note: state.note
|
|
1313
|
+
}),
|
|
1314
|
+
traceSource: () => trace.source,
|
|
1315
|
+
execute(task, signal) {
|
|
1316
|
+
return streamPiSession({
|
|
1317
|
+
task,
|
|
1318
|
+
signal,
|
|
1319
|
+
controller,
|
|
1320
|
+
seam,
|
|
1321
|
+
spec,
|
|
1322
|
+
inbox,
|
|
1323
|
+
activity,
|
|
1324
|
+
record: (step) => {
|
|
1325
|
+
trace.record(step);
|
|
1326
|
+
},
|
|
1327
|
+
state
|
|
1328
|
+
});
|
|
1329
|
+
},
|
|
1330
|
+
async teardown(grace) {
|
|
1331
|
+
controller.abort();
|
|
1332
|
+
const proc = state.proc;
|
|
1333
|
+
if (!proc || proc.exitCode !== null || proc.killed) return { destroyed: true };
|
|
1334
|
+
sendCommand(proc, { type: "abort" });
|
|
1335
|
+
return killPi(proc, grace);
|
|
1336
|
+
},
|
|
1337
|
+
resultArtifact() {
|
|
1338
|
+
if (!state.artifact) {
|
|
1339
|
+
throw new ValidationError("piExecutor: resultArtifact() read before stream drained");
|
|
1340
|
+
}
|
|
1341
|
+
return state.artifact;
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
};
|
|
1345
|
+
async function* streamPiSession(args) {
|
|
1346
|
+
const { seam, inbox, activity, state } = args;
|
|
1347
|
+
const started = Date.now();
|
|
1348
|
+
const tokens = { input: 0, output: 0 };
|
|
1349
|
+
let usd = 0;
|
|
1350
|
+
const proc = spawnPi(seam);
|
|
1351
|
+
state.proc = proc;
|
|
1352
|
+
state.note = "connected";
|
|
1353
|
+
const events = [];
|
|
1354
|
+
let idle = false;
|
|
1355
|
+
let failure;
|
|
1356
|
+
let wake;
|
|
1357
|
+
const notify = () => {
|
|
1358
|
+
const w = wake;
|
|
1359
|
+
wake = void 0;
|
|
1360
|
+
w?.();
|
|
1361
|
+
};
|
|
1362
|
+
const stdoutLines = readJsonLines(proc, (value) => {
|
|
1363
|
+
const ev = value;
|
|
1364
|
+
if (ev.type === "agent_end") idle = true;
|
|
1365
|
+
events.push(ev);
|
|
1366
|
+
notify();
|
|
1367
|
+
});
|
|
1368
|
+
proc.once("exit", (code) => {
|
|
1369
|
+
if (!idle && code !== 0 && code !== null) {
|
|
1370
|
+
failure = new ValidationError(`piExecutor: pi exited with code ${code}`);
|
|
1371
|
+
}
|
|
1372
|
+
idle = true;
|
|
1373
|
+
notify();
|
|
1374
|
+
});
|
|
1375
|
+
proc.once("error", (e) => {
|
|
1376
|
+
failure = new ValidationError(`piExecutor: pi failed to start: ${e.message}`);
|
|
1377
|
+
idle = true;
|
|
1378
|
+
notify();
|
|
1379
|
+
});
|
|
1380
|
+
const abortAll = () => {
|
|
1381
|
+
sendCommand(proc, { type: "abort" });
|
|
1382
|
+
idle = true;
|
|
1383
|
+
notify();
|
|
1384
|
+
};
|
|
1385
|
+
if (args.signal.aborted || args.controller.signal.aborted) abortAll();
|
|
1386
|
+
else {
|
|
1387
|
+
args.signal.addEventListener("abort", abortAll, { once: true });
|
|
1388
|
+
args.controller.signal.addEventListener("abort", abortAll, { once: true });
|
|
1389
|
+
}
|
|
1390
|
+
const system = args.spec.profile.prompt?.systemPrompt;
|
|
1391
|
+
const opening = system ? `${system}
|
|
1392
|
+
|
|
1393
|
+
${taskText(args.task)}` : taskText(args.task);
|
|
1394
|
+
const deadline = seam.turnTimeoutMs ? Date.now() + seam.turnTimeoutMs : void 0;
|
|
1395
|
+
try {
|
|
1396
|
+
sendCommand(proc, { type: "prompt", message: opening });
|
|
1397
|
+
state.note = "turn 0";
|
|
1398
|
+
for (; ; ) {
|
|
1399
|
+
forwardPending(proc, inbox, activity);
|
|
1400
|
+
while (events.length > 0) {
|
|
1401
|
+
const ev = events.shift();
|
|
1402
|
+
for (const usage of projectPiEvent(ev, args, tokens)) {
|
|
1403
|
+
if (usage.kind === "cost") usd += usage.usd;
|
|
1404
|
+
yield usage;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
if (failure) throw failure;
|
|
1408
|
+
if (deadline !== void 0 && Date.now() > deadline) {
|
|
1409
|
+
throw new ValidationError("piExecutor: turn exceeded turnTimeoutMs");
|
|
1410
|
+
}
|
|
1411
|
+
if (idle) {
|
|
1412
|
+
const pending = inbox.drain();
|
|
1413
|
+
if (pending.length === 0) break;
|
|
1414
|
+
idle = false;
|
|
1415
|
+
sendCommand(proc, { type: "prompt", message: inbox.fold(pending) });
|
|
1416
|
+
state.note = `turn ${state.turns}`;
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
await new Promise((resolve) => {
|
|
1420
|
+
wake = resolve;
|
|
1421
|
+
const t = setTimeout(() => {
|
|
1422
|
+
if (wake === resolve) {
|
|
1423
|
+
wake = void 0;
|
|
1424
|
+
resolve();
|
|
1425
|
+
}
|
|
1426
|
+
}, 50);
|
|
1427
|
+
if (typeof t.unref === "function") t.unref();
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
} finally {
|
|
1431
|
+
stdoutLines();
|
|
1432
|
+
state.note = "settled";
|
|
1433
|
+
args.signal.removeEventListener("abort", abortAll);
|
|
1434
|
+
args.controller.signal.removeEventListener("abort", abortAll);
|
|
1435
|
+
await killPi(proc, 2e3).catch(() => ({ destroyed: false }));
|
|
1436
|
+
}
|
|
1437
|
+
const spent = { iterations: state.turns, tokens, usd, ms: Date.now() - started };
|
|
1438
|
+
state.artifact = {
|
|
1439
|
+
outRef: `pi:${hash(state.lastText)}`,
|
|
1440
|
+
out: { content: state.lastText, turns: state.turns },
|
|
1441
|
+
spent
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
function forwardPending(proc, inbox, activity) {
|
|
1445
|
+
const pending = inbox.drain();
|
|
1446
|
+
for (const m of pending) {
|
|
1447
|
+
sendCommand(proc, {
|
|
1448
|
+
type: m.interrupt ? "steer" : "follow_up",
|
|
1449
|
+
message: renderOne(m)
|
|
1450
|
+
});
|
|
1451
|
+
activity.push({
|
|
1452
|
+
at: Date.now(),
|
|
1453
|
+
kind: "note",
|
|
1454
|
+
label: m.interrupt ? "steer" : "follow-up",
|
|
1455
|
+
detail: m.text.length > 80 ? `${m.text.slice(0, 77)}...` : m.text
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
function renderOne(m) {
|
|
1460
|
+
if (m.kind === "answer") {
|
|
1461
|
+
return `Answer to your question${m.questionId ? ` (${m.questionId})` : ""}: ${m.text}`;
|
|
1462
|
+
}
|
|
1463
|
+
return `New instruction from your supervisor: ${m.text}`;
|
|
1464
|
+
}
|
|
1465
|
+
function projectPiEvent(ev, args, tokens) {
|
|
1466
|
+
const out = [];
|
|
1467
|
+
const at = Date.now();
|
|
1468
|
+
if (ev.type === "tool_execution_start" && typeof ev.toolName === "string") {
|
|
1469
|
+
args.activity.push({ at, kind: "tool", label: ev.toolName, detail: describeArgs(ev.args) });
|
|
1470
|
+
return out;
|
|
1471
|
+
}
|
|
1472
|
+
if (ev.type === "tool_execution_end" && typeof ev.toolName === "string") {
|
|
1473
|
+
const status = ev.isError === true ? "error" : "ok";
|
|
1474
|
+
args.activity.push({ at, kind: "tool", label: ev.toolName, status });
|
|
1475
|
+
args.record({
|
|
1476
|
+
toolName: ev.toolName,
|
|
1477
|
+
args: ev.args ?? {},
|
|
1478
|
+
status,
|
|
1479
|
+
...typeof ev.toolCallId === "string" ? { callId: ev.toolCallId } : {}
|
|
1480
|
+
});
|
|
1481
|
+
return out;
|
|
1482
|
+
}
|
|
1483
|
+
if (ev.type === "turn_end" || ev.type === "message_end") {
|
|
1484
|
+
const usage = readUsage(ev.message);
|
|
1485
|
+
if (usage && (usage.input || usage.output)) {
|
|
1486
|
+
tokens.input += usage.input;
|
|
1487
|
+
tokens.output += usage.output;
|
|
1488
|
+
out.push({ kind: "tokens", input: usage.input, output: usage.output });
|
|
1489
|
+
}
|
|
1490
|
+
if (usage?.usd) out.push({ kind: "cost", usd: usage.usd });
|
|
1491
|
+
const text = readText(ev.message);
|
|
1492
|
+
if (text) args.state.lastText = text;
|
|
1493
|
+
if (ev.type === "turn_end") {
|
|
1494
|
+
args.state.turns += 1;
|
|
1495
|
+
args.activity.push({ at, kind: "turn", label: `turn ${args.state.turns}` });
|
|
1496
|
+
out.push({ kind: "iteration" });
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
return out;
|
|
1500
|
+
}
|
|
1501
|
+
function readUsage(message) {
|
|
1502
|
+
if (!message || typeof message !== "object") return void 0;
|
|
1503
|
+
const usage = message.usage;
|
|
1504
|
+
if (!usage || typeof usage !== "object") return void 0;
|
|
1505
|
+
const u = usage;
|
|
1506
|
+
const input = num(u.input) ?? num(u.inputTokens) ?? num(u.prompt_tokens) ?? 0;
|
|
1507
|
+
const output = num(u.output) ?? num(u.outputTokens) ?? num(u.completion_tokens) ?? 0;
|
|
1508
|
+
const costRaw = u.cost;
|
|
1509
|
+
const usd = num(costRaw) ?? (costRaw && typeof costRaw === "object" ? num(costRaw.totalCost) ?? 0 : 0);
|
|
1510
|
+
return { input, output, usd };
|
|
1511
|
+
}
|
|
1512
|
+
function readText(message) {
|
|
1513
|
+
if (!message || typeof message !== "object") return void 0;
|
|
1514
|
+
const content = message.content;
|
|
1515
|
+
if (typeof content === "string" && content.length > 0) return content;
|
|
1516
|
+
if (!Array.isArray(content)) return void 0;
|
|
1517
|
+
const text = content.map(
|
|
1518
|
+
(block) => block && typeof block === "object" && typeof block.text === "string" ? block.text ?? "" : ""
|
|
1519
|
+
).join("");
|
|
1520
|
+
return text.length > 0 ? text : void 0;
|
|
1521
|
+
}
|
|
1522
|
+
function num(v) {
|
|
1523
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
1524
|
+
}
|
|
1525
|
+
function describeArgs(argsValue) {
|
|
1526
|
+
if (!argsValue || typeof argsValue !== "object") return void 0;
|
|
1527
|
+
const a = argsValue;
|
|
1528
|
+
for (const key of ["filePath", "file_path", "path", "file", "command", "cmd", "pattern"]) {
|
|
1529
|
+
const v = a[key];
|
|
1530
|
+
if (typeof v === "string" && v.length > 0) return v.length > 120 ? `${v.slice(0, 117)}...` : v;
|
|
1531
|
+
}
|
|
1532
|
+
return void 0;
|
|
1533
|
+
}
|
|
1534
|
+
function taskText(task) {
|
|
1535
|
+
if (typeof task === "string") return task;
|
|
1536
|
+
try {
|
|
1537
|
+
return JSON.stringify(task) ?? String(task);
|
|
1538
|
+
} catch {
|
|
1539
|
+
return String(task);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
function spawnPi(seam) {
|
|
1543
|
+
const bin = seam.bin ?? "pi";
|
|
1544
|
+
const argv = ["--mode", "rpc"];
|
|
1545
|
+
if (seam.model) {
|
|
1546
|
+
const slash = seam.model.indexOf("/");
|
|
1547
|
+
if (slash > 0) {
|
|
1548
|
+
argv.push("--provider", seam.model.slice(0, slash), "--model", seam.model.slice(slash + 1));
|
|
1549
|
+
} else {
|
|
1550
|
+
argv.push("--model", seam.model);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
if (seam.args) argv.push(...seam.args);
|
|
1554
|
+
return spawn(bin, argv, {
|
|
1555
|
+
...seam.cwd ? { cwd: seam.cwd } : {},
|
|
1556
|
+
env: { ...process.env, ...seam.env ?? {} },
|
|
1557
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
function sendCommand(proc, command) {
|
|
1561
|
+
try {
|
|
1562
|
+
proc.stdin?.write(`${JSON.stringify(command)}
|
|
1563
|
+
`);
|
|
1564
|
+
} catch {
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
function readJsonLines(proc, onValue) {
|
|
1568
|
+
let buffer = "";
|
|
1569
|
+
const onData = (chunk) => {
|
|
1570
|
+
buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
1571
|
+
for (; ; ) {
|
|
1572
|
+
const nl = buffer.indexOf("\n");
|
|
1573
|
+
if (nl < 0) break;
|
|
1574
|
+
const line = buffer.slice(0, nl).trim();
|
|
1575
|
+
buffer = buffer.slice(nl + 1);
|
|
1576
|
+
if (!line) continue;
|
|
1577
|
+
try {
|
|
1578
|
+
onValue(JSON.parse(line));
|
|
1579
|
+
} catch {
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
proc.stdout?.on("data", onData);
|
|
1584
|
+
return () => {
|
|
1585
|
+
proc.stdout?.off("data", onData);
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
var PI_ABORT_GRACE_MS = 500;
|
|
1589
|
+
async function killPi(proc, grace) {
|
|
1590
|
+
if (proc.exitCode !== null || proc.killed) return { destroyed: true };
|
|
1591
|
+
if (grace === "brutalKill") {
|
|
1592
|
+
endStdin(proc);
|
|
1593
|
+
proc.kill("SIGKILL");
|
|
1594
|
+
return { destroyed: true };
|
|
1595
|
+
}
|
|
1596
|
+
const total = grace === "infinity" ? 1e4 : Math.max(0, grace);
|
|
1597
|
+
if (await waitForExit(proc, Math.min(PI_ABORT_GRACE_MS, total))) return { destroyed: true };
|
|
1598
|
+
endStdin(proc);
|
|
1599
|
+
proc.kill("SIGTERM");
|
|
1600
|
+
if (await waitForExit(proc, Math.max(0, total - PI_ABORT_GRACE_MS))) return { destroyed: true };
|
|
1601
|
+
proc.kill("SIGKILL");
|
|
1602
|
+
return { destroyed: true };
|
|
1603
|
+
}
|
|
1604
|
+
function endStdin(proc) {
|
|
1605
|
+
try {
|
|
1606
|
+
proc.stdin?.end();
|
|
1607
|
+
} catch {
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
function waitForExit(proc, ms) {
|
|
1611
|
+
if (proc.exitCode !== null) return Promise.resolve(true);
|
|
1612
|
+
return new Promise((resolve) => {
|
|
1613
|
+
const timer = setTimeout(() => resolve(false), ms);
|
|
1614
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
1615
|
+
proc.once("exit", () => {
|
|
1616
|
+
clearTimeout(timer);
|
|
1617
|
+
resolve(true);
|
|
1618
|
+
});
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
function readPiSeam(ctx) {
|
|
1622
|
+
const seam = ctx.seams[piSeamKey];
|
|
1623
|
+
if (seam === void 0) return {};
|
|
1624
|
+
if (!seam || typeof seam !== "object") {
|
|
1625
|
+
throw new ValidationError(`piExecutor: seams['${piSeamKey}'] must be a PiSeam object`);
|
|
1626
|
+
}
|
|
1627
|
+
return seam;
|
|
1628
|
+
}
|
|
1629
|
+
function hash(value) {
|
|
1630
|
+
let h = 2166136261;
|
|
1631
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
1632
|
+
h ^= value.charCodeAt(i);
|
|
1633
|
+
h = Math.imul(h, 16777619);
|
|
1634
|
+
}
|
|
1635
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
// src/runtime/supervise/sandbox-session.ts
|
|
1639
|
+
var DEFAULT_SANDBOX_STEERING_MAX_TURNS = 24;
|
|
1640
|
+
function createSteerableSandboxSession(args) {
|
|
1641
|
+
const now = args.now ?? Date.now;
|
|
1642
|
+
const maxTurns = Math.max(
|
|
1643
|
+
1,
|
|
1644
|
+
Math.floor(args.options?.maxTurns ?? DEFAULT_SANDBOX_STEERING_MAX_TURNS)
|
|
1645
|
+
);
|
|
1646
|
+
const activity = createActivityLog(args.options?.activityWindow ?? 12);
|
|
1647
|
+
const trace = createPushTraceSource({ runId: `sandbox-${args.harness}-${now()}`, now });
|
|
1648
|
+
const state = {
|
|
1649
|
+
turns: 0,
|
|
1650
|
+
lastText: "",
|
|
1651
|
+
seenToolCalls: /* @__PURE__ */ new Set(),
|
|
1652
|
+
artifact: void 0,
|
|
1653
|
+
teardown: void 0,
|
|
1654
|
+
note: "starting"
|
|
1655
|
+
};
|
|
1656
|
+
const recordEvent = (event) => {
|
|
1657
|
+
const at = now();
|
|
1658
|
+
const data = event.data;
|
|
1659
|
+
const part = data && typeof data === "object" ? data.part : void 0;
|
|
1660
|
+
if (part !== void 0) {
|
|
1661
|
+
const step = decodeToolPart(part, args.harness);
|
|
1662
|
+
if (step) {
|
|
1663
|
+
const key = step.callId ?? `${step.toolName}:${activity.size()}`;
|
|
1664
|
+
if (!state.seenToolCalls.has(key)) {
|
|
1665
|
+
state.seenToolCalls.add(key);
|
|
1666
|
+
trace.record(step);
|
|
1667
|
+
const note = {
|
|
1668
|
+
at,
|
|
1669
|
+
kind: "tool",
|
|
1670
|
+
label: step.toolName,
|
|
1671
|
+
...step.status ? { status: step.status } : {},
|
|
1672
|
+
...describeArgs2(step.args) ? { detail: describeArgs2(step.args) } : {}
|
|
1673
|
+
};
|
|
1674
|
+
activity.push(note);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
const text = readFinalText(event);
|
|
1679
|
+
if (text) state.lastText = text;
|
|
1680
|
+
};
|
|
1681
|
+
async function* stream(task, signal) {
|
|
1682
|
+
const capabilities = await probeSandboxCapabilities(args.sandboxClient);
|
|
1683
|
+
const lineage = createSandboxLineage(args.sandboxClient, capabilities, { maxConcurrency: 1 });
|
|
1684
|
+
state.teardown = () => lineage.teardown();
|
|
1685
|
+
const spec = {
|
|
1686
|
+
profile: args.profile,
|
|
1687
|
+
taskToPrompt: args.taskToPrompt,
|
|
1688
|
+
name: args.profile.name ?? String(args.harness),
|
|
1689
|
+
sandboxOverrides: { backend: { type: args.harness } }
|
|
1690
|
+
};
|
|
1691
|
+
const promptOptions = readPromptOptions(args.loopCtx);
|
|
1692
|
+
const started = now();
|
|
1693
|
+
const tokens = zeroTokenUsage();
|
|
1694
|
+
let usd = 0;
|
|
1695
|
+
let handle;
|
|
1696
|
+
let nextPrompt = args.taskToPrompt(task);
|
|
1697
|
+
try {
|
|
1698
|
+
for (let turn = 0; turn < maxTurns; turn += 1) {
|
|
1699
|
+
const pending = args.inbox.drain();
|
|
1700
|
+
if (pending.length > 0) {
|
|
1701
|
+
const folded = args.inbox.fold(pending);
|
|
1702
|
+
nextPrompt = turn === 0 && nextPrompt ? `${nextPrompt}
|
|
1703
|
+
|
|
1704
|
+
${folded}` : folded;
|
|
1705
|
+
activity.push({
|
|
1706
|
+
at: now(),
|
|
1707
|
+
kind: "note",
|
|
1708
|
+
label: "steer-folded",
|
|
1709
|
+
detail: `${pending.length} message(s)`
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
if (nextPrompt === void 0) break;
|
|
1713
|
+
const prompt = nextPrompt;
|
|
1714
|
+
nextPrompt = void 0;
|
|
1715
|
+
state.note = `turn ${turn}`;
|
|
1716
|
+
const interruptSig = args.inbox.freshInterrupt();
|
|
1717
|
+
const turnController = new AbortController();
|
|
1718
|
+
const abortTurn = () => turnController.abort();
|
|
1719
|
+
if (signal.aborted || args.controller.signal.aborted) turnController.abort();
|
|
1720
|
+
else {
|
|
1721
|
+
signal.addEventListener("abort", abortTurn);
|
|
1722
|
+
args.controller.signal.addEventListener("abort", abortTurn);
|
|
1723
|
+
}
|
|
1724
|
+
interruptSig.addEventListener("abort", abortTurn, { once: true });
|
|
1725
|
+
const timer = args.options?.turnTimeoutMs ? setTimeout(abortTurn, args.options.turnTimeoutMs) : void 0;
|
|
1726
|
+
const cleanup = () => {
|
|
1727
|
+
signal.removeEventListener("abort", abortTurn);
|
|
1728
|
+
args.controller.signal.removeEventListener("abort", abortTurn);
|
|
1729
|
+
if (timer) clearTimeout(timer);
|
|
1730
|
+
};
|
|
1731
|
+
let events;
|
|
1732
|
+
try {
|
|
1733
|
+
if (!handle) {
|
|
1734
|
+
const opened = await lineage.start(spec, prompt, turnController.signal, promptOptions);
|
|
1735
|
+
handle = opened.handle;
|
|
1736
|
+
events = opened.events;
|
|
1737
|
+
} else {
|
|
1738
|
+
events = await lineage.continue(handle, prompt, turnController.signal, promptOptions);
|
|
1739
|
+
}
|
|
1740
|
+
for await (const event of events) {
|
|
1741
|
+
recordEvent(event);
|
|
1742
|
+
const call = extractLlmCallEvent(event, spec.name ?? String(args.harness));
|
|
1743
|
+
if (!call) continue;
|
|
1744
|
+
const input = call.tokensIn ?? 0;
|
|
1745
|
+
const output = call.tokensOut ?? 0;
|
|
1746
|
+
if (input || output) {
|
|
1747
|
+
tokens.input += input;
|
|
1748
|
+
tokens.output += output;
|
|
1749
|
+
yield { kind: "tokens", input, output };
|
|
1750
|
+
}
|
|
1751
|
+
if (typeof call.costUsd === "number" && call.costUsd > 0) {
|
|
1752
|
+
usd += call.costUsd;
|
|
1753
|
+
yield { kind: "cost", usd: call.costUsd };
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
} catch (e) {
|
|
1757
|
+
cleanup();
|
|
1758
|
+
if (isInterruptAbort(e, interruptSig, signal, args.controller.signal)) {
|
|
1759
|
+
activity.push({ at: now(), kind: "note", label: "interrupted", detail: "replanning" });
|
|
1760
|
+
continue;
|
|
1761
|
+
}
|
|
1762
|
+
throw e;
|
|
1763
|
+
}
|
|
1764
|
+
cleanup();
|
|
1765
|
+
state.turns += 1;
|
|
1766
|
+
activity.push({ at: now(), kind: "turn", label: `turn ${turn}` });
|
|
1767
|
+
yield { kind: "iteration" };
|
|
1768
|
+
if (args.inbox.pending() === 0) break;
|
|
1769
|
+
}
|
|
1770
|
+
} finally {
|
|
1771
|
+
state.note = "settled";
|
|
1772
|
+
await lineage.teardown().catch(() => {
|
|
1773
|
+
});
|
|
1774
|
+
state.teardown = void 0;
|
|
1775
|
+
}
|
|
1776
|
+
const spent = { iterations: state.turns, tokens, usd, ms: now() - started };
|
|
1777
|
+
const out = {
|
|
1778
|
+
content: state.lastText,
|
|
1779
|
+
turns: state.turns,
|
|
1780
|
+
toolCalls: activity.read().filter((n) => n.kind === "tool").map((n) => n.label)
|
|
1781
|
+
};
|
|
1782
|
+
state.artifact = {
|
|
1783
|
+
outRef: args.contentRef("sandbox-steerable", { harness: args.harness, out }),
|
|
1784
|
+
out,
|
|
1785
|
+
spent
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
return {
|
|
1789
|
+
stream,
|
|
1790
|
+
progress: () => ({
|
|
1791
|
+
turns: state.turns,
|
|
1792
|
+
pendingMessages: args.inbox.pending(),
|
|
1793
|
+
recentActivity: activity.read(),
|
|
1794
|
+
note: state.note
|
|
1795
|
+
}),
|
|
1796
|
+
traceSource: () => trace.source,
|
|
1797
|
+
artifact: () => state.artifact,
|
|
1798
|
+
async teardown() {
|
|
1799
|
+
await state.teardown?.().catch(() => {
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
function isInterruptAbort(err, interrupt, external, owned) {
|
|
1805
|
+
const aborted = err instanceof DOMException && err.name === "AbortError" || typeof err === "object" && err !== null && err.name === "AbortError";
|
|
1806
|
+
return aborted && interrupt.aborted && !external.aborted && !owned.aborted;
|
|
1807
|
+
}
|
|
1808
|
+
function readFinalText(event) {
|
|
1809
|
+
const data = event.data;
|
|
1810
|
+
if (!data || typeof data !== "object") return void 0;
|
|
1811
|
+
const text = data.finalText;
|
|
1812
|
+
if (typeof text === "string" && text.length > 0) return text;
|
|
1813
|
+
const alt = data.text;
|
|
1814
|
+
return typeof alt === "string" && alt.length > 0 ? alt : void 0;
|
|
1815
|
+
}
|
|
1816
|
+
function describeArgs2(argsValue) {
|
|
1817
|
+
if (!argsValue || typeof argsValue !== "object") return void 0;
|
|
1818
|
+
const a = argsValue;
|
|
1819
|
+
for (const key of ["filePath", "file_path", "path", "file", "command", "cmd", "pattern"]) {
|
|
1820
|
+
const v = a[key];
|
|
1821
|
+
if (typeof v === "string" && v.length > 0) return v.length > 120 ? `${v.slice(0, 117)}...` : v;
|
|
1822
|
+
}
|
|
1823
|
+
return void 0;
|
|
1824
|
+
}
|
|
1825
|
+
function readPromptOptions(loopCtx) {
|
|
1826
|
+
const opts = loopCtx?.promptOptions;
|
|
1827
|
+
if (!opts || typeof opts !== "object") return void 0;
|
|
1828
|
+
return opts;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1099
1831
|
// src/runtime/supervise/worktree-cli-executor.ts
|
|
1100
1832
|
import { randomUUID } from "crypto";
|
|
1101
1833
|
function createWorktreeCliExecutor(options) {
|
|
@@ -1203,7 +1935,7 @@ function linkSignals(a, b) {
|
|
|
1203
1935
|
}
|
|
1204
1936
|
|
|
1205
1937
|
// src/runtime/supervise/runtime.ts
|
|
1206
|
-
import { spawn } from "child_process";
|
|
1938
|
+
import { spawn as spawn2 } from "child_process";
|
|
1207
1939
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1208
1940
|
import { request as httpRequest } from "http";
|
|
1209
1941
|
import { request as httpsRequest } from "https";
|
|
@@ -1216,15 +1948,15 @@ var bridgeSeamKey = "bridge";
|
|
|
1216
1948
|
var cliWorktreeSeamKey = "cli-worktree";
|
|
1217
1949
|
var providerSeamKey = "provider";
|
|
1218
1950
|
function contentRef(prefix, value) {
|
|
1219
|
-
let
|
|
1951
|
+
let str2;
|
|
1220
1952
|
try {
|
|
1221
|
-
|
|
1953
|
+
str2 = JSON.stringify(value) ?? String(value);
|
|
1222
1954
|
} catch {
|
|
1223
|
-
|
|
1955
|
+
str2 = String(value);
|
|
1224
1956
|
}
|
|
1225
1957
|
let h = 2166136261;
|
|
1226
|
-
for (let i = 0; i <
|
|
1227
|
-
h ^=
|
|
1958
|
+
for (let i = 0; i < str2.length; i += 1) {
|
|
1959
|
+
h ^= str2.charCodeAt(i);
|
|
1228
1960
|
h = Math.imul(h, 16777619);
|
|
1229
1961
|
}
|
|
1230
1962
|
return `${prefix}:${(h >>> 0).toString(16).padStart(8, "0")}`;
|
|
@@ -1463,6 +2195,43 @@ var sandboxExecutor = (spec, ctx) => {
|
|
|
1463
2195
|
abortIfSignalled();
|
|
1464
2196
|
if (!ctx.signal.aborted) ctx.signal.addEventListener("abort", abortIfSignalled, { once: true });
|
|
1465
2197
|
let artifact;
|
|
2198
|
+
if (seam.steering) {
|
|
2199
|
+
const inbox = createInbox();
|
|
2200
|
+
const session = createSteerableSandboxSession({
|
|
2201
|
+
controller,
|
|
2202
|
+
profile: spec.profile,
|
|
2203
|
+
harness,
|
|
2204
|
+
sandboxClient: seam.sandboxClient,
|
|
2205
|
+
inbox,
|
|
2206
|
+
taskToPrompt: (t) => taskToPrompt(t),
|
|
2207
|
+
options: seam.steering,
|
|
2208
|
+
...seam.loopCtx ? { loopCtx: seam.loopCtx } : {},
|
|
2209
|
+
contentRef
|
|
2210
|
+
});
|
|
2211
|
+
return {
|
|
2212
|
+
runtime: "sandbox",
|
|
2213
|
+
deliver: (m) => inbox.deliver(m),
|
|
2214
|
+
progress: () => session.progress(),
|
|
2215
|
+
traceSource: () => session.traceSource(),
|
|
2216
|
+
execute(task, signal) {
|
|
2217
|
+
return session.stream(task, signal);
|
|
2218
|
+
},
|
|
2219
|
+
async teardown(_grace) {
|
|
2220
|
+
controller.abort();
|
|
2221
|
+
await session.teardown();
|
|
2222
|
+
return { destroyed: true };
|
|
2223
|
+
},
|
|
2224
|
+
resultArtifact() {
|
|
2225
|
+
const a = session.artifact();
|
|
2226
|
+
if (!a) {
|
|
2227
|
+
throw new ValidationError(
|
|
2228
|
+
"sandboxExecutor(steering): resultArtifact() read before stream drained"
|
|
2229
|
+
);
|
|
2230
|
+
}
|
|
2231
|
+
return a;
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
1466
2235
|
const output = {
|
|
1467
2236
|
parse(events) {
|
|
1468
2237
|
return { events };
|
|
@@ -1598,7 +2367,7 @@ var cliExecutor = (_spec, ctx) => {
|
|
|
1598
2367
|
};
|
|
1599
2368
|
async function* streamCliLeaf(args) {
|
|
1600
2369
|
const prompt = taskToPrompt(args.task);
|
|
1601
|
-
const proc =
|
|
2370
|
+
const proc = spawn2(args.seam.bin, args.seam.args ?? [], {
|
|
1602
2371
|
...args.seam.cwd ? { cwd: args.seam.cwd } : {},
|
|
1603
2372
|
env: { ...process.env, ...args.seam.env ?? {} },
|
|
1604
2373
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -2164,6 +2933,8 @@ function createExecutor(config) {
|
|
|
2164
2933
|
return cliExecutor(spec, seamed);
|
|
2165
2934
|
case "cli-worktree":
|
|
2166
2935
|
return cliWorktreeExecutor(spec, seamed);
|
|
2936
|
+
case "pi":
|
|
2937
|
+
return piExecutor(spec, seamed);
|
|
2167
2938
|
case "provider": {
|
|
2168
2939
|
const providerSeam = readSeam(seamed, providerSeamKey, "provider");
|
|
2169
2940
|
const provider = resolveAgentEnvironmentProvider(
|
|
@@ -2185,6 +2956,7 @@ function createExecutorRegistry() {
|
|
|
2185
2956
|
factories.set("inline", routerInlineExecutor);
|
|
2186
2957
|
factories.set("sandbox", sandboxExecutor);
|
|
2187
2958
|
factories.set("cli", cliExecutor);
|
|
2959
|
+
factories.set(PI_RUNTIME, piExecutor);
|
|
2188
2960
|
return {
|
|
2189
2961
|
register(runtime, factory) {
|
|
2190
2962
|
if (factories.has(runtime)) {
|
|
@@ -2224,9 +2996,9 @@ function readSeam(ctx, key, who) {
|
|
|
2224
2996
|
function taskToPrompt(task) {
|
|
2225
2997
|
if (typeof task === "string") return task;
|
|
2226
2998
|
if (task && typeof task === "object") {
|
|
2227
|
-
const
|
|
2999
|
+
const obj2 = task;
|
|
2228
3000
|
for (const k of ["prompt", "content", "task", "message"]) {
|
|
2229
|
-
if (typeof
|
|
3001
|
+
if (typeof obj2[k] === "string") return obj2[k];
|
|
2230
3002
|
}
|
|
2231
3003
|
}
|
|
2232
3004
|
return JSON.stringify(task);
|
|
@@ -2285,6 +3057,119 @@ function createInMemoryRunContext(opts = {}) {
|
|
|
2285
3057
|
executors: opts.withDriver ? withDriverExecutor(base) : base
|
|
2286
3058
|
};
|
|
2287
3059
|
}
|
|
3060
|
+
function createFileRunContext(dir, opts = {}) {
|
|
3061
|
+
const base = createExecutorRegistry();
|
|
3062
|
+
return {
|
|
3063
|
+
journal: new FileSpawnJournal(`${dir}/spawn-journal.jsonl`),
|
|
3064
|
+
blobs: new FileResultBlobStore(`${dir}/blobs`),
|
|
3065
|
+
executors: opts.withDriver ? withDriverExecutor(base) : base,
|
|
3066
|
+
resume: true
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
// src/runtime/supervise/detector-monitor.ts
|
|
3071
|
+
import {
|
|
3072
|
+
argHash,
|
|
3073
|
+
errorStreakDetector,
|
|
3074
|
+
observeAll,
|
|
3075
|
+
repeatedActionDetector
|
|
3076
|
+
} from "@tangle-network/agent-eval";
|
|
3077
|
+
function defaultToolDetectors() {
|
|
3078
|
+
return [repeatedActionDetector({ maxRepeated: 3 }), errorStreakDetector({ maxErrors: 3 })];
|
|
3079
|
+
}
|
|
3080
|
+
function watchTrace(source, opts = {}) {
|
|
3081
|
+
const detectors = opts.detectors ?? defaultToolDetectors();
|
|
3082
|
+
return source.onSpan((span) => {
|
|
3083
|
+
let fingerprint;
|
|
3084
|
+
try {
|
|
3085
|
+
fingerprint = `${span.toolName}|${argHash(span.args)}`;
|
|
3086
|
+
} catch {
|
|
3087
|
+
fingerprint = `${span.toolName}|<unhashable>`;
|
|
3088
|
+
}
|
|
3089
|
+
const signals = observeAll(detectors, {
|
|
3090
|
+
actionFingerprint: fingerprint,
|
|
3091
|
+
...span.status ? { status: span.status } : {},
|
|
3092
|
+
label: span.toolName
|
|
3093
|
+
});
|
|
3094
|
+
for (const s of signals) void opts.onSignal?.(s, span);
|
|
3095
|
+
});
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
// src/runtime/supervise/dispatch.ts
|
|
3099
|
+
async function rollingDispatch(scope, opts) {
|
|
3100
|
+
if (!Number.isInteger(opts.width) || opts.width < 1) {
|
|
3101
|
+
throw new ValidationError(
|
|
3102
|
+
`rollingDispatch: width must be a positive integer, got ${String(opts.width)}`
|
|
3103
|
+
);
|
|
3104
|
+
}
|
|
3105
|
+
const settled = [];
|
|
3106
|
+
const rejected = [];
|
|
3107
|
+
const live = /* @__PURE__ */ new Set();
|
|
3108
|
+
let admitted = 0;
|
|
3109
|
+
let peakLive = 0;
|
|
3110
|
+
let queueDry = false;
|
|
3111
|
+
let stopReason = "drained";
|
|
3112
|
+
const fill = async () => {
|
|
3113
|
+
while (live.size < opts.width) {
|
|
3114
|
+
if (queueDry) return;
|
|
3115
|
+
if (scope.signal.aborted) {
|
|
3116
|
+
stopReason = "aborted";
|
|
3117
|
+
queueDry = true;
|
|
3118
|
+
return;
|
|
3119
|
+
}
|
|
3120
|
+
if (opts.shouldStop?.() === true) {
|
|
3121
|
+
stopReason = "stopped";
|
|
3122
|
+
queueDry = true;
|
|
3123
|
+
return;
|
|
3124
|
+
}
|
|
3125
|
+
const unit = await opts.nextUnit();
|
|
3126
|
+
if (unit === void 0) {
|
|
3127
|
+
queueDry = true;
|
|
3128
|
+
return;
|
|
3129
|
+
}
|
|
3130
|
+
const res = scope.spawn(unit.agent, unit.task, unit.opts);
|
|
3131
|
+
if (!res.ok) {
|
|
3132
|
+
rejected.push(`${unit.opts.label}: ${res.reason}`);
|
|
3133
|
+
stopReason = "not-admitted";
|
|
3134
|
+
queueDry = true;
|
|
3135
|
+
return;
|
|
3136
|
+
}
|
|
3137
|
+
live.add(res.handle.id);
|
|
3138
|
+
admitted += 1;
|
|
3139
|
+
if (live.size > peakLive) peakLive = live.size;
|
|
3140
|
+
}
|
|
3141
|
+
};
|
|
3142
|
+
await fill();
|
|
3143
|
+
while (live.size > 0) {
|
|
3144
|
+
const s = await scope.next();
|
|
3145
|
+
if (s === null) break;
|
|
3146
|
+
live.delete(s.handle.id);
|
|
3147
|
+
settled.push(s);
|
|
3148
|
+
await opts.onSettled?.(s);
|
|
3149
|
+
await fill();
|
|
3150
|
+
}
|
|
3151
|
+
return { settled, admitted, rejected, stopReason, peakLive };
|
|
3152
|
+
}
|
|
3153
|
+
function freeSlots(liveCount, cap) {
|
|
3154
|
+
if (cap === void 0 || cap <= 0) return null;
|
|
3155
|
+
return Math.max(0, cap - liveCount);
|
|
3156
|
+
}
|
|
3157
|
+
function effectiveConcurrency(caps) {
|
|
3158
|
+
const applicable = [caps.maxLiveWorkers, caps.maxSandboxes].filter(
|
|
3159
|
+
(c) => typeof c === "number" && c > 0
|
|
3160
|
+
);
|
|
3161
|
+
if (applicable.length === 0) return void 0;
|
|
3162
|
+
return Math.min(...applicable);
|
|
3163
|
+
}
|
|
3164
|
+
function queueOf(units, budget) {
|
|
3165
|
+
let i = 0;
|
|
3166
|
+
return () => {
|
|
3167
|
+
const u = units[i];
|
|
3168
|
+
if (u === void 0) return void 0;
|
|
3169
|
+
i += 1;
|
|
3170
|
+
return { agent: u.agent, task: u.task, opts: { budget, label: u.label } };
|
|
3171
|
+
};
|
|
3172
|
+
}
|
|
2288
3173
|
|
|
2289
3174
|
// src/runtime/supervise/event-bus.ts
|
|
2290
3175
|
function createEventBus(now = Date.now) {
|
|
@@ -2370,12 +3255,12 @@ function createCoordinationTools(opts) {
|
|
|
2370
3255
|
bus.subscribe((rec) => cb(rec.event));
|
|
2371
3256
|
}
|
|
2372
3257
|
const urgencyPriority = (u) => u === "blocks-run" ? 20 : u === "blocks-step" ? 10 : 0;
|
|
2373
|
-
const
|
|
3258
|
+
const str2 = (v, field) => {
|
|
2374
3259
|
if (typeof v !== "string" || v.length === 0)
|
|
2375
3260
|
throw new Error(`coordination tools: "${field}" must be a non-empty string`);
|
|
2376
3261
|
return v;
|
|
2377
3262
|
};
|
|
2378
|
-
const
|
|
3263
|
+
const obj2 = (raw) => {
|
|
2379
3264
|
if (!raw || typeof raw !== "object")
|
|
2380
3265
|
throw new Error("coordination tools: arguments must be an object");
|
|
2381
3266
|
return raw;
|
|
@@ -2413,14 +3298,17 @@ function createCoordinationTools(opts) {
|
|
|
2413
3298
|
);
|
|
2414
3299
|
};
|
|
2415
3300
|
const recordSettled = (s) => {
|
|
3301
|
+
const settledAt = Date.now();
|
|
2416
3302
|
const w = s.kind === "done" ? {
|
|
2417
3303
|
id: s.handle.id,
|
|
2418
3304
|
status: "done",
|
|
2419
3305
|
score: s.verdict?.score ?? 0,
|
|
2420
3306
|
valid: s.verdict?.valid ?? false,
|
|
2421
|
-
outRef: s.outRef
|
|
2422
|
-
|
|
3307
|
+
outRef: s.outRef,
|
|
3308
|
+
settledAt
|
|
3309
|
+
} : { id: s.handle.id, status: "down", reason: s.reason, settledAt };
|
|
2423
3310
|
ledger.push(w);
|
|
3311
|
+
unwatchWorker(w.id);
|
|
2424
3312
|
return w;
|
|
2425
3313
|
};
|
|
2426
3314
|
const drainSettlement = async () => {
|
|
@@ -2449,7 +3337,7 @@ function createCoordinationTools(opts) {
|
|
|
2449
3337
|
};
|
|
2450
3338
|
async function sendDown(type, down, questionId) {
|
|
2451
3339
|
await bus.publish(
|
|
2452
|
-
type === "answer" ? { type, down, questionId:
|
|
3340
|
+
type === "answer" ? { type, down, questionId: str2(questionId, "questionId") } : { type, down },
|
|
2453
3341
|
{ queue: false }
|
|
2454
3342
|
);
|
|
2455
3343
|
}
|
|
@@ -2472,13 +3360,13 @@ function createCoordinationTools(opts) {
|
|
|
2472
3360
|
};
|
|
2473
3361
|
const nextQuestionId = (from) => `${from}:q${questionSeq++}`;
|
|
2474
3362
|
const normalizeQuestion = (q, fallbackFrom) => {
|
|
2475
|
-
const from =
|
|
3363
|
+
const from = str2(q.from ?? fallbackFrom, "from");
|
|
2476
3364
|
return {
|
|
2477
3365
|
id: typeof q.id === "string" && q.id.length > 0 ? q.id : nextQuestionId(from),
|
|
2478
3366
|
from,
|
|
2479
3367
|
level: level(q.level),
|
|
2480
|
-
question:
|
|
2481
|
-
reason:
|
|
3368
|
+
question: str2(q.question, "question"),
|
|
3369
|
+
reason: str2(q.reason, "reason"),
|
|
2482
3370
|
...q.options ? { options: q.options } : {},
|
|
2483
3371
|
urgency: urgency(q.urgency)
|
|
2484
3372
|
};
|
|
@@ -2532,6 +3420,69 @@ function createCoordinationTools(opts) {
|
|
|
2532
3420
|
const isLive = (status) => status !== "done" && status !== "failed" && status !== "cancelled";
|
|
2533
3421
|
const liveWorkerCount = () => opts.scope.view.nodes.filter((n) => isLive(n.status)).length;
|
|
2534
3422
|
const liveSnapshot = () => opts.scope.view.nodes.filter((n) => isLive(n.status)).map((n) => ({ id: n.id, status: n.status, spent: n.spent }));
|
|
3423
|
+
const freeWorkerSlots = () => freeSlots(liveWorkerCount(), maxLiveWorkers);
|
|
3424
|
+
const readProgress = (id) => {
|
|
3425
|
+
const scope = opts.scope;
|
|
3426
|
+
if (typeof scope.progress !== "function") return void 0;
|
|
3427
|
+
try {
|
|
3428
|
+
return scope.progress(
|
|
3429
|
+
id,
|
|
3430
|
+
opts.stallAfterMs !== void 0 ? { stallAfterMs: opts.stallAfterMs } : {}
|
|
3431
|
+
);
|
|
3432
|
+
} catch {
|
|
3433
|
+
return void 0;
|
|
3434
|
+
}
|
|
3435
|
+
};
|
|
3436
|
+
const watchers = /* @__PURE__ */ new Map();
|
|
3437
|
+
const watchWorker = (id) => {
|
|
3438
|
+
const watch = opts.watchWorkers;
|
|
3439
|
+
if (!watch) return;
|
|
3440
|
+
const scope = opts.scope;
|
|
3441
|
+
if (typeof scope.traceSource !== "function") return;
|
|
3442
|
+
let source;
|
|
3443
|
+
try {
|
|
3444
|
+
source = scope.traceSource(id);
|
|
3445
|
+
} catch {
|
|
3446
|
+
return;
|
|
3447
|
+
}
|
|
3448
|
+
if (!source) return;
|
|
3449
|
+
const cap = watch.maxFindingsPerWorker ?? 3;
|
|
3450
|
+
let raised = 0;
|
|
3451
|
+
const unsub = watchTrace(source, {
|
|
3452
|
+
...watch.detectors ? { detectors: watch.detectors } : {},
|
|
3453
|
+
onSignal: async (signal, span) => {
|
|
3454
|
+
if (cap > 0 && raised >= cap) return;
|
|
3455
|
+
raised += 1;
|
|
3456
|
+
await bus.publish({
|
|
3457
|
+
type: "finding",
|
|
3458
|
+
finding: {
|
|
3459
|
+
fromWorker: id,
|
|
3460
|
+
analyst: `online:${signal.detector}`,
|
|
3461
|
+
findings: {
|
|
3462
|
+
detector: signal.detector,
|
|
3463
|
+
severity: signal.severity,
|
|
3464
|
+
reason: signal.reason,
|
|
3465
|
+
streak: signal.streak,
|
|
3466
|
+
...signal.failureClass ? { failureClass: signal.failureClass } : {},
|
|
3467
|
+
toolName: span.toolName,
|
|
3468
|
+
at: span.endedAt,
|
|
3469
|
+
progress: readProgress(id)
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
});
|
|
3473
|
+
}
|
|
3474
|
+
});
|
|
3475
|
+
watchers.set(id, unsub);
|
|
3476
|
+
};
|
|
3477
|
+
const unwatchWorker = (id) => {
|
|
3478
|
+
const unsub = watchers.get(id);
|
|
3479
|
+
if (!unsub) return;
|
|
3480
|
+
watchers.delete(id);
|
|
3481
|
+
try {
|
|
3482
|
+
unsub();
|
|
3483
|
+
} catch {
|
|
3484
|
+
}
|
|
3485
|
+
};
|
|
2535
3486
|
const awaitTimeoutMs = opts.awaitTimeoutMs ?? DEFAULT_AWAIT_EVENT_TIMEOUT_MS;
|
|
2536
3487
|
let inFlightDrain = null;
|
|
2537
3488
|
const ensureDrain = () => {
|
|
@@ -2557,7 +3508,7 @@ function createCoordinationTools(opts) {
|
|
|
2557
3508
|
const tools = [
|
|
2558
3509
|
{
|
|
2559
3510
|
name: "spawn_agent",
|
|
2560
|
-
description: 'Start a worker the driver will drive. `profile` is the worker or another driver; `task` is what it should do. Reserves budget from the conserved pool and fails closed. Pass an optional `budget` (per-field) to give a hard sub-task more than the default \u2014 it merges over the per-worker default; the conserved pool is still the hard fence. When a max-live-workers cap is set it also fails closed (`error: "max-live-workers"`) while that many workers are still in flight \u2014 settle or steer one before spawning another.',
|
|
3511
|
+
description: 'Start a worker the driver will drive. `profile` is the worker or another driver; `task` is what it should do. Reserves budget from the conserved pool and fails closed. Pass an optional `budget` (per-field) to give a hard sub-task more than the default \u2014 it merges over the per-worker default; the conserved pool is still the hard fence. When a max-live-workers cap is set it also fails closed (`error: "max-live-workers"`) while that many workers are still in flight \u2014 settle or steer one before spawning another. Returns `freeSlots`: how many MORE workers you can start right now (`null` = uncapped). While `freeSlots > 0` there is idle capacity \u2014 call this again to fill it rather than waiting; parallel workers finish the run sooner than one at a time.',
|
|
2561
3512
|
inputSchema: {
|
|
2562
3513
|
type: "object",
|
|
2563
3514
|
properties: {
|
|
@@ -2578,32 +3529,41 @@ function createCoordinationTools(opts) {
|
|
|
2578
3529
|
required: ["profile", "task"]
|
|
2579
3530
|
},
|
|
2580
3531
|
handler: (raw) => {
|
|
2581
|
-
const a =
|
|
3532
|
+
const a = obj2(raw);
|
|
2582
3533
|
if (maxLiveWorkers !== void 0 && maxLiveWorkers > 0 && liveWorkerCount() >= maxLiveWorkers)
|
|
2583
|
-
return Promise.resolve({
|
|
3534
|
+
return Promise.resolve({
|
|
3535
|
+
error: "max-live-workers",
|
|
3536
|
+
live: liveWorkerCount(),
|
|
3537
|
+
freeSlots: freeWorkerSlots()
|
|
3538
|
+
});
|
|
2584
3539
|
const agent = opts.makeWorkerAgent(a.profile);
|
|
2585
3540
|
const budget = a.budget === void 0 ? opts.perWorker : mergeBudget(opts.perWorker, a.budget);
|
|
2586
3541
|
const res = opts.scope.spawn(agent, a.task, {
|
|
2587
3542
|
budget,
|
|
2588
3543
|
label: typeof a.label === "string" ? a.label : "worker"
|
|
2589
3544
|
});
|
|
2590
|
-
|
|
3545
|
+
if (res.ok) watchWorker(res.handle.id);
|
|
3546
|
+
return Promise.resolve(
|
|
3547
|
+
res.ok ? { workerId: res.handle.id, live: liveWorkerCount(), freeSlots: freeWorkerSlots() } : { error: res.reason, live: liveWorkerCount(), freeSlots: freeWorkerSlots() }
|
|
3548
|
+
);
|
|
2591
3549
|
}
|
|
2592
3550
|
},
|
|
2593
3551
|
{
|
|
2594
3552
|
name: "observe_agent",
|
|
2595
|
-
description: "Inspect a worker status, spend, and settled output artifact when
|
|
3553
|
+
description: "Inspect a worker WHILE IT RUNS, not only after it finishes: status, spend so far, and `progress` \u2014 how long since it last did anything (`idleMs`), whether that counts as stalled, how many turns it has taken, the last tools/files it touched (`recentActivity`), whether a steer can even reach it (`steerable`), and how many steers it has not yet read (`pendingMessages`). Returns the settled output artifact once it exists. Use this BEFORE steer_agent: a steer is only worth sending when the progress says the worker is on the wrong path or has stopped making any.",
|
|
2596
3554
|
inputSchema: { type: "object", properties: { workerId: idArg }, required: ["workerId"] },
|
|
2597
3555
|
handler: async (raw) => {
|
|
2598
|
-
const id =
|
|
3556
|
+
const id = str2(obj2(raw).workerId, "workerId");
|
|
2599
3557
|
const node = opts.scope.view.nodes.find((n) => n.id === id);
|
|
2600
3558
|
if (!node) return { error: `unknown workerId ${JSON.stringify(id)}` };
|
|
2601
3559
|
const output = node.outRef ? await opts.blobs.get(node.outRef) : void 0;
|
|
3560
|
+
const progress = readProgress(id);
|
|
2602
3561
|
return {
|
|
2603
3562
|
status: node.status,
|
|
2604
3563
|
spent: node.spent,
|
|
2605
3564
|
outRef: node.outRef ?? null,
|
|
2606
|
-
output: output ?? null
|
|
3565
|
+
output: output ?? null,
|
|
3566
|
+
progress: progress ?? null
|
|
2607
3567
|
};
|
|
2608
3568
|
}
|
|
2609
3569
|
},
|
|
@@ -2623,18 +3583,21 @@ function createCoordinationTools(opts) {
|
|
|
2623
3583
|
required: ["workerId", "instruction"]
|
|
2624
3584
|
},
|
|
2625
3585
|
handler: async (raw) => {
|
|
2626
|
-
const a =
|
|
2627
|
-
const workerId =
|
|
2628
|
-
const instruction =
|
|
3586
|
+
const a = obj2(raw);
|
|
3587
|
+
const workerId = str2(a.workerId, "workerId");
|
|
3588
|
+
const instruction = str2(a.instruction, "instruction");
|
|
2629
3589
|
const interrupt = a.interrupt === true;
|
|
2630
3590
|
const delivered = opts.scope.send(workerId, { steer: instruction, interrupt });
|
|
2631
3591
|
await sendDown("steer", { toWorker: workerId, instruction, delivered });
|
|
2632
|
-
return { delivered };
|
|
3592
|
+
if (delivered) return { delivered, progress: readProgress(workerId) ?? null };
|
|
3593
|
+
const progress = readProgress(workerId);
|
|
3594
|
+
const reason2 = !progress ? "unknown-worker" : !progress.live ? "already-settled" : "runtime-has-no-inbox";
|
|
3595
|
+
return { delivered, reason: reason2, progress: progress ?? null };
|
|
2633
3596
|
}
|
|
2634
3597
|
},
|
|
2635
3598
|
{
|
|
2636
3599
|
name: "await_event",
|
|
2637
|
-
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. If a worker is still running when the wait elapses, returns { pending: true, live: [...] } (the workers still in flight) instead of blocking indefinitely \u2014 call await_event again to keep waiting; the settlement is not lost.",
|
|
3600
|
+
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. If a worker is still running when the wait elapses, returns { pending: true, live: [...] } (the workers still in flight) instead of blocking indefinitely \u2014 call await_event again to keep waiting; the settlement is not lost. Every reply carries `freeSlots`: how many more workers you can start right now (`null` = uncapped). A settled worker frees its slot, so `freeSlots > 0` means capacity is sitting idle \u2014 spawn into it before waiting again.",
|
|
2638
3601
|
inputSchema: {
|
|
2639
3602
|
type: "object",
|
|
2640
3603
|
properties: {
|
|
@@ -2646,15 +3609,16 @@ function createCoordinationTools(opts) {
|
|
|
2646
3609
|
}
|
|
2647
3610
|
},
|
|
2648
3611
|
handler: async (raw) => {
|
|
2649
|
-
const k =
|
|
3612
|
+
const k = obj2(raw).kinds;
|
|
2650
3613
|
const kinds = Array.isArray(k) ? k.filter((x) => x === "settled" || x === "question" || x === "finding") : void 0;
|
|
2651
3614
|
let ev = bus.pull(kinds);
|
|
2652
|
-
if (ev) return projectEvent(ev);
|
|
3615
|
+
if (ev) return { ...projectEvent(ev), freeSlots: freeWorkerSlots() };
|
|
2653
3616
|
const raced = await raceDrainWithTimeout(ensureDrain());
|
|
2654
|
-
if (raced === void 0)
|
|
3617
|
+
if (raced === void 0)
|
|
3618
|
+
return { pending: true, live: liveSnapshot(), freeSlots: freeWorkerSlots() };
|
|
2655
3619
|
ev = bus.pull(kinds);
|
|
2656
|
-
if (!ev) return { idle: !raced.drained };
|
|
2657
|
-
return projectEvent(ev);
|
|
3620
|
+
if (!ev) return { idle: !raced.drained, freeSlots: freeWorkerSlots() };
|
|
3621
|
+
return { ...projectEvent(ev), freeSlots: freeWorkerSlots() };
|
|
2658
3622
|
}
|
|
2659
3623
|
},
|
|
2660
3624
|
{
|
|
@@ -2679,8 +3643,8 @@ function createCoordinationTools(opts) {
|
|
|
2679
3643
|
required: ["questionId"]
|
|
2680
3644
|
},
|
|
2681
3645
|
handler: async (raw) => {
|
|
2682
|
-
const a =
|
|
2683
|
-
const questionId =
|
|
3646
|
+
const a = obj2(raw);
|
|
3647
|
+
const questionId = str2(a.questionId, "questionId");
|
|
2684
3648
|
if (typeof a.answer === "string" && a.answer.length > 0) {
|
|
2685
3649
|
const answer = a.answer;
|
|
2686
3650
|
const question = decideQuestion(questionId, {
|
|
@@ -2733,15 +3697,15 @@ function createCoordinationTools(opts) {
|
|
|
2733
3697
|
required: ["from", "level", "question", "reason", "urgency"]
|
|
2734
3698
|
},
|
|
2735
3699
|
handler: async (raw) => {
|
|
2736
|
-
const a =
|
|
2737
|
-
const from =
|
|
3700
|
+
const a = obj2(raw);
|
|
3701
|
+
const from = str2(a.from, "from");
|
|
2738
3702
|
const q = await emitNewQuestion(
|
|
2739
3703
|
addQuestion(
|
|
2740
3704
|
{
|
|
2741
3705
|
from,
|
|
2742
3706
|
level: level(a.level),
|
|
2743
|
-
question:
|
|
2744
|
-
reason:
|
|
3707
|
+
question: str2(a.question, "question"),
|
|
3708
|
+
reason: str2(a.reason, "reason"),
|
|
2745
3709
|
urgency: urgency(a.urgency)
|
|
2746
3710
|
},
|
|
2747
3711
|
from,
|
|
@@ -2768,7 +3732,7 @@ function createCoordinationTools(opts) {
|
|
|
2768
3732
|
});
|
|
2769
3733
|
}
|
|
2770
3734
|
stopped = true;
|
|
2771
|
-
const r =
|
|
3735
|
+
const r = obj2(raw).reason;
|
|
2772
3736
|
reason = typeof r === "string" ? r : void 0;
|
|
2773
3737
|
return Promise.resolve({ stopped: true });
|
|
2774
3738
|
}
|
|
@@ -2793,14 +3757,14 @@ function createCoordinationTools(opts) {
|
|
|
2793
3757
|
required: ["kind", "workerId"]
|
|
2794
3758
|
},
|
|
2795
3759
|
handler: async (raw) => {
|
|
2796
|
-
const a =
|
|
2797
|
-
const id =
|
|
3760
|
+
const a = obj2(raw);
|
|
3761
|
+
const id = str2(a.workerId, "workerId");
|
|
2798
3762
|
const node = opts.scope.view.nodes.find((n) => n.id === id);
|
|
2799
3763
|
if (!node) return { error: `unknown workerId ${JSON.stringify(id)}` };
|
|
2800
3764
|
if (!node.outRef)
|
|
2801
3765
|
return { error: `worker ${JSON.stringify(id)} has not settled \u2014 no trace to analyze yet` };
|
|
2802
3766
|
const trace = await opts.blobs.get(node.outRef);
|
|
2803
|
-
return { findings: await opts.analysts?.run(
|
|
3767
|
+
return { findings: await opts.analysts?.run(str2(a.kind, "kind"), trace) };
|
|
2804
3768
|
}
|
|
2805
3769
|
});
|
|
2806
3770
|
}
|
|
@@ -2817,6 +3781,287 @@ function createCoordinationTools(opts) {
|
|
|
2817
3781
|
};
|
|
2818
3782
|
}
|
|
2819
3783
|
|
|
3784
|
+
// src/runtime/anytime.ts
|
|
3785
|
+
function bestSoFar(values) {
|
|
3786
|
+
const out = [];
|
|
3787
|
+
let best = 0;
|
|
3788
|
+
for (const v of values) {
|
|
3789
|
+
if (typeof v === "number" && v > best) best = v;
|
|
3790
|
+
out.push(best);
|
|
3791
|
+
}
|
|
3792
|
+
return out;
|
|
3793
|
+
}
|
|
3794
|
+
function areaUnderCurve(curve) {
|
|
3795
|
+
if (curve.length === 0) return 0;
|
|
3796
|
+
return curve.reduce((s, v) => s + v, 0) / curve.length;
|
|
3797
|
+
}
|
|
3798
|
+
function plateauLength(curve, minDelta) {
|
|
3799
|
+
if (curve.length === 0) return 0;
|
|
3800
|
+
const last = curve[curve.length - 1];
|
|
3801
|
+
let i = curve.length - 1;
|
|
3802
|
+
while (i > 0 && last - curve[i - 1] <= minDelta) i -= 1;
|
|
3803
|
+
return curve.length - 1 - i;
|
|
3804
|
+
}
|
|
3805
|
+
var median = (xs) => {
|
|
3806
|
+
if (xs.length === 0) return null;
|
|
3807
|
+
const s = [...xs].sort((a, b) => a - b);
|
|
3808
|
+
const mid = Math.floor(s.length / 2);
|
|
3809
|
+
return s.length % 2 === 1 ? s[mid] : (s[mid - 1] + s[mid]) / 2;
|
|
3810
|
+
};
|
|
3811
|
+
function anytimeReport(spans, opts) {
|
|
3812
|
+
const targets = opts?.targets ?? [1];
|
|
3813
|
+
const byRun = /* @__PURE__ */ new Map();
|
|
3814
|
+
for (const s of spans) {
|
|
3815
|
+
if (!s.label.startsWith("shot:")) continue;
|
|
3816
|
+
const list = byRun.get(s.runId) ?? [];
|
|
3817
|
+
list.push(s);
|
|
3818
|
+
byRun.set(s.runId, list);
|
|
3819
|
+
}
|
|
3820
|
+
const perTask = [];
|
|
3821
|
+
for (const [runId, shots] of byRun) {
|
|
3822
|
+
const m = runId.match(/^agentic:(.+):(.+)$/);
|
|
3823
|
+
const strategy = m?.[1] ?? runId;
|
|
3824
|
+
const taskId = m?.[2] ?? runId;
|
|
3825
|
+
const ordered = [...shots].sort((a, b) => (a.endMs ?? a.startMs) - (b.endMs ?? b.startMs));
|
|
3826
|
+
const t0 = Math.min(...ordered.map((s) => s.startMs));
|
|
3827
|
+
const taskTargets = opts?.targetFor ? [opts.targetFor(taskId)] : targets;
|
|
3828
|
+
const bests = bestSoFar(ordered.map((s) => typeof s.score === "number" ? s.score : void 0));
|
|
3829
|
+
let cumUsd = 0;
|
|
3830
|
+
const points = [];
|
|
3831
|
+
const hits = {};
|
|
3832
|
+
for (const t of taskTargets) hits[String(t)] = null;
|
|
3833
|
+
for (const [i, s] of ordered.entries()) {
|
|
3834
|
+
cumUsd += s.usd;
|
|
3835
|
+
const best = bests[i];
|
|
3836
|
+
const elapsedMs = (s.endMs ?? s.startMs) - t0;
|
|
3837
|
+
points.push({ elapsedMs, cumUsd, best });
|
|
3838
|
+
for (const t of taskTargets) {
|
|
3839
|
+
if (hits[String(t)] === null && best >= t) {
|
|
3840
|
+
hits[String(t)] = { ms: elapsedMs, shots: points.length, usd: cumUsd };
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
perTask.push({ taskId, strategy, points, hits });
|
|
3845
|
+
}
|
|
3846
|
+
const byStrategy = /* @__PURE__ */ new Map();
|
|
3847
|
+
for (const t of perTask) {
|
|
3848
|
+
const list = byStrategy.get(t.strategy) ?? [];
|
|
3849
|
+
list.push(t);
|
|
3850
|
+
byStrategy.set(t.strategy, list);
|
|
3851
|
+
}
|
|
3852
|
+
const perStrategy = [];
|
|
3853
|
+
for (const [strategy, tasks] of byStrategy) {
|
|
3854
|
+
const totalMs = tasks.reduce((s, t) => s + (t.points[t.points.length - 1]?.elapsedMs ?? 0), 0);
|
|
3855
|
+
const totalUsd = tasks.reduce((s, t) => s + (t.points[t.points.length - 1]?.cumUsd ?? 0), 0);
|
|
3856
|
+
const maxShots = Math.max(0, ...tasks.map((t) => t.points.length));
|
|
3857
|
+
const curveByShot = [];
|
|
3858
|
+
for (let i = 0; i < maxShots; i += 1) {
|
|
3859
|
+
const vals = tasks.map(
|
|
3860
|
+
(t) => t.points[Math.min(i, t.points.length - 1)].best
|
|
3861
|
+
);
|
|
3862
|
+
curveByShot.push(vals.reduce((s, v) => s + v, 0) / vals.length);
|
|
3863
|
+
}
|
|
3864
|
+
const auc = areaUnderCurve(curveByShot);
|
|
3865
|
+
const summaryTargets = opts?.targetFor ? [Number.NaN] : targets;
|
|
3866
|
+
for (const t of summaryTargets) {
|
|
3867
|
+
const key = (taskCurve) => opts?.targetFor ? Object.values(taskCurve.hits)[0] ?? null : taskCurve.hits[String(t)] ?? null;
|
|
3868
|
+
const reached = tasks.filter((x) => key(x) !== null);
|
|
3869
|
+
perStrategy.push({
|
|
3870
|
+
strategy,
|
|
3871
|
+
target: t,
|
|
3872
|
+
tasks: tasks.length,
|
|
3873
|
+
reachedTarget: reached.length,
|
|
3874
|
+
medianTttMs: median(reached.map((x) => key(x).ms)),
|
|
3875
|
+
medianShotsToTarget: median(reached.map((x) => key(x).shots)),
|
|
3876
|
+
ertMs: reached.length > 0 ? totalMs / reached.length : null,
|
|
3877
|
+
erUsd: reached.length > 0 ? totalUsd / reached.length : null,
|
|
3878
|
+
curveByShot,
|
|
3879
|
+
auc
|
|
3880
|
+
});
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
perStrategy.sort((a, b) => a.strategy.localeCompare(b.strategy) || a.target - b.target);
|
|
3884
|
+
return { targets, perTask, perStrategy };
|
|
3885
|
+
}
|
|
3886
|
+
function renderAnytimeTable(report) {
|
|
3887
|
+
const lines = [
|
|
3888
|
+
`anytime metrics \xB7 satisficing targets [${report.targets.join(", ")}] \xB7 ERT = \u03A3 all wall-time / #successes (COCO)`,
|
|
3889
|
+
"strategy \u2265tgt reach med-TTT med-shots ERT(all-in) $/success AUC curve"
|
|
3890
|
+
];
|
|
3891
|
+
for (const s of report.perStrategy) {
|
|
3892
|
+
const curve = s.curveByShot.map((v) => "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588"[Math.min(7, Math.floor(v * 8))]).join("");
|
|
3893
|
+
const tgt = Number.isNaN(s.target) ? "task" : s.target.toFixed(2);
|
|
3894
|
+
lines.push(
|
|
3895
|
+
`${s.strategy.padEnd(19)} ${tgt.padStart(4)} ${String(s.reachedTarget).padStart(4)}/${String(s.tasks).padEnd(3)} ${s.medianTttMs === null ? " \u2014" : `${(s.medianTttMs / 1e3).toFixed(1).padStart(6)}s`} ${s.medianShotsToTarget === null ? " \u2014" : String(s.medianShotsToTarget).padStart(5)} ${s.ertMs === null ? " \u2014" : `${(s.ertMs / 1e3).toFixed(1).padStart(9)}s`} ${s.erUsd === null ? " \u2014" : `$${s.erUsd.toFixed(4)}`} ${s.auc.toFixed(2)} ${curve}`
|
|
3896
|
+
);
|
|
3897
|
+
}
|
|
3898
|
+
return lines.join("\n");
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3901
|
+
// src/runtime/supervise/stop-rules.ts
|
|
3902
|
+
var CONTINUE = { stop: false };
|
|
3903
|
+
function createProgressTracker(opts = {}) {
|
|
3904
|
+
const now = opts.now ?? Date.now;
|
|
3905
|
+
const requireDelivered = opts.requireDelivered ?? true;
|
|
3906
|
+
const minImprovement = opts.minImprovement ?? 0;
|
|
3907
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3908
|
+
const recorded = [];
|
|
3909
|
+
return {
|
|
3910
|
+
record(sample) {
|
|
3911
|
+
if (seen.has(sample.id)) return false;
|
|
3912
|
+
seen.add(sample.id);
|
|
3913
|
+
recorded.push(sample);
|
|
3914
|
+
return true;
|
|
3915
|
+
},
|
|
3916
|
+
samples: () => [...recorded],
|
|
3917
|
+
view(scope, viewOpts) {
|
|
3918
|
+
const objectives = recorded.map(
|
|
3919
|
+
(s) => requireDelivered && !s.delivered ? void 0 : s.objective
|
|
3920
|
+
);
|
|
3921
|
+
const curve = bestSoFar(objectives);
|
|
3922
|
+
const best = curve.length > 0 ? curve[curve.length - 1] : 0;
|
|
3923
|
+
let lastImprovementAt = 0;
|
|
3924
|
+
let lastImprovementIdx = -1;
|
|
3925
|
+
let prev = 0;
|
|
3926
|
+
for (const [i, value] of curve.entries()) {
|
|
3927
|
+
if (value - prev > minImprovement) {
|
|
3928
|
+
lastImprovementAt = recorded[i].at;
|
|
3929
|
+
lastImprovementIdx = i;
|
|
3930
|
+
}
|
|
3931
|
+
prev = value;
|
|
3932
|
+
}
|
|
3933
|
+
const treeView = scope?.view;
|
|
3934
|
+
const workers = [];
|
|
3935
|
+
if (scope && treeView) {
|
|
3936
|
+
for (const node of treeView.nodes) {
|
|
3937
|
+
if (node.status === "done" || node.status === "failed" || node.status === "cancelled") {
|
|
3938
|
+
continue;
|
|
3939
|
+
}
|
|
3940
|
+
if (node.status === "waiting") continue;
|
|
3941
|
+
const p = scope.progress(
|
|
3942
|
+
node.id,
|
|
3943
|
+
viewOpts?.stallAfterMs !== void 0 ? { now: now(), stallAfterMs: viewOpts.stallAfterMs } : { now: now() }
|
|
3944
|
+
);
|
|
3945
|
+
if (p) workers.push(p);
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
return {
|
|
3949
|
+
now: now(),
|
|
3950
|
+
settles: recorded.length,
|
|
3951
|
+
delivered: recorded.filter((s) => s.delivered).length,
|
|
3952
|
+
curve,
|
|
3953
|
+
best,
|
|
3954
|
+
auc: areaUnderCurve(curve),
|
|
3955
|
+
lastSettleAt: recorded.length > 0 ? recorded[recorded.length - 1].at : 0,
|
|
3956
|
+
lastImprovementAt,
|
|
3957
|
+
settlesSinceImprovement: lastImprovementIdx < 0 ? recorded.length : recorded.length - 1 - lastImprovementIdx,
|
|
3958
|
+
workers,
|
|
3959
|
+
inFlight: treeView?.inFlight ?? 0,
|
|
3960
|
+
waiting: treeView?.waiting ?? 0
|
|
3961
|
+
};
|
|
3962
|
+
},
|
|
3963
|
+
evaluate(rule, scope, viewOpts) {
|
|
3964
|
+
return rule(this.view(scope, viewOpts));
|
|
3965
|
+
}
|
|
3966
|
+
};
|
|
3967
|
+
}
|
|
3968
|
+
function sampleFromSettled(settled, at) {
|
|
3969
|
+
if (settled.kind === "down") {
|
|
3970
|
+
return { id: settled.handle.id, at, delivered: false };
|
|
3971
|
+
}
|
|
3972
|
+
return {
|
|
3973
|
+
id: settled.handle.id,
|
|
3974
|
+
at,
|
|
3975
|
+
...settled.verdict?.score !== void 0 ? { objective: settled.verdict.score } : {},
|
|
3976
|
+
delivered: settled.verdict?.valid === true
|
|
3977
|
+
};
|
|
3978
|
+
}
|
|
3979
|
+
function noProgressFor(opts) {
|
|
3980
|
+
if (opts.ms === void 0 && opts.settles === void 0) {
|
|
3981
|
+
throw new ValidationError("noProgressFor: set at least one of { ms, settles }");
|
|
3982
|
+
}
|
|
3983
|
+
if (opts.ms !== void 0 && opts.ms <= 0) {
|
|
3984
|
+
throw new ValidationError("noProgressFor: ms must be > 0");
|
|
3985
|
+
}
|
|
3986
|
+
if (opts.settles !== void 0 && opts.settles < 1) {
|
|
3987
|
+
throw new ValidationError("noProgressFor: settles must be >= 1");
|
|
3988
|
+
}
|
|
3989
|
+
const minSettles = opts.minSettles ?? 1;
|
|
3990
|
+
return (v) => {
|
|
3991
|
+
if (v.settles < minSettles) return CONTINUE;
|
|
3992
|
+
if (opts.settles !== void 0 && v.settlesSinceImprovement >= opts.settles) {
|
|
3993
|
+
return {
|
|
3994
|
+
stop: true,
|
|
3995
|
+
reason: `no-progress: ${v.settlesSinceImprovement} settles with no improvement (limit ${opts.settles}), best=${v.best}`
|
|
3996
|
+
};
|
|
3997
|
+
}
|
|
3998
|
+
if (opts.ms !== void 0 && v.waiting === 0 && v.lastSettleAt > 0) {
|
|
3999
|
+
const idle = v.now - v.lastSettleAt;
|
|
4000
|
+
if (idle >= opts.ms) {
|
|
4001
|
+
return {
|
|
4002
|
+
stop: true,
|
|
4003
|
+
reason: `no-progress: ${idle}ms since the last settlement (limit ${opts.ms}ms)`
|
|
4004
|
+
};
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
return CONTINUE;
|
|
4008
|
+
};
|
|
4009
|
+
}
|
|
4010
|
+
function plateau(opts) {
|
|
4011
|
+
if (!Number.isInteger(opts.window) || opts.window < 1) {
|
|
4012
|
+
throw new ValidationError("plateau: window must be a positive integer");
|
|
4013
|
+
}
|
|
4014
|
+
if (!Number.isFinite(opts.minDelta) || opts.minDelta < 0) {
|
|
4015
|
+
throw new ValidationError("plateau: minDelta must be >= 0");
|
|
4016
|
+
}
|
|
4017
|
+
const minSettles = opts.minSettles ?? opts.window;
|
|
4018
|
+
return (v) => {
|
|
4019
|
+
if (v.settles < minSettles) return CONTINUE;
|
|
4020
|
+
const flat = plateauLength(v.curve, opts.minDelta);
|
|
4021
|
+
if (flat >= opts.window) {
|
|
4022
|
+
return {
|
|
4023
|
+
stop: true,
|
|
4024
|
+
reason: `plateau: best-so-far rose <= ${opts.minDelta} over the last ${flat} settles (window ${opts.window}), best=${v.best}, auc=${v.auc.toFixed(3)}`
|
|
4025
|
+
};
|
|
4026
|
+
}
|
|
4027
|
+
return CONTINUE;
|
|
4028
|
+
};
|
|
4029
|
+
}
|
|
4030
|
+
function allWorkersStalled(opts = {}) {
|
|
4031
|
+
const minWorkers = opts.minWorkers ?? 1;
|
|
4032
|
+
return (v) => {
|
|
4033
|
+
if (v.waiting > 0) return CONTINUE;
|
|
4034
|
+
if (v.workers.length < minWorkers) return CONTINUE;
|
|
4035
|
+
if (!v.workers.every((w) => w.stalled)) return CONTINUE;
|
|
4036
|
+
const worst = Math.max(...v.workers.map((w) => w.idleMs));
|
|
4037
|
+
return {
|
|
4038
|
+
stop: true,
|
|
4039
|
+
reason: `all-stalled: ${v.workers.length} live workers idle, worst ${worst}ms`
|
|
4040
|
+
};
|
|
4041
|
+
};
|
|
4042
|
+
}
|
|
4043
|
+
function anyOf(...rules) {
|
|
4044
|
+
return (v) => {
|
|
4045
|
+
for (const rule of rules) {
|
|
4046
|
+
const d = rule(v);
|
|
4047
|
+
if (d.stop) return d;
|
|
4048
|
+
}
|
|
4049
|
+
return CONTINUE;
|
|
4050
|
+
};
|
|
4051
|
+
}
|
|
4052
|
+
function allOf(...rules) {
|
|
4053
|
+
if (rules.length === 0) throw new ValidationError("allOf: needs at least one rule");
|
|
4054
|
+
return (v) => {
|
|
4055
|
+
const reasons = [];
|
|
4056
|
+
for (const rule of rules) {
|
|
4057
|
+
const d = rule(v);
|
|
4058
|
+
if (!d.stop) return CONTINUE;
|
|
4059
|
+
reasons.push(d.reason);
|
|
4060
|
+
}
|
|
4061
|
+
return { stop: true, reason: reasons.join(" AND ") };
|
|
4062
|
+
};
|
|
4063
|
+
}
|
|
4064
|
+
|
|
2820
4065
|
// src/runtime/supervise/coordination-driver.ts
|
|
2821
4066
|
var distillInstruction = "CONTEXT COMPACTION. Your detailed turn-by-turn history is about to be discarded to free your context window. Write a COMPLETE, compact handoff note for your future self so you can keep going without it. Cover: (1) what you have accomplished; (2) every worker you spawned and its current status/result; (3) what subtasks remain unfinished, failing, or unverified \u2014 be specific and exhaustive here, this is the part you must not lose; (4) your immediate next action. Do not call any tools; respond with the note only.";
|
|
2822
4067
|
function summarizeRoster(view, settled) {
|
|
@@ -2842,6 +4087,17 @@ function deadlinePassed(scope, now) {
|
|
|
2842
4087
|
const b = scope.budget;
|
|
2843
4088
|
return b.deadlineMs > 0 && now() >= b.deadlineMs;
|
|
2844
4089
|
}
|
|
4090
|
+
function progressStop(tracker, rule, coord, scope, now, stallAfterMs) {
|
|
4091
|
+
for (const w of coord.settled()) {
|
|
4092
|
+
tracker.record({
|
|
4093
|
+
id: w.id,
|
|
4094
|
+
at: w.settledAt ?? now(),
|
|
4095
|
+
...w.score !== void 0 ? { objective: w.score } : {},
|
|
4096
|
+
delivered: w.status === "done" && w.valid === true
|
|
4097
|
+
});
|
|
4098
|
+
}
|
|
4099
|
+
return tracker.evaluate(rule, scope, stallAfterMs !== void 0 ? { stallAfterMs } : void 0);
|
|
4100
|
+
}
|
|
2845
4101
|
function driverAgent(opts) {
|
|
2846
4102
|
if (typeof opts.brain !== "function") {
|
|
2847
4103
|
throw new ValidationError("driverAgent: opts.brain must be a function");
|
|
@@ -2881,7 +4137,9 @@ function driverAgent(opts) {
|
|
|
2881
4137
|
perWorker: opts.perWorker,
|
|
2882
4138
|
...opts.maxLiveWorkers !== void 0 ? { maxLiveWorkers: opts.maxLiveWorkers } : {},
|
|
2883
4139
|
...opts.analysts ? { analysts: opts.analysts } : {},
|
|
2884
|
-
...opts.analyzeOnSettle ? { analyzeOnSettle: opts.analyzeOnSettle } : {}
|
|
4140
|
+
...opts.analyzeOnSettle ? { analyzeOnSettle: opts.analyzeOnSettle } : {},
|
|
4141
|
+
...opts.watchWorkers ? { watchWorkers: opts.watchWorkers } : {},
|
|
4142
|
+
...opts.stallAfterMs !== void 0 ? { stallAfterMs: opts.stallAfterMs } : {}
|
|
2885
4143
|
});
|
|
2886
4144
|
const byName = new Map(coord.tools.map((t) => [t.name, t]));
|
|
2887
4145
|
const toolSpecs = [
|
|
@@ -2896,6 +4154,8 @@ function driverAgent(opts) {
|
|
|
2896
4154
|
}))
|
|
2897
4155
|
];
|
|
2898
4156
|
const system = typeof opts.systemPrompt === "function" ? opts.systemPrompt(task) : opts.systemPrompt;
|
|
4157
|
+
const tracker = opts.stopRule ? createProgressTracker({ now }) : void 0;
|
|
4158
|
+
let progressStopReason;
|
|
2899
4159
|
let driverTurn = 0;
|
|
2900
4160
|
const meteredBrain = async (messages, tools, detail) => {
|
|
2901
4161
|
const res = await opts.brain(messages, tools);
|
|
@@ -2971,7 +4231,26 @@ Summary unavailable: ${errMessage(e)}`;
|
|
|
2971
4231
|
// that can no longer spawn (pool starved) or has run past the deadline stops here instead of
|
|
2972
4232
|
// burning turns. Checked before each inference turn.
|
|
2973
4233
|
hooks: {
|
|
2974
|
-
stopBefore: () =>
|
|
4234
|
+
stopBefore: () => {
|
|
4235
|
+
if (coord.isStopped() || scope.signal.aborted || poolStarved(scope, opts.perWorker) || deadlinePassed(scope, now)) {
|
|
4236
|
+
return true;
|
|
4237
|
+
}
|
|
4238
|
+
if (!opts.stopRule || !tracker) return false;
|
|
4239
|
+
const decision = progressStop(
|
|
4240
|
+
tracker,
|
|
4241
|
+
opts.stopRule,
|
|
4242
|
+
coord,
|
|
4243
|
+
scope,
|
|
4244
|
+
now,
|
|
4245
|
+
opts.stallAfterMs
|
|
4246
|
+
);
|
|
4247
|
+
if (!decision.stop) return false;
|
|
4248
|
+
if (progressStopReason === void 0) {
|
|
4249
|
+
progressStopReason = decision.reason;
|
|
4250
|
+
opts.onProgressStop?.(decision.reason);
|
|
4251
|
+
}
|
|
4252
|
+
return true;
|
|
4253
|
+
}
|
|
2975
4254
|
}
|
|
2976
4255
|
});
|
|
2977
4256
|
await coord.drainResolved();
|
|
@@ -3755,15 +5034,15 @@ function randomTaskId() {
|
|
|
3755
5034
|
return `dlg-${t}-${r}`;
|
|
3756
5035
|
}
|
|
3757
5036
|
function hashIdempotencyInput(value) {
|
|
3758
|
-
let
|
|
5037
|
+
let str2;
|
|
3759
5038
|
try {
|
|
3760
|
-
|
|
5039
|
+
str2 = JSON.stringify(canonicalize(value));
|
|
3761
5040
|
} catch {
|
|
3762
|
-
|
|
5041
|
+
str2 = String(value);
|
|
3763
5042
|
}
|
|
3764
5043
|
let h = 2166136261;
|
|
3765
|
-
for (let i = 0; i <
|
|
3766
|
-
h ^=
|
|
5044
|
+
for (let i = 0; i < str2.length; i += 1) {
|
|
5045
|
+
h ^= str2.charCodeAt(i);
|
|
3767
5046
|
h = Math.imul(h, 16777619);
|
|
3768
5047
|
}
|
|
3769
5048
|
return (h >>> 0).toString(16).padStart(8, "0");
|
|
@@ -4774,6 +6053,8 @@ async function serveCoordinationMcp(opts) {
|
|
|
4774
6053
|
...opts.awaitTimeoutMs !== void 0 ? { awaitTimeoutMs: opts.awaitTimeoutMs } : {},
|
|
4775
6054
|
...opts.analysts ? { analysts: opts.analysts } : {},
|
|
4776
6055
|
...opts.analyzeOnSettle ? { analyzeOnSettle: opts.analyzeOnSettle } : {},
|
|
6056
|
+
...opts.watchWorkers ? { watchWorkers: opts.watchWorkers } : {},
|
|
6057
|
+
...opts.stallAfterMs !== void 0 ? { stallAfterMs: opts.stallAfterMs } : {},
|
|
4777
6058
|
...opts.onEvent ? { onEvent: opts.onEvent } : {},
|
|
4778
6059
|
...opts.questionPolicy ? { questionPolicy: opts.questionPolicy } : {}
|
|
4779
6060
|
});
|
|
@@ -4876,6 +6157,10 @@ function supervisorAgent(profile, deps) {
|
|
|
4876
6157
|
...deps.executeExtraTool ? { executeExtraTool: deps.executeExtraTool } : {},
|
|
4877
6158
|
...deps.analysts ? { analysts: deps.analysts } : {},
|
|
4878
6159
|
...deps.analyzeOnSettle ? { analyzeOnSettle: deps.analyzeOnSettle } : {},
|
|
6160
|
+
...deps.watchWorkers ? { watchWorkers: deps.watchWorkers } : {},
|
|
6161
|
+
...deps.stallAfterMs !== void 0 ? { stallAfterMs: deps.stallAfterMs } : {},
|
|
6162
|
+
...deps.stopRule ? { stopRule: deps.stopRule } : {},
|
|
6163
|
+
...deps.onProgressStop ? { onProgressStop: deps.onProgressStop } : {},
|
|
4879
6164
|
...deps.maxTurns !== void 0 ? { maxTurns: deps.maxTurns } : {},
|
|
4880
6165
|
...deps.compaction ? { compaction: deps.compaction } : {}
|
|
4881
6166
|
});
|
|
@@ -4896,7 +6181,9 @@ function supervisorAgent(profile, deps) {
|
|
|
4896
6181
|
perWorker: deps.perWorker,
|
|
4897
6182
|
...deps.maxLiveWorkers !== void 0 ? { maxLiveWorkers: deps.maxLiveWorkers } : {},
|
|
4898
6183
|
...deps.analysts ? { analysts: deps.analysts } : {},
|
|
4899
|
-
...deps.analyzeOnSettle ? { analyzeOnSettle: deps.analyzeOnSettle } : {}
|
|
6184
|
+
...deps.analyzeOnSettle ? { analyzeOnSettle: deps.analyzeOnSettle } : {},
|
|
6185
|
+
...deps.watchWorkers ? { watchWorkers: deps.watchWorkers } : {},
|
|
6186
|
+
...deps.stallAfterMs !== void 0 ? { stallAfterMs: deps.stallAfterMs } : {}
|
|
4900
6187
|
});
|
|
4901
6188
|
try {
|
|
4902
6189
|
await driveHarness({ profile, task, scope, coordinationMcpUrl: mcp.url });
|
|
@@ -4943,7 +6230,7 @@ function supervise(profile, task, opts) {
|
|
|
4943
6230
|
typeof backendModel === "string" ? backendModel : void 0,
|
|
4944
6231
|
opts.allowedModels
|
|
4945
6232
|
);
|
|
4946
|
-
const ctx = createInMemoryRunContext({ withDriver: true });
|
|
6233
|
+
const ctx = opts.runDir !== void 0 ? createFileRunContext(opts.runDir, { withDriver: true }) : createInMemoryRunContext({ withDriver: true });
|
|
4947
6234
|
const blobs = opts.blobs ?? ctx.blobs;
|
|
4948
6235
|
const perWorker = opts.perWorker ?? defaultPerWorker(opts.budget);
|
|
4949
6236
|
let makeWorkerAgent = opts.makeWorkerAgent;
|
|
@@ -4967,16 +6254,22 @@ function supervise(profile, task, opts) {
|
|
|
4967
6254
|
...opts.executeExtraTool ? { executeExtraTool: opts.executeExtraTool } : {},
|
|
4968
6255
|
...opts.analysts ? { analysts: opts.analysts } : {},
|
|
4969
6256
|
...opts.analyzeOnSettle ? { analyzeOnSettle: opts.analyzeOnSettle } : {},
|
|
6257
|
+
...opts.watchWorkers ? { watchWorkers: opts.watchWorkers } : {},
|
|
6258
|
+
...opts.stallAfterMs !== void 0 ? { stallAfterMs: opts.stallAfterMs } : {},
|
|
6259
|
+
...opts.stopRule ? { stopRule: opts.stopRule } : {},
|
|
6260
|
+
...opts.onProgressStop ? { onProgressStop: opts.onProgressStop } : {},
|
|
4970
6261
|
...opts.maxTurns !== void 0 ? { maxTurns: opts.maxTurns } : {},
|
|
4971
6262
|
...opts.compaction ? { compaction: opts.compaction } : {}
|
|
4972
6263
|
});
|
|
4973
6264
|
return createSupervisor().run(agent, task, {
|
|
4974
6265
|
budget: opts.budget,
|
|
4975
6266
|
runId: opts.runId ?? "supervise",
|
|
4976
|
-
journal: ctx.journal,
|
|
6267
|
+
journal: opts.journal ?? ctx.journal,
|
|
4977
6268
|
blobs,
|
|
4978
6269
|
executors: ctx.executors,
|
|
4979
6270
|
maxDepth: opts.maxDepth ?? 8,
|
|
6271
|
+
...opts.probes ? { probes: opts.probes } : {},
|
|
6272
|
+
...ctx.resume === true ? { resume: true } : {},
|
|
4980
6273
|
...opts.now ? { now: opts.now } : {}
|
|
4981
6274
|
});
|
|
4982
6275
|
}
|
|
@@ -5021,13 +6314,40 @@ export {
|
|
|
5021
6314
|
createSandboxForSpec,
|
|
5022
6315
|
defaultSelectWinner,
|
|
5023
6316
|
createInbox,
|
|
6317
|
+
decodeToolPart,
|
|
6318
|
+
createPushTraceSource,
|
|
6319
|
+
sandboxSessionTraceSource,
|
|
6320
|
+
PI_RUNTIME,
|
|
6321
|
+
piSeamKey,
|
|
6322
|
+
piExecutor,
|
|
6323
|
+
DEFAULT_SANDBOX_STEERING_MAX_TURNS,
|
|
6324
|
+
createSteerableSandboxSession,
|
|
5024
6325
|
createWorktreeCliExecutor,
|
|
5025
6326
|
cliWorktreeExecutor,
|
|
5026
6327
|
createExecutor,
|
|
5027
6328
|
createExecutorRegistry,
|
|
5028
6329
|
createInMemoryRunContext,
|
|
6330
|
+
createFileRunContext,
|
|
6331
|
+
defaultToolDetectors,
|
|
6332
|
+
watchTrace,
|
|
6333
|
+
rollingDispatch,
|
|
6334
|
+
freeSlots,
|
|
6335
|
+
effectiveConcurrency,
|
|
6336
|
+
queueOf,
|
|
5029
6337
|
createEventBus,
|
|
5030
6338
|
createCoordinationTools,
|
|
6339
|
+
bestSoFar,
|
|
6340
|
+
areaUnderCurve,
|
|
6341
|
+
plateauLength,
|
|
6342
|
+
anytimeReport,
|
|
6343
|
+
renderAnytimeTable,
|
|
6344
|
+
createProgressTracker,
|
|
6345
|
+
sampleFromSettled,
|
|
6346
|
+
noProgressFor,
|
|
6347
|
+
plateau,
|
|
6348
|
+
allWorkersStalled,
|
|
6349
|
+
anyOf,
|
|
6350
|
+
allOf,
|
|
5031
6351
|
driverAgent,
|
|
5032
6352
|
finalizeBestDelivered,
|
|
5033
6353
|
InMemoryFeedbackStore,
|
|
@@ -5084,4 +6404,4 @@ export {
|
|
|
5084
6404
|
workerFromBackend,
|
|
5085
6405
|
supervise
|
|
5086
6406
|
};
|
|
5087
|
-
//# sourceMappingURL=chunk-
|
|
6407
|
+
//# sourceMappingURL=chunk-EAQ5YRRY.js.map
|