@dougbots/avenor-pi 0.10.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/dist/index.d.ts +15 -3
- package/dist/index.js +397 -41
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InspectResult, StatusResult, Supervisor, answerPermissionTool, dial, eventsTool, followUpTool, inspectTool, observeRun, resultTool, shutdownTool, spawnTool, statusTool } from "@dougbots/avenor-core";
|
|
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";
|
|
2
2
|
import { EventBus, ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
|
|
4
4
|
//#region src/types.d.ts
|
|
@@ -118,7 +118,15 @@ interface ExtensionDeps {
|
|
|
118
118
|
followUpTool: typeof followUpTool;
|
|
119
119
|
inspectTool: typeof inspectTool;
|
|
120
120
|
resultTool: typeof resultTool;
|
|
121
|
+
brokerReceiveTool: typeof brokerReceiveTool;
|
|
122
|
+
brokerReplyTool: typeof brokerReplyTool;
|
|
121
123
|
shutdownTool: typeof shutdownTool;
|
|
124
|
+
workflowStatusTool: typeof workflowStatusTool;
|
|
125
|
+
workflowWaitTool: typeof workflowWaitTool;
|
|
126
|
+
workflowInspectTool: typeof workflowInspectTool;
|
|
127
|
+
workflowEventsTool: typeof workflowEventsTool;
|
|
128
|
+
workflowCompleteTool: typeof workflowCompleteTool;
|
|
129
|
+
workflowGateTool: typeof workflowGateTool;
|
|
122
130
|
observeRun: typeof observeRun;
|
|
123
131
|
dial: typeof dial;
|
|
124
132
|
Supervisor: typeof Supervisor;
|
|
@@ -141,7 +149,11 @@ declare function isTerminalStatus(status: string | undefined): boolean;
|
|
|
141
149
|
declare function renderStatusLines(entries: RunStatusEntry[]): string[];
|
|
142
150
|
declare function compactWhitespace(text: string | undefined, limit?: number): string | undefined;
|
|
143
151
|
declare function buildInspectPayload(result: InspectResult): Record<string, unknown>;
|
|
144
|
-
|
|
152
|
+
interface ExtensionOptions {
|
|
153
|
+
/** Polling cadence; injectable so tests can drive deterministic ticks. */
|
|
154
|
+
pollIntervalMs?: number;
|
|
155
|
+
}
|
|
156
|
+
declare function createExtension(deps?: ExtensionDeps, options?: ExtensionOptions): (pi: ExtensionAPI) => Promise<void>;
|
|
145
157
|
declare const _default: (pi: ExtensionAPI) => Promise<void>;
|
|
146
158
|
//#endregion
|
|
147
|
-
export { type AvenorEventBus, type AvenorEventChannel, type AvenorEventMap, type AvenorRunStatus, CHANNEL_POLL_COMPLETED, CHANNEL_POLL_ERROR, CHANNEL_RUN_TERMINAL, ExtensionDeps, type PollCompletedPayload, type PollErrorPayload, type PollErrorPayloadInput, type PollErrorSource, type RunTerminalPayload, type RunTerminalPayloadInput, buildCompletionText, buildInspectPayload, compactWhitespace, createExtension, createPollCompletedPayload, createPollErrorPayload, createRunTerminalPayload, _default as default, emitAvenorEvent, isTerminalStatus, onAvenorEvent, renderStatusLines, spawnIdentityMetadata, statusSupervisorId };
|
|
159
|
+
export { type AvenorEventBus, type AvenorEventChannel, type AvenorEventMap, type AvenorRunStatus, CHANNEL_POLL_COMPLETED, CHANNEL_POLL_ERROR, CHANNEL_RUN_TERMINAL, ExtensionDeps, ExtensionOptions, type PollCompletedPayload, type PollErrorPayload, type PollErrorPayloadInput, type PollErrorSource, type RunTerminalPayload, type RunTerminalPayloadInput, buildCompletionText, buildInspectPayload, compactWhitespace, createExtension, createPollCompletedPayload, createPollErrorPayload, createRunTerminalPayload, _default as default, emitAvenorEvent, isTerminalStatus, onAvenorEvent, renderStatusLines, spawnIdentityMetadata, statusSupervisorId };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Input, Key, Text, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { Supervisor, answerPermissionTool, dial, eventsTool, extractEventText, followUpTool, inspectTool, observeRun, resultTool, shutdownTool, socketsRoot, spawnTool, statusTool, validateSpawnSelection } from "@dougbots/avenor-core";
|
|
3
|
+
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
4
|
import * as fs from "node:fs";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
@@ -475,12 +475,12 @@ var RunInspectorOverlay = class {
|
|
|
475
475
|
function matchingWatchRuns(runs, selector) {
|
|
476
476
|
const query = selector.trim().toLowerCase();
|
|
477
477
|
if (!query) return [];
|
|
478
|
-
const byId = runs.
|
|
479
|
-
if (byId) return
|
|
478
|
+
const byId = runs.filter((run) => run.runId.toLowerCase() === query);
|
|
479
|
+
if (byId.length > 0) return byId;
|
|
480
480
|
return runs.filter((run) => run.label?.toLowerCase() === query || run.agent?.toLowerCase() === query);
|
|
481
481
|
}
|
|
482
482
|
function watchRunChoice(run) {
|
|
483
|
-
return `${sanitizeText(run.label ?? run.runId)}${run.agent ? ` · ${sanitizeText(run.agent)}` : ""} · ${sanitizeText(run.runId).slice(0, 8)}`;
|
|
483
|
+
return `${sanitizeText(run.label ?? run.runId)}${run.agent ? ` · ${sanitizeText(run.agent)}` : ""}${run.supervisorId ? ` · ${sanitizeText(run.supervisorId)}` : ""} · ${sanitizeText(run.runId).slice(0, 8)}`;
|
|
484
484
|
}
|
|
485
485
|
async function selectWatchRun(ctx, runs) {
|
|
486
486
|
const choices = runs.map(watchRunChoice);
|
|
@@ -522,7 +522,20 @@ async function openRunInspector(ctx, options) {
|
|
|
522
522
|
}
|
|
523
523
|
//#endregion
|
|
524
524
|
//#region src/types.ts
|
|
525
|
-
|
|
525
|
+
/**
|
|
526
|
+
* Find a live status for a tracked run from the unqualified live-status list.
|
|
527
|
+
*
|
|
528
|
+
* `statusTool({})` only describes the current singleton supervisor, so it is
|
|
529
|
+
* authoritative solely for runs that live in the singleton namespace. Runs on
|
|
530
|
+
* another supervisor must be polled through their own supervisor; matching
|
|
531
|
+
* them against this list could correlate a run with a colliding id (for
|
|
532
|
+
* example a shared `rt_1`) owned by a different supervisor.
|
|
533
|
+
*
|
|
534
|
+
* @param singletonScope true when the tracked run belongs to the singleton
|
|
535
|
+
* supervisor (no explicit socket, or a socket equal to the current instance).
|
|
536
|
+
*/
|
|
537
|
+
function findLiveStatusForTrackedRun(run, liveStatuses, singletonScope) {
|
|
538
|
+
if (!singletonScope) return void 0;
|
|
526
539
|
const knownRunIds = new Set([run.runId, run.lastStatus?.run_id].filter((id) => !!id));
|
|
527
540
|
let runtimeMatch;
|
|
528
541
|
for (const status of liveStatuses) {
|
|
@@ -556,16 +569,18 @@ function formatRunLine(entry) {
|
|
|
556
569
|
return `${emoji} ${entry.label} (${entry.agent}) — ${entry.status}${phase}${perm}`;
|
|
557
570
|
}
|
|
558
571
|
/**
|
|
559
|
-
* decideCompletion determines whether to skip
|
|
572
|
+
* decideCompletion determines whether to skip or send the completion
|
|
560
573
|
* steering message for a tracked run that has reached terminal status.
|
|
561
574
|
*
|
|
562
|
-
* The
|
|
563
|
-
*
|
|
564
|
-
*
|
|
575
|
+
* The message is delivered on the same terminal tick — never deferred to a
|
|
576
|
+
* later cycle — so a run that is the last live one still gets its completion.
|
|
577
|
+
* `blocking` suppresses it while avenor_result is awaiting (it will deliver
|
|
578
|
+
* the result itself and set `consumed`), and `consumed` suppresses it once
|
|
579
|
+
* avenor_result has already returned the result successfully.
|
|
565
580
|
*/
|
|
566
581
|
function decideCompletion(run) {
|
|
567
582
|
if (run.blocking) return "skip";
|
|
568
|
-
if (
|
|
583
|
+
if (run.consumed) return "skip";
|
|
569
584
|
return "send";
|
|
570
585
|
}
|
|
571
586
|
//#endregion
|
|
@@ -1256,11 +1271,44 @@ const defaultDeps = {
|
|
|
1256
1271
|
followUpTool,
|
|
1257
1272
|
inspectTool,
|
|
1258
1273
|
resultTool,
|
|
1274
|
+
brokerReceiveTool,
|
|
1275
|
+
brokerReplyTool,
|
|
1259
1276
|
shutdownTool,
|
|
1277
|
+
workflowStatusTool,
|
|
1278
|
+
workflowWaitTool,
|
|
1279
|
+
workflowInspectTool,
|
|
1280
|
+
workflowEventsTool,
|
|
1281
|
+
workflowCompleteTool,
|
|
1282
|
+
workflowGateTool,
|
|
1260
1283
|
observeRun,
|
|
1261
1284
|
dial,
|
|
1262
1285
|
Supervisor
|
|
1263
1286
|
};
|
|
1287
|
+
let hostPollTimer = null;
|
|
1288
|
+
const hostPendingAsks = /* @__PURE__ */ new Map();
|
|
1289
|
+
function stopHostAskPoll() {
|
|
1290
|
+
if (hostPollTimer) {
|
|
1291
|
+
clearInterval(hostPollTimer);
|
|
1292
|
+
hostPollTimer = null;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
async function startHostAskPoll(ctx) {
|
|
1296
|
+
stopHostAskPoll();
|
|
1297
|
+
hostPollTimer = setInterval(async () => {
|
|
1298
|
+
try {
|
|
1299
|
+
const { asks } = await deps.brokerReceiveTool({});
|
|
1300
|
+
for (const ask of asks) {
|
|
1301
|
+
hostPendingAsks.set(ask.message_id, {
|
|
1302
|
+
from_run_id: ask.from_run_id,
|
|
1303
|
+
message_id: ask.message_id
|
|
1304
|
+
});
|
|
1305
|
+
const body = `**📨 Ask from sub-agent** (\`${ask.role && ask.role !== "agent" ? `${ask.from_run_id} (${ask.role})` : ask.from_run_id}\`)\n\n${ask.message}\n\nTo answer, use the intercom tool: avenor_reply({ from_run_id: ${JSON.stringify(ask.from_run_id)}, reply_to_message_id: ${JSON.stringify(ask.message_id)}, message: "..." })`;
|
|
1306
|
+
await ctx.sendUserMessage(body, { deliverAs: "steer" });
|
|
1307
|
+
}
|
|
1308
|
+
} catch (error) {}
|
|
1309
|
+
}, 2e3);
|
|
1310
|
+
hostPollTimer.unref?.();
|
|
1311
|
+
}
|
|
1264
1312
|
function statusSupervisorId(requestedSupervisorId, spawnedSupervisorId) {
|
|
1265
1313
|
return requestedSupervisorId ? spawnedSupervisorId : void 0;
|
|
1266
1314
|
}
|
|
@@ -1387,8 +1435,19 @@ function toWatchRunRef(run) {
|
|
|
1387
1435
|
supervisorId: run.supervisorId
|
|
1388
1436
|
};
|
|
1389
1437
|
}
|
|
1390
|
-
|
|
1438
|
+
/**
|
|
1439
|
+
* Namespace a run id by its supervisor so runs on different supervisors can
|
|
1440
|
+
* never collide or be correlated, even when they share runtime ids (e.g.
|
|
1441
|
+
* `rt_1`). An undefined supervisorId denotes the singleton namespace. The
|
|
1442
|
+
* NUL separator cannot appear in socket paths or generated run ids, so the
|
|
1443
|
+
* singleton and each explicit supervisor stay disjoint.
|
|
1444
|
+
*/
|
|
1445
|
+
function trackedRunKey(supervisorId, runId) {
|
|
1446
|
+
return `${supervisorId ?? ""}\u0000${runId}`;
|
|
1447
|
+
}
|
|
1448
|
+
function createExtension(deps = defaultDeps, options = {}) {
|
|
1391
1449
|
return async function(pi) {
|
|
1450
|
+
const pollIntervalMs = options.pollIntervalMs ?? POLL_INTERVAL_MS;
|
|
1392
1451
|
let telemetryGeneration = 0;
|
|
1393
1452
|
const trackedRuns = /* @__PURE__ */ new Map();
|
|
1394
1453
|
let pollingActive = false;
|
|
@@ -1418,8 +1477,9 @@ function createExtension(deps = defaultDeps) {
|
|
|
1418
1477
|
updateWidget(lastStatusEntries);
|
|
1419
1478
|
}
|
|
1420
1479
|
function upsertTrackedRun(run, status, blocking = false) {
|
|
1421
|
-
const
|
|
1422
|
-
trackedRuns.
|
|
1480
|
+
const key = trackedRunKey(run.supervisorId, run.runId);
|
|
1481
|
+
const current = trackedRuns.get(key);
|
|
1482
|
+
trackedRuns.set(key, {
|
|
1423
1483
|
runId: run.runId,
|
|
1424
1484
|
agent: run.agent ?? status?.agent ?? current?.agent ?? "unknown",
|
|
1425
1485
|
label: run.label ?? status?.label ?? current?.label ?? run.runId,
|
|
@@ -1429,9 +1489,29 @@ function createExtension(deps = defaultDeps) {
|
|
|
1429
1489
|
lastStatus: status ?? current?.lastStatus,
|
|
1430
1490
|
blocking: current?.blocking ?? blocking,
|
|
1431
1491
|
permissionNotified: current?.permissionNotified,
|
|
1432
|
-
|
|
1492
|
+
consumed: current?.consumed
|
|
1433
1493
|
});
|
|
1434
1494
|
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Look a run up in the per-supervisor tracking namespace. When a
|
|
1497
|
+
* supervisor is supplied, resolve exactly. Without one, prefer the
|
|
1498
|
+
* singleton namespace and only fall back to a run id walk when it is
|
|
1499
|
+
* unambiguous across supervisors. Duplicate run ids across supervisors
|
|
1500
|
+
* without an explicit supervisor return undefined so callers never bind
|
|
1501
|
+
* flags or telemetry to the wrong supervisor's run.
|
|
1502
|
+
*/
|
|
1503
|
+
function getTrackedRun(runId, supervisorId) {
|
|
1504
|
+
if (supervisorId !== void 0) return trackedRuns.get(trackedRunKey(supervisorId, runId));
|
|
1505
|
+
const singleton = trackedRuns.get(trackedRunKey(void 0, runId));
|
|
1506
|
+
if (singleton) return singleton;
|
|
1507
|
+
let match;
|
|
1508
|
+
for (const run of trackedRuns.values()) {
|
|
1509
|
+
if (run.runId !== runId) continue;
|
|
1510
|
+
if (match) return void 0;
|
|
1511
|
+
match = run;
|
|
1512
|
+
}
|
|
1513
|
+
return match;
|
|
1514
|
+
}
|
|
1435
1515
|
async function resolveFinalOutput(run, status) {
|
|
1436
1516
|
const preview = finalOutputPreview(status);
|
|
1437
1517
|
if (preview) return preview;
|
|
@@ -1458,7 +1538,8 @@ function createExtension(deps = defaultDeps) {
|
|
|
1458
1538
|
}
|
|
1459
1539
|
const entries = [];
|
|
1460
1540
|
for (const run of trackedRuns.values()) {
|
|
1461
|
-
const
|
|
1541
|
+
const singletonScope = !run.supervisorId || Boolean(deps.Supervisor.isCurrentInstance?.(run.supervisorId));
|
|
1542
|
+
const live = singletonScope ? findLiveStatusForTrackedRun(run, liveMap.values(), singletonScope) : void 0;
|
|
1462
1543
|
if (live) {
|
|
1463
1544
|
liveMap.delete(live.run_id);
|
|
1464
1545
|
run.lastStatus = live;
|
|
@@ -1577,18 +1658,16 @@ function createExtension(deps = defaultDeps) {
|
|
|
1577
1658
|
updateWidget(entries);
|
|
1578
1659
|
emitAvenorEvent(pi.events, CHANNEL_POLL_COMPLETED, createPollCompletedPayload(entries, Date.now(), ++telemetryGeneration));
|
|
1579
1660
|
for (const entry of entries) {
|
|
1580
|
-
const
|
|
1661
|
+
const entryKey = trackedRunKey(entry.supervisorId, entry.runId);
|
|
1662
|
+
const run = trackedRuns.get(entryKey);
|
|
1581
1663
|
if (!run) continue;
|
|
1582
1664
|
if (isTerminalStatus(entry.status)) {
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
if (run.blocking) continue;
|
|
1586
|
-
const action = decideCompletion(run);
|
|
1587
|
-
if (action === "defer") {
|
|
1588
|
-
run.completionPending = true;
|
|
1665
|
+
if (decideCompletion(run) === "skip") {
|
|
1666
|
+
if (run.consumed) trackedRuns.delete(entryKey);
|
|
1589
1667
|
continue;
|
|
1590
1668
|
}
|
|
1591
|
-
|
|
1669
|
+
const prevStatus = prevStatuses.get(entryKey);
|
|
1670
|
+
if (!(prevStatus && isTerminalStatus(prevStatus))) sessionCtx?.ui.notify(`Sub-agent "${entry.label}" finished: ${entry.status}`, entry.status === "done" ? "info" : "warning");
|
|
1592
1671
|
const finalOutput = await resolveFinalOutput(run, run.lastStatus);
|
|
1593
1672
|
try {
|
|
1594
1673
|
pi.sendUserMessage(buildCompletionText({
|
|
@@ -1608,7 +1687,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1608
1687
|
nestedCount: entry.nestedCount,
|
|
1609
1688
|
backend: entry.backend
|
|
1610
1689
|
}));
|
|
1611
|
-
trackedRuns.delete(
|
|
1690
|
+
trackedRuns.delete(entryKey);
|
|
1612
1691
|
} else if (entry.pendingPermission && !run.blocking && !run.permissionNotified) {
|
|
1613
1692
|
run.permissionNotified = true;
|
|
1614
1693
|
if (run.lastStatus) try {
|
|
@@ -1627,7 +1706,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1627
1706
|
pollingActive = false;
|
|
1628
1707
|
console.error("avenor polling loop failed", err);
|
|
1629
1708
|
});
|
|
1630
|
-
},
|
|
1709
|
+
}, pollIntervalMs);
|
|
1631
1710
|
else {
|
|
1632
1711
|
pollingActive = false;
|
|
1633
1712
|
updateWidget([]);
|
|
@@ -1805,7 +1884,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1805
1884
|
async function waitForRunByPolling(run, signal, onUpdate) {
|
|
1806
1885
|
let firstPoll = true;
|
|
1807
1886
|
while (!signal?.aborted) {
|
|
1808
|
-
if (!firstPoll) await sleep(
|
|
1887
|
+
if (!firstPoll) await sleep(pollIntervalMs);
|
|
1809
1888
|
firstPoll = false;
|
|
1810
1889
|
try {
|
|
1811
1890
|
const raw = await deps.statusTool({
|
|
@@ -1845,9 +1924,12 @@ function createExtension(deps = defaultDeps) {
|
|
|
1845
1924
|
lastStatusEntries = [];
|
|
1846
1925
|
pollingErrorCount = 0;
|
|
1847
1926
|
pollingErrors.length = 0;
|
|
1927
|
+
startHostAskPoll(ctx);
|
|
1848
1928
|
});
|
|
1849
1929
|
pi.on("session_shutdown", async () => {
|
|
1850
1930
|
await stopPolling();
|
|
1931
|
+
stopHostAskPoll();
|
|
1932
|
+
hostPendingAsks.clear();
|
|
1851
1933
|
sessionCtx = null;
|
|
1852
1934
|
});
|
|
1853
1935
|
pi.on("before_agent_start", async () => {
|
|
@@ -1876,7 +1958,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1876
1958
|
pi.registerTool({
|
|
1877
1959
|
name: "avenor_spawn",
|
|
1878
1960
|
label: "Avenor Spawn",
|
|
1879
|
-
description:
|
|
1961
|
+
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.`,
|
|
1880
1962
|
promptSnippet: "Dispatch sub-agent runs via avenor (spawn, status, result, inspect, events, follow-up, shutdown)",
|
|
1881
1963
|
promptGuidelines: [
|
|
1882
1964
|
"Use avenor_spawn to delegate well-defined tasks to sub-agents.",
|
|
@@ -1983,7 +2065,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1983
2065
|
});
|
|
1984
2066
|
const waitResult = await waitForRun(runRef, signal, onUpdate);
|
|
1985
2067
|
if (waitResult.aborted) {
|
|
1986
|
-
trackedRuns.delete(result.run_id);
|
|
2068
|
+
trackedRuns.delete(trackedRunKey(supervisorId, result.run_id));
|
|
1987
2069
|
return {
|
|
1988
2070
|
content: [{
|
|
1989
2071
|
type: "text",
|
|
@@ -2009,7 +2091,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
2009
2091
|
};
|
|
2010
2092
|
upsertTrackedRun(runRef, status, wait);
|
|
2011
2093
|
if (status.status === "waiting") {
|
|
2012
|
-
const tracked =
|
|
2094
|
+
const tracked = getTrackedRun(result.run_id, supervisorId);
|
|
2013
2095
|
if (tracked) tracked.permissionNotified = true;
|
|
2014
2096
|
return {
|
|
2015
2097
|
content: [{
|
|
@@ -2043,7 +2125,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
2043
2125
|
status: status.status,
|
|
2044
2126
|
backend: status.backend
|
|
2045
2127
|
}));
|
|
2046
|
-
trackedRuns.delete(result.run_id);
|
|
2128
|
+
trackedRuns.delete(trackedRunKey(supervisorId, result.run_id));
|
|
2047
2129
|
return {
|
|
2048
2130
|
content: [{
|
|
2049
2131
|
type: "text",
|
|
@@ -2142,7 +2224,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
2142
2224
|
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
|
|
2143
2225
|
}),
|
|
2144
2226
|
async execute(_toolCallId, params, signal) {
|
|
2145
|
-
const tracked =
|
|
2227
|
+
const tracked = getTrackedRun(params.run_id, params.supervisor_id);
|
|
2146
2228
|
const previousBlocking = tracked?.blocking;
|
|
2147
2229
|
if (tracked) tracked.blocking = true;
|
|
2148
2230
|
let completed = false;
|
|
@@ -2156,6 +2238,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
2156
2238
|
});
|
|
2157
2239
|
completed = result.ready;
|
|
2158
2240
|
if (completed) {
|
|
2241
|
+
if (tracked) tracked.consumed = true;
|
|
2159
2242
|
emitAvenorEvent(pi.events, CHANNEL_RUN_TERMINAL, createRunTerminalPayload({
|
|
2160
2243
|
runId: params.run_id,
|
|
2161
2244
|
supervisorId: tracked?.supervisorId ?? params.supervisor_id,
|
|
@@ -2164,7 +2247,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
2164
2247
|
agent: tracked?.agent ?? "unknown",
|
|
2165
2248
|
status: result.status ?? "done"
|
|
2166
2249
|
}));
|
|
2167
|
-
trackedRuns.delete(
|
|
2250
|
+
if (tracked) trackedRuns.delete(trackedRunKey(tracked.supervisorId, tracked.runId));
|
|
2168
2251
|
} else if (tracked && result.status === "waiting") tracked.permissionNotified = true;
|
|
2169
2252
|
return {
|
|
2170
2253
|
content: [{
|
|
@@ -2174,7 +2257,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
2174
2257
|
details: result
|
|
2175
2258
|
};
|
|
2176
2259
|
} finally {
|
|
2177
|
-
if (!completed && tracked &&
|
|
2260
|
+
if (!completed && tracked && getTrackedRun(params.run_id, params.supervisor_id) === tracked) tracked.blocking = previousBlocking ?? false;
|
|
2178
2261
|
}
|
|
2179
2262
|
},
|
|
2180
2263
|
renderCall(args, theme, context) {
|
|
@@ -2315,6 +2398,94 @@ function createExtension(deps = defaultDeps) {
|
|
|
2315
2398
|
}, theme, context.args);
|
|
2316
2399
|
}
|
|
2317
2400
|
});
|
|
2401
|
+
pi.registerTool({
|
|
2402
|
+
name: "avenor_ask",
|
|
2403
|
+
label: "Avenor Ask",
|
|
2404
|
+
description: "Ask another agent run a question and block until it replies.",
|
|
2405
|
+
parameters: Type.Object({
|
|
2406
|
+
to_run_id: Type.String({ description: "Target run ID" }),
|
|
2407
|
+
message: Type.String({ description: "Question to ask the other agent" })
|
|
2408
|
+
}),
|
|
2409
|
+
async execute(_toolCallId, params) {
|
|
2410
|
+
return askTool({
|
|
2411
|
+
toRunId: params.to_run_id,
|
|
2412
|
+
message: params.message
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
});
|
|
2416
|
+
pi.registerTool({
|
|
2417
|
+
name: "avenor_reply",
|
|
2418
|
+
label: "Avenor Reply (host)",
|
|
2419
|
+
description: "Reply to an inbound ask received from a sub-agent. Answer the most recent ask from a given sub-agent, or reply to a specific message_id from avenor_ask.",
|
|
2420
|
+
parameters: Type.Object({
|
|
2421
|
+
from_run_id: Type.Optional(Type.String({ description: "Sub-agent run id to reply to (the asker)" })),
|
|
2422
|
+
reply_to_message_id: Type.Optional(Type.String({ description: "The ask's broker message id to answer specifically" })),
|
|
2423
|
+
message: Type.String({ description: "Your answer" }),
|
|
2424
|
+
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
|
|
2425
|
+
}),
|
|
2426
|
+
async execute(_toolCallId, params) {
|
|
2427
|
+
let fromRunId = params.from_run_id;
|
|
2428
|
+
let messageId = params.reply_to_message_id;
|
|
2429
|
+
if (!messageId) {
|
|
2430
|
+
let match;
|
|
2431
|
+
for (const [, pending] of hostPendingAsks) if (pending.from_run_id === fromRunId && (!match || pending.message_id > match.message_id)) match = pending;
|
|
2432
|
+
if (match) messageId = match.message_id;
|
|
2433
|
+
}
|
|
2434
|
+
if (!fromRunId || !messageId) return {
|
|
2435
|
+
content: [{
|
|
2436
|
+
type: "text",
|
|
2437
|
+
text: "No pending ask found to reply to. Pass from_run_id and/or reply_to_message_id."
|
|
2438
|
+
}],
|
|
2439
|
+
details: { error: true }
|
|
2440
|
+
};
|
|
2441
|
+
await deps.brokerReplyTool({
|
|
2442
|
+
toRunId: fromRunId,
|
|
2443
|
+
replyTo: messageId,
|
|
2444
|
+
message: params.message,
|
|
2445
|
+
supervisorId: params.supervisor_id
|
|
2446
|
+
});
|
|
2447
|
+
hostPendingAsks.delete(messageId);
|
|
2448
|
+
return { content: [{
|
|
2449
|
+
type: "text",
|
|
2450
|
+
text: "Reply sent"
|
|
2451
|
+
}] };
|
|
2452
|
+
}
|
|
2453
|
+
});
|
|
2454
|
+
pi.registerTool({
|
|
2455
|
+
name: "avenor_peers",
|
|
2456
|
+
label: "Avenor Peers",
|
|
2457
|
+
description: "List all connected agent sessions with their status and metadata.",
|
|
2458
|
+
parameters: Type.Object({}),
|
|
2459
|
+
async execute() {
|
|
2460
|
+
const result = await peersTool({});
|
|
2461
|
+
if (result.peers.length === 0) return { content: [{
|
|
2462
|
+
type: "text",
|
|
2463
|
+
text: "No other agent sessions connected."
|
|
2464
|
+
}] };
|
|
2465
|
+
return { content: [{
|
|
2466
|
+
type: "text",
|
|
2467
|
+
text: `**Active sessions:**\n${result.peers.map((p) => {
|
|
2468
|
+
const parts = [p.label ? `${p.label} (${p.run_id})` : p.run_id];
|
|
2469
|
+
const tags = [
|
|
2470
|
+
p.status,
|
|
2471
|
+
p.backend,
|
|
2472
|
+
p.model
|
|
2473
|
+
].filter(Boolean);
|
|
2474
|
+
if (tags.length) parts.push(`[${tags.join(", ")}]`);
|
|
2475
|
+
return ` ${parts.join(" ")}`;
|
|
2476
|
+
}).join("\n")}`
|
|
2477
|
+
}] };
|
|
2478
|
+
}
|
|
2479
|
+
});
|
|
2480
|
+
pi.registerTool({
|
|
2481
|
+
name: "avenor_cancel",
|
|
2482
|
+
label: "Avenor Cancel",
|
|
2483
|
+
description: "Cancel a pending ask by its message ID.",
|
|
2484
|
+
parameters: Type.Object({ message_id: Type.String({ description: "Message ID of the pending ask to cancel" }) }),
|
|
2485
|
+
async execute(_toolCallId, params) {
|
|
2486
|
+
return cancelTool({ messageId: params.message_id });
|
|
2487
|
+
}
|
|
2488
|
+
});
|
|
2318
2489
|
pi.registerTool({
|
|
2319
2490
|
name: "avenor_events",
|
|
2320
2491
|
label: "Avenor Events",
|
|
@@ -2359,8 +2530,20 @@ function createExtension(deps = defaultDeps) {
|
|
|
2359
2530
|
force: Type.Optional(Type.Boolean({ description: "Force shutdown instead of graceful" }))
|
|
2360
2531
|
}),
|
|
2361
2532
|
async execute(_toolCallId, params) {
|
|
2362
|
-
|
|
2363
|
-
|
|
2533
|
+
const target = params.supervisor_id;
|
|
2534
|
+
const targetIsSingleton = target === void 0 || Boolean(deps.Supervisor.isCurrentInstance?.(target));
|
|
2535
|
+
let removed = 0;
|
|
2536
|
+
for (const [key, run] of trackedRuns) if (targetIsSingleton ? !run.supervisorId || Boolean(deps.Supervisor.isCurrentInstance?.(run.supervisorId)) : run.supervisorId === target) {
|
|
2537
|
+
trackedRuns.delete(key);
|
|
2538
|
+
removed++;
|
|
2539
|
+
}
|
|
2540
|
+
if (removed > 0 && trackedRuns.size === 0) await stopPolling();
|
|
2541
|
+
else if (removed > 0) {
|
|
2542
|
+
pollingActive = false;
|
|
2543
|
+
pollingGeneration++;
|
|
2544
|
+
await pollInFlight?.catch(() => {});
|
|
2545
|
+
startPolling();
|
|
2546
|
+
}
|
|
2364
2547
|
const result = await deps.shutdownTool({
|
|
2365
2548
|
supervisorId: params.supervisor_id,
|
|
2366
2549
|
force: params.force
|
|
@@ -2383,6 +2566,173 @@ function createExtension(deps = defaultDeps) {
|
|
|
2383
2566
|
}, theme, context.args);
|
|
2384
2567
|
}
|
|
2385
2568
|
});
|
|
2569
|
+
pi.registerTool({
|
|
2570
|
+
name: "avenor_workflow_status",
|
|
2571
|
+
label: "Avenor Workflow Status",
|
|
2572
|
+
description: "Get lightweight status for a workflow instance",
|
|
2573
|
+
parameters: Type.Object({
|
|
2574
|
+
workflow_id: Type.String({ description: "Workflow ID" }),
|
|
2575
|
+
supervisor_id: Type.Optional(Type.String({ description: "Supervisor ID for multi-supervisor mode" }))
|
|
2576
|
+
}),
|
|
2577
|
+
async execute(_toolCallId, params) {
|
|
2578
|
+
const result = await deps.workflowStatusTool({
|
|
2579
|
+
workflowId: params.workflow_id,
|
|
2580
|
+
supervisorId: params.supervisor_id
|
|
2581
|
+
});
|
|
2582
|
+
return {
|
|
2583
|
+
content: [{
|
|
2584
|
+
type: "text",
|
|
2585
|
+
text: JSON.stringify(result, null, 2)
|
|
2586
|
+
}],
|
|
2587
|
+
details: result
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
});
|
|
2591
|
+
pi.registerTool({
|
|
2592
|
+
name: "avenor_workflow_wait",
|
|
2593
|
+
label: "Avenor Workflow Wait",
|
|
2594
|
+
description: "Wait for a workflow to reach a terminal state or until timeout",
|
|
2595
|
+
parameters: Type.Object({
|
|
2596
|
+
workflow_id: Type.String({ description: "Workflow ID" }),
|
|
2597
|
+
timeout: Type.Optional(Type.String({ description: "Timeout (e.g. 30s, 5m)" })),
|
|
2598
|
+
supervisor_id: Type.Optional(Type.String({ description: "Supervisor ID for multi-supervisor mode" }))
|
|
2599
|
+
}),
|
|
2600
|
+
async execute(_toolCallId, params) {
|
|
2601
|
+
const result = await deps.workflowWaitTool({
|
|
2602
|
+
workflowId: params.workflow_id,
|
|
2603
|
+
timeout: params.timeout,
|
|
2604
|
+
supervisorId: params.supervisor_id
|
|
2605
|
+
});
|
|
2606
|
+
return {
|
|
2607
|
+
content: [{
|
|
2608
|
+
type: "text",
|
|
2609
|
+
text: JSON.stringify(result, null, 2)
|
|
2610
|
+
}],
|
|
2611
|
+
details: result
|
|
2612
|
+
};
|
|
2613
|
+
}
|
|
2614
|
+
});
|
|
2615
|
+
pi.registerTool({
|
|
2616
|
+
name: "avenor_workflow_inspect",
|
|
2617
|
+
label: "Avenor Workflow Inspect",
|
|
2618
|
+
description: "Return the full instance detail for a workflow",
|
|
2619
|
+
parameters: Type.Object({
|
|
2620
|
+
workflow_id: Type.String({ description: "Workflow ID" }),
|
|
2621
|
+
supervisor_id: Type.Optional(Type.String({ description: "Supervisor ID for multi-supervisor mode" }))
|
|
2622
|
+
}),
|
|
2623
|
+
async execute(_toolCallId, params) {
|
|
2624
|
+
const result = await deps.workflowInspectTool({
|
|
2625
|
+
workflowId: params.workflow_id,
|
|
2626
|
+
supervisorId: params.supervisor_id
|
|
2627
|
+
});
|
|
2628
|
+
return {
|
|
2629
|
+
content: [{
|
|
2630
|
+
type: "text",
|
|
2631
|
+
text: JSON.stringify(result, null, 2)
|
|
2632
|
+
}],
|
|
2633
|
+
details: result
|
|
2634
|
+
};
|
|
2635
|
+
}
|
|
2636
|
+
});
|
|
2637
|
+
pi.registerTool({
|
|
2638
|
+
name: "avenor_workflow_events",
|
|
2639
|
+
label: "Avenor Workflow Events",
|
|
2640
|
+
description: "Read log events from a workflow instance's event log",
|
|
2641
|
+
parameters: Type.Object({
|
|
2642
|
+
workflow_id: Type.String({ description: "Workflow ID" }),
|
|
2643
|
+
after_seq: Type.Optional(Type.Number({ description: "Only include events after this sequence number" })),
|
|
2644
|
+
limit: Type.Optional(Type.Number({ description: "Max events to return (default 50)" })),
|
|
2645
|
+
supervisor_id: Type.Optional(Type.String({ description: "Supervisor ID for multi-supervisor mode" }))
|
|
2646
|
+
}),
|
|
2647
|
+
async execute(_toolCallId, params) {
|
|
2648
|
+
const result = await deps.workflowEventsTool({
|
|
2649
|
+
workflowId: params.workflow_id,
|
|
2650
|
+
afterSeq: params.after_seq,
|
|
2651
|
+
limit: params.limit,
|
|
2652
|
+
supervisorId: params.supervisor_id
|
|
2653
|
+
});
|
|
2654
|
+
return {
|
|
2655
|
+
content: [{
|
|
2656
|
+
type: "text",
|
|
2657
|
+
text: JSON.stringify(result, null, 2)
|
|
2658
|
+
}],
|
|
2659
|
+
details: result
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2662
|
+
});
|
|
2663
|
+
pi.registerTool({
|
|
2664
|
+
name: "avenor_workflow_complete",
|
|
2665
|
+
label: "Avenor Workflow Complete",
|
|
2666
|
+
description: "Atomically complete a machine/external handoff activation",
|
|
2667
|
+
parameters: Type.Object({
|
|
2668
|
+
workflow_id: Type.String({ description: "Workflow ID" }),
|
|
2669
|
+
node_id: Type.String({ description: "Node ID" }),
|
|
2670
|
+
activation_id: Type.String({ description: "Activation ID" }),
|
|
2671
|
+
attempt_id: Type.String({ description: "Attempt ID" }),
|
|
2672
|
+
lease_id: Type.String({ description: "Lease ID" }),
|
|
2673
|
+
owner_token: Type.String({ description: "Owner token" }),
|
|
2674
|
+
outcome: Type.String({ description: "Outcome" }),
|
|
2675
|
+
outputs: Type.Optional(Type.Unknown({ description: "Outputs" })),
|
|
2676
|
+
artifacts: Type.Optional(Type.Unknown({ description: "Artifacts" })),
|
|
2677
|
+
supervisor_id: Type.Optional(Type.String({ description: "Supervisor ID for multi-supervisor mode" }))
|
|
2678
|
+
}),
|
|
2679
|
+
async execute(_toolCallId, params) {
|
|
2680
|
+
const { supervisor_id, ...rest } = params;
|
|
2681
|
+
const result = await deps.workflowCompleteTool({
|
|
2682
|
+
...rest,
|
|
2683
|
+
supervisorId: supervisor_id
|
|
2684
|
+
});
|
|
2685
|
+
return {
|
|
2686
|
+
content: [{
|
|
2687
|
+
type: "text",
|
|
2688
|
+
text: JSON.stringify(result, null, 2)
|
|
2689
|
+
}],
|
|
2690
|
+
details: result
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
});
|
|
2694
|
+
pi.registerTool({
|
|
2695
|
+
name: "avenor_workflow_gate",
|
|
2696
|
+
label: "Avenor Workflow Gate",
|
|
2697
|
+
description: "Record a gate decision on a parked awaiting_gate activation",
|
|
2698
|
+
parameters: Type.Object({
|
|
2699
|
+
workflow_id: Type.String({ description: "Workflow ID" }),
|
|
2700
|
+
node_id: Type.String({ description: "Node ID" }),
|
|
2701
|
+
gate_id: Type.String({ description: "Gate ID" }),
|
|
2702
|
+
activation_id: Type.String({ description: "Activation ID" }),
|
|
2703
|
+
operation: Type.Union([
|
|
2704
|
+
Type.Literal("satisfy"),
|
|
2705
|
+
Type.Literal("reject"),
|
|
2706
|
+
Type.Literal("waive"),
|
|
2707
|
+
Type.Literal("external_result")
|
|
2708
|
+
], { description: "Gate operation" }),
|
|
2709
|
+
actor: Type.Optional(Type.String({ description: "Actor" })),
|
|
2710
|
+
reason: Type.Optional(Type.String({ description: "Reason" })),
|
|
2711
|
+
outcome: Type.Optional(Type.String({ description: "Outcome" })),
|
|
2712
|
+
subject: Type.Optional(Type.Unknown({ description: "Subject" })),
|
|
2713
|
+
poll_id: Type.Optional(Type.String({ description: "Poll ID" })),
|
|
2714
|
+
source: Type.Optional(Type.String({ description: "Source" })),
|
|
2715
|
+
result: Type.Optional(Type.String({ description: "Result" })),
|
|
2716
|
+
response_hash: Type.Optional(Type.String({ description: "Response hash" })),
|
|
2717
|
+
observed_at: Type.Optional(Type.String({ description: "Observed at" })),
|
|
2718
|
+
evidence_ids: Type.Optional(Type.Array(Type.String(), { description: "Evidence IDs" })),
|
|
2719
|
+
supervisor_id: Type.Optional(Type.String({ description: "Supervisor ID for multi-supervisor mode" }))
|
|
2720
|
+
}),
|
|
2721
|
+
async execute(_toolCallId, params) {
|
|
2722
|
+
const { supervisor_id, ...rest } = params;
|
|
2723
|
+
const result = await deps.workflowGateTool({
|
|
2724
|
+
...rest,
|
|
2725
|
+
supervisorId: supervisor_id
|
|
2726
|
+
});
|
|
2727
|
+
return {
|
|
2728
|
+
content: [{
|
|
2729
|
+
type: "text",
|
|
2730
|
+
text: JSON.stringify(result, null, 2)
|
|
2731
|
+
}],
|
|
2732
|
+
details: result
|
|
2733
|
+
};
|
|
2734
|
+
}
|
|
2735
|
+
});
|
|
2386
2736
|
pi.registerCommand("avenor-status", {
|
|
2387
2737
|
description: "Show status of all avenor runs",
|
|
2388
2738
|
handler: async (_args, ctx) => {
|
|
@@ -2438,10 +2788,13 @@ function createExtension(deps = defaultDeps) {
|
|
|
2438
2788
|
pi.registerCommand("avenor-cancel", {
|
|
2439
2789
|
description: "Cancel a running avenor sub-agent",
|
|
2440
2790
|
getArgumentCompletions: (prefix) => {
|
|
2441
|
-
const
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2791
|
+
const runs = Array.from(trackedRuns.values());
|
|
2792
|
+
const items = runs.filter((run) => run.label.startsWith(prefix) || run.agent?.startsWith(prefix) || run.runId.startsWith(prefix)).map((run) => {
|
|
2793
|
+
return {
|
|
2794
|
+
value: runs.some((other) => other !== run && other.runId === run.runId) ? `${run.supervisorId ?? ""}::${run.runId}` : run.runId,
|
|
2795
|
+
label: `${sanitizeText(run.label)} (${sanitizeText(run.agent ?? "roster")}, ${sanitizeText(run.runId).slice(0, 8)})`
|
|
2796
|
+
};
|
|
2797
|
+
});
|
|
2445
2798
|
return items.length > 0 ? items : null;
|
|
2446
2799
|
},
|
|
2447
2800
|
handler: async (args, ctx) => {
|
|
@@ -2449,8 +2802,11 @@ function createExtension(deps = defaultDeps) {
|
|
|
2449
2802
|
ctx.ui.notify("Usage: /avenor-cancel <run_id>", "warning");
|
|
2450
2803
|
return;
|
|
2451
2804
|
}
|
|
2452
|
-
const
|
|
2453
|
-
const
|
|
2805
|
+
const raw = args.trim();
|
|
2806
|
+
const sep = raw.lastIndexOf("::");
|
|
2807
|
+
const supervisorId = sep >= 0 ? raw.slice(0, sep) || void 0 : void 0;
|
|
2808
|
+
const runId = sep >= 0 ? raw.slice(sep + 2) : raw;
|
|
2809
|
+
const run = getTrackedRun(runId, supervisorId);
|
|
2454
2810
|
const label = run?.label ?? runId;
|
|
2455
2811
|
const runtimeId = run?.runtimeId ?? run?.lastStatus?.runtime_id;
|
|
2456
2812
|
if (!runtimeId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dougbots/avenor-pi",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.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.10.
|
|
38
|
+
"@dougbots/avenor-core": "^0.10.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@earendil-works/pi-coding-agent": ">=0.74.0",
|