@dougbots/avenor-pi 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -1
- package/dist/index.js +297 -45
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InspectResult, StatusResult, Supervisor, answerPermissionTool, brokerReceiveTool, brokerReplyTool, dial, eventsTool, followUpTool, inspectTool, observeRun, resultTool, shutdownTool, spawnTool, statusTool, workflowCompleteTool, workflowEventsTool, workflowGateTool, workflowInspectTool, workflowStatusTool, workflowWaitTool } from "@dougbots/avenor-core";
|
|
1
|
+
import { InspectResult, StatusResult, Supervisor, answerPermissionTool, brokerReceiveTool, brokerReplyTool, dial, eventsTool, followUpTool, inspectTool, observeRun, resultTool, shutdownTool, spawnTool, statusTool, workflowCompleteTool, workflowControllerStatusTool, workflowEventsTool, workflowGateTool, workflowInspectTool, workflowReadyTool, workflowStatusTool, workflowWaitTool } from "@dougbots/avenor-core";
|
|
2
2
|
import { EventBus, ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
|
|
4
4
|
//#region src/subagent-broker.d.ts
|
|
@@ -134,6 +134,8 @@ interface ExtensionDeps {
|
|
|
134
134
|
workflowEventsTool: typeof workflowEventsTool;
|
|
135
135
|
workflowCompleteTool: typeof workflowCompleteTool;
|
|
136
136
|
workflowGateTool: typeof workflowGateTool;
|
|
137
|
+
workflowControllerStatusTool: typeof workflowControllerStatusTool;
|
|
138
|
+
workflowReadyTool: typeof workflowReadyTool;
|
|
137
139
|
observeRun: typeof observeRun;
|
|
138
140
|
dial: typeof dial;
|
|
139
141
|
Supervisor: typeof Supervisor;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Input, Key, Text, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
-
import { Supervisor, THINKING_LEVELS, answerPermissionTool, askTool, brokerReceiveTool, brokerReplyTool, cancelTool, dial, eventsTool, extractEventText, followUpTool, inspectTool, observeRun, peersTool, resultTool, shutdownTool, socketsRoot, spawnTool, statusTool, validateSpawnSelection, workflowCompleteTool, workflowEventsTool, workflowGateTool, workflowInspectTool, workflowStatusTool, workflowWaitTool } from "@dougbots/avenor-core";
|
|
4
|
+
import { Supervisor, THINKING_LEVELS, answerPermissionTool, askTool, brokerReceiveTool, brokerReplyTool, cancelTool, dial, eventsTool, extractEventText, followUpTool, inspectTool, observeRun, peersTool, resultTool, shutdownTool, socketsRoot, spawnTool, statusTool, validateSpawnSelection, workflowCompleteTool, workflowControllerStatusTool, workflowEventsTool, workflowGateTool, workflowInspectTool, workflowReadyTool, workflowStatusTool, workflowWaitTool } from "@dougbots/avenor-core";
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
//#region src/subagent-broker.ts
|
|
@@ -1230,6 +1230,66 @@ function shutdownLines(details, args, expanded) {
|
|
|
1230
1230
|
...paths.length > CLEANUP_ITEMS ? [itemMarker(paths.length - CLEANUP_ITEMS)] : []
|
|
1231
1231
|
];
|
|
1232
1232
|
}
|
|
1233
|
+
function controllerLeaderLine(leader) {
|
|
1234
|
+
if (!leader) return "Leader: none";
|
|
1235
|
+
const owner = leader.is_this_process === true ? "this process" : `other owner (${scalar(leader.owner_id)})`;
|
|
1236
|
+
const expires = optionalScalar(leader.expires_at);
|
|
1237
|
+
return `Leader: ${owner}${expires ? ` — lease expires ${expires}` : ""}`;
|
|
1238
|
+
}
|
|
1239
|
+
function controllerStatusLines(details, args, expanded) {
|
|
1240
|
+
const status = asRecord(details);
|
|
1241
|
+
if (!status || !stringValue(status.controller_id) || !stringValue(status.desired_state)) return void 0;
|
|
1242
|
+
const controllerId = selected(status, "controller_id", args.controller_id);
|
|
1243
|
+
const desiredState = selected(status, "desired_state");
|
|
1244
|
+
const inflight = numberValue(status.inflight) ?? 0;
|
|
1245
|
+
const maxInflight = numberValue(status.max_inflight);
|
|
1246
|
+
const blocked = asRecord(status.capacity_blocked);
|
|
1247
|
+
const blockedDetail = blocked ? optionalScalar(blocked.detail) : void 0;
|
|
1248
|
+
const lines = [`Controller: ${controllerId} — ${desiredState}`, controllerLeaderLine(asRecord(status.leader))];
|
|
1249
|
+
const candidates = numberValue(status.candidate_count);
|
|
1250
|
+
if (candidates !== void 0) lines.push(`Candidates: ${candidates} (advisory)`);
|
|
1251
|
+
lines.push(`In-flight: ${inflight}${maxInflight !== void 0 ? ` of ${maxInflight}` : ""}`);
|
|
1252
|
+
const lastReconcile = optionalScalar(status.last_reconcile);
|
|
1253
|
+
if (lastReconcile) lines.push(`Last reconcile: ${lastReconcile}`);
|
|
1254
|
+
if (blocked) lines.push(`Capacity blocked: ${selected(blocked, "source")}${blockedDetail ? ` — ${blockedDetail}` : ""}`);
|
|
1255
|
+
const nextPoll = optionalScalar(status.next_poll_at);
|
|
1256
|
+
if (nextPoll) lines.push(`Next external poll: ${nextPoll}`);
|
|
1257
|
+
lines.push(desiredState === "disabled" ? `Guidance: Run \`avenor workflow controller enable ${controllerId}\` to start dispatch and polling.` : `Guidance: Call avenor_workflow_controller_status without controller_id to review all controllers.`);
|
|
1258
|
+
if (!expanded) return lines;
|
|
1259
|
+
const supervisor = stringValue(args.supervisor_id) ? scalar(args.supervisor_id) : "singleton";
|
|
1260
|
+
return [
|
|
1261
|
+
...lines,
|
|
1262
|
+
`Supervisor: ${supervisor}`,
|
|
1263
|
+
...maxInflight !== void 0 ? [`Max in-flight: ${maxInflight}`] : [],
|
|
1264
|
+
...optionalNumber(status.revision) !== void 0 ? [`Revision: ${optionalNumber(status.revision)}`] : [],
|
|
1265
|
+
...optionalNumber(status.owner_epoch) !== void 0 ? [`Owner epoch: ${optionalNumber(status.owner_epoch)}`] : []
|
|
1266
|
+
];
|
|
1267
|
+
}
|
|
1268
|
+
function controllerListLines(details, args, expanded) {
|
|
1269
|
+
const result = asRecord(details);
|
|
1270
|
+
if (!result || !Array.isArray(result.controllers) || result.controllers.some((controller) => !asRecord(controller))) return void 0;
|
|
1271
|
+
const controllers = result.controllers.map((controller) => controller);
|
|
1272
|
+
stringValue(args.supervisor_id) && scalar(args.supervisor_id);
|
|
1273
|
+
if (controllers.length === 0) return ["No workflow controllers configured."];
|
|
1274
|
+
const displayed = controllers.slice(0, STATUS_ROWS);
|
|
1275
|
+
const lines = [
|
|
1276
|
+
`Workflow controllers — ${controllers.length}`,
|
|
1277
|
+
...displayed.map(controllerListRow),
|
|
1278
|
+
...controllers.length > displayed.length ? [itemMarker(controllers.length - displayed.length)] : []
|
|
1279
|
+
];
|
|
1280
|
+
if (!expanded) return lines;
|
|
1281
|
+
for (const controller of displayed) {
|
|
1282
|
+
const leader = asRecord(controller.leader);
|
|
1283
|
+
const ownerEpoch = leader ? numberValue(leader.owner_epoch) : void 0;
|
|
1284
|
+
lines.push(`${selected(controller, "controller_id")}:`, controllerLeaderLine(leader), ...ownerEpoch !== void 0 ? [`Owner epoch: ${ownerEpoch}`] : []);
|
|
1285
|
+
}
|
|
1286
|
+
return lines;
|
|
1287
|
+
}
|
|
1288
|
+
function controllerListRow(controller) {
|
|
1289
|
+
const leader = asRecord(controller.leader);
|
|
1290
|
+
const owner = leader ? leader.is_this_process === true ? "this process" : `other owner (${scalar(leader.owner_id)})` : "none";
|
|
1291
|
+
return `${selected(controller, "controller_id")} — ${selected(controller, "desired_state")} — leader: ${owner}`;
|
|
1292
|
+
}
|
|
1233
1293
|
function render(tool, result, options, theme, build) {
|
|
1234
1294
|
try {
|
|
1235
1295
|
return linesText(options.isPartial ? partial(result) : build() ?? fallback(tool), theme);
|
|
@@ -1322,8 +1382,20 @@ function renderEventsResult(result, options, theme, args) {
|
|
|
1322
1382
|
function renderShutdownResult(result, options, theme, args) {
|
|
1323
1383
|
return render("shutdown", result, options, theme, () => shutdownLines(result.details, args, options.expanded));
|
|
1324
1384
|
}
|
|
1385
|
+
function renderWorkflowControllerStatusCall(args, theme) {
|
|
1386
|
+
const pieces = stringValue(args.controller_id) ? [`avenor_workflow_controller_status controller_id ${quoted(args.controller_id)}`] : ["avenor_workflow_controller_status (all controllers)"];
|
|
1387
|
+
const supervisor = optionalQuoted(args.supervisor_id);
|
|
1388
|
+
if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
|
|
1389
|
+
return linesText([pieces.join(" ")], theme);
|
|
1390
|
+
}
|
|
1391
|
+
function renderWorkflowControllerStatusResult(result, options, theme, args) {
|
|
1392
|
+
return render("workflow controller status", result, options, theme, () => controllerStatusLines(result.details, args, options.expanded) ?? controllerListLines(result.details, args, options.expanded));
|
|
1393
|
+
}
|
|
1325
1394
|
//#endregion
|
|
1326
1395
|
//#region src/index.ts
|
|
1396
|
+
function asStatusResult(raw) {
|
|
1397
|
+
return raw;
|
|
1398
|
+
}
|
|
1327
1399
|
const POLL_INTERVAL_MS = 3e3;
|
|
1328
1400
|
const INSPECT_LIMIT = 128;
|
|
1329
1401
|
const COMPLETION_OUTPUT_CHARS = 600;
|
|
@@ -1349,6 +1421,8 @@ const defaultDeps = {
|
|
|
1349
1421
|
workflowEventsTool,
|
|
1350
1422
|
workflowCompleteTool,
|
|
1351
1423
|
workflowGateTool,
|
|
1424
|
+
workflowControllerStatusTool,
|
|
1425
|
+
workflowReadyTool,
|
|
1352
1426
|
observeRun,
|
|
1353
1427
|
dial,
|
|
1354
1428
|
Supervisor
|
|
@@ -1526,6 +1600,88 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
1526
1600
|
let pollingErrorCount = 0;
|
|
1527
1601
|
const pollingErrors = [];
|
|
1528
1602
|
let lastStatusEntries = [];
|
|
1603
|
+
const pendingWaits = /* @__PURE__ */ new Set();
|
|
1604
|
+
function interruptPendingWaits(stop, excludeRunKey) {
|
|
1605
|
+
for (const wait of [...pendingWaits]) {
|
|
1606
|
+
if (wait.runKey === excludeRunKey) continue;
|
|
1607
|
+
pendingWaits.delete(wait);
|
|
1608
|
+
wait.interrupt(stop);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
function registerPendingWait(runKey, interrupt) {
|
|
1612
|
+
const wait = {
|
|
1613
|
+
runKey,
|
|
1614
|
+
interrupt
|
|
1615
|
+
};
|
|
1616
|
+
pendingWaits.add(wait);
|
|
1617
|
+
return wait;
|
|
1618
|
+
}
|
|
1619
|
+
function createInterruptibleWait(runKey, signal) {
|
|
1620
|
+
const controller = new AbortController();
|
|
1621
|
+
let stopped;
|
|
1622
|
+
const onSignalAbort = () => controller.abort();
|
|
1623
|
+
if (signal?.aborted) controller.abort();
|
|
1624
|
+
else signal?.addEventListener("abort", onSignalAbort);
|
|
1625
|
+
const pending = registerPendingWait(runKey, (stop) => {
|
|
1626
|
+
if (controller.signal.aborted) return;
|
|
1627
|
+
stopped = stop;
|
|
1628
|
+
controller.abort();
|
|
1629
|
+
});
|
|
1630
|
+
return {
|
|
1631
|
+
signal: controller.signal,
|
|
1632
|
+
get stopped() {
|
|
1633
|
+
return stopped;
|
|
1634
|
+
},
|
|
1635
|
+
dispose() {
|
|
1636
|
+
pendingWaits.delete(pending);
|
|
1637
|
+
signal?.removeEventListener("abort", onSignalAbort);
|
|
1638
|
+
}
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
/** Build the early return for a wait interrupted by a sibling stopping
|
|
1642
|
+
* point. When the awaited run itself has since reached a stopping point
|
|
1643
|
+
* (possible when the caller omitted supervisor_id and the registry key
|
|
1644
|
+
* diverged), deliver its real state instead of an interruption notice. */
|
|
1645
|
+
async function resultAfterInterrupt(params, stop, tracked) {
|
|
1646
|
+
const runId = tracked?.runId ?? params.run_id;
|
|
1647
|
+
const supervisorId = tracked?.supervisorId ?? params.supervisor_id;
|
|
1648
|
+
let current;
|
|
1649
|
+
try {
|
|
1650
|
+
current = asStatusResult(await deps.statusTool({
|
|
1651
|
+
runId,
|
|
1652
|
+
supervisorId,
|
|
1653
|
+
view: "full"
|
|
1654
|
+
}));
|
|
1655
|
+
} catch {}
|
|
1656
|
+
if (current !== void 0 && (isTerminalStatus(current.status) || current.status === "waiting")) try {
|
|
1657
|
+
return {
|
|
1658
|
+
...await deps.resultTool({
|
|
1659
|
+
runId,
|
|
1660
|
+
supervisorId,
|
|
1661
|
+
wait: true,
|
|
1662
|
+
timeout: params.timeout,
|
|
1663
|
+
signal: params.signal
|
|
1664
|
+
}),
|
|
1665
|
+
interrupted_by: {
|
|
1666
|
+
run_id: stop.runId,
|
|
1667
|
+
label: stop.label,
|
|
1668
|
+
status: stop.status
|
|
1669
|
+
}
|
|
1670
|
+
};
|
|
1671
|
+
} catch {}
|
|
1672
|
+
return {
|
|
1673
|
+
run_id: current?.run_id ?? runId,
|
|
1674
|
+
label: current?.label ?? runId,
|
|
1675
|
+
status: current?.status ?? "running",
|
|
1676
|
+
ready: current !== void 0 && isTerminalStatus(current.status),
|
|
1677
|
+
...current?.pending_permission && { pending_permission: current.pending_permission },
|
|
1678
|
+
interrupted_by: {
|
|
1679
|
+
run_id: stop.runId,
|
|
1680
|
+
label: stop.label,
|
|
1681
|
+
status: stop.status
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1529
1685
|
const subAgentEnv = options.subAgentEnv !== void 0 ? options.subAgentEnv : readSubAgentEnv();
|
|
1530
1686
|
const isSubAgentBrokerMode = subAgentEnv !== null;
|
|
1531
1687
|
let subAgentPollTimer = null;
|
|
@@ -1643,7 +1799,7 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
1643
1799
|
const liveMap = /* @__PURE__ */ new Map();
|
|
1644
1800
|
try {
|
|
1645
1801
|
const allLive = await deps.statusTool({});
|
|
1646
|
-
const liveList =
|
|
1802
|
+
const liveList = "runs" in allLive ? allLive.runs : [allLive];
|
|
1647
1803
|
for (const result of liveList) liveMap.set(result.run_id, result);
|
|
1648
1804
|
} catch (err) {
|
|
1649
1805
|
recordPollingError("singleton-list", "failed to list singleton runs", err);
|
|
@@ -1673,11 +1829,10 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
1673
1829
|
continue;
|
|
1674
1830
|
}
|
|
1675
1831
|
try {
|
|
1676
|
-
const
|
|
1832
|
+
const result = asStatusResult(await deps.statusTool({
|
|
1677
1833
|
runId: run.runId,
|
|
1678
1834
|
supervisorId: run.supervisorId
|
|
1679
|
-
});
|
|
1680
|
-
const result = Array.isArray(raw) ? raw[0] : raw;
|
|
1835
|
+
}));
|
|
1681
1836
|
if (!result) throw new Error("status response was empty");
|
|
1682
1837
|
run.lastStatus = result;
|
|
1683
1838
|
run.agent = result.agent ?? run.agent;
|
|
@@ -1760,7 +1915,10 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
1760
1915
|
const tick = async () => {
|
|
1761
1916
|
if (!pollingActive || generation !== pollingGeneration) return;
|
|
1762
1917
|
const prevStatuses = /* @__PURE__ */ new Map();
|
|
1763
|
-
for (const [id, run] of trackedRuns) prevStatuses.set(id,
|
|
1918
|
+
for (const [id, run] of trackedRuns) prevStatuses.set(id, {
|
|
1919
|
+
status: run.lastStatus?.status,
|
|
1920
|
+
pendingPermission: !!run.lastStatus?.pending_permission
|
|
1921
|
+
});
|
|
1764
1922
|
const currentPoll = pollRuns();
|
|
1765
1923
|
pollInFlight = currentPoll;
|
|
1766
1924
|
const entries = await currentPoll.finally(() => {
|
|
@@ -1773,13 +1931,19 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
1773
1931
|
const entryKey = trackedRunKey(entry.supervisorId, entry.runId);
|
|
1774
1932
|
const run = trackedRuns.get(entryKey);
|
|
1775
1933
|
if (!run) continue;
|
|
1934
|
+
const prevStatus = prevStatuses.get(entryKey);
|
|
1935
|
+
const wasStopped = prevStatus?.status !== void 0 && isTerminalStatus(prevStatus.status) || !!prevStatus?.pendingPermission;
|
|
1936
|
+
if ((entry.pendingPermission || isTerminalStatus(entry.status)) && !wasStopped) interruptPendingWaits({
|
|
1937
|
+
runId: entry.runId,
|
|
1938
|
+
label: entry.label,
|
|
1939
|
+
status: isTerminalStatus(entry.status) ? entry.status : "waiting"
|
|
1940
|
+
}, entryKey);
|
|
1776
1941
|
if (isTerminalStatus(entry.status)) {
|
|
1777
1942
|
if (decideCompletion(run) === "skip") {
|
|
1778
1943
|
if (run.consumed) trackedRuns.delete(entryKey);
|
|
1779
1944
|
continue;
|
|
1780
1945
|
}
|
|
1781
|
-
|
|
1782
|
-
if (!(prevStatus && isTerminalStatus(prevStatus))) sessionCtx?.ui.notify(`Sub-agent "${entry.label}" finished: ${entry.status}`, entry.status === "done" ? "info" : "warning");
|
|
1946
|
+
if (!(prevStatus?.status !== void 0 && isTerminalStatus(prevStatus.status))) sessionCtx?.ui.notify(`Sub-agent "${entry.label}" finished: ${entry.status}`, entry.status === "done" ? "info" : "warning");
|
|
1783
1947
|
const finalOutput = await resolveFinalOutput(run, run.lastStatus);
|
|
1784
1948
|
try {
|
|
1785
1949
|
pi.sendUserMessage(buildCompletionText({
|
|
@@ -1899,11 +2063,10 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
1899
2063
|
supervisorId: run.supervisorId
|
|
1900
2064
|
});
|
|
1901
2065
|
try {
|
|
1902
|
-
const
|
|
2066
|
+
const status = asStatusResult(await deps.statusTool({
|
|
1903
2067
|
runId: result.run_id,
|
|
1904
2068
|
supervisorId: run.supervisorId
|
|
1905
|
-
});
|
|
1906
|
-
const status = Array.isArray(rawStatus) ? rawStatus[0] : rawStatus;
|
|
2069
|
+
}));
|
|
1907
2070
|
upsertTrackedRun({
|
|
1908
2071
|
runId: result.run_id,
|
|
1909
2072
|
label: result.label,
|
|
@@ -1981,12 +2144,11 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
1981
2144
|
await observer?.close().catch(() => {});
|
|
1982
2145
|
if (outcome === "aborted") return { aborted: true };
|
|
1983
2146
|
try {
|
|
1984
|
-
const raw = await deps.statusTool({
|
|
1985
|
-
runId: run.runId,
|
|
1986
|
-
supervisorId: run.supervisorId
|
|
1987
|
-
});
|
|
1988
2147
|
return {
|
|
1989
|
-
status:
|
|
2148
|
+
status: asStatusResult(await deps.statusTool({
|
|
2149
|
+
runId: run.runId,
|
|
2150
|
+
supervisorId: run.supervisorId
|
|
2151
|
+
})),
|
|
1990
2152
|
aborted: false
|
|
1991
2153
|
};
|
|
1992
2154
|
} catch {
|
|
@@ -1999,11 +2161,10 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
1999
2161
|
if (!firstPoll) await sleep(pollIntervalMs);
|
|
2000
2162
|
firstPoll = false;
|
|
2001
2163
|
try {
|
|
2002
|
-
const
|
|
2164
|
+
const status = asStatusResult(await deps.statusTool({
|
|
2003
2165
|
runId: run.runId,
|
|
2004
2166
|
supervisorId: run.supervisorId
|
|
2005
|
-
});
|
|
2006
|
-
const status = Array.isArray(raw) ? raw[0] : raw;
|
|
2167
|
+
}));
|
|
2007
2168
|
if (!status) continue;
|
|
2008
2169
|
const text = status.pending_permission ? `[blocked] ${run.label ?? run.runId} — permission: ${status.pending_permission.description}` : `${run.label ?? run.runId}${status.phase_label ? ` (${status.phase_label})` : ""}`;
|
|
2009
2170
|
onUpdate?.({
|
|
@@ -2045,6 +2206,14 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2045
2206
|
stopSubAgentPoll();
|
|
2046
2207
|
hostPendingAsks.clear();
|
|
2047
2208
|
subAgentPendingAsks.clear();
|
|
2209
|
+
for (const wait of [...pendingWaits]) {
|
|
2210
|
+
pendingWaits.delete(wait);
|
|
2211
|
+
wait.interrupt({
|
|
2212
|
+
runId: "",
|
|
2213
|
+
status: "shutdown",
|
|
2214
|
+
synthetic: true
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2048
2217
|
sessionCtx = null;
|
|
2049
2218
|
});
|
|
2050
2219
|
pi.on("before_agent_start", async () => {
|
|
@@ -2073,12 +2242,12 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2073
2242
|
pi.registerTool({
|
|
2074
2243
|
name: "avenor_spawn",
|
|
2075
2244
|
label: "Avenor Spawn",
|
|
2076
|
-
description: `Dispatch an agent run via avenor. Optional thinking accepts ${[...THINKING_LEVELS].slice(0, -1).join(", ")}, or ${THINKING_LEVELS[THINKING_LEVELS.length - 1]}; unsupported backends reject explicit values. Blocks by default, showing live progress. Set wait=false for fire-and-forget.`,
|
|
2245
|
+
description: `Dispatch an agent run via avenor. Optional thinking accepts ${[...THINKING_LEVELS].slice(0, -1).join(", ")}, or ${THINKING_LEVELS[THINKING_LEVELS.length - 1]}; unsupported backends reject explicit values. Blocks by default, showing live progress. Set wait=false for fire-and-forget. With several blocking spawns in parallel, the first stopping point (permission or completion) interrupts the other waits.`,
|
|
2077
2246
|
promptSnippet: "Dispatch sub-agent runs via avenor (spawn, status, result, inspect, events, follow-up, shutdown)",
|
|
2078
2247
|
promptGuidelines: [
|
|
2079
2248
|
"Use avenor_spawn to delegate well-defined tasks to sub-agents.",
|
|
2080
2249
|
"Use avenor_status (view \"lifecycle\") only to check progress or pending permissions.",
|
|
2081
|
-
"Use avenor_result to retrieve the final result of a sub-agent; it waits by default, but set wait=false for runs that already completed.",
|
|
2250
|
+
"Use avenor_result to retrieve the final result of a sub-agent; it waits by default, but set wait=false for runs that already completed. When awaiting multiple runs, the first stopping point (permission or completion) interrupts the other waits.",
|
|
2082
2251
|
"Use avenor_inspect to review a bounded transcript and tool activity from a run.",
|
|
2083
2252
|
"Use avenor_events only when you specifically need raw event payloads.",
|
|
2084
2253
|
"Use avenor_follow_up to iterate on a completed run.",
|
|
@@ -2180,21 +2349,56 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2180
2349
|
run_id: result.run_id
|
|
2181
2350
|
}
|
|
2182
2351
|
});
|
|
2183
|
-
const
|
|
2352
|
+
const blockingWait = createInterruptibleWait(trackedRunKey(supervisorId, result.run_id), signal);
|
|
2353
|
+
let waitResult;
|
|
2354
|
+
try {
|
|
2355
|
+
waitResult = await waitForRun(runRef, blockingWait.signal, onUpdate);
|
|
2356
|
+
} finally {
|
|
2357
|
+
blockingWait.dispose();
|
|
2358
|
+
}
|
|
2359
|
+
let waitStatus = waitResult.status;
|
|
2184
2360
|
if (waitResult.aborted) {
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
}
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2361
|
+
const stop = blockingWait.stopped;
|
|
2362
|
+
if (stop && !stop.synthetic) {
|
|
2363
|
+
const probed = asStatusResult(await deps.statusTool({
|
|
2364
|
+
runId: result.run_id,
|
|
2365
|
+
supervisorId
|
|
2366
|
+
}).catch(() => void 0));
|
|
2367
|
+
if (probed && (isTerminalStatus(probed.status) || probed.status === "waiting")) waitStatus = probed;
|
|
2368
|
+
else {
|
|
2369
|
+
const tracked = getTrackedRun(result.run_id, supervisorId);
|
|
2370
|
+
if (tracked) tracked.blocking = false;
|
|
2371
|
+
return {
|
|
2372
|
+
content: [{
|
|
2373
|
+
type: "text",
|
|
2374
|
+
text: `Monitoring of "${label}" (run_id: ${result.run_id}) was interrupted: "${stop.label ?? stop.runId}" reached ${stop.status === "waiting" ? "a permission request" : stop.status}. Handle that first; this run's final result is delivered automatically.`
|
|
2375
|
+
}],
|
|
2376
|
+
details: {
|
|
2377
|
+
run_id: result.run_id,
|
|
2378
|
+
interrupted_by: {
|
|
2379
|
+
run_id: stop.runId,
|
|
2380
|
+
label: stop.label,
|
|
2381
|
+
status: stop.status
|
|
2382
|
+
},
|
|
2383
|
+
...spawnIdentityMetadata(hostParams)
|
|
2384
|
+
}
|
|
2385
|
+
};
|
|
2194
2386
|
}
|
|
2195
|
-
}
|
|
2387
|
+
} else {
|
|
2388
|
+
trackedRuns.delete(trackedRunKey(supervisorId, result.run_id));
|
|
2389
|
+
return {
|
|
2390
|
+
content: [{
|
|
2391
|
+
type: "text",
|
|
2392
|
+
text: `Monitoring of "${label}" (run_id: ${result.run_id}) was interrupted. Use avenor_status or avenor_inspect to check.`
|
|
2393
|
+
}],
|
|
2394
|
+
details: {
|
|
2395
|
+
run_id: result.run_id,
|
|
2396
|
+
...spawnIdentityMetadata(hostParams)
|
|
2397
|
+
}
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2196
2400
|
}
|
|
2197
|
-
const status =
|
|
2401
|
+
const status = waitStatus;
|
|
2198
2402
|
if (!status) return {
|
|
2199
2403
|
content: [{
|
|
2200
2404
|
type: "text",
|
|
@@ -2333,7 +2537,7 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2333
2537
|
pi.registerTool({
|
|
2334
2538
|
name: "avenor_result",
|
|
2335
2539
|
label: "Avenor Result",
|
|
2336
|
-
description:
|
|
2540
|
+
description: `Retrieve the complete final output of a run without transcript or event details. Waits by default; set wait=false to retrieve an already-completed run without blocking. When several runs are pending, the first stopping point (a permission request or completion on any run) interrupts other blocking waits so they can be handled; re-issue the interrupted wait afterwards.`,
|
|
2337
2541
|
parameters: Type.Object({
|
|
2338
2542
|
run_id: Type.String({ description: "Run ID or label" }),
|
|
2339
2543
|
wait: Type.Optional(Type.Boolean({ description: "Wait for a terminal result (default true); false retrieves the current state without blocking" })),
|
|
@@ -2345,14 +2549,21 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2345
2549
|
const previousBlocking = tracked?.blocking;
|
|
2346
2550
|
if (tracked) tracked.blocking = true;
|
|
2347
2551
|
let completed = false;
|
|
2552
|
+
const blockingWait = params.wait ?? true ? createInterruptibleWait(trackedRunKey(tracked?.supervisorId ?? params.supervisor_id, tracked?.runId ?? params.run_id), signal) : void 0;
|
|
2348
2553
|
try {
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2554
|
+
let result;
|
|
2555
|
+
try {
|
|
2556
|
+
result = await deps.resultTool({
|
|
2557
|
+
runId: params.run_id,
|
|
2558
|
+
supervisorId: params.supervisor_id,
|
|
2559
|
+
wait: params.wait,
|
|
2560
|
+
timeout: params.timeout,
|
|
2561
|
+
signal: blockingWait?.signal ?? signal
|
|
2562
|
+
});
|
|
2563
|
+
} catch (err) {
|
|
2564
|
+
if (!blockingWait?.stopped || blockingWait.stopped.synthetic) throw err;
|
|
2565
|
+
result = await resultAfterInterrupt(params, blockingWait.stopped, tracked);
|
|
2566
|
+
}
|
|
2356
2567
|
completed = result.ready;
|
|
2357
2568
|
if (completed) {
|
|
2358
2569
|
if (tracked) tracked.consumed = true;
|
|
@@ -2374,6 +2585,7 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2374
2585
|
details: result
|
|
2375
2586
|
};
|
|
2376
2587
|
} finally {
|
|
2588
|
+
blockingWait?.dispose();
|
|
2377
2589
|
if (!completed && tracked && getTrackedRun(params.run_id, params.supervisor_id) === tracked) tracked.blocking = previousBlocking ?? false;
|
|
2378
2590
|
}
|
|
2379
2591
|
},
|
|
@@ -2477,11 +2689,10 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2477
2689
|
supervisorId: params.supervisor_id
|
|
2478
2690
|
});
|
|
2479
2691
|
try {
|
|
2480
|
-
const
|
|
2692
|
+
const status = asStatusResult(await deps.statusTool({
|
|
2481
2693
|
runId: result.run_id,
|
|
2482
2694
|
supervisorId: params.supervisor_id
|
|
2483
|
-
});
|
|
2484
|
-
const status = Array.isArray(rawStatus) ? rawStatus[0] : rawStatus;
|
|
2695
|
+
}));
|
|
2485
2696
|
upsertTrackedRun({
|
|
2486
2697
|
runId: result.run_id,
|
|
2487
2698
|
label: result.label,
|
|
@@ -2518,16 +2729,18 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2518
2729
|
pi.registerTool({
|
|
2519
2730
|
name: "avenor_ask",
|
|
2520
2731
|
label: "Avenor Ask",
|
|
2521
|
-
description: "Ask another agent run a question and block until it replies.",
|
|
2732
|
+
description: "Ask another agent run a question and block until it replies. An optional timeout_ms bounds the wait; a failed ask reports its message_id and whether it is still pending (withdrawable via avenor_cancel) or already withdrawn.",
|
|
2522
2733
|
parameters: Type.Object({
|
|
2523
2734
|
to_run_id: Type.String({ description: "Target run ID" }),
|
|
2524
2735
|
message: Type.String({ description: "Question to ask the other agent" }),
|
|
2736
|
+
timeout_ms: Type.Optional(Type.Integer({ description: "Optional timeout in milliseconds for the ask" })),
|
|
2525
2737
|
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
|
|
2526
2738
|
}),
|
|
2527
2739
|
async execute(_toolCallId, params) {
|
|
2528
2740
|
return askTool({
|
|
2529
2741
|
toRunId: params.to_run_id,
|
|
2530
2742
|
message: params.message,
|
|
2743
|
+
timeoutMs: params.timeout_ms,
|
|
2531
2744
|
supervisorId: params.supervisor_id
|
|
2532
2745
|
});
|
|
2533
2746
|
}
|
|
@@ -2885,6 +3098,45 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2885
3098
|
};
|
|
2886
3099
|
}
|
|
2887
3100
|
});
|
|
3101
|
+
pi.registerTool({
|
|
3102
|
+
name: "avenor_workflow_controller_status",
|
|
3103
|
+
label: "Avenor Workflow Controller Status",
|
|
3104
|
+
description: "Show workflow controller status. With controller_id, returns that controller's full status; without it, lists all controllers with summary state. Create, enable, and disable are CLI-only (avenor workflow controller ...).",
|
|
3105
|
+
parameters: Type.Object({
|
|
3106
|
+
controller_id: Type.Optional(Type.String({ description: "Controller ID; omit to list all controllers" })),
|
|
3107
|
+
supervisor_id: Type.Optional(Type.String({ description: "Supervisor ID for multi-supervisor mode" }))
|
|
3108
|
+
}),
|
|
3109
|
+
async execute(_toolCallId, params) {
|
|
3110
|
+
const result = await deps.workflowControllerStatusTool({
|
|
3111
|
+
controllerId: params.controller_id,
|
|
3112
|
+
supervisorId: params.supervisor_id
|
|
3113
|
+
});
|
|
3114
|
+
const details = { ...result };
|
|
3115
|
+
if (params.controller_id) try {
|
|
3116
|
+
const ready = await deps.workflowReadyTool({
|
|
3117
|
+
controllerId: params.controller_id,
|
|
3118
|
+
supervisorId: params.supervisor_id
|
|
3119
|
+
});
|
|
3120
|
+
if (Array.isArray(ready.candidates)) details.candidate_count = ready.candidates.length;
|
|
3121
|
+
} catch {}
|
|
3122
|
+
return {
|
|
3123
|
+
content: [{
|
|
3124
|
+
type: "text",
|
|
3125
|
+
text: JSON.stringify(result, null, 2)
|
|
3126
|
+
}],
|
|
3127
|
+
details
|
|
3128
|
+
};
|
|
3129
|
+
},
|
|
3130
|
+
renderCall(args, theme, context) {
|
|
3131
|
+
return renderWorkflowControllerStatusCall(context.args, theme);
|
|
3132
|
+
},
|
|
3133
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
3134
|
+
return renderWorkflowControllerStatusResult(result, {
|
|
3135
|
+
expanded,
|
|
3136
|
+
isPartial
|
|
3137
|
+
}, theme, context.args);
|
|
3138
|
+
}
|
|
3139
|
+
});
|
|
2888
3140
|
pi.registerCommand("avenor-status", {
|
|
2889
3141
|
description: "Show status of all avenor runs",
|
|
2890
3142
|
handler: async (_args, ctx) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dougbots/avenor-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"dev": "tsdown --watch"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@dougbots/avenor-core": "^0.
|
|
38
|
+
"@dougbots/avenor-core": "^0.12.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@earendil-works/pi-coding-agent": ">=0.74.0",
|