@dougbots/avenor-core 0.9.0 → 0.10.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 +27 -1
- package/dist/index.d.ts +294 -7
- package/dist/index.js +1031 -90
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -6,6 +6,8 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import * as crypto from "node:crypto";
|
|
9
|
+
import { umpire } from "@umpire/core";
|
|
10
|
+
import { fromJson } from "@umpire/json";
|
|
9
11
|
//#region src/client.ts
|
|
10
12
|
function normalizeSubscribeOptions(options) {
|
|
11
13
|
if (!options) return void 0;
|
|
@@ -280,6 +282,107 @@ var Client = class {
|
|
|
280
282
|
const params = runtimeId ? { runtime_id: runtimeId } : void 0;
|
|
281
283
|
await this.call("cancel", params);
|
|
282
284
|
}
|
|
285
|
+
async workflowStatus(workflowId) {
|
|
286
|
+
return this.call("workflow.status", { workflow_id: workflowId });
|
|
287
|
+
}
|
|
288
|
+
async workflowWait(workflowId, timeoutMs) {
|
|
289
|
+
const params = { workflow_id: workflowId };
|
|
290
|
+
if (timeoutMs !== void 0) params.timeout_ms = timeoutMs;
|
|
291
|
+
return this.call("workflow.wait", params);
|
|
292
|
+
}
|
|
293
|
+
async workflowInspect(workflowId) {
|
|
294
|
+
return this.call("workflow.inspect", { workflow_id: workflowId });
|
|
295
|
+
}
|
|
296
|
+
async workflowEvents(workflowId, opts) {
|
|
297
|
+
const params = { workflow_id: workflowId };
|
|
298
|
+
if (opts?.afterSeq !== void 0) params.after_seq = opts.afterSeq;
|
|
299
|
+
if (opts?.limit !== void 0) params.limit = opts.limit;
|
|
300
|
+
return this.call("workflow.events", params);
|
|
301
|
+
}
|
|
302
|
+
async workflowCommand(workflowId, command) {
|
|
303
|
+
return this.call("workflow.command", {
|
|
304
|
+
workflow_id: workflowId,
|
|
305
|
+
command
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
async workflowComplete(params) {
|
|
309
|
+
const command = {
|
|
310
|
+
op: "complete",
|
|
311
|
+
node_id: params.node_id,
|
|
312
|
+
activation_id: params.activation_id,
|
|
313
|
+
attempt_id: params.attempt_id,
|
|
314
|
+
lease_id: params.lease_id,
|
|
315
|
+
owner_token: params.owner_token,
|
|
316
|
+
outcome: params.outcome
|
|
317
|
+
};
|
|
318
|
+
if (params.outputs !== void 0) command.outputs = params.outputs;
|
|
319
|
+
if (params.artifacts !== void 0) command.artifacts = params.artifacts;
|
|
320
|
+
return this.workflowCommand(params.workflow_id, command);
|
|
321
|
+
}
|
|
322
|
+
async workflowGate(params) {
|
|
323
|
+
const command = {
|
|
324
|
+
op: "gate",
|
|
325
|
+
node_id: params.node_id,
|
|
326
|
+
activation_id: params.activation_id,
|
|
327
|
+
gate_id: params.gate_id,
|
|
328
|
+
operation: params.operation
|
|
329
|
+
};
|
|
330
|
+
const optional = [
|
|
331
|
+
["actor", params.actor],
|
|
332
|
+
["reason", params.reason],
|
|
333
|
+
["outcome", params.outcome],
|
|
334
|
+
["subject", params.subject],
|
|
335
|
+
["poll_id", params.poll_id],
|
|
336
|
+
["source", params.source],
|
|
337
|
+
["result", params.result],
|
|
338
|
+
["response_hash", params.response_hash],
|
|
339
|
+
["observed_at", params.observed_at],
|
|
340
|
+
["evidence_ids", params.evidence_ids]
|
|
341
|
+
];
|
|
342
|
+
for (const [key, value] of optional) if (value !== void 0) command[key] = value;
|
|
343
|
+
return this.workflowCommand(params.workflow_id, command);
|
|
344
|
+
}
|
|
345
|
+
async workflowHeartbeat(params) {
|
|
346
|
+
const command = {
|
|
347
|
+
op: "heartbeat",
|
|
348
|
+
node_id: params.node_id,
|
|
349
|
+
lease_id: params.lease_id,
|
|
350
|
+
owner_token: params.owner_token
|
|
351
|
+
};
|
|
352
|
+
if (params.activation_id !== void 0) command.activation_id = params.activation_id;
|
|
353
|
+
return this.workflowCommand(params.workflow_id, command);
|
|
354
|
+
}
|
|
355
|
+
async brokerSend(fromRunId, toRunId, message, role) {
|
|
356
|
+
await this.call("broker_send", {
|
|
357
|
+
from_run_id: fromRunId,
|
|
358
|
+
to_run_id: toRunId,
|
|
359
|
+
message,
|
|
360
|
+
role: role ?? "agent"
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
async brokerAsk(toRunId, message, role) {
|
|
364
|
+
return await this.call("broker_ask", {
|
|
365
|
+
to_run_id: toRunId,
|
|
366
|
+
message,
|
|
367
|
+
role: role ?? "agent"
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
async brokerPeers() {
|
|
371
|
+
return (await this.call("broker_peers")).sessions ?? [];
|
|
372
|
+
}
|
|
373
|
+
async brokerCancel(messageId) {
|
|
374
|
+
await this.call("broker_cancel", { message_id: messageId });
|
|
375
|
+
}
|
|
376
|
+
async brokerReceive() {
|
|
377
|
+
return await this.call("broker_receive");
|
|
378
|
+
}
|
|
379
|
+
async brokerReply(toRunId, replyTo, message) {
|
|
380
|
+
await this.call("broker_reply", {
|
|
381
|
+
to_run_id: toRunId,
|
|
382
|
+
reply_to: replyTo,
|
|
383
|
+
message
|
|
384
|
+
});
|
|
385
|
+
}
|
|
283
386
|
isClosed() {
|
|
284
387
|
return this.closed || this.socket.destroyed || this.socket.writableEnded;
|
|
285
388
|
}
|
|
@@ -1074,6 +1177,95 @@ function observeRun(client, options = {}) {
|
|
|
1074
1177
|
};
|
|
1075
1178
|
}
|
|
1076
1179
|
//#endregion
|
|
1180
|
+
//#region ../../schemas/spawn_selection.umpire.json
|
|
1181
|
+
var spawn_selection_umpire_default = {
|
|
1182
|
+
version: 1,
|
|
1183
|
+
conditions: { "rosterConfigured": {
|
|
1184
|
+
"type": "boolean",
|
|
1185
|
+
"description": "Whether the caller has a configured roster context"
|
|
1186
|
+
} },
|
|
1187
|
+
fields: {
|
|
1188
|
+
"agent": { "isEmpty": "string" },
|
|
1189
|
+
"model": { "isEmpty": "string" },
|
|
1190
|
+
"backend": { "isEmpty": "string" },
|
|
1191
|
+
"roster_file": { "isEmpty": "string" },
|
|
1192
|
+
"roster_entry": { "isEmpty": "string" }
|
|
1193
|
+
},
|
|
1194
|
+
rules: [
|
|
1195
|
+
{
|
|
1196
|
+
"type": "requires",
|
|
1197
|
+
"field": "roster_file",
|
|
1198
|
+
"dependency": "roster_entry",
|
|
1199
|
+
"reason": "roster_file requires roster_entry"
|
|
1200
|
+
},
|
|
1201
|
+
{
|
|
1202
|
+
"type": "fairWhen",
|
|
1203
|
+
"field": "roster_entry",
|
|
1204
|
+
"when": {
|
|
1205
|
+
"op": "or",
|
|
1206
|
+
"exprs": [{
|
|
1207
|
+
"op": "present",
|
|
1208
|
+
"field": "roster_file"
|
|
1209
|
+
}, {
|
|
1210
|
+
"op": "cond",
|
|
1211
|
+
"condition": "rosterConfigured"
|
|
1212
|
+
}]
|
|
1213
|
+
},
|
|
1214
|
+
"reason": "roster_entry requires roster_file unless a roster context is configured"
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
"type": "disables",
|
|
1218
|
+
"source": "roster_entry",
|
|
1219
|
+
"targets": [
|
|
1220
|
+
"agent",
|
|
1221
|
+
"model",
|
|
1222
|
+
"backend"
|
|
1223
|
+
],
|
|
1224
|
+
"reason": "direct identity fields are disabled in roster mode"
|
|
1225
|
+
}
|
|
1226
|
+
]
|
|
1227
|
+
};
|
|
1228
|
+
//#endregion
|
|
1229
|
+
//#region src/spawn-selection.ts
|
|
1230
|
+
const selectorFields = [
|
|
1231
|
+
"agent",
|
|
1232
|
+
"model",
|
|
1233
|
+
"backend",
|
|
1234
|
+
"roster_file",
|
|
1235
|
+
"roster_entry"
|
|
1236
|
+
];
|
|
1237
|
+
const selectorFieldSet = new Set(selectorFields);
|
|
1238
|
+
const parsedSchema = fromJson(spawn_selection_umpire_default);
|
|
1239
|
+
const spawnSelectionUmpire = umpire({
|
|
1240
|
+
fields: parsedSchema.fields,
|
|
1241
|
+
rules: parsedSchema.rules
|
|
1242
|
+
});
|
|
1243
|
+
function assertInput(input) {
|
|
1244
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) throw new Error("invalid spawn selector: expected an object");
|
|
1245
|
+
for (const key of Object.keys(input)) {
|
|
1246
|
+
if (!selectorFieldSet.has(key)) throw new Error(`invalid spawn selector: unknown field "${key}"`);
|
|
1247
|
+
const value = input[key];
|
|
1248
|
+
if (value !== void 0 && typeof value !== "string") throw new Error(`invalid spawn selector: ${key} must be a string`);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
/** Validate the direct-versus-roster selector contract. */
|
|
1252
|
+
function validateSpawnSelection(input, rosterConfigured = false) {
|
|
1253
|
+
assertInput(input);
|
|
1254
|
+
if (typeof rosterConfigured !== "boolean") throw new Error("invalid spawn selector: rosterConfigured must be a boolean");
|
|
1255
|
+
const values = {};
|
|
1256
|
+
for (const field of selectorFields) {
|
|
1257
|
+
const value = input[field];
|
|
1258
|
+
if (value !== void 0 && value !== "") values[field] = value;
|
|
1259
|
+
}
|
|
1260
|
+
const availability = spawnSelectionUmpire.check(values, { rosterConfigured });
|
|
1261
|
+
for (const field of selectorFields) {
|
|
1262
|
+
const value = values[field];
|
|
1263
|
+
const status = availability[field];
|
|
1264
|
+
if (value === void 0 || status?.enabled && status.fair) continue;
|
|
1265
|
+
throw new Error(`invalid spawn selector: ${status?.reason ?? `${field} is disabled`}`);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
//#endregion
|
|
1077
1269
|
//#region src/paths.ts
|
|
1078
1270
|
function avenorHome() {
|
|
1079
1271
|
return process.env.AVENOR_HOME || path.join(os.homedir(), ".avenor");
|
|
@@ -1098,6 +1290,37 @@ function ensureRunPaths(runId) {
|
|
|
1098
1290
|
}
|
|
1099
1291
|
//#endregion
|
|
1100
1292
|
//#region src/supervisor.ts
|
|
1293
|
+
function retainLiveIdentity(runInfo, liveStatus) {
|
|
1294
|
+
const value = (candidate) => typeof candidate === "string" && candidate.length > 0 ? candidate : void 0;
|
|
1295
|
+
const liveIdentity = (effectiveKey, directKey) => {
|
|
1296
|
+
if (typeof liveStatus[effectiveKey] === "string") return {
|
|
1297
|
+
present: true,
|
|
1298
|
+
value: value(liveStatus[effectiveKey])
|
|
1299
|
+
};
|
|
1300
|
+
if (typeof liveStatus[directKey] === "string") return {
|
|
1301
|
+
present: true,
|
|
1302
|
+
value: value(liveStatus[directKey])
|
|
1303
|
+
};
|
|
1304
|
+
return { present: false };
|
|
1305
|
+
};
|
|
1306
|
+
const effectiveAgent = liveIdentity("effective_agent", "agent");
|
|
1307
|
+
const effectiveModel = liveIdentity("effective_model", "model");
|
|
1308
|
+
const effectiveBackend = liveIdentity("effective_backend", "backend");
|
|
1309
|
+
const sessionId = value(liveStatus.session_id);
|
|
1310
|
+
const rosterFilePresent = typeof liveStatus.roster_file === "string";
|
|
1311
|
+
const rosterFile = value(liveStatus.roster_file);
|
|
1312
|
+
const rosterEntryPresent = typeof liveStatus.roster_entry === "string";
|
|
1313
|
+
const rosterEntry = value(liveStatus.roster_entry);
|
|
1314
|
+
const agentProfilePresent = typeof liveStatus.agent_profile === "string";
|
|
1315
|
+
const agentProfile = value(liveStatus.agent_profile);
|
|
1316
|
+
if (effectiveAgent.present) runInfo.agent = runInfo.effectiveAgent = effectiveAgent.value;
|
|
1317
|
+
if (effectiveModel.present) runInfo.model = runInfo.effectiveModel = effectiveModel.value;
|
|
1318
|
+
if (effectiveBackend.present) runInfo.backend = runInfo.effectiveBackend = effectiveBackend.value;
|
|
1319
|
+
if (sessionId) runInfo.sessionId = sessionId;
|
|
1320
|
+
if (rosterFilePresent) runInfo.rosterFile = rosterFile;
|
|
1321
|
+
if (rosterEntryPresent) runInfo.rosterEntry = rosterEntry;
|
|
1322
|
+
if (agentProfilePresent) runInfo.agentProfile = agentProfile;
|
|
1323
|
+
}
|
|
1101
1324
|
function findAvenorBinary() {
|
|
1102
1325
|
const envBin = process.env.AVENOR_BIN;
|
|
1103
1326
|
if (envBin) try {
|
|
@@ -1154,6 +1377,7 @@ var Supervisor = class Supervisor {
|
|
|
1154
1377
|
crashed = false;
|
|
1155
1378
|
crashCode = null;
|
|
1156
1379
|
runs = /* @__PURE__ */ new Map();
|
|
1380
|
+
aliases = /* @__PURE__ */ new Map();
|
|
1157
1381
|
binaryPath;
|
|
1158
1382
|
callTimeoutMs;
|
|
1159
1383
|
constructor(binaryPath, socketPath, opts) {
|
|
@@ -1292,7 +1516,14 @@ var Supervisor = class Supervisor {
|
|
|
1292
1516
|
}
|
|
1293
1517
|
return this.client;
|
|
1294
1518
|
}
|
|
1295
|
-
async spawn(params, runId = crypto.randomUUID()) {
|
|
1519
|
+
async spawn(params, runId = crypto.randomUUID(), metadata) {
|
|
1520
|
+
if (!(typeof params.loop_file === "string" || typeof params.team_file === "string")) validateSpawnSelection({
|
|
1521
|
+
agent: params.agent,
|
|
1522
|
+
model: params.model,
|
|
1523
|
+
backend: params.backend,
|
|
1524
|
+
roster_file: params.roster_file,
|
|
1525
|
+
roster_entry: params.roster_entry
|
|
1526
|
+
});
|
|
1296
1527
|
const client = this.getClient();
|
|
1297
1528
|
const { sentinelPath, eventLogPath } = ensureRunPaths(runId);
|
|
1298
1529
|
const spawnParams = {
|
|
@@ -1301,26 +1532,60 @@ var Supervisor = class Supervisor {
|
|
|
1301
1532
|
sentinel_file: sentinelPath,
|
|
1302
1533
|
on_event: eventLogPath
|
|
1303
1534
|
};
|
|
1535
|
+
if (spawnParams.roster_entry) {
|
|
1536
|
+
for (const field of [
|
|
1537
|
+
"agent",
|
|
1538
|
+
"model",
|
|
1539
|
+
"backend"
|
|
1540
|
+
]) if (spawnParams[field] === "") delete spawnParams[field];
|
|
1541
|
+
}
|
|
1304
1542
|
const result = await client.spawn(spawnParams);
|
|
1543
|
+
let identityResult = result;
|
|
1544
|
+
if (spawnParams.roster_entry && result.runtime_id) {
|
|
1545
|
+
const hasBackend = [result.effective_backend, result.backend].some((value) => typeof value === "string" && value.length > 0);
|
|
1546
|
+
const hasAgentOrModel = [
|
|
1547
|
+
result.effective_agent,
|
|
1548
|
+
result.agent,
|
|
1549
|
+
result.effective_model,
|
|
1550
|
+
result.model
|
|
1551
|
+
].some((value) => typeof value === "string" && value.length > 0);
|
|
1552
|
+
if (!(hasBackend && hasAgentOrModel)) try {
|
|
1553
|
+
identityResult = {
|
|
1554
|
+
...result,
|
|
1555
|
+
...await client.status(result.runtime_id)
|
|
1556
|
+
};
|
|
1557
|
+
} catch {}
|
|
1558
|
+
}
|
|
1559
|
+
const value = (candidate) => typeof candidate === "string" && candidate.length > 0 ? candidate : void 0;
|
|
1560
|
+
const effectiveAgent = metadata?.effectiveAgent ?? value(identityResult.effective_agent) ?? value(identityResult.agent) ?? value(spawnParams.agent);
|
|
1561
|
+
const effectiveModel = metadata?.effectiveModel ?? value(identityResult.effective_model) ?? value(identityResult.model) ?? value(spawnParams.model);
|
|
1562
|
+
const effectiveBackend = metadata?.effectiveBackend ?? value(identityResult.effective_backend) ?? value(identityResult.backend) ?? value(spawnParams.backend);
|
|
1563
|
+
const rosterFile = metadata?.rosterFile ?? value(identityResult.roster_file) ?? value(spawnParams.roster_file);
|
|
1564
|
+
const rosterEntry = metadata?.rosterEntry ?? value(identityResult.roster_entry) ?? value(spawnParams.roster_entry);
|
|
1305
1565
|
const runInfo = {
|
|
1306
1566
|
runId,
|
|
1307
1567
|
label: spawnParams.label ?? runId,
|
|
1308
1568
|
sentinelPath: result.sentinel_file ?? sentinelPath,
|
|
1309
1569
|
eventLogPath: result.on_event ?? eventLogPath,
|
|
1310
|
-
runtimeId:
|
|
1311
|
-
sessionId:
|
|
1312
|
-
agent:
|
|
1313
|
-
agentProfile: spawnParams.agent_profile,
|
|
1314
|
-
backend:
|
|
1315
|
-
model:
|
|
1570
|
+
runtimeId: value(identityResult.runtime_id),
|
|
1571
|
+
sessionId: value(identityResult.session_id),
|
|
1572
|
+
agent: effectiveAgent,
|
|
1573
|
+
agentProfile: value(identityResult.agent_profile) ?? spawnParams.agent_profile,
|
|
1574
|
+
backend: effectiveBackend,
|
|
1575
|
+
model: effectiveModel,
|
|
1576
|
+
effectiveAgent,
|
|
1577
|
+
effectiveModel,
|
|
1578
|
+
effectiveBackend,
|
|
1579
|
+
rosterFile,
|
|
1580
|
+
rosterEntry,
|
|
1316
1581
|
thinking: spawnParams.thinking,
|
|
1317
1582
|
dir: spawnParams.dir,
|
|
1318
1583
|
brokerUrl: result.broker_url,
|
|
1319
1584
|
parentToken: result.parent_token,
|
|
1320
1585
|
autoApprove: spawnParams.auto_approve
|
|
1321
1586
|
};
|
|
1322
|
-
this.runs.set(runInfo.label, runInfo);
|
|
1323
1587
|
this.runs.set(runId, runInfo);
|
|
1588
|
+
this.aliases.set(runInfo.label, runInfo);
|
|
1324
1589
|
return runInfo;
|
|
1325
1590
|
}
|
|
1326
1591
|
async close(options = {}) {
|
|
@@ -1343,6 +1608,7 @@ var Supervisor = class Supervisor {
|
|
|
1343
1608
|
this.crashed = false;
|
|
1344
1609
|
this.crashCode = null;
|
|
1345
1610
|
this.runs.clear();
|
|
1611
|
+
this.aliases.clear();
|
|
1346
1612
|
Supervisor.instance = null;
|
|
1347
1613
|
}
|
|
1348
1614
|
};
|
|
@@ -1369,6 +1635,9 @@ function validateSupervisorSocketPath(supervisorId) {
|
|
|
1369
1635
|
if (parent !== root || !/^avenor-mcp-[^/\\]+\.sock$/.test(basename)) throw new Error(`invalid supervisorId: ${supervisorId}`);
|
|
1370
1636
|
return path.join(parent, basename);
|
|
1371
1637
|
}
|
|
1638
|
+
function validateObservedAt(observedAt) {
|
|
1639
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(observedAt)) throw new Error("observed_at must be an RFC3339 timestamp");
|
|
1640
|
+
}
|
|
1372
1641
|
//#endregion
|
|
1373
1642
|
//#region src/tools/get-supervisor-client.ts
|
|
1374
1643
|
async function getSupervisorClient(supervisorId) {
|
|
@@ -1388,8 +1657,125 @@ async function getSupervisorClient(supervisorId) {
|
|
|
1388
1657
|
};
|
|
1389
1658
|
}
|
|
1390
1659
|
//#endregion
|
|
1660
|
+
//#region src/tools/run-registry.ts
|
|
1661
|
+
const externalRuns = /* @__PURE__ */ new Map();
|
|
1662
|
+
function runsFor(supervisorId) {
|
|
1663
|
+
let runs = externalRuns.get(supervisorId);
|
|
1664
|
+
if (!runs) {
|
|
1665
|
+
runs = {
|
|
1666
|
+
byRunId: /* @__PURE__ */ new Map(),
|
|
1667
|
+
byAlias: /* @__PURE__ */ new Map()
|
|
1668
|
+
};
|
|
1669
|
+
externalRuns.set(supervisorId, runs);
|
|
1670
|
+
}
|
|
1671
|
+
return runs;
|
|
1672
|
+
}
|
|
1673
|
+
function indexRun(runInfo) {
|
|
1674
|
+
const runs = runsFor(runInfo.supervisorId);
|
|
1675
|
+
runs.byRunId.set(runInfo.runId, runInfo);
|
|
1676
|
+
runs.byAlias.set(runInfo.label, runInfo);
|
|
1677
|
+
if (runInfo.runtimeId) runs.byAlias.set(runInfo.runtimeId, runInfo);
|
|
1678
|
+
}
|
|
1679
|
+
function isSafeRunReference(reference) {
|
|
1680
|
+
return /^[a-zA-Z0-9_-]+$/.test(reference);
|
|
1681
|
+
}
|
|
1682
|
+
function externalRunMetadataPath(runId) {
|
|
1683
|
+
if (!isSafeRunReference(runId)) throw new Error(`unsafe external run reference: ${runId}`);
|
|
1684
|
+
return path.join(runsRoot(), runId, "run.json");
|
|
1685
|
+
}
|
|
1686
|
+
function optionalString(value) {
|
|
1687
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1688
|
+
}
|
|
1689
|
+
function readPersistedRun(supervisorId, runId) {
|
|
1690
|
+
if (!isSafeRunReference(runId)) return void 0;
|
|
1691
|
+
try {
|
|
1692
|
+
const parsed = JSON.parse(fs.readFileSync(externalRunMetadataPath(runId), "utf-8"));
|
|
1693
|
+
if (parsed.schemaVersion !== 1 || parsed.runId !== runId || parsed.supervisorId !== supervisorId || typeof parsed.label !== "string") return;
|
|
1694
|
+
const runDir = path.join(runsRoot(), runId);
|
|
1695
|
+
const runInfo = {
|
|
1696
|
+
runId,
|
|
1697
|
+
label: parsed.label,
|
|
1698
|
+
supervisorId,
|
|
1699
|
+
sentinelPath: path.join(runDir, "sentinel.done"),
|
|
1700
|
+
eventLogPath: path.join(runDir, "events.log"),
|
|
1701
|
+
runtimeId: optionalString(parsed.runtimeId),
|
|
1702
|
+
sessionId: optionalString(parsed.sessionId),
|
|
1703
|
+
agent: optionalString(parsed.agent),
|
|
1704
|
+
agentProfile: optionalString(parsed.agentProfile),
|
|
1705
|
+
backend: optionalString(parsed.backend),
|
|
1706
|
+
model: optionalString(parsed.model),
|
|
1707
|
+
effectiveAgent: optionalString(parsed.effectiveAgent),
|
|
1708
|
+
effectiveModel: optionalString(parsed.effectiveModel),
|
|
1709
|
+
effectiveBackend: optionalString(parsed.effectiveBackend),
|
|
1710
|
+
rosterFile: optionalString(parsed.rosterFile),
|
|
1711
|
+
rosterEntry: optionalString(parsed.rosterEntry),
|
|
1712
|
+
thinking: parsed.thinking,
|
|
1713
|
+
dir: optionalString(parsed.dir),
|
|
1714
|
+
brokerUrl: optionalString(parsed.brokerUrl),
|
|
1715
|
+
parentToken: optionalString(parsed.parentToken),
|
|
1716
|
+
autoApprove: typeof parsed.autoApprove === "boolean" ? parsed.autoApprove : void 0
|
|
1717
|
+
};
|
|
1718
|
+
indexRun(runInfo);
|
|
1719
|
+
return runInfo;
|
|
1720
|
+
} catch {
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
/** Persist the public-to-runtime identity for runs spawned through an external supervisor. */
|
|
1725
|
+
function registerExternalRun(runInfo) {
|
|
1726
|
+
const persisted = {
|
|
1727
|
+
schemaVersion: 1,
|
|
1728
|
+
...runInfo,
|
|
1729
|
+
parentToken: void 0
|
|
1730
|
+
};
|
|
1731
|
+
const metadataPath = externalRunMetadataPath(runInfo.runId);
|
|
1732
|
+
const temporaryPath = `${metadataPath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
1733
|
+
fs.writeFileSync(temporaryPath, `${JSON.stringify(persisted, null, 2)}\n`, {
|
|
1734
|
+
mode: 384,
|
|
1735
|
+
flag: "wx"
|
|
1736
|
+
});
|
|
1737
|
+
try {
|
|
1738
|
+
fs.renameSync(temporaryPath, metadataPath);
|
|
1739
|
+
} catch (error) {
|
|
1740
|
+
try {
|
|
1741
|
+
fs.unlinkSync(temporaryPath);
|
|
1742
|
+
} catch {}
|
|
1743
|
+
throw error;
|
|
1744
|
+
}
|
|
1745
|
+
indexRun(runInfo);
|
|
1746
|
+
}
|
|
1747
|
+
function findExternalRun(supervisorId, reference) {
|
|
1748
|
+
const exact = externalRuns.get(supervisorId)?.byRunId.get(reference);
|
|
1749
|
+
if (exact) return exact;
|
|
1750
|
+
const persisted = readPersistedRun(supervisorId, reference);
|
|
1751
|
+
if (persisted) return persisted;
|
|
1752
|
+
listExternalRuns(supervisorId);
|
|
1753
|
+
return externalRuns.get(supervisorId)?.byAlias.get(reference);
|
|
1754
|
+
}
|
|
1755
|
+
function listExternalRuns(supervisorId) {
|
|
1756
|
+
try {
|
|
1757
|
+
for (const entry of fs.readdirSync(runsRoot(), { withFileTypes: true })) if (entry.isDirectory()) readPersistedRun(supervisorId, entry.name);
|
|
1758
|
+
} catch {}
|
|
1759
|
+
return [...externalRuns.get(supervisorId)?.byRunId.values() ?? []];
|
|
1760
|
+
}
|
|
1761
|
+
function forgetExternalRuns(supervisorId) {
|
|
1762
|
+
externalRuns.delete(supervisorId);
|
|
1763
|
+
}
|
|
1764
|
+
function clearExternalRuns(supervisorId) {
|
|
1765
|
+
const runs = listExternalRuns(supervisorId);
|
|
1766
|
+
forgetExternalRuns(supervisorId);
|
|
1767
|
+
return runs;
|
|
1768
|
+
}
|
|
1769
|
+
//#endregion
|
|
1391
1770
|
//#region src/tools/spawn.ts
|
|
1392
|
-
async function
|
|
1771
|
+
async function executeSpawnTool(args, getSupervisorClient) {
|
|
1772
|
+
validateSpawnSelection({
|
|
1773
|
+
agent: args.agent,
|
|
1774
|
+
model: args.model,
|
|
1775
|
+
backend: args.backend,
|
|
1776
|
+
roster_file: args.rosterFile,
|
|
1777
|
+
roster_entry: args.rosterEntry
|
|
1778
|
+
});
|
|
1393
1779
|
const runId = crypto.randomUUID();
|
|
1394
1780
|
const label = args.label ?? runId;
|
|
1395
1781
|
const baseParams = {
|
|
@@ -1405,10 +1791,23 @@ async function spawnTool(args) {
|
|
|
1405
1791
|
session_id: args.sessionId,
|
|
1406
1792
|
parent_run_id: args.parent_run_id
|
|
1407
1793
|
};
|
|
1794
|
+
if (args.rosterFile) baseParams.roster_file = args.rosterFile;
|
|
1795
|
+
if (args.rosterEntry) baseParams.roster_entry = args.rosterEntry;
|
|
1408
1796
|
if (args.agent) baseParams.agent = args.agent;
|
|
1409
1797
|
if (args.thinking !== void 0) baseParams.thinking = args.thinking;
|
|
1410
1798
|
if (args.supervisorId) {
|
|
1411
|
-
const { client, isSingleton, supervisorId } = await getSupervisorClient(args.supervisorId);
|
|
1799
|
+
const { client, isSingleton, sup, supervisorId } = await getSupervisorClient(args.supervisorId);
|
|
1800
|
+
if (isSingleton && sup) {
|
|
1801
|
+
const runInfo = await sup.spawn(baseParams, runId);
|
|
1802
|
+
return {
|
|
1803
|
+
run_id: runInfo.runId,
|
|
1804
|
+
label: runInfo.label,
|
|
1805
|
+
supervisor_id: supervisorId,
|
|
1806
|
+
runtime_id: runInfo.runtimeId,
|
|
1807
|
+
broker_url: runInfo.brokerUrl,
|
|
1808
|
+
parent_token: runInfo.parentToken
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1412
1811
|
const { sentinelPath, eventLogPath } = ensureRunPaths(runId);
|
|
1413
1812
|
try {
|
|
1414
1813
|
const result = await client.spawn({
|
|
@@ -1416,13 +1815,37 @@ async function spawnTool(args) {
|
|
|
1416
1815
|
sentinel_file: sentinelPath,
|
|
1417
1816
|
on_event: eventLogPath
|
|
1418
1817
|
});
|
|
1818
|
+
const stringValue = (candidate) => typeof candidate === "string" && candidate.length > 0 ? candidate : void 0;
|
|
1819
|
+
const runtimeId = stringValue(result.runtime_id);
|
|
1820
|
+
registerExternalRun({
|
|
1821
|
+
runId,
|
|
1822
|
+
label,
|
|
1823
|
+
supervisorId,
|
|
1824
|
+
sentinelPath,
|
|
1825
|
+
eventLogPath,
|
|
1826
|
+
runtimeId,
|
|
1827
|
+
sessionId: stringValue(result.session_id),
|
|
1828
|
+
agent: stringValue(result.effective_agent) ?? stringValue(result.agent) ?? baseParams.agent,
|
|
1829
|
+
model: stringValue(result.effective_model) ?? stringValue(result.model) ?? baseParams.model,
|
|
1830
|
+
backend: stringValue(result.effective_backend) ?? stringValue(result.backend) ?? baseParams.backend,
|
|
1831
|
+
effectiveAgent: stringValue(result.effective_agent) ?? stringValue(result.agent) ?? baseParams.agent,
|
|
1832
|
+
effectiveModel: stringValue(result.effective_model) ?? stringValue(result.model) ?? baseParams.model,
|
|
1833
|
+
effectiveBackend: stringValue(result.effective_backend) ?? stringValue(result.backend) ?? baseParams.backend,
|
|
1834
|
+
agentProfile: stringValue(result.agent_profile) ?? baseParams.agent_profile,
|
|
1835
|
+
rosterFile: stringValue(result.roster_file) ?? baseParams.roster_file,
|
|
1836
|
+
rosterEntry: stringValue(result.roster_entry) ?? baseParams.roster_entry,
|
|
1837
|
+
thinking: baseParams.thinking,
|
|
1838
|
+
dir: baseParams.dir,
|
|
1839
|
+
brokerUrl: stringValue(result.broker_url),
|
|
1840
|
+
parentToken: stringValue(result.parent_token)
|
|
1841
|
+
});
|
|
1419
1842
|
return {
|
|
1420
|
-
run_id:
|
|
1843
|
+
run_id: runId,
|
|
1421
1844
|
label,
|
|
1422
1845
|
supervisor_id: supervisorId,
|
|
1423
|
-
runtime_id:
|
|
1424
|
-
broker_url: result.broker_url,
|
|
1425
|
-
parent_token: result.parent_token
|
|
1846
|
+
runtime_id: runtimeId,
|
|
1847
|
+
broker_url: stringValue(result.broker_url),
|
|
1848
|
+
parent_token: stringValue(result.parent_token)
|
|
1426
1849
|
};
|
|
1427
1850
|
} finally {
|
|
1428
1851
|
if (!isSingleton) client.close();
|
|
@@ -1439,14 +1862,251 @@ async function spawnTool(args) {
|
|
|
1439
1862
|
parent_token: runInfo.parentToken
|
|
1440
1863
|
};
|
|
1441
1864
|
}
|
|
1865
|
+
function createSpawnTool(getSupervisorClient) {
|
|
1866
|
+
return (args) => executeSpawnTool(args, getSupervisorClient);
|
|
1867
|
+
}
|
|
1868
|
+
const spawnTool = createSpawnTool(getSupervisorClient);
|
|
1442
1869
|
//#endregion
|
|
1443
|
-
//#region
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1870
|
+
//#region ../../schemas/thinking_policy.umpire.json
|
|
1871
|
+
var thinking_policy_umpire_default = {
|
|
1872
|
+
version: 1,
|
|
1873
|
+
conditions: {
|
|
1874
|
+
"backend": {
|
|
1875
|
+
"type": "string",
|
|
1876
|
+
"description": "Backend identifier (e.g. codex-app-server, pi, claude)."
|
|
1877
|
+
},
|
|
1878
|
+
"resume": {
|
|
1879
|
+
"type": "boolean",
|
|
1880
|
+
"description": "Whether the value is being applied on an explicit resume rather than a fresh start."
|
|
1881
|
+
}
|
|
1882
|
+
},
|
|
1883
|
+
fields: { "thinking": {
|
|
1884
|
+
"isEmpty": "string",
|
|
1885
|
+
"description": "Canonical thinking level, or empty to use the backend default."
|
|
1886
|
+
} },
|
|
1887
|
+
rules: [{
|
|
1888
|
+
"type": "check",
|
|
1889
|
+
"field": "thinking",
|
|
1890
|
+
"check": {
|
|
1891
|
+
"op": "in",
|
|
1892
|
+
"field": "thinking",
|
|
1893
|
+
"value": [
|
|
1894
|
+
"off",
|
|
1895
|
+
"minimal",
|
|
1896
|
+
"low",
|
|
1897
|
+
"medium",
|
|
1898
|
+
"high",
|
|
1899
|
+
"xhigh",
|
|
1900
|
+
"max"
|
|
1901
|
+
]
|
|
1902
|
+
},
|
|
1903
|
+
"reason": "not a canonical thinking value (off, minimal, low, medium, high, xhigh, max)"
|
|
1904
|
+
}, {
|
|
1905
|
+
"type": "eitherOf",
|
|
1906
|
+
"group": "thinkingPolicy",
|
|
1907
|
+
"field": "thinking",
|
|
1908
|
+
"branches": {
|
|
1909
|
+
"empty": [{
|
|
1910
|
+
"type": "fairWhen",
|
|
1911
|
+
"field": "thinking",
|
|
1912
|
+
"when": {
|
|
1913
|
+
"op": "absent",
|
|
1914
|
+
"field": "thinking"
|
|
1915
|
+
},
|
|
1916
|
+
"reason": "thinking is empty (backend default)"
|
|
1917
|
+
}],
|
|
1918
|
+
"fullSupport": [{
|
|
1919
|
+
"type": "fairWhen",
|
|
1920
|
+
"field": "thinking",
|
|
1921
|
+
"when": {
|
|
1922
|
+
"op": "and",
|
|
1923
|
+
"exprs": [{
|
|
1924
|
+
"op": "condIn",
|
|
1925
|
+
"condition": "backend",
|
|
1926
|
+
"values": ["codex-app-server", "pi"]
|
|
1927
|
+
}, {
|
|
1928
|
+
"op": "in",
|
|
1929
|
+
"field": "thinking",
|
|
1930
|
+
"values": [
|
|
1931
|
+
"off",
|
|
1932
|
+
"minimal",
|
|
1933
|
+
"low",
|
|
1934
|
+
"medium",
|
|
1935
|
+
"high",
|
|
1936
|
+
"xhigh",
|
|
1937
|
+
"max"
|
|
1938
|
+
]
|
|
1939
|
+
}]
|
|
1940
|
+
},
|
|
1941
|
+
"reason": "codex-app-server and pi support all canonical thinking values on start and resume"
|
|
1942
|
+
}],
|
|
1943
|
+
"claudeStart": [{
|
|
1944
|
+
"type": "fairWhen",
|
|
1945
|
+
"field": "thinking",
|
|
1946
|
+
"when": {
|
|
1947
|
+
"op": "and",
|
|
1948
|
+
"exprs": [
|
|
1949
|
+
{
|
|
1950
|
+
"op": "condIn",
|
|
1951
|
+
"condition": "backend",
|
|
1952
|
+
"values": ["claude", "claude-channel"]
|
|
1953
|
+
},
|
|
1954
|
+
{
|
|
1955
|
+
"op": "condEq",
|
|
1956
|
+
"condition": "resume",
|
|
1957
|
+
"value": false
|
|
1958
|
+
},
|
|
1959
|
+
{
|
|
1960
|
+
"op": "in",
|
|
1961
|
+
"field": "thinking",
|
|
1962
|
+
"values": [
|
|
1963
|
+
"low",
|
|
1964
|
+
"medium",
|
|
1965
|
+
"high",
|
|
1966
|
+
"xhigh",
|
|
1967
|
+
"max"
|
|
1968
|
+
]
|
|
1969
|
+
}
|
|
1970
|
+
]
|
|
1971
|
+
},
|
|
1972
|
+
"reason": "claude and claude-channel support low through max only on a new session"
|
|
1973
|
+
}]
|
|
1974
|
+
}
|
|
1975
|
+
}]
|
|
1976
|
+
};
|
|
1977
|
+
//#endregion
|
|
1978
|
+
//#region src/thinking-policy.ts
|
|
1979
|
+
/**
|
|
1980
|
+
* Portable thinking-policy contract.
|
|
1981
|
+
*
|
|
1982
|
+
* THINKING_LEVELS is the single TypeScript source for the canonical tuple and
|
|
1983
|
+
* is exported for hosts (MCP, OpenCode, Pi) to derive their enums from. It is
|
|
1984
|
+
* verified against schemas/thinking_policy.umpire.json and the Go tuple by
|
|
1985
|
+
* introspection and conformance tests so all languages agree.
|
|
1986
|
+
*
|
|
1987
|
+
* The backend policy (which canonical values each backend supports, on start
|
|
1988
|
+
* vs. explicit resume) is modelled directly in the Umpire schema's eitherOf
|
|
1989
|
+
* branches with fairWhen rules and conditions (backend, resume). Both Go and
|
|
1990
|
+
* TypeScript derive the policy from the schema, eliminating hand-written
|
|
1991
|
+
* policy tables that could drift apart.
|
|
1992
|
+
*/
|
|
1993
|
+
const THINKING_LEVELS = [
|
|
1994
|
+
"off",
|
|
1995
|
+
"minimal",
|
|
1996
|
+
"low",
|
|
1997
|
+
"medium",
|
|
1998
|
+
"high",
|
|
1999
|
+
"xhigh",
|
|
2000
|
+
"max"
|
|
2001
|
+
];
|
|
2002
|
+
function isThinkingLevel(value) {
|
|
2003
|
+
return THINKING_LEVELS.includes(value);
|
|
2004
|
+
}
|
|
2005
|
+
/** Accepts an empty value (backend default) or a canonical thinking level. */
|
|
2006
|
+
function validateThinking(value) {
|
|
2007
|
+
if (value === void 0 || value === "") return;
|
|
2008
|
+
if (typeof value !== "string" || !isThinkingLevel(value)) throw new Error(`invalid thinking value ${JSON.stringify(value)} (allowed: ${THINKING_LEVELS.join(", ")})`);
|
|
2009
|
+
}
|
|
2010
|
+
const thinkingSchema = thinking_policy_umpire_default;
|
|
2011
|
+
thinkingSchema.rules.find((r) => r.check?.op === "in")?.check?.value;
|
|
2012
|
+
const policyBranches = (() => {
|
|
2013
|
+
const eitherOf = thinkingSchema.rules.find((r) => r.type === "eitherOf");
|
|
2014
|
+
if (!eitherOf?.branches) return [];
|
|
2015
|
+
const branches = [];
|
|
2016
|
+
for (const branchRules of Object.values(eitherOf.branches)) for (const rule of branchRules) if (rule.type === "fairWhen") branches.push({
|
|
2017
|
+
when: rule.when,
|
|
2018
|
+
reason: rule.reason
|
|
2019
|
+
});
|
|
2020
|
+
return branches;
|
|
2021
|
+
})();
|
|
2022
|
+
const supportedBackends = (() => {
|
|
2023
|
+
const backends = /* @__PURE__ */ new Set();
|
|
2024
|
+
for (const branch of policyBranches) collectBackends(branch.when, backends);
|
|
2025
|
+
return backends;
|
|
2026
|
+
})();
|
|
2027
|
+
function collectBackends(expr, backends) {
|
|
2028
|
+
if (expr.op === "condIn" && expr.condition === "backend") {
|
|
2029
|
+
for (const v of expr.values) backends.add(v);
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
if (expr.op === "and" || expr.op === "or") for (const sub of expr.exprs) collectBackends(sub, backends);
|
|
2033
|
+
}
|
|
2034
|
+
function evalExpr(expr, value, backend, resume) {
|
|
2035
|
+
switch (expr.op) {
|
|
2036
|
+
case "absent": return value === "";
|
|
2037
|
+
case "in": return expr.values.includes(value);
|
|
2038
|
+
case "condIn": return expr.condition === "backend" && expr.values.includes(backend);
|
|
2039
|
+
case "condEq":
|
|
2040
|
+
if (expr.condition === "backend") return backend === expr.value;
|
|
2041
|
+
if (expr.condition === "resume") return resume === expr.value;
|
|
2042
|
+
return false;
|
|
2043
|
+
case "and": return expr.exprs.every((e) => evalExpr(e, value, backend, resume));
|
|
2044
|
+
case "or": return expr.exprs.some((e) => evalExpr(e, value, backend, resume));
|
|
2045
|
+
default: return false;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
/** Evaluates the schema-derived backend policy for a (backend, value, resume) combination. */
|
|
2049
|
+
function evaluateThinkingPolicy(backend, value, resume = false) {
|
|
2050
|
+
if (value === "") return "ok";
|
|
2051
|
+
if (policyBranches.some((b) => evalExpr(b.when, value, backend, resume))) return "ok";
|
|
2052
|
+
if (!supportedBackends.has(backend)) return "unsupportedCapability";
|
|
2053
|
+
if (resume) {
|
|
2054
|
+
if (policyBranches.some((b) => evalExpr(b.when, value, backend, false))) return "startOnly";
|
|
2055
|
+
}
|
|
2056
|
+
return "unsupportedValue";
|
|
2057
|
+
}
|
|
2058
|
+
/** Returns the canonical values supported by backend on a fresh start. */
|
|
2059
|
+
function startValues(backend) {
|
|
2060
|
+
if (!supportedBackends.has(backend)) return [];
|
|
2061
|
+
return THINKING_LEVELS.filter((v) => policyBranches.some((b) => evalExpr(b.when, v, backend, false)));
|
|
2062
|
+
}
|
|
2063
|
+
/** Returns the canonical values supported by backend on an explicit resume. */
|
|
2064
|
+
function resumeValues(backend) {
|
|
2065
|
+
if (!supportedBackends.has(backend)) return [];
|
|
2066
|
+
return THINKING_LEVELS.filter((v) => policyBranches.some((b) => evalExpr(b.when, v, backend, true)));
|
|
2067
|
+
}
|
|
2068
|
+
/** Accepts a value iff it is valid under the static backend policy (start). */
|
|
2069
|
+
function validateThinkingForBackend(backend, value) {
|
|
2070
|
+
validateThinking(value);
|
|
2071
|
+
const v = value === void 0 ? "" : String(value);
|
|
2072
|
+
if (v === "") return;
|
|
2073
|
+
const outcome = evaluateThinkingPolicy(backend, v, false);
|
|
2074
|
+
if (outcome === "ok") return;
|
|
2075
|
+
if (outcome === "unsupportedValue") {
|
|
2076
|
+
const allowed = startValues(backend).join(", ");
|
|
2077
|
+
throw new Error(`backend ${JSON.stringify(backend)} does not support thinking value ${JSON.stringify(v)} (allowed: ${allowed})`);
|
|
2078
|
+
}
|
|
2079
|
+
throw new Error(`backend ${JSON.stringify(backend)} does not support parameter "thinking"`);
|
|
2080
|
+
}
|
|
2081
|
+
/** Accepts a value iff it is valid on an explicit resume for the backend. */
|
|
2082
|
+
function validateThinkingForBackendResume(backend, value) {
|
|
2083
|
+
validateThinking(value);
|
|
2084
|
+
const v = value === void 0 ? "" : String(value);
|
|
2085
|
+
if (v === "") return;
|
|
2086
|
+
const outcome = evaluateThinkingPolicy(backend, v, true);
|
|
2087
|
+
if (outcome === "ok") return;
|
|
2088
|
+
if (outcome === "startOnly") throw new Error(`backend ${JSON.stringify(backend)} supports parameter "thinking" only when starting a session`);
|
|
2089
|
+
if (outcome === "unsupportedValue") {
|
|
2090
|
+
const allowed = resumeValues(backend).join(", ");
|
|
2091
|
+
throw new Error(`backend ${JSON.stringify(backend)} does not support thinking value ${JSON.stringify(v)} (allowed: ${allowed})`);
|
|
2092
|
+
}
|
|
2093
|
+
throw new Error(`backend ${JSON.stringify(backend)} does not support parameter "thinking"`);
|
|
1449
2094
|
}
|
|
2095
|
+
//#endregion
|
|
2096
|
+
//#region src/tools/run-resolution.ts
|
|
2097
|
+
/** Resolve a local public run ID before considering a label alias. */
|
|
2098
|
+
function findLocalRunByReference(sup, reference) {
|
|
2099
|
+
const { runs, aliases } = sup;
|
|
2100
|
+
return runs.get(reference) ?? aliases.get(reference);
|
|
2101
|
+
}
|
|
2102
|
+
/** Match a supervisor list entry to its stored runtime identity. */
|
|
2103
|
+
function findLocalRunByRuntimeId(sup, runtimeId) {
|
|
2104
|
+
if (!runtimeId) return void 0;
|
|
2105
|
+
const { runs } = sup;
|
|
2106
|
+
for (const runInfo of runs.values()) if (runInfo.runtimeId === runtimeId) return runInfo;
|
|
2107
|
+
}
|
|
2108
|
+
//#endregion
|
|
2109
|
+
//#region src/tools/status.ts
|
|
1450
2110
|
async function parseSentinel$1(filePath) {
|
|
1451
2111
|
try {
|
|
1452
2112
|
const content = (await fs.promises.readFile(filePath, "utf-8")).trim();
|
|
@@ -1534,6 +2194,15 @@ function extractPendingPermission(liveStatus) {
|
|
|
1534
2194
|
return normalizePermission(asRecord(pendingPermission));
|
|
1535
2195
|
}
|
|
1536
2196
|
function buildBaseStatus(source, fallback, translatedStatus) {
|
|
2197
|
+
const identity = fallback.identity;
|
|
2198
|
+
const authoritativeValue = (effectiveKey, directKey, fallbackEffective, fallbackDirect) => {
|
|
2199
|
+
if (typeof source[effectiveKey] === "string") return stringField(source, effectiveKey);
|
|
2200
|
+
if (typeof source[directKey] === "string") return stringField(source, directKey);
|
|
2201
|
+
return fallbackEffective ?? fallbackDirect;
|
|
2202
|
+
};
|
|
2203
|
+
const effectiveAgent = authoritativeValue("effective_agent", "agent", identity?.effective_agent, identity?.agent);
|
|
2204
|
+
const effectiveModel = authoritativeValue("effective_model", "model", identity?.effective_model, identity?.model);
|
|
2205
|
+
const effectiveBackend = authoritativeValue("effective_backend", "backend", identity?.effective_backend, identity?.backend);
|
|
1537
2206
|
return {
|
|
1538
2207
|
run_id: fallback.prefer_fallback_run_id ? fallback.run_id : stringField(source, "run_id") ?? fallback.run_id,
|
|
1539
2208
|
label: stringField(source, "label") ?? fallback.label,
|
|
@@ -1544,9 +2213,15 @@ function buildBaseStatus(source, fallback, translatedStatus) {
|
|
|
1544
2213
|
pending_permission: extractPendingPermission(source),
|
|
1545
2214
|
session_id: stringField(source, "session_id") ?? fallback.session_id,
|
|
1546
2215
|
stop_reason: stringField(source, "stop_reason") ?? fallback.stop_reason,
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
2216
|
+
roster_file: typeof source.roster_file === "string" ? stringField(source, "roster_file") : identity?.roster_file,
|
|
2217
|
+
roster_entry: typeof source.roster_entry === "string" ? stringField(source, "roster_entry") : identity?.roster_entry,
|
|
2218
|
+
backend: effectiveBackend,
|
|
2219
|
+
agent: effectiveAgent,
|
|
2220
|
+
model: effectiveModel,
|
|
2221
|
+
agent_profile: typeof source.agent_profile === "string" ? stringField(source, "agent_profile") : identity?.agent_profile,
|
|
2222
|
+
effective_backend: effectiveBackend,
|
|
2223
|
+
effective_agent: effectiveAgent,
|
|
2224
|
+
effective_model: effectiveModel,
|
|
1550
2225
|
dir: stringField(source, "dir"),
|
|
1551
2226
|
parent_id: stringField(source, "parent_id", "parentId"),
|
|
1552
2227
|
children: stringArrayField(source, "children"),
|
|
@@ -1573,6 +2248,7 @@ function shapeStatusResult(result, view = "full") {
|
|
|
1573
2248
|
};
|
|
1574
2249
|
}
|
|
1575
2250
|
async function buildRunStatus(runInfo, liveStatus) {
|
|
2251
|
+
if (liveStatus) retainLiveIdentity(runInfo, liveStatus);
|
|
1576
2252
|
let sentinel = null;
|
|
1577
2253
|
if (await sentinelExists(runInfo.sentinelPath)) sentinel = await parseSentinel$1(runInfo.sentinelPath);
|
|
1578
2254
|
const translated = translateStatus(rawStatusPhase(liveStatus), sentinel);
|
|
@@ -1582,7 +2258,18 @@ async function buildRunStatus(runInfo, liveStatus) {
|
|
|
1582
2258
|
prefer_fallback_run_id: true,
|
|
1583
2259
|
runtime_id: runInfo.runtimeId,
|
|
1584
2260
|
session_id: sentinel?.SESSION ?? liveStatus?.session_id ?? runInfo.sessionId,
|
|
1585
|
-
stop_reason: sentinel?.
|
|
2261
|
+
stop_reason: sentinel?.STOP_REASON,
|
|
2262
|
+
identity: {
|
|
2263
|
+
roster_file: runInfo.rosterFile,
|
|
2264
|
+
roster_entry: runInfo.rosterEntry,
|
|
2265
|
+
agent: runInfo.agent,
|
|
2266
|
+
model: runInfo.model,
|
|
2267
|
+
backend: runInfo.backend,
|
|
2268
|
+
agent_profile: runInfo.agentProfile,
|
|
2269
|
+
effective_agent: runInfo.effectiveAgent,
|
|
2270
|
+
effective_model: runInfo.effectiveModel,
|
|
2271
|
+
effective_backend: runInfo.effectiveBackend
|
|
2272
|
+
}
|
|
1586
2273
|
}, translated);
|
|
1587
2274
|
}
|
|
1588
2275
|
async function queryLiveStatus(client, runtimeId) {
|
|
@@ -1595,11 +2282,11 @@ async function queryLiveStatus(client, runtimeId) {
|
|
|
1595
2282
|
}
|
|
1596
2283
|
async function executeStatusTool(args, getSupervisorClient) {
|
|
1597
2284
|
if (args.supervisorId) {
|
|
1598
|
-
const { client, isSingleton, sup } = await getSupervisorClient(args.supervisorId);
|
|
2285
|
+
const { client, isSingleton, sup, supervisorId } = await getSupervisorClient(args.supervisorId);
|
|
1599
2286
|
try {
|
|
1600
2287
|
if (args.runId) {
|
|
1601
2288
|
validateRunId(args.runId);
|
|
1602
|
-
const runInfo = isSingleton && sup ?
|
|
2289
|
+
const runInfo = isSingleton && sup ? findLocalRunByReference(sup, args.runId) : findExternalRun(supervisorId, args.runId);
|
|
1603
2290
|
if (runInfo) return shapeStatusResult(await buildRunStatus(runInfo, await queryLiveStatus(client, runInfo.runtimeId)), args.view);
|
|
1604
2291
|
let liveStatus = await queryLiveStatus(client, args.runId);
|
|
1605
2292
|
if (!liveStatus) try {
|
|
@@ -1617,17 +2304,30 @@ async function executeStatusTool(args, getSupervisorClient) {
|
|
|
1617
2304
|
return shapeStatusResult(buildBaseStatus(liveStatus ?? {}, {
|
|
1618
2305
|
run_id: args.runId,
|
|
1619
2306
|
label: args.runId,
|
|
2307
|
+
prefer_fallback_run_id: true,
|
|
1620
2308
|
runtime_id: stringField(liveStatus ?? {}, "runtime_id", "id"),
|
|
1621
2309
|
session_id: sentinel?.SESSION ?? liveStatus?.session_id,
|
|
1622
|
-
stop_reason: sentinel?.
|
|
2310
|
+
stop_reason: sentinel?.STOP_REASON
|
|
1623
2311
|
}, translateStatus(rawStatusPhase(liveStatus), sentinel)), args.view);
|
|
1624
2312
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
2313
|
+
const list = await client.list();
|
|
2314
|
+
if (!isSingleton) listExternalRuns(supervisorId);
|
|
2315
|
+
const results = [];
|
|
2316
|
+
for (const entry of list) {
|
|
2317
|
+
const entryId = String(entry.runtime_id ?? entry.id ?? "");
|
|
2318
|
+
const runInfo = !isSingleton ? findExternalRun(supervisorId, entryId) ?? findExternalRun(supervisorId, String(entry.run_id ?? "")) ?? findExternalRun(supervisorId, String(entry.label ?? "")) : void 0;
|
|
2319
|
+
if (runInfo) {
|
|
2320
|
+
results.push(shapeStatusResult(await buildRunStatus(runInfo, entry), args.view));
|
|
2321
|
+
continue;
|
|
2322
|
+
}
|
|
2323
|
+
results.push(shapeStatusResult(buildBaseStatus(entry, {
|
|
2324
|
+
run_id: String(entry.run_id ?? entryId),
|
|
2325
|
+
label: String(entry.label ?? entryId),
|
|
2326
|
+
runtime_id: entryId,
|
|
2327
|
+
session_id: entry.session_id
|
|
2328
|
+
}, translateStatus(rawStatusPhase(entry), null)), args.view));
|
|
2329
|
+
}
|
|
2330
|
+
return results;
|
|
1631
2331
|
} finally {
|
|
1632
2332
|
if (!isSingleton) client.close();
|
|
1633
2333
|
}
|
|
@@ -1635,7 +2335,7 @@ async function executeStatusTool(args, getSupervisorClient) {
|
|
|
1635
2335
|
const sup = await Supervisor.get();
|
|
1636
2336
|
const client = sup.getClient();
|
|
1637
2337
|
if (args.runId) {
|
|
1638
|
-
const runInfo =
|
|
2338
|
+
const runInfo = findLocalRunByReference(sup, args.runId);
|
|
1639
2339
|
if (runInfo) return shapeStatusResult(await buildRunStatus(runInfo, await queryLiveStatus(client, runInfo.runtimeId)), args.view);
|
|
1640
2340
|
validateRunId(args.runId);
|
|
1641
2341
|
const liveStatus = await queryLiveStatus(client, args.runId);
|
|
@@ -1647,7 +2347,7 @@ async function executeStatusTool(args, getSupervisorClient) {
|
|
|
1647
2347
|
label: args.runId,
|
|
1648
2348
|
runtime_id: stringField(liveStatus ?? {}, "runtime_id", "id"),
|
|
1649
2349
|
session_id: sentinel?.SESSION ?? liveStatus?.session_id,
|
|
1650
|
-
stop_reason: sentinel?.
|
|
2350
|
+
stop_reason: sentinel?.STOP_REASON
|
|
1651
2351
|
}, translateStatus(rawStatusPhase(liveStatus), sentinel)), args.view);
|
|
1652
2352
|
}
|
|
1653
2353
|
const list = await client.list();
|
|
@@ -1655,9 +2355,9 @@ async function executeStatusTool(args, getSupervisorClient) {
|
|
|
1655
2355
|
for (const entry of list) {
|
|
1656
2356
|
const entryId = String(entry.runtime_id ?? entry.id ?? "");
|
|
1657
2357
|
const entryLabel = String(entry.label ?? entryId);
|
|
1658
|
-
const runInfo =
|
|
2358
|
+
const runInfo = findLocalRunByRuntimeId(sup, entryId) ?? findLocalRunByReference(sup, entryId) ?? findLocalRunByReference(sup, entryLabel);
|
|
1659
2359
|
const liveStatus = runInfo ? await queryLiveStatus(client, runInfo.runtimeId) : null;
|
|
1660
|
-
if (runInfo
|
|
2360
|
+
if (runInfo) results.push(shapeStatusResult(await buildRunStatus(runInfo, liveStatus), args.view));
|
|
1661
2361
|
else results.push(shapeStatusResult(buildBaseStatus(entry, {
|
|
1662
2362
|
run_id: String(entry.run_id ?? entryId),
|
|
1663
2363
|
label: entryLabel,
|
|
@@ -1677,12 +2377,6 @@ const MAX_EVENT_LIMIT = 1e3;
|
|
|
1677
2377
|
const MAX_STRING_CHARS = 4e3;
|
|
1678
2378
|
const MAX_ARRAY_ITEMS = 32;
|
|
1679
2379
|
const MAX_OBJECT_KEYS = 64;
|
|
1680
|
-
function findRunByLabel$2(sup, runId) {
|
|
1681
|
-
const runs = sup.runs;
|
|
1682
|
-
const byKey = runs.get(runId);
|
|
1683
|
-
if (byKey) return byKey;
|
|
1684
|
-
for (const info of runs.values()) if (info.label === runId) return info;
|
|
1685
|
-
}
|
|
1686
2380
|
function normalizeLimit(limit) {
|
|
1687
2381
|
return Math.max(1, Math.min(MAX_EVENT_LIMIT, Math.trunc(limit ?? 50)));
|
|
1688
2382
|
}
|
|
@@ -1752,8 +2446,8 @@ async function readNdjsonEvents(filePath, args) {
|
|
|
1752
2446
|
}
|
|
1753
2447
|
async function resolveRun(args, getSupervisorClient) {
|
|
1754
2448
|
if (args.supervisorId) {
|
|
1755
|
-
const { client, isSingleton, sup } = await getSupervisorClient(args.supervisorId);
|
|
1756
|
-
const runInfo = isSingleton && sup ?
|
|
2449
|
+
const { client, isSingleton, sup, supervisorId } = await getSupervisorClient(args.supervisorId);
|
|
2450
|
+
const runInfo = isSingleton && sup ? findLocalRunByReference(sup, args.runId) : findExternalRun(supervisorId, args.runId);
|
|
1757
2451
|
if (runInfo) return {
|
|
1758
2452
|
client,
|
|
1759
2453
|
isSingleton,
|
|
@@ -1796,7 +2490,7 @@ async function resolveRun(args, getSupervisorClient) {
|
|
|
1796
2490
|
};
|
|
1797
2491
|
}
|
|
1798
2492
|
const sup = await Supervisor.get();
|
|
1799
|
-
const runInfo =
|
|
2493
|
+
const runInfo = findLocalRunByReference(sup, args.runId);
|
|
1800
2494
|
if (runInfo) return {
|
|
1801
2495
|
client: sup.getClient(),
|
|
1802
2496
|
isSingleton: true,
|
|
@@ -1822,6 +2516,7 @@ async function resolveRun(args, getSupervisorClient) {
|
|
|
1822
2516
|
};
|
|
1823
2517
|
}
|
|
1824
2518
|
async function executeEventsTool(args, getSupervisorClient) {
|
|
2519
|
+
validateRunId(args.runId);
|
|
1825
2520
|
const limit = normalizeLimit(args.limit);
|
|
1826
2521
|
const resolved = await resolveRun(args, getSupervisorClient);
|
|
1827
2522
|
try {
|
|
@@ -2030,12 +2725,6 @@ async function resultTool(args) {
|
|
|
2030
2725
|
}
|
|
2031
2726
|
//#endregion
|
|
2032
2727
|
//#region src/tools/answer-permission.ts
|
|
2033
|
-
function findRunByLabel$1(sup, runId) {
|
|
2034
|
-
const runs = sup.runs;
|
|
2035
|
-
const byKey = runs.get(runId);
|
|
2036
|
-
if (byKey) return byKey;
|
|
2037
|
-
for (const info of runs.values()) if (info.label === runId) return info;
|
|
2038
|
-
}
|
|
2039
2728
|
function pendingPermissionRequestID(liveStatus) {
|
|
2040
2729
|
const status = asRecord(liveStatus);
|
|
2041
2730
|
if (!status) return void 0;
|
|
@@ -2055,7 +2744,7 @@ async function answerPermissionTool(args) {
|
|
|
2055
2744
|
}
|
|
2056
2745
|
try {
|
|
2057
2746
|
validateRunId(args.runId);
|
|
2058
|
-
const runtimeId = (sup ?
|
|
2747
|
+
const runtimeId = (sup ? findLocalRunByReference(sup, args.runId) : supervisorId ? findExternalRun(supervisorId, args.runId) : void 0)?.runtimeId ?? args.runId;
|
|
2059
2748
|
if (!requestId) {
|
|
2060
2749
|
requestId = pendingPermissionRequestID(await client.status(runtimeId));
|
|
2061
2750
|
if (!requestId) throw new Error("no pending permission request for this run");
|
|
@@ -2068,20 +2757,18 @@ async function answerPermissionTool(args) {
|
|
|
2068
2757
|
}
|
|
2069
2758
|
//#endregion
|
|
2070
2759
|
//#region src/tools/follow-up.ts
|
|
2071
|
-
function findRunByLabel(sup, runId) {
|
|
2072
|
-
const runs = sup.runs;
|
|
2073
|
-
const byKey = runs.get(runId);
|
|
2074
|
-
if (byKey) return byKey;
|
|
2075
|
-
for (const info of runs.values()) if (info.label === runId) return info;
|
|
2076
|
-
}
|
|
2077
2760
|
function resolveAutoApprove(liveStatus, runInfo) {
|
|
2078
2761
|
const liveAutoApprove = liveStatus?.auto_approve;
|
|
2079
2762
|
if (typeof liveAutoApprove === "boolean") return liveAutoApprove;
|
|
2080
2763
|
return typeof runInfo?.autoApprove === "boolean" ? runInfo.autoApprove : void 0;
|
|
2081
2764
|
}
|
|
2082
|
-
function
|
|
2083
|
-
|
|
2084
|
-
|
|
2765
|
+
function value(candidate) {
|
|
2766
|
+
return typeof candidate === "string" && candidate.length > 0 ? candidate : void 0;
|
|
2767
|
+
}
|
|
2768
|
+
function resolvedIdentity(liveStatus, effectiveKey, directKey, fallbackEffective, fallbackDirect) {
|
|
2769
|
+
if (liveStatus && typeof liveStatus[effectiveKey] === "string") return value(liveStatus[effectiveKey]);
|
|
2770
|
+
if (liveStatus && typeof liveStatus[directKey] === "string") return value(liveStatus[directKey]);
|
|
2771
|
+
return fallbackEffective ?? fallbackDirect;
|
|
2085
2772
|
}
|
|
2086
2773
|
async function parseSentinel(filePath) {
|
|
2087
2774
|
try {
|
|
@@ -2101,85 +2788,144 @@ async function parseSentinel(filePath) {
|
|
|
2101
2788
|
return null;
|
|
2102
2789
|
}
|
|
2103
2790
|
}
|
|
2791
|
+
function rejectSessionConflict(sentinel, liveStatus) {
|
|
2792
|
+
const sentinelStopReason = value(sentinel?.STOP_REASON);
|
|
2793
|
+
const liveStopReason = value(liveStatus?.stop_reason);
|
|
2794
|
+
if (sentinelStopReason === "session_id_conflict" || liveStopReason === "session_id_conflict") throw new Error("run is not resumable: session_id_conflict");
|
|
2795
|
+
}
|
|
2104
2796
|
async function executeFollowUpTool(args, getSupervisorClient) {
|
|
2105
2797
|
if (args.supervisorId) {
|
|
2106
|
-
const { client, isSingleton, sup } = await getSupervisorClient(args.supervisorId);
|
|
2798
|
+
const { client, isSingleton, sup, supervisorId } = await getSupervisorClient(args.supervisorId);
|
|
2107
2799
|
try {
|
|
2108
2800
|
validateRunId(args.runId);
|
|
2109
2801
|
let runInfo;
|
|
2110
|
-
if (isSingleton && sup) runInfo =
|
|
2802
|
+
if (isSingleton && sup) runInfo = findLocalRunByReference(sup, args.runId);
|
|
2803
|
+
else runInfo = findExternalRun(supervisorId, args.runId);
|
|
2111
2804
|
const fallbackSessionId = runInfo?.sessionId;
|
|
2112
2805
|
let liveStatus = null;
|
|
2113
2806
|
try {
|
|
2114
2807
|
liveStatus = await client.status(runInfo?.runtimeId ?? args.runId);
|
|
2115
2808
|
} catch {}
|
|
2116
|
-
const
|
|
2809
|
+
const sentinel = await parseSentinel(liveStatus?.sentinel_file ?? runInfo?.sentinelPath ?? path.join(runsRoot(), args.runId, "sentinel.done"));
|
|
2810
|
+
rejectSessionConflict(sentinel, liveStatus);
|
|
2811
|
+
const sessionId = sentinel?.SESSION ?? liveStatus?.session_id ?? fallbackSessionId;
|
|
2117
2812
|
if (!sessionId) throw new Error("run has no session to resume");
|
|
2118
|
-
const agent =
|
|
2119
|
-
const
|
|
2120
|
-
const
|
|
2813
|
+
const agent = resolvedIdentity(liveStatus, "effective_agent", "agent", runInfo?.effectiveAgent, runInfo?.agent);
|
|
2814
|
+
const model = resolvedIdentity(liveStatus, "effective_model", "model", runInfo?.effectiveModel, runInfo?.model);
|
|
2815
|
+
const backend = resolvedIdentity(liveStatus, "effective_backend", "backend", runInfo?.effectiveBackend, runInfo?.backend);
|
|
2816
|
+
if (runInfo && liveStatus) retainLiveIdentity(runInfo, liveStatus);
|
|
2121
2817
|
const thinking = liveStatus?.thinking ?? runInfo?.thinking;
|
|
2122
2818
|
const dir = liveStatus?.dir ?? runInfo?.dir;
|
|
2123
2819
|
const agentProfile = liveStatus?.agent_profile ?? runInfo?.agentProfile;
|
|
2124
2820
|
const autoApprove = resolveAutoApprove(liveStatus, runInfo);
|
|
2125
2821
|
const followUpRunId = crypto.randomUUID();
|
|
2126
2822
|
const followUpLabel = args.label ?? `${args.runId}-followup`;
|
|
2127
|
-
const { sentinelPath: followUpSentinelPath, eventLogPath } = ensureRunPaths(followUpRunId);
|
|
2128
2823
|
const spawnParams = {
|
|
2129
|
-
agent,
|
|
2130
2824
|
prompt: args.message,
|
|
2131
2825
|
label: followUpLabel,
|
|
2132
|
-
session_id: sessionId
|
|
2133
|
-
sentinel_file: followUpSentinelPath,
|
|
2134
|
-
on_event: eventLogPath
|
|
2826
|
+
session_id: sessionId
|
|
2135
2827
|
};
|
|
2828
|
+
if (agent) spawnParams.agent = agent;
|
|
2136
2829
|
if (backend) spawnParams.backend = backend;
|
|
2137
2830
|
if (model) spawnParams.model = model;
|
|
2138
2831
|
if (thinking) spawnParams.thinking = thinking;
|
|
2139
2832
|
if (dir) spawnParams.dir = dir;
|
|
2140
2833
|
if (agentProfile) spawnParams.agent_profile = agentProfile;
|
|
2141
2834
|
if (autoApprove === true) spawnParams.auto_approve = true;
|
|
2835
|
+
if (isSingleton && sup) {
|
|
2836
|
+
const followUpRun = await sup.spawn(spawnParams, followUpRunId, {
|
|
2837
|
+
rosterFile: runInfo?.rosterFile,
|
|
2838
|
+
rosterEntry: runInfo?.rosterEntry,
|
|
2839
|
+
effectiveAgent: agent,
|
|
2840
|
+
effectiveModel: model,
|
|
2841
|
+
effectiveBackend: backend
|
|
2842
|
+
});
|
|
2843
|
+
return {
|
|
2844
|
+
run_id: followUpRun.runId,
|
|
2845
|
+
label: followUpRun.label,
|
|
2846
|
+
runtime_id: followUpRun.runtimeId
|
|
2847
|
+
};
|
|
2848
|
+
}
|
|
2849
|
+
const { sentinelPath: followUpSentinelPath, eventLogPath } = ensureRunPaths(followUpRunId);
|
|
2850
|
+
spawnParams.sentinel_file = followUpSentinelPath;
|
|
2851
|
+
spawnParams.on_event = eventLogPath;
|
|
2852
|
+
const result = await client.spawn(spawnParams);
|
|
2853
|
+
const runtimeId = value(result.runtime_id);
|
|
2854
|
+
registerExternalRun({
|
|
2855
|
+
runId: followUpRunId,
|
|
2856
|
+
label: followUpLabel,
|
|
2857
|
+
supervisorId,
|
|
2858
|
+
sentinelPath: followUpSentinelPath,
|
|
2859
|
+
eventLogPath,
|
|
2860
|
+
runtimeId,
|
|
2861
|
+
sessionId: value(result.session_id) ?? sessionId,
|
|
2862
|
+
agent,
|
|
2863
|
+
model,
|
|
2864
|
+
backend,
|
|
2865
|
+
effectiveAgent: agent,
|
|
2866
|
+
effectiveModel: model,
|
|
2867
|
+
effectiveBackend: backend,
|
|
2868
|
+
rosterFile: runInfo?.rosterFile,
|
|
2869
|
+
rosterEntry: runInfo?.rosterEntry,
|
|
2870
|
+
thinking,
|
|
2871
|
+
dir,
|
|
2872
|
+
agentProfile,
|
|
2873
|
+
autoApprove
|
|
2874
|
+
});
|
|
2142
2875
|
return {
|
|
2143
|
-
run_id:
|
|
2144
|
-
label: followUpLabel
|
|
2876
|
+
run_id: followUpRunId,
|
|
2877
|
+
label: followUpLabel,
|
|
2878
|
+
runtime_id: runtimeId
|
|
2145
2879
|
};
|
|
2146
2880
|
} finally {
|
|
2147
2881
|
if (!isSingleton) client.close();
|
|
2148
2882
|
}
|
|
2149
2883
|
}
|
|
2150
2884
|
const sup = await Supervisor.get();
|
|
2151
|
-
const runInfo =
|
|
2885
|
+
const runInfo = findLocalRunByReference(sup, args.runId);
|
|
2152
2886
|
if (!runInfo) throw new Error(`run not found: ${args.runId}`);
|
|
2153
|
-
const
|
|
2887
|
+
const sentinel = await parseSentinel(runInfo.sentinelPath);
|
|
2888
|
+
rejectSessionConflict(sentinel, null);
|
|
2889
|
+
const sessionId = sentinel?.SESSION ?? runInfo.sessionId;
|
|
2154
2890
|
if (!sessionId) throw new Error("run has no session to resume");
|
|
2155
2891
|
const client = sup.getClient();
|
|
2156
2892
|
let liveStatus = null;
|
|
2157
2893
|
if (runInfo.runtimeId) try {
|
|
2158
2894
|
liveStatus = await client.status(runInfo.runtimeId);
|
|
2159
2895
|
} catch {}
|
|
2160
|
-
|
|
2161
|
-
const
|
|
2162
|
-
const model = liveStatus
|
|
2896
|
+
rejectSessionConflict(sentinel, liveStatus);
|
|
2897
|
+
const agent = resolvedIdentity(liveStatus, "effective_agent", "agent", runInfo.effectiveAgent, runInfo.agent);
|
|
2898
|
+
const model = resolvedIdentity(liveStatus, "effective_model", "model", runInfo.effectiveModel, runInfo.model);
|
|
2899
|
+
const backend = resolvedIdentity(liveStatus, "effective_backend", "backend", runInfo.effectiveBackend, runInfo.backend);
|
|
2900
|
+
if (liveStatus) retainLiveIdentity(runInfo, liveStatus);
|
|
2163
2901
|
const thinking = liveStatus?.thinking ?? runInfo.thinking;
|
|
2164
2902
|
const dir = liveStatus?.dir ?? runInfo.dir;
|
|
2165
2903
|
const agentProfile = liveStatus?.agent_profile ?? runInfo.agentProfile;
|
|
2166
2904
|
const autoApprove = resolveAutoApprove(liveStatus, runInfo);
|
|
2167
2905
|
const followUpLabel = args.label ?? `${runInfo.label}-followup`;
|
|
2168
|
-
const
|
|
2169
|
-
agent,
|
|
2170
|
-
backend,
|
|
2171
|
-
model,
|
|
2172
|
-
thinking,
|
|
2173
|
-
dir,
|
|
2174
|
-
agent_profile: agentProfile,
|
|
2906
|
+
const followUpParams = {
|
|
2907
|
+
...agent ? { agent } : {},
|
|
2908
|
+
...backend ? { backend } : {},
|
|
2909
|
+
...model ? { model } : {},
|
|
2910
|
+
...thinking ? { thinking } : {},
|
|
2911
|
+
...dir ? { dir } : {},
|
|
2912
|
+
...agentProfile ? { agent_profile: agentProfile } : {},
|
|
2175
2913
|
prompt: args.message,
|
|
2176
2914
|
session_id: sessionId,
|
|
2177
2915
|
label: followUpLabel,
|
|
2178
2916
|
...autoApprove === true ? { auto_approve: true } : {}
|
|
2917
|
+
};
|
|
2918
|
+
const followUpRun = await sup.spawn(followUpParams, void 0, {
|
|
2919
|
+
rosterFile: runInfo.rosterFile,
|
|
2920
|
+
rosterEntry: runInfo.rosterEntry,
|
|
2921
|
+
effectiveAgent: agent,
|
|
2922
|
+
effectiveModel: model,
|
|
2923
|
+
effectiveBackend: backend
|
|
2179
2924
|
});
|
|
2180
2925
|
return {
|
|
2181
2926
|
run_id: followUpRun.runId,
|
|
2182
|
-
label: followUpRun.label
|
|
2927
|
+
label: followUpRun.label,
|
|
2928
|
+
runtime_id: followUpRun.runtimeId
|
|
2183
2929
|
};
|
|
2184
2930
|
}
|
|
2185
2931
|
function createFollowUpTool(getSupervisorClient) {
|
|
@@ -2212,9 +2958,24 @@ async function shutdownTool(args) {
|
|
|
2212
2958
|
const client = await dial(supervisorId);
|
|
2213
2959
|
try {
|
|
2214
2960
|
await client.shutdown(args.force ? "force" : "graceful");
|
|
2215
|
-
}
|
|
2961
|
+
} finally {
|
|
2216
2962
|
client.close();
|
|
2217
2963
|
}
|
|
2964
|
+
for (const info of clearExternalRuns(supervisorId)) {
|
|
2965
|
+
try {
|
|
2966
|
+
await fs.promises.unlink(info.sentinelPath);
|
|
2967
|
+
cleanedUp.push(info.sentinelPath);
|
|
2968
|
+
} catch {}
|
|
2969
|
+
try {
|
|
2970
|
+
await fs.promises.unlink(info.eventLogPath);
|
|
2971
|
+
cleanedUp.push(info.eventLogPath);
|
|
2972
|
+
} catch {}
|
|
2973
|
+
const metadataPath = externalRunMetadataPath(info.runId);
|
|
2974
|
+
try {
|
|
2975
|
+
await fs.promises.unlink(metadataPath);
|
|
2976
|
+
cleanedUp.push(metadataPath);
|
|
2977
|
+
} catch {}
|
|
2978
|
+
}
|
|
2218
2979
|
}
|
|
2219
2980
|
return {
|
|
2220
2981
|
ok: true,
|
|
@@ -2243,4 +3004,184 @@ async function shutdownTool(args) {
|
|
|
2243
3004
|
};
|
|
2244
3005
|
}
|
|
2245
3006
|
//#endregion
|
|
2246
|
-
|
|
3007
|
+
//#region src/tools/ask.ts
|
|
3008
|
+
function extractReplyMessage(result) {
|
|
3009
|
+
if (result.timeout === true) return "(timeout: no reply received)";
|
|
3010
|
+
if (result.cancelled === true) return "(cancelled)";
|
|
3011
|
+
const payload = result.payload;
|
|
3012
|
+
if (payload && typeof payload === "object") {
|
|
3013
|
+
const msg = payload.message;
|
|
3014
|
+
if (typeof msg === "string" && msg) return msg;
|
|
3015
|
+
}
|
|
3016
|
+
return JSON.stringify(result);
|
|
3017
|
+
}
|
|
3018
|
+
async function executeAskTool(args, getSupervisorClient) {
|
|
3019
|
+
const { client, isSingleton } = await getSupervisorClient(args.supervisorId);
|
|
3020
|
+
try {
|
|
3021
|
+
const result = await client.brokerAsk(args.toRunId, args.message);
|
|
3022
|
+
return {
|
|
3023
|
+
reply: extractReplyMessage(result),
|
|
3024
|
+
from_run_id: result.from_run_id ?? ""
|
|
3025
|
+
};
|
|
3026
|
+
} finally {
|
|
3027
|
+
if (!isSingleton) client.close();
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
function createAskTool(getSupervisorClient) {
|
|
3031
|
+
return (args) => executeAskTool(args, getSupervisorClient);
|
|
3032
|
+
}
|
|
3033
|
+
const askTool = createAskTool(getSupervisorClient);
|
|
3034
|
+
//#endregion
|
|
3035
|
+
//#region src/tools/peers.ts
|
|
3036
|
+
async function executePeersTool(args, getSupervisorClient) {
|
|
3037
|
+
const { client, isSingleton } = await getSupervisorClient(args.supervisorId);
|
|
3038
|
+
try {
|
|
3039
|
+
return { peers: await client.brokerPeers() };
|
|
3040
|
+
} finally {
|
|
3041
|
+
if (!isSingleton) client.close();
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
function createPeersTool(getSupervisorClient) {
|
|
3045
|
+
return (args) => executePeersTool(args, getSupervisorClient);
|
|
3046
|
+
}
|
|
3047
|
+
const peersTool = createPeersTool(getSupervisorClient);
|
|
3048
|
+
//#endregion
|
|
3049
|
+
//#region src/tools/cancel.ts
|
|
3050
|
+
async function executeCancelTool(args, getSupervisorClient) {
|
|
3051
|
+
const { client, isSingleton } = await getSupervisorClient(args.supervisorId);
|
|
3052
|
+
try {
|
|
3053
|
+
await client.brokerCancel(args.messageId);
|
|
3054
|
+
return { cancelled: true };
|
|
3055
|
+
} finally {
|
|
3056
|
+
if (!isSingleton) client.close();
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
function createCancelTool(getSupervisorClient) {
|
|
3060
|
+
return (args) => executeCancelTool(args, getSupervisorClient);
|
|
3061
|
+
}
|
|
3062
|
+
const cancelTool = createCancelTool(getSupervisorClient);
|
|
3063
|
+
//#endregion
|
|
3064
|
+
//#region src/tools/broker-receive.ts
|
|
3065
|
+
async function executeBrokerReceive(args, getSupervisorClient) {
|
|
3066
|
+
const { client, isSingleton } = await getSupervisorClient(args.supervisorId);
|
|
3067
|
+
try {
|
|
3068
|
+
return { asks: await client.brokerReceive() };
|
|
3069
|
+
} finally {
|
|
3070
|
+
if (!isSingleton) client.close();
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
function createBrokerReceiveTool(getSupervisorClient) {
|
|
3074
|
+
return (args) => executeBrokerReceive(args, getSupervisorClient);
|
|
3075
|
+
}
|
|
3076
|
+
const brokerReceiveTool = createBrokerReceiveTool(getSupervisorClient);
|
|
3077
|
+
//#endregion
|
|
3078
|
+
//#region src/tools/broker-reply.ts
|
|
3079
|
+
async function executeBrokerReply(args, getSupervisorClient) {
|
|
3080
|
+
const { client, isSingleton } = await getSupervisorClient(args.supervisorId);
|
|
3081
|
+
try {
|
|
3082
|
+
await client.brokerReply(args.toRunId, args.replyTo, args.message);
|
|
3083
|
+
return { ok: true };
|
|
3084
|
+
} finally {
|
|
3085
|
+
if (!isSingleton) client.close();
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
function createBrokerReplyTool(getSupervisorClient) {
|
|
3089
|
+
return (args) => executeBrokerReply(args, getSupervisorClient);
|
|
3090
|
+
}
|
|
3091
|
+
const brokerReplyTool = createBrokerReplyTool(getSupervisorClient);
|
|
3092
|
+
//#endregion
|
|
3093
|
+
//#region src/tools/workflow-client.ts
|
|
3094
|
+
/**
|
|
3095
|
+
* Run `work` against the client for the given supervisor, or the singleton when none is
|
|
3096
|
+
* supplied. External supervisors are dialed on demand and closed again; the singleton client is
|
|
3097
|
+
* reused. Workflow host tools correlate by supervisor ID so runtime ids (e.g. rt_1) that collide
|
|
3098
|
+
* across supervisors never route to the wrong host.
|
|
3099
|
+
*/
|
|
3100
|
+
async function withWorkflowClient(supervisorId, work, getSupervisorClient$1 = getSupervisorClient) {
|
|
3101
|
+
if (supervisorId) {
|
|
3102
|
+
const { client, isSingleton } = await getSupervisorClient$1(supervisorId);
|
|
3103
|
+
try {
|
|
3104
|
+
return await work(client);
|
|
3105
|
+
} finally {
|
|
3106
|
+
if (!isSingleton) client.close();
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
return work((await Supervisor.get()).getClient());
|
|
3110
|
+
}
|
|
3111
|
+
//#endregion
|
|
3112
|
+
//#region src/tools/workflow-status.ts
|
|
3113
|
+
function createWorkflowStatusTool(getSupervisorClient) {
|
|
3114
|
+
return async (args) => {
|
|
3115
|
+
if (!args.workflowId) throw new Error("workflowId is required");
|
|
3116
|
+
return withWorkflowClient(args.supervisorId, (client) => client.workflowStatus(args.workflowId), getSupervisorClient);
|
|
3117
|
+
};
|
|
3118
|
+
}
|
|
3119
|
+
const workflowStatusTool = createWorkflowStatusTool(getSupervisorClient);
|
|
3120
|
+
//#endregion
|
|
3121
|
+
//#region src/tools/workflow-wait.ts
|
|
3122
|
+
function createWorkflowWaitTool(getSupervisorClient) {
|
|
3123
|
+
return async (args) => {
|
|
3124
|
+
if (!args.workflowId) throw new Error("workflowId is required");
|
|
3125
|
+
const timeoutMs = args.timeout ? validateTimeout(args.timeout) * 1e3 : 3e4;
|
|
3126
|
+
return withWorkflowClient(args.supervisorId, (client) => client.workflowWait(args.workflowId, timeoutMs), getSupervisorClient);
|
|
3127
|
+
};
|
|
3128
|
+
}
|
|
3129
|
+
const workflowWaitTool = createWorkflowWaitTool(getSupervisorClient);
|
|
3130
|
+
//#endregion
|
|
3131
|
+
//#region src/tools/workflow-inspect.ts
|
|
3132
|
+
function createWorkflowInspectTool(getSupervisorClient) {
|
|
3133
|
+
return async (args) => {
|
|
3134
|
+
if (!args.workflowId) throw new Error("workflowId is required");
|
|
3135
|
+
return withWorkflowClient(args.supervisorId, (client) => client.workflowInspect(args.workflowId), getSupervisorClient);
|
|
3136
|
+
};
|
|
3137
|
+
}
|
|
3138
|
+
const workflowInspectTool = createWorkflowInspectTool(getSupervisorClient);
|
|
3139
|
+
//#endregion
|
|
3140
|
+
//#region src/tools/workflow-events.ts
|
|
3141
|
+
function createWorkflowEventsTool(getSupervisorClient) {
|
|
3142
|
+
return async (args) => {
|
|
3143
|
+
if (!args.workflowId) throw new Error("workflowId is required");
|
|
3144
|
+
return withWorkflowClient(args.supervisorId, (client) => client.workflowEvents(args.workflowId, {
|
|
3145
|
+
afterSeq: args.afterSeq,
|
|
3146
|
+
limit: args.limit
|
|
3147
|
+
}), getSupervisorClient);
|
|
3148
|
+
};
|
|
3149
|
+
}
|
|
3150
|
+
const workflowEventsTool = createWorkflowEventsTool(getSupervisorClient);
|
|
3151
|
+
//#endregion
|
|
3152
|
+
//#region src/tools/workflow-complete.ts
|
|
3153
|
+
function createWorkflowCompleteTool(getSupervisorClient) {
|
|
3154
|
+
return async (args) => {
|
|
3155
|
+
if (!args.workflow_id) throw new Error("workflow_id is required");
|
|
3156
|
+
if (!args.node_id) throw new Error("node_id is required");
|
|
3157
|
+
if (!args.activation_id) throw new Error("activation_id is required");
|
|
3158
|
+
if (!args.attempt_id) throw new Error("attempt_id is required");
|
|
3159
|
+
if (!args.lease_id) throw new Error("lease_id is required");
|
|
3160
|
+
if (!args.owner_token) throw new Error("owner_token is required");
|
|
3161
|
+
if (!args.outcome) throw new Error("outcome is required");
|
|
3162
|
+
return withWorkflowClient(args.supervisorId, (client) => client.workflowComplete(args), getSupervisorClient);
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
const workflowCompleteTool = createWorkflowCompleteTool(getSupervisorClient);
|
|
3166
|
+
//#endregion
|
|
3167
|
+
//#region src/tools/workflow-gate.ts
|
|
3168
|
+
function createWorkflowGateTool(getSupervisorClient) {
|
|
3169
|
+
return async (args) => {
|
|
3170
|
+
if (!args.workflow_id) throw new Error("workflow_id is required");
|
|
3171
|
+
if (!args.node_id) throw new Error("node_id is required");
|
|
3172
|
+
if (!args.gate_id) throw new Error("gate_id is required");
|
|
3173
|
+
if (!args.activation_id) throw new Error("activation_id is required");
|
|
3174
|
+
if (!args.operation) throw new Error("operation is required");
|
|
3175
|
+
if (![
|
|
3176
|
+
"satisfy",
|
|
3177
|
+
"reject",
|
|
3178
|
+
"waive",
|
|
3179
|
+
"external_result"
|
|
3180
|
+
].includes(args.operation)) throw new Error(`unknown gate operation "${args.operation}" (allowed: satisfy, reject, waive, external_result)`);
|
|
3181
|
+
if (args.observed_at) validateObservedAt(args.observed_at);
|
|
3182
|
+
return withWorkflowClient(args.supervisorId, (client) => client.workflowGate(args), getSupervisorClient);
|
|
3183
|
+
};
|
|
3184
|
+
}
|
|
3185
|
+
const workflowGateTool = createWorkflowGateTool(getSupervisorClient);
|
|
3186
|
+
//#endregion
|
|
3187
|
+
export { DEFAULT_RUN_REDUCER_LIMITS, RunReducer, Supervisor, THINKING_LEVELS, answerPermissionTool, askTool, brokerReceiveTool, brokerReplyTool, cancelTool, createRunSnapshot, createWorkflowCompleteTool, createWorkflowEventsTool, createWorkflowGateTool, createWorkflowInspectTool, createWorkflowStatusTool, createWorkflowWaitTool, dial, evaluateThinkingPolicy, eventsTool, extractEventText, findAvenorBinary, followUpTool, getInstallDir, getVersion, inspectTool, installerBinaryPath, isThinkingLevel, normalizeRunEvent, observeRun, peersTool, reduceRunSnapshot, resultTool, shutdownTool, socketsRoot, spawnTool, statusTool, validateSpawnSelection, validateThinking, validateThinkingForBackend, validateThinkingForBackendResume, workflowCompleteTool, workflowEventsTool, workflowGateTool, workflowInspectTool, workflowStatusTool, workflowWaitTool };
|