@dougbots/avenor-pi 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 +18 -2
- package/dist/index.d.ts +52 -3
- package/dist/index.js +546 -107
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -112,7 +112,7 @@ Available tools for LLM sub-agent management:
|
|
|
112
112
|
|
|
113
113
|
| Tool | Description |
|
|
114
114
|
|---|---|
|
|
115
|
-
| `avenor_spawn` | Dispatch an agent run (blocking or fire-and-forget). Uses the `pi` backend by default; accepts `agent`, `model`, `prompt`, and `
|
|
115
|
+
| `avenor_spawn` | Dispatch an agent run (blocking or fire-and-forget). Uses the `pi` backend by default in direct mode; accepts optional `agent`, `model`, `prompt`, `dir`, `roster_file`, and `roster_entry`. |
|
|
116
116
|
| `avenor_status` | Get status of a run or all runs; use `view="lifecycle"` for compact progress and permission checks |
|
|
117
117
|
| `avenor_result` | Wait for a run and return its complete final output without transcript details |
|
|
118
118
|
| `avenor_inspect` | Review a bounded transcript, tool activity, permissions, and final output |
|
|
@@ -121,17 +121,33 @@ Available tools for LLM sub-agent management:
|
|
|
121
121
|
| `avenor_events` | Read events from a run |
|
|
122
122
|
| `avenor_shutdown` | Shut down the avenor supervisor |
|
|
123
123
|
|
|
124
|
+
### Direct and roster spawns
|
|
125
|
+
|
|
126
|
+
Direct mode permits `agent`, `model`, and `backend` to be omitted independently, including a backend-only request; omitted values use runtime defaults. Pi supplies `backend: "pi"` for direct tool calls when no backend is given. Roster mode uses both `roster_file` and `roster_entry`, and leaves backend selection to the roster entry instead of applying Pi's direct default:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"prompt": "Review the repository",
|
|
131
|
+
"roster_file": "/repo/roster.json",
|
|
132
|
+
"roster_entry": "reviewer"
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Each roster entry must contain `backend` and at least one of `agent` or `model`. Do not combine roster mode with direct `agent`, `model`, or `backend` overrides. Roster entries currently do not support `system` or `thinking`; `thinking` remains a run-level option and is validated against the effective backend. A resumed session must stay on its original backend. Use an absolute `roster_file` path when the Pi process and target project have different working directories.
|
|
137
|
+
|
|
124
138
|
### Pi-specific features
|
|
125
139
|
|
|
126
140
|
Beyond the tools, the extension integrates with Pi's TUI and event system:
|
|
127
141
|
|
|
128
142
|
- **Status widget** — persistent widget showing all active runs with status, phase, and permission state
|
|
129
|
-
- **Footer status** — active runs shown in the Pi footer bar
|
|
143
|
+
- **Footer status** — active runs and polling error counts shown in the Pi footer bar
|
|
144
|
+
- **Polling diagnostics** — bounded polling errors are emitted on `avenor:poll:error` for companion extensions
|
|
130
145
|
- **Live progress** — blocking `avenor_spawn` calls stream progress updates via `onUpdate`
|
|
131
146
|
- **Context enrichment** — active sub-agents are automatically surfaced in the system prompt via `before_agent_start`
|
|
132
147
|
- **Custom rendering** — The Pi extension renders all Avenor tool calls and results as themed, bounded summaries. Results have collapsed and expanded views.
|
|
133
148
|
- **Commands** — interactive commands for run management:
|
|
134
149
|
- `/avenor-status` — show status of all runs
|
|
150
|
+
- `/avenor-errors` — show and clear recent polling errors
|
|
135
151
|
- `/avenor-watch <run_id>` — open a live event feed overlay
|
|
136
152
|
- `/avenor-cancel <run_id>` — cancel a running sub-agent
|
|
137
153
|
|
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
|
|
@@ -28,6 +28,31 @@ interface RunStatusEntry {
|
|
|
28
28
|
declare const CHANNEL_POLL_COMPLETED: "avenor:poll:completed";
|
|
29
29
|
/** Emitted when the extension observes a terminal run result. */
|
|
30
30
|
declare const CHANNEL_RUN_TERMINAL: "avenor:run:terminal";
|
|
31
|
+
/** Emitted when a best-effort polling operation fails. */
|
|
32
|
+
declare const CHANNEL_POLL_ERROR: "avenor:poll:error";
|
|
33
|
+
type PollErrorSource = 'singleton-list' | 'run-status' | 'spawn-status';
|
|
34
|
+
interface PollErrorPayload {
|
|
35
|
+
/** Polling operation that failed. */
|
|
36
|
+
readonly source: PollErrorSource;
|
|
37
|
+
/** Run associated with the failure, when the operation was run-specific. */
|
|
38
|
+
readonly runId?: string;
|
|
39
|
+
/** Human-readable operation description. */
|
|
40
|
+
readonly message: string;
|
|
41
|
+
/** Bounded, sanitized error text suitable for debug consumers. */
|
|
42
|
+
readonly error: string;
|
|
43
|
+
/** Number of polling errors observed by this extension instance. */
|
|
44
|
+
readonly count: number;
|
|
45
|
+
/** Wall-clock timestamp in epoch milliseconds. */
|
|
46
|
+
readonly timestamp: number;
|
|
47
|
+
}
|
|
48
|
+
interface PollErrorPayloadInput {
|
|
49
|
+
readonly source: PollErrorSource;
|
|
50
|
+
readonly runId?: string;
|
|
51
|
+
readonly message: string;
|
|
52
|
+
readonly error: unknown;
|
|
53
|
+
readonly count: number;
|
|
54
|
+
readonly timestamp: number;
|
|
55
|
+
}
|
|
31
56
|
type AvenorRunStatus = Readonly<Omit<RunStatusEntry, 'supervisorId' | 'pid' | 'permissionDescription'> & {
|
|
32
57
|
supervisorKey: string;
|
|
33
58
|
}>;
|
|
@@ -65,6 +90,7 @@ interface RunTerminalPayloadInput {
|
|
|
65
90
|
interface AvenorEventMap {
|
|
66
91
|
[CHANNEL_POLL_COMPLETED]: PollCompletedPayload;
|
|
67
92
|
[CHANNEL_RUN_TERMINAL]: RunTerminalPayload;
|
|
93
|
+
[CHANNEL_POLL_ERROR]: PollErrorPayload;
|
|
68
94
|
}
|
|
69
95
|
type AvenorEventChannel = keyof AvenorEventMap;
|
|
70
96
|
type AvenorEventBus = Pick<EventBus, 'emit' | 'on'>;
|
|
@@ -80,6 +106,8 @@ declare function onAvenorEvent<K extends AvenorEventChannel>(bus: Pick<EventBus,
|
|
|
80
106
|
declare function createPollCompletedPayload(entries: readonly RunStatusEntry[], timestamp: number, generation: number): PollCompletedPayload;
|
|
81
107
|
/** Copy and freeze a terminal payload before publishing it. */
|
|
82
108
|
declare function createRunTerminalPayload(payload: RunTerminalPayloadInput): RunTerminalPayload;
|
|
109
|
+
/** Copy, bound, sanitize, and freeze a polling error before publishing it. */
|
|
110
|
+
declare function createPollErrorPayload(payload: PollErrorPayloadInput): PollErrorPayload;
|
|
83
111
|
//#endregion
|
|
84
112
|
//#region src/index.d.ts
|
|
85
113
|
interface ExtensionDeps {
|
|
@@ -90,12 +118,29 @@ interface ExtensionDeps {
|
|
|
90
118
|
followUpTool: typeof followUpTool;
|
|
91
119
|
inspectTool: typeof inspectTool;
|
|
92
120
|
resultTool: typeof resultTool;
|
|
121
|
+
brokerReceiveTool: typeof brokerReceiveTool;
|
|
122
|
+
brokerReplyTool: typeof brokerReplyTool;
|
|
93
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;
|
|
94
130
|
observeRun: typeof observeRun;
|
|
95
131
|
dial: typeof dial;
|
|
96
132
|
Supervisor: typeof Supervisor;
|
|
97
133
|
}
|
|
98
134
|
declare function statusSupervisorId(requestedSupervisorId: string | undefined, spawnedSupervisorId: string): string | undefined;
|
|
135
|
+
type SpawnHostParams = {
|
|
136
|
+
agent?: string;
|
|
137
|
+
model?: string;
|
|
138
|
+
backend?: string;
|
|
139
|
+
roster_file?: string;
|
|
140
|
+
roster_entry?: string;
|
|
141
|
+
};
|
|
142
|
+
/** Project shared spawn identity into Pi tool metadata without resolving rosters here. */
|
|
143
|
+
declare function spawnIdentityMetadata(params: SpawnHostParams, status?: Pick<StatusResult, 'roster_file' | 'roster_entry' | 'agent' | 'model' | 'backend' | 'effective_agent' | 'effective_model' | 'effective_backend'>): Record<string, unknown>;
|
|
99
144
|
declare function buildCompletionText(run: {
|
|
100
145
|
runId: string;
|
|
101
146
|
label: string;
|
|
@@ -104,7 +149,11 @@ declare function isTerminalStatus(status: string | undefined): boolean;
|
|
|
104
149
|
declare function renderStatusLines(entries: RunStatusEntry[]): string[];
|
|
105
150
|
declare function compactWhitespace(text: string | undefined, limit?: number): string | undefined;
|
|
106
151
|
declare function buildInspectPayload(result: InspectResult): Record<string, unknown>;
|
|
107
|
-
|
|
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>;
|
|
108
157
|
declare const _default: (pi: ExtensionAPI) => Promise<void>;
|
|
109
158
|
//#endregion
|
|
110
|
-
export { type AvenorEventBus, type AvenorEventChannel, type AvenorEventMap, type AvenorRunStatus, CHANNEL_POLL_COMPLETED, CHANNEL_RUN_TERMINAL, ExtensionDeps, type PollCompletedPayload, type RunTerminalPayload, type RunTerminalPayloadInput, buildCompletionText, buildInspectPayload, compactWhitespace, createExtension, createPollCompletedPayload, createRunTerminalPayload, _default as default, emitAvenorEvent, isTerminalStatus, onAvenorEvent, renderStatusLines, 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 } 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
|
|
@@ -718,7 +733,15 @@ function resolveAgentProfile(entries, environment = process.env) {
|
|
|
718
733
|
const CHANNEL_POLL_COMPLETED = "avenor:poll:completed";
|
|
719
734
|
/** Emitted when the extension observes a terminal run result. */
|
|
720
735
|
const CHANNEL_RUN_TERMINAL = "avenor:run:terminal";
|
|
721
|
-
/**
|
|
736
|
+
/** Emitted when a best-effort polling operation fails. */
|
|
737
|
+
const CHANNEL_POLL_ERROR = "avenor:poll:error";
|
|
738
|
+
/** Maximum sanitized error text exposed to companion extensions. */
|
|
739
|
+
const POLL_ERROR_TEXT_CHARS = 600;
|
|
740
|
+
function sanitizeAndBoundText(value) {
|
|
741
|
+
const text = sanitizeText(String(value)).trim();
|
|
742
|
+
if (!text) return "unknown error";
|
|
743
|
+
return text.length <= POLL_ERROR_TEXT_CHARS ? text : `${text.slice(0, POLL_ERROR_TEXT_CHARS - 1)}…`;
|
|
744
|
+
}
|
|
722
745
|
function createSupervisorKey(supervisorId) {
|
|
723
746
|
if (!supervisorId) return "singleton";
|
|
724
747
|
return `supervisor:${createHash("sha256").update(supervisorId).digest("hex").slice(0, 16)}`;
|
|
@@ -769,6 +792,17 @@ function createRunTerminalPayload(payload) {
|
|
|
769
792
|
...payload.backend !== void 0 && { backend: payload.backend }
|
|
770
793
|
});
|
|
771
794
|
}
|
|
795
|
+
/** Copy, bound, sanitize, and freeze a polling error before publishing it. */
|
|
796
|
+
function createPollErrorPayload(payload) {
|
|
797
|
+
return Object.freeze({
|
|
798
|
+
source: payload.source,
|
|
799
|
+
...payload.runId !== void 0 && { runId: payload.runId },
|
|
800
|
+
message: sanitizeAndBoundText(payload.message),
|
|
801
|
+
error: sanitizeAndBoundText(payload.error),
|
|
802
|
+
count: payload.count,
|
|
803
|
+
timestamp: payload.timestamp
|
|
804
|
+
});
|
|
805
|
+
}
|
|
772
806
|
//#endregion
|
|
773
807
|
//#region src/render.ts
|
|
774
808
|
const FINAL_OUTPUT_CHARS = 600;
|
|
@@ -1031,9 +1065,11 @@ function eventsLines(details, args, expanded) {
|
|
|
1031
1065
|
const displayedTypes = types.slice(0, COLLECTION_ITEMS).map((type) => scalar(type)).join(", ");
|
|
1032
1066
|
const lines = [`Events: ${runId} — ${events.length} events`, ...types.length > 0 ? [`Filter: ${displayedTypes}${types.length > COLLECTION_ITEMS ? ` ${itemMarker(types.length - COLLECTION_ITEMS)}` : ""}`] : []];
|
|
1033
1067
|
if (events.length === 0) return [...lines, "No events."];
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1068
|
+
if (!expanded) {
|
|
1069
|
+
lines.push(`Preview: ${eventRow(events[0])}`);
|
|
1070
|
+
if (events.length > 1) lines.push(itemMarker(events.length - 1));
|
|
1071
|
+
return lines;
|
|
1072
|
+
}
|
|
1037
1073
|
const displayed = events.slice(0, STATUS_ROWS);
|
|
1038
1074
|
lines.push(...displayed.map(eventRow));
|
|
1039
1075
|
if (events.length > displayed.length) lines.push(itemMarker(events.length - displayed.length));
|
|
@@ -1226,6 +1262,7 @@ const INSPECT_JSON_STRING_CHARS = 600;
|
|
|
1226
1262
|
const INSPECT_JSON_ARRAY_ITEMS = 24;
|
|
1227
1263
|
const INSPECT_JSON_OBJECT_KEYS = 24;
|
|
1228
1264
|
const INSPECT_JSON_DEPTH = 8;
|
|
1265
|
+
const MAX_POLL_ERRORS = 100;
|
|
1229
1266
|
const defaultDeps = {
|
|
1230
1267
|
spawnTool,
|
|
1231
1268
|
statusTool,
|
|
@@ -1234,14 +1271,60 @@ const defaultDeps = {
|
|
|
1234
1271
|
followUpTool,
|
|
1235
1272
|
inspectTool,
|
|
1236
1273
|
resultTool,
|
|
1274
|
+
brokerReceiveTool,
|
|
1275
|
+
brokerReplyTool,
|
|
1237
1276
|
shutdownTool,
|
|
1277
|
+
workflowStatusTool,
|
|
1278
|
+
workflowWaitTool,
|
|
1279
|
+
workflowInspectTool,
|
|
1280
|
+
workflowEventsTool,
|
|
1281
|
+
workflowCompleteTool,
|
|
1282
|
+
workflowGateTool,
|
|
1238
1283
|
observeRun,
|
|
1239
1284
|
dial,
|
|
1240
1285
|
Supervisor
|
|
1241
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
|
+
}
|
|
1242
1312
|
function statusSupervisorId(requestedSupervisorId, spawnedSupervisorId) {
|
|
1243
1313
|
return requestedSupervisorId ? spawnedSupervisorId : void 0;
|
|
1244
1314
|
}
|
|
1315
|
+
/** Project shared spawn identity into Pi tool metadata without resolving rosters here. */
|
|
1316
|
+
function spawnIdentityMetadata(params, status) {
|
|
1317
|
+
const metadata = {};
|
|
1318
|
+
const add = (key, value) => {
|
|
1319
|
+
if (typeof value === "string" && value.length > 0) metadata[key] = value;
|
|
1320
|
+
};
|
|
1321
|
+
add("roster_file", status?.roster_file ?? params.roster_file);
|
|
1322
|
+
add("roster_entry", status?.roster_entry ?? params.roster_entry);
|
|
1323
|
+
add("effective_agent", status?.effective_agent ?? status?.agent ?? params.agent);
|
|
1324
|
+
add("effective_model", status?.effective_model ?? status?.model ?? params.model);
|
|
1325
|
+
add("effective_backend", status?.effective_backend ?? status?.backend ?? params.backend);
|
|
1326
|
+
return metadata;
|
|
1327
|
+
}
|
|
1245
1328
|
function sleep(ms) {
|
|
1246
1329
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1247
1330
|
}
|
|
@@ -1267,7 +1350,8 @@ function buildCompletionText(run, result, finalOutput) {
|
|
|
1267
1350
|
}
|
|
1268
1351
|
function buildWaitingText(run, status) {
|
|
1269
1352
|
const perm = status.pending_permission;
|
|
1270
|
-
const
|
|
1353
|
+
const agent = run.agent ?? status.effective_agent ?? status.agent ?? "roster";
|
|
1354
|
+
const lines = [`Sub-agent "${run.label}" (${agent}) is waiting for input.`];
|
|
1271
1355
|
if (perm) lines.push(`Permission request: ${perm.description}`, `To answer: call \`avenor_answer_permission\` with run_id "${run.runId}", option_id "allow_once" | "allow_always" | "deny"`);
|
|
1272
1356
|
else if (status.phase_label) lines.push(`Question: ${status.phase_label}`);
|
|
1273
1357
|
if (!perm) lines.push(`Use \`avenor_follow_up\` with run_id "${run.runId}" to respond, or \`avenor_inspect\` to review context first.`);
|
|
@@ -1351,30 +1435,51 @@ function toWatchRunRef(run) {
|
|
|
1351
1435
|
supervisorId: run.supervisorId
|
|
1352
1436
|
};
|
|
1353
1437
|
}
|
|
1354
|
-
|
|
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 = {}) {
|
|
1355
1449
|
return async function(pi) {
|
|
1450
|
+
const pollIntervalMs = options.pollIntervalMs ?? POLL_INTERVAL_MS;
|
|
1356
1451
|
let telemetryGeneration = 0;
|
|
1357
1452
|
const trackedRuns = /* @__PURE__ */ new Map();
|
|
1358
1453
|
let pollingActive = false;
|
|
1359
1454
|
let pollingGeneration = 0;
|
|
1360
1455
|
let pollInFlight = null;
|
|
1361
1456
|
let sessionCtx = null;
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1457
|
+
let pollingErrorCount = 0;
|
|
1458
|
+
const pollingErrors = [];
|
|
1459
|
+
let lastStatusEntries = [];
|
|
1460
|
+
function recordPollingError(source, message, error, runId) {
|
|
1461
|
+
pollingErrorCount++;
|
|
1462
|
+
const payload = createPollErrorPayload({
|
|
1463
|
+
source,
|
|
1464
|
+
runId,
|
|
1465
|
+
message,
|
|
1466
|
+
error,
|
|
1467
|
+
count: pollingErrorCount,
|
|
1468
|
+
timestamp: Date.now()
|
|
1469
|
+
});
|
|
1470
|
+
pollingErrors.push(payload);
|
|
1471
|
+
if (pollingErrors.length > MAX_POLL_ERRORS) pollingErrors.shift();
|
|
1472
|
+
emitAvenorEvent(pi.events, CHANNEL_POLL_ERROR, payload);
|
|
1370
1473
|
}
|
|
1371
|
-
function
|
|
1372
|
-
|
|
1373
|
-
|
|
1474
|
+
function clearPollingErrors() {
|
|
1475
|
+
pollingErrorCount = 0;
|
|
1476
|
+
pollingErrors.length = 0;
|
|
1477
|
+
updateWidget(lastStatusEntries);
|
|
1374
1478
|
}
|
|
1375
1479
|
function upsertTrackedRun(run, status, blocking = false) {
|
|
1376
|
-
const
|
|
1377
|
-
trackedRuns.
|
|
1480
|
+
const key = trackedRunKey(run.supervisorId, run.runId);
|
|
1481
|
+
const current = trackedRuns.get(key);
|
|
1482
|
+
trackedRuns.set(key, {
|
|
1378
1483
|
runId: run.runId,
|
|
1379
1484
|
agent: run.agent ?? status?.agent ?? current?.agent ?? "unknown",
|
|
1380
1485
|
label: run.label ?? status?.label ?? current?.label ?? run.runId,
|
|
@@ -1384,9 +1489,29 @@ function createExtension(deps = defaultDeps) {
|
|
|
1384
1489
|
lastStatus: status ?? current?.lastStatus,
|
|
1385
1490
|
blocking: current?.blocking ?? blocking,
|
|
1386
1491
|
permissionNotified: current?.permissionNotified,
|
|
1387
|
-
|
|
1492
|
+
consumed: current?.consumed
|
|
1388
1493
|
});
|
|
1389
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
|
+
}
|
|
1390
1515
|
async function resolveFinalOutput(run, status) {
|
|
1391
1516
|
const preview = finalOutputPreview(status);
|
|
1392
1517
|
if (preview) return preview;
|
|
@@ -1402,22 +1527,23 @@ function createExtension(deps = defaultDeps) {
|
|
|
1402
1527
|
}
|
|
1403
1528
|
}
|
|
1404
1529
|
async function pollRuns() {
|
|
1530
|
+
const errorCountAtPollStart = pollingErrorCount;
|
|
1405
1531
|
const liveMap = /* @__PURE__ */ new Map();
|
|
1406
1532
|
try {
|
|
1407
1533
|
const allLive = await deps.statusTool({});
|
|
1408
1534
|
const liveList = Array.isArray(allLive) ? allLive : [allLive];
|
|
1409
1535
|
for (const result of liveList) liveMap.set(result.run_id, result);
|
|
1410
|
-
clearPollingFailure("singleton-list");
|
|
1411
1536
|
} catch (err) {
|
|
1412
|
-
|
|
1537
|
+
recordPollingError("singleton-list", "failed to list singleton runs", err);
|
|
1413
1538
|
}
|
|
1414
1539
|
const entries = [];
|
|
1415
1540
|
for (const run of trackedRuns.values()) {
|
|
1416
|
-
const
|
|
1541
|
+
const singletonScope = !run.supervisorId || Boolean(deps.Supervisor.isCurrentInstance?.(run.supervisorId));
|
|
1542
|
+
const live = singletonScope ? findLiveStatusForTrackedRun(run, liveMap.values(), singletonScope) : void 0;
|
|
1417
1543
|
if (live) {
|
|
1418
1544
|
liveMap.delete(live.run_id);
|
|
1419
|
-
clearPollingFailure(`run-status:${run.supervisorId ?? "singleton"}:${run.runId}`);
|
|
1420
1545
|
run.lastStatus = live;
|
|
1546
|
+
run.agent = live.agent ?? run.agent;
|
|
1421
1547
|
entries.push({
|
|
1422
1548
|
runId: run.runId,
|
|
1423
1549
|
supervisorId: run.supervisorId,
|
|
@@ -1426,7 +1552,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1426
1552
|
status: live.status,
|
|
1427
1553
|
phase: live.phase,
|
|
1428
1554
|
phaseLabel: live.phase_label,
|
|
1429
|
-
agent: run.agent,
|
|
1555
|
+
agent: run.agent ?? "unknown",
|
|
1430
1556
|
pendingPermission: !!live.pending_permission,
|
|
1431
1557
|
permissionDescription: live.pending_permission?.description,
|
|
1432
1558
|
pid: live.pid,
|
|
@@ -1441,8 +1567,8 @@ function createExtension(deps = defaultDeps) {
|
|
|
1441
1567
|
});
|
|
1442
1568
|
const result = Array.isArray(raw) ? raw[0] : raw;
|
|
1443
1569
|
if (!result) throw new Error("status response was empty");
|
|
1444
|
-
clearPollingFailure(`run-status:${run.supervisorId ?? "singleton"}:${run.runId}`);
|
|
1445
1570
|
run.lastStatus = result;
|
|
1571
|
+
run.agent = result.agent ?? run.agent;
|
|
1446
1572
|
entries.push({
|
|
1447
1573
|
runId: run.runId,
|
|
1448
1574
|
supervisorId: run.supervisorId,
|
|
@@ -1451,14 +1577,14 @@ function createExtension(deps = defaultDeps) {
|
|
|
1451
1577
|
status: result.status,
|
|
1452
1578
|
phase: result.phase,
|
|
1453
1579
|
phaseLabel: result.phase_label,
|
|
1454
|
-
agent: run.agent,
|
|
1580
|
+
agent: run.agent ?? "unknown",
|
|
1455
1581
|
pendingPermission: !!result.pending_permission,
|
|
1456
1582
|
permissionDescription: result.pending_permission?.description,
|
|
1457
1583
|
pid: result.pid,
|
|
1458
1584
|
backend: result.backend
|
|
1459
1585
|
});
|
|
1460
1586
|
} catch (err) {
|
|
1461
|
-
|
|
1587
|
+
recordPollingError("run-status", "statusTool failed while polling a run", err, run.runId);
|
|
1462
1588
|
const previous = run.lastStatus;
|
|
1463
1589
|
entries.push({
|
|
1464
1590
|
runId: run.runId,
|
|
@@ -1468,7 +1594,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1468
1594
|
status: previous?.status ?? "unavailable",
|
|
1469
1595
|
phase: previous?.phase,
|
|
1470
1596
|
phaseLabel: previous?.phase_label,
|
|
1471
|
-
agent: run.agent,
|
|
1597
|
+
agent: run.agent ?? previous?.agent ?? "unknown",
|
|
1472
1598
|
pendingPermission: !!previous?.pending_permission,
|
|
1473
1599
|
permissionDescription: previous?.pending_permission?.description,
|
|
1474
1600
|
pid: previous?.pid,
|
|
@@ -1495,21 +1621,24 @@ function createExtension(deps = defaultDeps) {
|
|
|
1495
1621
|
let idx = 0;
|
|
1496
1622
|
for (const entry of entries) if (entry.backend === "pi" && entry.pid && !isTerminalStatus(entry.status)) entry.nestedCount = nestedCounts[idx++] ?? 0;
|
|
1497
1623
|
}
|
|
1624
|
+
if (pollingErrorCount === errorCountAtPollStart) pollingErrorCount = 0;
|
|
1498
1625
|
return entries;
|
|
1499
1626
|
}
|
|
1500
1627
|
function updateWidget(entries) {
|
|
1628
|
+
lastStatusEntries = entries;
|
|
1501
1629
|
if (!sessionCtx) return;
|
|
1502
1630
|
const lines = renderStatusLines(entries);
|
|
1631
|
+
const errorSuffix = pollingErrorCount > 0 ? `errors:${pollingErrorCount}` : "";
|
|
1503
1632
|
if (lines.length > 0) {
|
|
1504
1633
|
sessionCtx.ui.setWidget("avenor-status", lines);
|
|
1505
1634
|
const statusStr = lines.map((line) => line.slice(0, 60)).join(" ");
|
|
1506
1635
|
const nestedTotal = entries.filter((e) => !isTerminalStatus(e.status)).reduce((sum, e) => sum + (e.nestedCount ?? 0), 0);
|
|
1507
1636
|
const total = lines.length + nestedTotal;
|
|
1508
|
-
const statusWithTotal = nestedTotal > 0 ? `${statusStr} total:${total}` : statusStr;
|
|
1637
|
+
const statusWithTotal = [nestedTotal > 0 ? `${statusStr} total:${total}` : statusStr, errorSuffix].filter(Boolean).join(" ");
|
|
1509
1638
|
sessionCtx.ui.setStatus("avenor", statusWithTotal);
|
|
1510
1639
|
} else {
|
|
1511
1640
|
sessionCtx.ui.setWidget("avenor-status", void 0);
|
|
1512
|
-
sessionCtx.ui.setStatus("avenor",
|
|
1641
|
+
sessionCtx.ui.setStatus("avenor", errorSuffix);
|
|
1513
1642
|
}
|
|
1514
1643
|
}
|
|
1515
1644
|
function startPolling() {
|
|
@@ -1529,18 +1658,16 @@ function createExtension(deps = defaultDeps) {
|
|
|
1529
1658
|
updateWidget(entries);
|
|
1530
1659
|
emitAvenorEvent(pi.events, CHANNEL_POLL_COMPLETED, createPollCompletedPayload(entries, Date.now(), ++telemetryGeneration));
|
|
1531
1660
|
for (const entry of entries) {
|
|
1532
|
-
const
|
|
1661
|
+
const entryKey = trackedRunKey(entry.supervisorId, entry.runId);
|
|
1662
|
+
const run = trackedRuns.get(entryKey);
|
|
1533
1663
|
if (!run) continue;
|
|
1534
1664
|
if (isTerminalStatus(entry.status)) {
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
if (run.blocking) continue;
|
|
1538
|
-
const action = decideCompletion(run);
|
|
1539
|
-
if (action === "defer") {
|
|
1540
|
-
run.completionPending = true;
|
|
1665
|
+
if (decideCompletion(run) === "skip") {
|
|
1666
|
+
if (run.consumed) trackedRuns.delete(entryKey);
|
|
1541
1667
|
continue;
|
|
1542
1668
|
}
|
|
1543
|
-
|
|
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");
|
|
1544
1671
|
const finalOutput = await resolveFinalOutput(run, run.lastStatus);
|
|
1545
1672
|
try {
|
|
1546
1673
|
pi.sendUserMessage(buildCompletionText({
|
|
@@ -1560,8 +1687,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1560
1687
|
nestedCount: entry.nestedCount,
|
|
1561
1688
|
backend: entry.backend
|
|
1562
1689
|
}));
|
|
1563
|
-
|
|
1564
|
-
trackedRuns.delete(entry.runId);
|
|
1690
|
+
trackedRuns.delete(entryKey);
|
|
1565
1691
|
} else if (entry.pendingPermission && !run.blocking && !run.permissionNotified) {
|
|
1566
1692
|
run.permissionNotified = true;
|
|
1567
1693
|
if (run.lastStatus) try {
|
|
@@ -1580,12 +1706,10 @@ function createExtension(deps = defaultDeps) {
|
|
|
1580
1706
|
pollingActive = false;
|
|
1581
1707
|
console.error("avenor polling loop failed", err);
|
|
1582
1708
|
});
|
|
1583
|
-
},
|
|
1709
|
+
}, pollIntervalMs);
|
|
1584
1710
|
else {
|
|
1585
1711
|
pollingActive = false;
|
|
1586
|
-
|
|
1587
|
-
sessionCtx?.ui.setStatus("avenor", "");
|
|
1588
|
-
sessionCtx?.ui.setWidget("avenor-status", void 0);
|
|
1712
|
+
updateWidget([]);
|
|
1589
1713
|
}
|
|
1590
1714
|
};
|
|
1591
1715
|
tick().catch((err) => {
|
|
@@ -1596,10 +1720,11 @@ function createExtension(deps = defaultDeps) {
|
|
|
1596
1720
|
async function stopPolling() {
|
|
1597
1721
|
pollingActive = false;
|
|
1598
1722
|
pollingGeneration++;
|
|
1599
|
-
reportedPollingFailures.clear();
|
|
1600
1723
|
await pollInFlight?.catch(() => {});
|
|
1601
|
-
|
|
1602
|
-
|
|
1724
|
+
pollingErrorCount = 0;
|
|
1725
|
+
pollingErrors.length = 0;
|
|
1726
|
+
lastStatusEntries = [];
|
|
1727
|
+
updateWidget([]);
|
|
1603
1728
|
}
|
|
1604
1729
|
function createWatchDeps() {
|
|
1605
1730
|
async function resolveSupervisorSocket(run) {
|
|
@@ -1759,7 +1884,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1759
1884
|
async function waitForRunByPolling(run, signal, onUpdate) {
|
|
1760
1885
|
let firstPoll = true;
|
|
1761
1886
|
while (!signal?.aborted) {
|
|
1762
|
-
if (!firstPoll) await sleep(
|
|
1887
|
+
if (!firstPoll) await sleep(pollIntervalMs);
|
|
1763
1888
|
firstPoll = false;
|
|
1764
1889
|
try {
|
|
1765
1890
|
const raw = await deps.statusTool({
|
|
@@ -1769,7 +1894,6 @@ function createExtension(deps = defaultDeps) {
|
|
|
1769
1894
|
const status = Array.isArray(raw) ? raw[0] : raw;
|
|
1770
1895
|
if (!status) continue;
|
|
1771
1896
|
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})` : ""}`;
|
|
1772
|
-
clearPollingFailure(`spawn-status:${run.supervisorId ?? "singleton"}:${run.runId}`);
|
|
1773
1897
|
onUpdate?.({
|
|
1774
1898
|
content: [{
|
|
1775
1899
|
type: "text",
|
|
@@ -1785,7 +1909,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1785
1909
|
aborted: false
|
|
1786
1910
|
};
|
|
1787
1911
|
} catch (err) {
|
|
1788
|
-
|
|
1912
|
+
recordPollingError("spawn-status", "statusTool failed while waiting for a run", err, run.runId);
|
|
1789
1913
|
}
|
|
1790
1914
|
}
|
|
1791
1915
|
return { aborted: true };
|
|
@@ -1797,9 +1921,15 @@ function createExtension(deps = defaultDeps) {
|
|
|
1797
1921
|
}
|
|
1798
1922
|
pi.on("session_start", async (_event, ctx) => {
|
|
1799
1923
|
sessionCtx = ctx;
|
|
1924
|
+
lastStatusEntries = [];
|
|
1925
|
+
pollingErrorCount = 0;
|
|
1926
|
+
pollingErrors.length = 0;
|
|
1927
|
+
startHostAskPoll(ctx);
|
|
1800
1928
|
});
|
|
1801
1929
|
pi.on("session_shutdown", async () => {
|
|
1802
1930
|
await stopPolling();
|
|
1931
|
+
stopHostAskPoll();
|
|
1932
|
+
hostPendingAsks.clear();
|
|
1803
1933
|
sessionCtx = null;
|
|
1804
1934
|
});
|
|
1805
1935
|
pi.on("before_agent_start", async () => {
|
|
@@ -1828,7 +1958,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1828
1958
|
pi.registerTool({
|
|
1829
1959
|
name: "avenor_spawn",
|
|
1830
1960
|
label: "Avenor Spawn",
|
|
1831
|
-
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.`,
|
|
1832
1962
|
promptSnippet: "Dispatch sub-agent runs via avenor (spawn, status, result, inspect, events, follow-up, shutdown)",
|
|
1833
1963
|
promptGuidelines: [
|
|
1834
1964
|
"Use avenor_spawn to delegate well-defined tasks to sub-agents.",
|
|
@@ -1840,7 +1970,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1840
1970
|
"Use avenor_shutdown when done delegating work."
|
|
1841
1971
|
],
|
|
1842
1972
|
parameters: Type.Object({
|
|
1843
|
-
agent: Type.String({ description: "
|
|
1973
|
+
agent: Type.Optional(Type.String({ description: "Optional agent name; omission uses the supplied model or runtime defaults" })),
|
|
1844
1974
|
prompt: Type.Optional(Type.String({ description: "Prompt text" })),
|
|
1845
1975
|
prompt_file: Type.Optional(Type.String({ description: "Path to prompt file" })),
|
|
1846
1976
|
dir: Type.Optional(Type.String({ description: "Working directory (defaults to session directory)" })),
|
|
@@ -1856,7 +1986,9 @@ function createExtension(deps = defaultDeps) {
|
|
|
1856
1986
|
Type.Literal("xhigh"),
|
|
1857
1987
|
Type.Literal("max")
|
|
1858
1988
|
], { description: "Thinking level; omission uses the backend default" })),
|
|
1859
|
-
backend: Type.Optional(Type.String({ description: "Backend override (defaults to pi)" })),
|
|
1989
|
+
backend: Type.Optional(Type.String({ description: "Backend override (defaults to pi for direct mode)" })),
|
|
1990
|
+
roster_file: Type.Optional(Type.String({ description: "Path to the roster map" })),
|
|
1991
|
+
roster_entry: Type.Optional(Type.String({ description: "Roster entry to select" })),
|
|
1860
1992
|
server_url: Type.Optional(Type.String({ description: "Backend server URL" })),
|
|
1861
1993
|
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" })),
|
|
1862
1994
|
wait: Type.Optional(Type.Boolean({ description: "Block until complete (default true)" }))
|
|
@@ -1866,25 +1998,39 @@ function createExtension(deps = defaultDeps) {
|
|
|
1866
1998
|
type: "text",
|
|
1867
1999
|
text: "Cancelled"
|
|
1868
2000
|
}] };
|
|
2001
|
+
validateSpawnSelection({
|
|
2002
|
+
agent: params.agent,
|
|
2003
|
+
model: params.model,
|
|
2004
|
+
backend: params.backend,
|
|
2005
|
+
roster_file: params.roster_file,
|
|
2006
|
+
roster_entry: params.roster_entry
|
|
2007
|
+
});
|
|
1869
2008
|
const wait = params.wait ?? true;
|
|
1870
2009
|
const dir = params.dir ?? ctx.cwd;
|
|
1871
|
-
const label = params.label ?? `${params.agent}-${Date.now()}`;
|
|
1872
|
-
const
|
|
2010
|
+
const label = params.label ?? `${params.agent ?? params.roster_entry ?? params.model ?? "avenor"}-${Date.now()}`;
|
|
2011
|
+
const rosterMode = Boolean(params.roster_entry);
|
|
2012
|
+
const backend = rosterMode ? params.backend : params.backend ?? "pi";
|
|
1873
2013
|
const entries = ctx.sessionManager?.getEntries?.();
|
|
1874
|
-
const agentProfile = backend === "pi" ? resolveAgentProfile(entries) : void 0;
|
|
2014
|
+
const agentProfile = rosterMode || backend === "pi" ? resolveAgentProfile(entries) : void 0;
|
|
2015
|
+
const hostParams = {
|
|
2016
|
+
...params,
|
|
2017
|
+
backend
|
|
2018
|
+
};
|
|
1875
2019
|
const result = await deps.spawnTool({
|
|
1876
|
-
agent: params.agent,
|
|
1877
|
-
prompt: params.prompt,
|
|
1878
|
-
promptFile: params.prompt_file,
|
|
2020
|
+
...params.agent !== void 0 ? { agent: params.agent } : {},
|
|
2021
|
+
...params.prompt !== void 0 ? { prompt: params.prompt } : {},
|
|
2022
|
+
...params.prompt_file !== void 0 ? { promptFile: params.prompt_file } : {},
|
|
1879
2023
|
label,
|
|
1880
2024
|
dir,
|
|
1881
|
-
timeout: params.timeout,
|
|
1882
|
-
model: params.model,
|
|
1883
|
-
thinking: params.thinking,
|
|
1884
|
-
backend,
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
2025
|
+
...params.timeout !== void 0 ? { timeout: params.timeout } : {},
|
|
2026
|
+
...params.model !== void 0 ? { model: params.model } : {},
|
|
2027
|
+
...params.thinking !== void 0 ? { thinking: params.thinking } : {},
|
|
2028
|
+
...backend !== void 0 ? { backend } : {},
|
|
2029
|
+
...params.roster_file !== void 0 ? { rosterFile: params.roster_file } : {},
|
|
2030
|
+
...params.roster_entry !== void 0 ? { rosterEntry: params.roster_entry } : {},
|
|
2031
|
+
...agentProfile !== void 0 ? { agentProfile } : {},
|
|
2032
|
+
...params.server_url !== void 0 ? { serverUrl: params.server_url } : {},
|
|
2033
|
+
...params.supervisor_id !== void 0 ? { supervisorId: params.supervisor_id } : {}
|
|
1888
2034
|
});
|
|
1889
2035
|
const supervisorId = statusSupervisorId(params.supervisor_id, result.supervisor_id);
|
|
1890
2036
|
const runRef = {
|
|
@@ -1903,7 +2049,8 @@ function createExtension(deps = defaultDeps) {
|
|
|
1903
2049
|
}],
|
|
1904
2050
|
details: {
|
|
1905
2051
|
run_id: result.run_id,
|
|
1906
|
-
label
|
|
2052
|
+
label,
|
|
2053
|
+
...spawnIdentityMetadata(hostParams)
|
|
1907
2054
|
}
|
|
1908
2055
|
};
|
|
1909
2056
|
onUpdate?.({
|
|
@@ -1918,14 +2065,16 @@ function createExtension(deps = defaultDeps) {
|
|
|
1918
2065
|
});
|
|
1919
2066
|
const waitResult = await waitForRun(runRef, signal, onUpdate);
|
|
1920
2067
|
if (waitResult.aborted) {
|
|
1921
|
-
|
|
1922
|
-
trackedRuns.delete(result.run_id);
|
|
2068
|
+
trackedRuns.delete(trackedRunKey(supervisorId, result.run_id));
|
|
1923
2069
|
return {
|
|
1924
2070
|
content: [{
|
|
1925
2071
|
type: "text",
|
|
1926
2072
|
text: `Monitoring of "${label}" (run_id: ${result.run_id}) was interrupted. Use avenor_status or avenor_inspect to check.`
|
|
1927
2073
|
}],
|
|
1928
|
-
details: {
|
|
2074
|
+
details: {
|
|
2075
|
+
run_id: result.run_id,
|
|
2076
|
+
...spawnIdentityMetadata(hostParams)
|
|
2077
|
+
}
|
|
1929
2078
|
};
|
|
1930
2079
|
}
|
|
1931
2080
|
const status = waitResult.status;
|
|
@@ -1936,12 +2085,13 @@ function createExtension(deps = defaultDeps) {
|
|
|
1936
2085
|
}],
|
|
1937
2086
|
details: {
|
|
1938
2087
|
status: "running",
|
|
1939
|
-
run_id: result.run_id
|
|
2088
|
+
run_id: result.run_id,
|
|
2089
|
+
...spawnIdentityMetadata(hostParams)
|
|
1940
2090
|
}
|
|
1941
2091
|
};
|
|
1942
2092
|
upsertTrackedRun(runRef, status, wait);
|
|
1943
2093
|
if (status.status === "waiting") {
|
|
1944
|
-
const tracked =
|
|
2094
|
+
const tracked = getTrackedRun(result.run_id, supervisorId);
|
|
1945
2095
|
if (tracked) tracked.permissionNotified = true;
|
|
1946
2096
|
return {
|
|
1947
2097
|
content: [{
|
|
@@ -1956,7 +2106,8 @@ function createExtension(deps = defaultDeps) {
|
|
|
1956
2106
|
status: "waiting",
|
|
1957
2107
|
run_id: result.run_id,
|
|
1958
2108
|
session_id: status.session_id,
|
|
1959
|
-
...status.pending_permission && { pending_permission: status.pending_permission }
|
|
2109
|
+
...status.pending_permission && { pending_permission: status.pending_permission },
|
|
2110
|
+
...spawnIdentityMetadata(hostParams, status)
|
|
1960
2111
|
}
|
|
1961
2112
|
};
|
|
1962
2113
|
}
|
|
@@ -1974,8 +2125,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1974
2125
|
status: status.status,
|
|
1975
2126
|
backend: status.backend
|
|
1976
2127
|
}));
|
|
1977
|
-
|
|
1978
|
-
trackedRuns.delete(result.run_id);
|
|
2128
|
+
trackedRuns.delete(trackedRunKey(supervisorId, result.run_id));
|
|
1979
2129
|
return {
|
|
1980
2130
|
content: [{
|
|
1981
2131
|
type: "text",
|
|
@@ -1988,7 +2138,8 @@ function createExtension(deps = defaultDeps) {
|
|
|
1988
2138
|
status: status.status,
|
|
1989
2139
|
run_id: result.run_id,
|
|
1990
2140
|
session_id: status.session_id,
|
|
1991
|
-
final_output: finalOutput
|
|
2141
|
+
final_output: finalOutput,
|
|
2142
|
+
...spawnIdentityMetadata(hostParams, status)
|
|
1992
2143
|
}
|
|
1993
2144
|
};
|
|
1994
2145
|
}
|
|
@@ -1999,13 +2150,14 @@ function createExtension(deps = defaultDeps) {
|
|
|
1999
2150
|
}],
|
|
2000
2151
|
details: {
|
|
2001
2152
|
status: status.status,
|
|
2002
|
-
run_id: result.run_id
|
|
2153
|
+
run_id: result.run_id,
|
|
2154
|
+
...spawnIdentityMetadata(hostParams, status)
|
|
2003
2155
|
}
|
|
2004
2156
|
};
|
|
2005
2157
|
},
|
|
2006
2158
|
renderCall(args, theme) {
|
|
2007
2159
|
const agent = theme.fg("toolTitle", theme.bold("avenor_spawn "));
|
|
2008
|
-
const agentName = theme.fg("accent", args.agent);
|
|
2160
|
+
const agentName = theme.fg("accent", args.agent ?? args.roster_entry ?? args.model ?? "roster");
|
|
2009
2161
|
const label = args.label ? theme.fg("dim", ` "${args.label}"`) : "";
|
|
2010
2162
|
const wait = args.wait === false ? theme.fg("warning", " [async]") : "";
|
|
2011
2163
|
return new Text(agent + agentName + label + wait, 0, 0);
|
|
@@ -2072,7 +2224,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
2072
2224
|
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
|
|
2073
2225
|
}),
|
|
2074
2226
|
async execute(_toolCallId, params, signal) {
|
|
2075
|
-
const tracked =
|
|
2227
|
+
const tracked = getTrackedRun(params.run_id, params.supervisor_id);
|
|
2076
2228
|
const previousBlocking = tracked?.blocking;
|
|
2077
2229
|
if (tracked) tracked.blocking = true;
|
|
2078
2230
|
let completed = false;
|
|
@@ -2086,6 +2238,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
2086
2238
|
});
|
|
2087
2239
|
completed = result.ready;
|
|
2088
2240
|
if (completed) {
|
|
2241
|
+
if (tracked) tracked.consumed = true;
|
|
2089
2242
|
emitAvenorEvent(pi.events, CHANNEL_RUN_TERMINAL, createRunTerminalPayload({
|
|
2090
2243
|
runId: params.run_id,
|
|
2091
2244
|
supervisorId: tracked?.supervisorId ?? params.supervisor_id,
|
|
@@ -2094,11 +2247,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
2094
2247
|
agent: tracked?.agent ?? "unknown",
|
|
2095
2248
|
status: result.status ?? "done"
|
|
2096
2249
|
}));
|
|
2097
|
-
|
|
2098
|
-
runId: params.run_id,
|
|
2099
|
-
supervisorId: tracked?.supervisorId ?? params.supervisor_id
|
|
2100
|
-
});
|
|
2101
|
-
trackedRuns.delete(params.run_id);
|
|
2250
|
+
if (tracked) trackedRuns.delete(trackedRunKey(tracked.supervisorId, tracked.runId));
|
|
2102
2251
|
} else if (tracked && result.status === "waiting") tracked.permissionNotified = true;
|
|
2103
2252
|
return {
|
|
2104
2253
|
content: [{
|
|
@@ -2108,7 +2257,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
2108
2257
|
details: result
|
|
2109
2258
|
};
|
|
2110
2259
|
} finally {
|
|
2111
|
-
if (!completed && tracked &&
|
|
2260
|
+
if (!completed && tracked && getTrackedRun(params.run_id, params.supervisor_id) === tracked) tracked.blocking = previousBlocking ?? false;
|
|
2112
2261
|
}
|
|
2113
2262
|
},
|
|
2114
2263
|
renderCall(args, theme, context) {
|
|
@@ -2249,6 +2398,94 @@ function createExtension(deps = defaultDeps) {
|
|
|
2249
2398
|
}, theme, context.args);
|
|
2250
2399
|
}
|
|
2251
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
|
+
});
|
|
2252
2489
|
pi.registerTool({
|
|
2253
2490
|
name: "avenor_events",
|
|
2254
2491
|
label: "Avenor Events",
|
|
@@ -2293,8 +2530,20 @@ function createExtension(deps = defaultDeps) {
|
|
|
2293
2530
|
force: Type.Optional(Type.Boolean({ description: "Force shutdown instead of graceful" }))
|
|
2294
2531
|
}),
|
|
2295
2532
|
async execute(_toolCallId, params) {
|
|
2296
|
-
|
|
2297
|
-
|
|
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
|
+
}
|
|
2298
2547
|
const result = await deps.shutdownTool({
|
|
2299
2548
|
supervisorId: params.supervisor_id,
|
|
2300
2549
|
force: params.force
|
|
@@ -2317,10 +2566,178 @@ function createExtension(deps = defaultDeps) {
|
|
|
2317
2566
|
}, theme, context.args);
|
|
2318
2567
|
}
|
|
2319
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
|
+
});
|
|
2320
2736
|
pi.registerCommand("avenor-status", {
|
|
2321
2737
|
description: "Show status of all avenor runs",
|
|
2322
2738
|
handler: async (_args, ctx) => {
|
|
2323
2739
|
const entries = await pollRuns();
|
|
2740
|
+
updateWidget(entries);
|
|
2324
2741
|
if (entries.length === 0) {
|
|
2325
2742
|
ctx.ui.notify("No active avenor runs", "info");
|
|
2326
2743
|
return;
|
|
@@ -2328,12 +2745,28 @@ function createExtension(deps = defaultDeps) {
|
|
|
2328
2745
|
ctx.ui.setWidget("avenor-status-detail", entries.map((entry) => formatRunLine(entry)));
|
|
2329
2746
|
}
|
|
2330
2747
|
});
|
|
2748
|
+
pi.registerCommand("avenor-errors", {
|
|
2749
|
+
description: "Show and clear recent Avenor polling errors",
|
|
2750
|
+
handler: async (_args, ctx) => {
|
|
2751
|
+
if (pollingErrors.length === 0) {
|
|
2752
|
+
ctx.ui.setWidget("avenor-errors", void 0);
|
|
2753
|
+
ctx.ui.notify("No recorded avenor polling errors", "info");
|
|
2754
|
+
return;
|
|
2755
|
+
}
|
|
2756
|
+
const errorLines = pollingErrors.map((error) => {
|
|
2757
|
+
const run = error.runId ? ` [${error.runId}]` : "";
|
|
2758
|
+
return `${new Date(error.timestamp).toLocaleTimeString()} ${error.message}${run}: ${error.error}`;
|
|
2759
|
+
});
|
|
2760
|
+
ctx.ui.setWidget("avenor-errors", errorLines);
|
|
2761
|
+
clearPollingErrors();
|
|
2762
|
+
}
|
|
2763
|
+
});
|
|
2331
2764
|
pi.registerCommand("avenor-watch", {
|
|
2332
2765
|
description: "Open a live run inspector by run ID, label, or agent name",
|
|
2333
2766
|
getArgumentCompletions: (prefix) => {
|
|
2334
|
-
const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.agent
|
|
2767
|
+
const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.agent?.startsWith(prefix) || run.runId.startsWith(prefix)).map((run) => ({
|
|
2335
2768
|
value: run.runId,
|
|
2336
|
-
label: `${sanitizeText(run.label)} (${sanitizeText(run.agent)}, ${sanitizeText(run.runId).slice(0, 8)})`
|
|
2769
|
+
label: `${sanitizeText(run.label)} (${sanitizeText(run.agent ?? "roster")}, ${sanitizeText(run.runId).slice(0, 8)})`
|
|
2337
2770
|
}));
|
|
2338
2771
|
return items.length > 0 ? items : null;
|
|
2339
2772
|
},
|
|
@@ -2355,10 +2788,13 @@ function createExtension(deps = defaultDeps) {
|
|
|
2355
2788
|
pi.registerCommand("avenor-cancel", {
|
|
2356
2789
|
description: "Cancel a running avenor sub-agent",
|
|
2357
2790
|
getArgumentCompletions: (prefix) => {
|
|
2358
|
-
const
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
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
|
+
});
|
|
2362
2798
|
return items.length > 0 ? items : null;
|
|
2363
2799
|
},
|
|
2364
2800
|
handler: async (args, ctx) => {
|
|
@@ -2366,8 +2802,11 @@ function createExtension(deps = defaultDeps) {
|
|
|
2366
2802
|
ctx.ui.notify("Usage: /avenor-cancel <run_id>", "warning");
|
|
2367
2803
|
return;
|
|
2368
2804
|
}
|
|
2369
|
-
const
|
|
2370
|
-
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);
|
|
2371
2810
|
const label = run?.label ?? runId;
|
|
2372
2811
|
const runtimeId = run?.runtimeId ?? run?.lastStatus?.runtime_id;
|
|
2373
2812
|
if (!runtimeId) {
|
|
@@ -2392,4 +2831,4 @@ function createExtension(deps = defaultDeps) {
|
|
|
2392
2831
|
}
|
|
2393
2832
|
var src_default = createExtension();
|
|
2394
2833
|
//#endregion
|
|
2395
|
-
export { CHANNEL_POLL_COMPLETED, CHANNEL_RUN_TERMINAL, buildCompletionText, buildInspectPayload, compactWhitespace, createExtension, createPollCompletedPayload, createRunTerminalPayload, src_default as default, emitAvenorEvent, isTerminalStatus, onAvenorEvent, renderStatusLines, statusSupervisorId };
|
|
2834
|
+
export { CHANNEL_POLL_COMPLETED, CHANNEL_POLL_ERROR, CHANNEL_RUN_TERMINAL, buildCompletionText, buildInspectPayload, compactWhitespace, createExtension, createPollCompletedPayload, createPollErrorPayload, createRunTerminalPayload, src_default as default, emitAvenorEvent, isTerminalStatus, onAvenorEvent, renderStatusLines, spawnIdentityMetadata, statusSupervisorId };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dougbots/avenor-pi",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
38
|
+
"@dougbots/avenor-core": "^0.10.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@earendil-works/pi-coding-agent": ">=0.74.0",
|