@dougbots/avenor-pi 0.9.0 → 0.10.0
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 +38 -1
- package/dist/index.js +152 -69
- 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
|
@@ -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 {
|
|
@@ -96,6 +124,15 @@ interface ExtensionDeps {
|
|
|
96
124
|
Supervisor: typeof Supervisor;
|
|
97
125
|
}
|
|
98
126
|
declare function statusSupervisorId(requestedSupervisorId: string | undefined, spawnedSupervisorId: string): string | undefined;
|
|
127
|
+
type SpawnHostParams = {
|
|
128
|
+
agent?: string;
|
|
129
|
+
model?: string;
|
|
130
|
+
backend?: string;
|
|
131
|
+
roster_file?: string;
|
|
132
|
+
roster_entry?: string;
|
|
133
|
+
};
|
|
134
|
+
/** Project shared spawn identity into Pi tool metadata without resolving rosters here. */
|
|
135
|
+
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
136
|
declare function buildCompletionText(run: {
|
|
100
137
|
runId: string;
|
|
101
138
|
label: string;
|
|
@@ -107,4 +144,4 @@ declare function buildInspectPayload(result: InspectResult): Record<string, unkn
|
|
|
107
144
|
declare function createExtension(deps?: ExtensionDeps): (pi: ExtensionAPI) => Promise<void>;
|
|
108
145
|
declare const _default: (pi: ExtensionAPI) => Promise<void>;
|
|
109
146
|
//#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 };
|
|
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 };
|
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, answerPermissionTool, dial, eventsTool, extractEventText, followUpTool, inspectTool, observeRun, resultTool, shutdownTool, socketsRoot, spawnTool, statusTool, validateSpawnSelection } 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";
|
|
@@ -718,7 +718,15 @@ function resolveAgentProfile(entries, environment = process.env) {
|
|
|
718
718
|
const CHANNEL_POLL_COMPLETED = "avenor:poll:completed";
|
|
719
719
|
/** Emitted when the extension observes a terminal run result. */
|
|
720
720
|
const CHANNEL_RUN_TERMINAL = "avenor:run:terminal";
|
|
721
|
-
/**
|
|
721
|
+
/** Emitted when a best-effort polling operation fails. */
|
|
722
|
+
const CHANNEL_POLL_ERROR = "avenor:poll:error";
|
|
723
|
+
/** Maximum sanitized error text exposed to companion extensions. */
|
|
724
|
+
const POLL_ERROR_TEXT_CHARS = 600;
|
|
725
|
+
function sanitizeAndBoundText(value) {
|
|
726
|
+
const text = sanitizeText(String(value)).trim();
|
|
727
|
+
if (!text) return "unknown error";
|
|
728
|
+
return text.length <= POLL_ERROR_TEXT_CHARS ? text : `${text.slice(0, POLL_ERROR_TEXT_CHARS - 1)}…`;
|
|
729
|
+
}
|
|
722
730
|
function createSupervisorKey(supervisorId) {
|
|
723
731
|
if (!supervisorId) return "singleton";
|
|
724
732
|
return `supervisor:${createHash("sha256").update(supervisorId).digest("hex").slice(0, 16)}`;
|
|
@@ -769,6 +777,17 @@ function createRunTerminalPayload(payload) {
|
|
|
769
777
|
...payload.backend !== void 0 && { backend: payload.backend }
|
|
770
778
|
});
|
|
771
779
|
}
|
|
780
|
+
/** Copy, bound, sanitize, and freeze a polling error before publishing it. */
|
|
781
|
+
function createPollErrorPayload(payload) {
|
|
782
|
+
return Object.freeze({
|
|
783
|
+
source: payload.source,
|
|
784
|
+
...payload.runId !== void 0 && { runId: payload.runId },
|
|
785
|
+
message: sanitizeAndBoundText(payload.message),
|
|
786
|
+
error: sanitizeAndBoundText(payload.error),
|
|
787
|
+
count: payload.count,
|
|
788
|
+
timestamp: payload.timestamp
|
|
789
|
+
});
|
|
790
|
+
}
|
|
772
791
|
//#endregion
|
|
773
792
|
//#region src/render.ts
|
|
774
793
|
const FINAL_OUTPUT_CHARS = 600;
|
|
@@ -1031,9 +1050,11 @@ function eventsLines(details, args, expanded) {
|
|
|
1031
1050
|
const displayedTypes = types.slice(0, COLLECTION_ITEMS).map((type) => scalar(type)).join(", ");
|
|
1032
1051
|
const lines = [`Events: ${runId} — ${events.length} events`, ...types.length > 0 ? [`Filter: ${displayedTypes}${types.length > COLLECTION_ITEMS ? ` ${itemMarker(types.length - COLLECTION_ITEMS)}` : ""}`] : []];
|
|
1033
1052
|
if (events.length === 0) return [...lines, "No events."];
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1053
|
+
if (!expanded) {
|
|
1054
|
+
lines.push(`Preview: ${eventRow(events[0])}`);
|
|
1055
|
+
if (events.length > 1) lines.push(itemMarker(events.length - 1));
|
|
1056
|
+
return lines;
|
|
1057
|
+
}
|
|
1037
1058
|
const displayed = events.slice(0, STATUS_ROWS);
|
|
1038
1059
|
lines.push(...displayed.map(eventRow));
|
|
1039
1060
|
if (events.length > displayed.length) lines.push(itemMarker(events.length - displayed.length));
|
|
@@ -1226,6 +1247,7 @@ const INSPECT_JSON_STRING_CHARS = 600;
|
|
|
1226
1247
|
const INSPECT_JSON_ARRAY_ITEMS = 24;
|
|
1227
1248
|
const INSPECT_JSON_OBJECT_KEYS = 24;
|
|
1228
1249
|
const INSPECT_JSON_DEPTH = 8;
|
|
1250
|
+
const MAX_POLL_ERRORS = 100;
|
|
1229
1251
|
const defaultDeps = {
|
|
1230
1252
|
spawnTool,
|
|
1231
1253
|
statusTool,
|
|
@@ -1242,6 +1264,19 @@ const defaultDeps = {
|
|
|
1242
1264
|
function statusSupervisorId(requestedSupervisorId, spawnedSupervisorId) {
|
|
1243
1265
|
return requestedSupervisorId ? spawnedSupervisorId : void 0;
|
|
1244
1266
|
}
|
|
1267
|
+
/** Project shared spawn identity into Pi tool metadata without resolving rosters here. */
|
|
1268
|
+
function spawnIdentityMetadata(params, status) {
|
|
1269
|
+
const metadata = {};
|
|
1270
|
+
const add = (key, value) => {
|
|
1271
|
+
if (typeof value === "string" && value.length > 0) metadata[key] = value;
|
|
1272
|
+
};
|
|
1273
|
+
add("roster_file", status?.roster_file ?? params.roster_file);
|
|
1274
|
+
add("roster_entry", status?.roster_entry ?? params.roster_entry);
|
|
1275
|
+
add("effective_agent", status?.effective_agent ?? status?.agent ?? params.agent);
|
|
1276
|
+
add("effective_model", status?.effective_model ?? status?.model ?? params.model);
|
|
1277
|
+
add("effective_backend", status?.effective_backend ?? status?.backend ?? params.backend);
|
|
1278
|
+
return metadata;
|
|
1279
|
+
}
|
|
1245
1280
|
function sleep(ms) {
|
|
1246
1281
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1247
1282
|
}
|
|
@@ -1267,7 +1302,8 @@ function buildCompletionText(run, result, finalOutput) {
|
|
|
1267
1302
|
}
|
|
1268
1303
|
function buildWaitingText(run, status) {
|
|
1269
1304
|
const perm = status.pending_permission;
|
|
1270
|
-
const
|
|
1305
|
+
const agent = run.agent ?? status.effective_agent ?? status.agent ?? "roster";
|
|
1306
|
+
const lines = [`Sub-agent "${run.label}" (${agent}) is waiting for input.`];
|
|
1271
1307
|
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
1308
|
else if (status.phase_label) lines.push(`Question: ${status.phase_label}`);
|
|
1273
1309
|
if (!perm) lines.push(`Use \`avenor_follow_up\` with run_id "${run.runId}" to respond, or \`avenor_inspect\` to review context first.`);
|
|
@@ -1359,18 +1395,27 @@ function createExtension(deps = defaultDeps) {
|
|
|
1359
1395
|
let pollingGeneration = 0;
|
|
1360
1396
|
let pollInFlight = null;
|
|
1361
1397
|
let sessionCtx = null;
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1398
|
+
let pollingErrorCount = 0;
|
|
1399
|
+
const pollingErrors = [];
|
|
1400
|
+
let lastStatusEntries = [];
|
|
1401
|
+
function recordPollingError(source, message, error, runId) {
|
|
1402
|
+
pollingErrorCount++;
|
|
1403
|
+
const payload = createPollErrorPayload({
|
|
1404
|
+
source,
|
|
1405
|
+
runId,
|
|
1406
|
+
message,
|
|
1407
|
+
error,
|
|
1408
|
+
count: pollingErrorCount,
|
|
1409
|
+
timestamp: Date.now()
|
|
1410
|
+
});
|
|
1411
|
+
pollingErrors.push(payload);
|
|
1412
|
+
if (pollingErrors.length > MAX_POLL_ERRORS) pollingErrors.shift();
|
|
1413
|
+
emitAvenorEvent(pi.events, CHANNEL_POLL_ERROR, payload);
|
|
1370
1414
|
}
|
|
1371
|
-
function
|
|
1372
|
-
|
|
1373
|
-
|
|
1415
|
+
function clearPollingErrors() {
|
|
1416
|
+
pollingErrorCount = 0;
|
|
1417
|
+
pollingErrors.length = 0;
|
|
1418
|
+
updateWidget(lastStatusEntries);
|
|
1374
1419
|
}
|
|
1375
1420
|
function upsertTrackedRun(run, status, blocking = false) {
|
|
1376
1421
|
const current = trackedRuns.get(run.runId);
|
|
@@ -1402,22 +1447,22 @@ function createExtension(deps = defaultDeps) {
|
|
|
1402
1447
|
}
|
|
1403
1448
|
}
|
|
1404
1449
|
async function pollRuns() {
|
|
1450
|
+
const errorCountAtPollStart = pollingErrorCount;
|
|
1405
1451
|
const liveMap = /* @__PURE__ */ new Map();
|
|
1406
1452
|
try {
|
|
1407
1453
|
const allLive = await deps.statusTool({});
|
|
1408
1454
|
const liveList = Array.isArray(allLive) ? allLive : [allLive];
|
|
1409
1455
|
for (const result of liveList) liveMap.set(result.run_id, result);
|
|
1410
|
-
clearPollingFailure("singleton-list");
|
|
1411
1456
|
} catch (err) {
|
|
1412
|
-
|
|
1457
|
+
recordPollingError("singleton-list", "failed to list singleton runs", err);
|
|
1413
1458
|
}
|
|
1414
1459
|
const entries = [];
|
|
1415
1460
|
for (const run of trackedRuns.values()) {
|
|
1416
1461
|
const live = findLiveStatusForTrackedRun(run, liveMap.values());
|
|
1417
1462
|
if (live) {
|
|
1418
1463
|
liveMap.delete(live.run_id);
|
|
1419
|
-
clearPollingFailure(`run-status:${run.supervisorId ?? "singleton"}:${run.runId}`);
|
|
1420
1464
|
run.lastStatus = live;
|
|
1465
|
+
run.agent = live.agent ?? run.agent;
|
|
1421
1466
|
entries.push({
|
|
1422
1467
|
runId: run.runId,
|
|
1423
1468
|
supervisorId: run.supervisorId,
|
|
@@ -1426,7 +1471,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1426
1471
|
status: live.status,
|
|
1427
1472
|
phase: live.phase,
|
|
1428
1473
|
phaseLabel: live.phase_label,
|
|
1429
|
-
agent: run.agent,
|
|
1474
|
+
agent: run.agent ?? "unknown",
|
|
1430
1475
|
pendingPermission: !!live.pending_permission,
|
|
1431
1476
|
permissionDescription: live.pending_permission?.description,
|
|
1432
1477
|
pid: live.pid,
|
|
@@ -1441,8 +1486,8 @@ function createExtension(deps = defaultDeps) {
|
|
|
1441
1486
|
});
|
|
1442
1487
|
const result = Array.isArray(raw) ? raw[0] : raw;
|
|
1443
1488
|
if (!result) throw new Error("status response was empty");
|
|
1444
|
-
clearPollingFailure(`run-status:${run.supervisorId ?? "singleton"}:${run.runId}`);
|
|
1445
1489
|
run.lastStatus = result;
|
|
1490
|
+
run.agent = result.agent ?? run.agent;
|
|
1446
1491
|
entries.push({
|
|
1447
1492
|
runId: run.runId,
|
|
1448
1493
|
supervisorId: run.supervisorId,
|
|
@@ -1451,14 +1496,14 @@ function createExtension(deps = defaultDeps) {
|
|
|
1451
1496
|
status: result.status,
|
|
1452
1497
|
phase: result.phase,
|
|
1453
1498
|
phaseLabel: result.phase_label,
|
|
1454
|
-
agent: run.agent,
|
|
1499
|
+
agent: run.agent ?? "unknown",
|
|
1455
1500
|
pendingPermission: !!result.pending_permission,
|
|
1456
1501
|
permissionDescription: result.pending_permission?.description,
|
|
1457
1502
|
pid: result.pid,
|
|
1458
1503
|
backend: result.backend
|
|
1459
1504
|
});
|
|
1460
1505
|
} catch (err) {
|
|
1461
|
-
|
|
1506
|
+
recordPollingError("run-status", "statusTool failed while polling a run", err, run.runId);
|
|
1462
1507
|
const previous = run.lastStatus;
|
|
1463
1508
|
entries.push({
|
|
1464
1509
|
runId: run.runId,
|
|
@@ -1468,7 +1513,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1468
1513
|
status: previous?.status ?? "unavailable",
|
|
1469
1514
|
phase: previous?.phase,
|
|
1470
1515
|
phaseLabel: previous?.phase_label,
|
|
1471
|
-
agent: run.agent,
|
|
1516
|
+
agent: run.agent ?? previous?.agent ?? "unknown",
|
|
1472
1517
|
pendingPermission: !!previous?.pending_permission,
|
|
1473
1518
|
permissionDescription: previous?.pending_permission?.description,
|
|
1474
1519
|
pid: previous?.pid,
|
|
@@ -1495,21 +1540,24 @@ function createExtension(deps = defaultDeps) {
|
|
|
1495
1540
|
let idx = 0;
|
|
1496
1541
|
for (const entry of entries) if (entry.backend === "pi" && entry.pid && !isTerminalStatus(entry.status)) entry.nestedCount = nestedCounts[idx++] ?? 0;
|
|
1497
1542
|
}
|
|
1543
|
+
if (pollingErrorCount === errorCountAtPollStart) pollingErrorCount = 0;
|
|
1498
1544
|
return entries;
|
|
1499
1545
|
}
|
|
1500
1546
|
function updateWidget(entries) {
|
|
1547
|
+
lastStatusEntries = entries;
|
|
1501
1548
|
if (!sessionCtx) return;
|
|
1502
1549
|
const lines = renderStatusLines(entries);
|
|
1550
|
+
const errorSuffix = pollingErrorCount > 0 ? `errors:${pollingErrorCount}` : "";
|
|
1503
1551
|
if (lines.length > 0) {
|
|
1504
1552
|
sessionCtx.ui.setWidget("avenor-status", lines);
|
|
1505
1553
|
const statusStr = lines.map((line) => line.slice(0, 60)).join(" ");
|
|
1506
1554
|
const nestedTotal = entries.filter((e) => !isTerminalStatus(e.status)).reduce((sum, e) => sum + (e.nestedCount ?? 0), 0);
|
|
1507
1555
|
const total = lines.length + nestedTotal;
|
|
1508
|
-
const statusWithTotal = nestedTotal > 0 ? `${statusStr} total:${total}` : statusStr;
|
|
1556
|
+
const statusWithTotal = [nestedTotal > 0 ? `${statusStr} total:${total}` : statusStr, errorSuffix].filter(Boolean).join(" ");
|
|
1509
1557
|
sessionCtx.ui.setStatus("avenor", statusWithTotal);
|
|
1510
1558
|
} else {
|
|
1511
1559
|
sessionCtx.ui.setWidget("avenor-status", void 0);
|
|
1512
|
-
sessionCtx.ui.setStatus("avenor",
|
|
1560
|
+
sessionCtx.ui.setStatus("avenor", errorSuffix);
|
|
1513
1561
|
}
|
|
1514
1562
|
}
|
|
1515
1563
|
function startPolling() {
|
|
@@ -1560,7 +1608,6 @@ function createExtension(deps = defaultDeps) {
|
|
|
1560
1608
|
nestedCount: entry.nestedCount,
|
|
1561
1609
|
backend: entry.backend
|
|
1562
1610
|
}));
|
|
1563
|
-
clearRunPollingFailures(run);
|
|
1564
1611
|
trackedRuns.delete(entry.runId);
|
|
1565
1612
|
} else if (entry.pendingPermission && !run.blocking && !run.permissionNotified) {
|
|
1566
1613
|
run.permissionNotified = true;
|
|
@@ -1583,9 +1630,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1583
1630
|
}, POLL_INTERVAL_MS);
|
|
1584
1631
|
else {
|
|
1585
1632
|
pollingActive = false;
|
|
1586
|
-
|
|
1587
|
-
sessionCtx?.ui.setStatus("avenor", "");
|
|
1588
|
-
sessionCtx?.ui.setWidget("avenor-status", void 0);
|
|
1633
|
+
updateWidget([]);
|
|
1589
1634
|
}
|
|
1590
1635
|
};
|
|
1591
1636
|
tick().catch((err) => {
|
|
@@ -1596,10 +1641,11 @@ function createExtension(deps = defaultDeps) {
|
|
|
1596
1641
|
async function stopPolling() {
|
|
1597
1642
|
pollingActive = false;
|
|
1598
1643
|
pollingGeneration++;
|
|
1599
|
-
reportedPollingFailures.clear();
|
|
1600
1644
|
await pollInFlight?.catch(() => {});
|
|
1601
|
-
|
|
1602
|
-
|
|
1645
|
+
pollingErrorCount = 0;
|
|
1646
|
+
pollingErrors.length = 0;
|
|
1647
|
+
lastStatusEntries = [];
|
|
1648
|
+
updateWidget([]);
|
|
1603
1649
|
}
|
|
1604
1650
|
function createWatchDeps() {
|
|
1605
1651
|
async function resolveSupervisorSocket(run) {
|
|
@@ -1769,7 +1815,6 @@ function createExtension(deps = defaultDeps) {
|
|
|
1769
1815
|
const status = Array.isArray(raw) ? raw[0] : raw;
|
|
1770
1816
|
if (!status) continue;
|
|
1771
1817
|
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
1818
|
onUpdate?.({
|
|
1774
1819
|
content: [{
|
|
1775
1820
|
type: "text",
|
|
@@ -1785,7 +1830,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1785
1830
|
aborted: false
|
|
1786
1831
|
};
|
|
1787
1832
|
} catch (err) {
|
|
1788
|
-
|
|
1833
|
+
recordPollingError("spawn-status", "statusTool failed while waiting for a run", err, run.runId);
|
|
1789
1834
|
}
|
|
1790
1835
|
}
|
|
1791
1836
|
return { aborted: true };
|
|
@@ -1797,6 +1842,9 @@ function createExtension(deps = defaultDeps) {
|
|
|
1797
1842
|
}
|
|
1798
1843
|
pi.on("session_start", async (_event, ctx) => {
|
|
1799
1844
|
sessionCtx = ctx;
|
|
1845
|
+
lastStatusEntries = [];
|
|
1846
|
+
pollingErrorCount = 0;
|
|
1847
|
+
pollingErrors.length = 0;
|
|
1800
1848
|
});
|
|
1801
1849
|
pi.on("session_shutdown", async () => {
|
|
1802
1850
|
await stopPolling();
|
|
@@ -1840,7 +1888,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1840
1888
|
"Use avenor_shutdown when done delegating work."
|
|
1841
1889
|
],
|
|
1842
1890
|
parameters: Type.Object({
|
|
1843
|
-
agent: Type.String({ description: "
|
|
1891
|
+
agent: Type.Optional(Type.String({ description: "Optional agent name; omission uses the supplied model or runtime defaults" })),
|
|
1844
1892
|
prompt: Type.Optional(Type.String({ description: "Prompt text" })),
|
|
1845
1893
|
prompt_file: Type.Optional(Type.String({ description: "Path to prompt file" })),
|
|
1846
1894
|
dir: Type.Optional(Type.String({ description: "Working directory (defaults to session directory)" })),
|
|
@@ -1856,7 +1904,9 @@ function createExtension(deps = defaultDeps) {
|
|
|
1856
1904
|
Type.Literal("xhigh"),
|
|
1857
1905
|
Type.Literal("max")
|
|
1858
1906
|
], { description: "Thinking level; omission uses the backend default" })),
|
|
1859
|
-
backend: Type.Optional(Type.String({ description: "Backend override (defaults to pi)" })),
|
|
1907
|
+
backend: Type.Optional(Type.String({ description: "Backend override (defaults to pi for direct mode)" })),
|
|
1908
|
+
roster_file: Type.Optional(Type.String({ description: "Path to the roster map" })),
|
|
1909
|
+
roster_entry: Type.Optional(Type.String({ description: "Roster entry to select" })),
|
|
1860
1910
|
server_url: Type.Optional(Type.String({ description: "Backend server URL" })),
|
|
1861
1911
|
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" })),
|
|
1862
1912
|
wait: Type.Optional(Type.Boolean({ description: "Block until complete (default true)" }))
|
|
@@ -1866,25 +1916,39 @@ function createExtension(deps = defaultDeps) {
|
|
|
1866
1916
|
type: "text",
|
|
1867
1917
|
text: "Cancelled"
|
|
1868
1918
|
}] };
|
|
1919
|
+
validateSpawnSelection({
|
|
1920
|
+
agent: params.agent,
|
|
1921
|
+
model: params.model,
|
|
1922
|
+
backend: params.backend,
|
|
1923
|
+
roster_file: params.roster_file,
|
|
1924
|
+
roster_entry: params.roster_entry
|
|
1925
|
+
});
|
|
1869
1926
|
const wait = params.wait ?? true;
|
|
1870
1927
|
const dir = params.dir ?? ctx.cwd;
|
|
1871
|
-
const label = params.label ?? `${params.agent}-${Date.now()}`;
|
|
1872
|
-
const
|
|
1928
|
+
const label = params.label ?? `${params.agent ?? params.roster_entry ?? params.model ?? "avenor"}-${Date.now()}`;
|
|
1929
|
+
const rosterMode = Boolean(params.roster_entry);
|
|
1930
|
+
const backend = rosterMode ? params.backend : params.backend ?? "pi";
|
|
1873
1931
|
const entries = ctx.sessionManager?.getEntries?.();
|
|
1874
|
-
const agentProfile = backend === "pi" ? resolveAgentProfile(entries) : void 0;
|
|
1932
|
+
const agentProfile = rosterMode || backend === "pi" ? resolveAgentProfile(entries) : void 0;
|
|
1933
|
+
const hostParams = {
|
|
1934
|
+
...params,
|
|
1935
|
+
backend
|
|
1936
|
+
};
|
|
1875
1937
|
const result = await deps.spawnTool({
|
|
1876
|
-
agent: params.agent,
|
|
1877
|
-
prompt: params.prompt,
|
|
1878
|
-
promptFile: params.prompt_file,
|
|
1938
|
+
...params.agent !== void 0 ? { agent: params.agent } : {},
|
|
1939
|
+
...params.prompt !== void 0 ? { prompt: params.prompt } : {},
|
|
1940
|
+
...params.prompt_file !== void 0 ? { promptFile: params.prompt_file } : {},
|
|
1879
1941
|
label,
|
|
1880
1942
|
dir,
|
|
1881
|
-
timeout: params.timeout,
|
|
1882
|
-
model: params.model,
|
|
1883
|
-
thinking: params.thinking,
|
|
1884
|
-
backend,
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1943
|
+
...params.timeout !== void 0 ? { timeout: params.timeout } : {},
|
|
1944
|
+
...params.model !== void 0 ? { model: params.model } : {},
|
|
1945
|
+
...params.thinking !== void 0 ? { thinking: params.thinking } : {},
|
|
1946
|
+
...backend !== void 0 ? { backend } : {},
|
|
1947
|
+
...params.roster_file !== void 0 ? { rosterFile: params.roster_file } : {},
|
|
1948
|
+
...params.roster_entry !== void 0 ? { rosterEntry: params.roster_entry } : {},
|
|
1949
|
+
...agentProfile !== void 0 ? { agentProfile } : {},
|
|
1950
|
+
...params.server_url !== void 0 ? { serverUrl: params.server_url } : {},
|
|
1951
|
+
...params.supervisor_id !== void 0 ? { supervisorId: params.supervisor_id } : {}
|
|
1888
1952
|
});
|
|
1889
1953
|
const supervisorId = statusSupervisorId(params.supervisor_id, result.supervisor_id);
|
|
1890
1954
|
const runRef = {
|
|
@@ -1903,7 +1967,8 @@ function createExtension(deps = defaultDeps) {
|
|
|
1903
1967
|
}],
|
|
1904
1968
|
details: {
|
|
1905
1969
|
run_id: result.run_id,
|
|
1906
|
-
label
|
|
1970
|
+
label,
|
|
1971
|
+
...spawnIdentityMetadata(hostParams)
|
|
1907
1972
|
}
|
|
1908
1973
|
};
|
|
1909
1974
|
onUpdate?.({
|
|
@@ -1918,14 +1983,16 @@ function createExtension(deps = defaultDeps) {
|
|
|
1918
1983
|
});
|
|
1919
1984
|
const waitResult = await waitForRun(runRef, signal, onUpdate);
|
|
1920
1985
|
if (waitResult.aborted) {
|
|
1921
|
-
clearRunPollingFailures(runRef);
|
|
1922
1986
|
trackedRuns.delete(result.run_id);
|
|
1923
1987
|
return {
|
|
1924
1988
|
content: [{
|
|
1925
1989
|
type: "text",
|
|
1926
1990
|
text: `Monitoring of "${label}" (run_id: ${result.run_id}) was interrupted. Use avenor_status or avenor_inspect to check.`
|
|
1927
1991
|
}],
|
|
1928
|
-
details: {
|
|
1992
|
+
details: {
|
|
1993
|
+
run_id: result.run_id,
|
|
1994
|
+
...spawnIdentityMetadata(hostParams)
|
|
1995
|
+
}
|
|
1929
1996
|
};
|
|
1930
1997
|
}
|
|
1931
1998
|
const status = waitResult.status;
|
|
@@ -1936,7 +2003,8 @@ function createExtension(deps = defaultDeps) {
|
|
|
1936
2003
|
}],
|
|
1937
2004
|
details: {
|
|
1938
2005
|
status: "running",
|
|
1939
|
-
run_id: result.run_id
|
|
2006
|
+
run_id: result.run_id,
|
|
2007
|
+
...spawnIdentityMetadata(hostParams)
|
|
1940
2008
|
}
|
|
1941
2009
|
};
|
|
1942
2010
|
upsertTrackedRun(runRef, status, wait);
|
|
@@ -1956,7 +2024,8 @@ function createExtension(deps = defaultDeps) {
|
|
|
1956
2024
|
status: "waiting",
|
|
1957
2025
|
run_id: result.run_id,
|
|
1958
2026
|
session_id: status.session_id,
|
|
1959
|
-
...status.pending_permission && { pending_permission: status.pending_permission }
|
|
2027
|
+
...status.pending_permission && { pending_permission: status.pending_permission },
|
|
2028
|
+
...spawnIdentityMetadata(hostParams, status)
|
|
1960
2029
|
}
|
|
1961
2030
|
};
|
|
1962
2031
|
}
|
|
@@ -1974,7 +2043,6 @@ function createExtension(deps = defaultDeps) {
|
|
|
1974
2043
|
status: status.status,
|
|
1975
2044
|
backend: status.backend
|
|
1976
2045
|
}));
|
|
1977
|
-
clearRunPollingFailures(runRef);
|
|
1978
2046
|
trackedRuns.delete(result.run_id);
|
|
1979
2047
|
return {
|
|
1980
2048
|
content: [{
|
|
@@ -1988,7 +2056,8 @@ function createExtension(deps = defaultDeps) {
|
|
|
1988
2056
|
status: status.status,
|
|
1989
2057
|
run_id: result.run_id,
|
|
1990
2058
|
session_id: status.session_id,
|
|
1991
|
-
final_output: finalOutput
|
|
2059
|
+
final_output: finalOutput,
|
|
2060
|
+
...spawnIdentityMetadata(hostParams, status)
|
|
1992
2061
|
}
|
|
1993
2062
|
};
|
|
1994
2063
|
}
|
|
@@ -1999,13 +2068,14 @@ function createExtension(deps = defaultDeps) {
|
|
|
1999
2068
|
}],
|
|
2000
2069
|
details: {
|
|
2001
2070
|
status: status.status,
|
|
2002
|
-
run_id: result.run_id
|
|
2071
|
+
run_id: result.run_id,
|
|
2072
|
+
...spawnIdentityMetadata(hostParams, status)
|
|
2003
2073
|
}
|
|
2004
2074
|
};
|
|
2005
2075
|
},
|
|
2006
2076
|
renderCall(args, theme) {
|
|
2007
2077
|
const agent = theme.fg("toolTitle", theme.bold("avenor_spawn "));
|
|
2008
|
-
const agentName = theme.fg("accent", args.agent);
|
|
2078
|
+
const agentName = theme.fg("accent", args.agent ?? args.roster_entry ?? args.model ?? "roster");
|
|
2009
2079
|
const label = args.label ? theme.fg("dim", ` "${args.label}"`) : "";
|
|
2010
2080
|
const wait = args.wait === false ? theme.fg("warning", " [async]") : "";
|
|
2011
2081
|
return new Text(agent + agentName + label + wait, 0, 0);
|
|
@@ -2094,10 +2164,6 @@ function createExtension(deps = defaultDeps) {
|
|
|
2094
2164
|
agent: tracked?.agent ?? "unknown",
|
|
2095
2165
|
status: result.status ?? "done"
|
|
2096
2166
|
}));
|
|
2097
|
-
clearRunPollingFailures({
|
|
2098
|
-
runId: params.run_id,
|
|
2099
|
-
supervisorId: tracked?.supervisorId ?? params.supervisor_id
|
|
2100
|
-
});
|
|
2101
2167
|
trackedRuns.delete(params.run_id);
|
|
2102
2168
|
} else if (tracked && result.status === "waiting") tracked.permissionNotified = true;
|
|
2103
2169
|
return {
|
|
@@ -2321,6 +2387,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
2321
2387
|
description: "Show status of all avenor runs",
|
|
2322
2388
|
handler: async (_args, ctx) => {
|
|
2323
2389
|
const entries = await pollRuns();
|
|
2390
|
+
updateWidget(entries);
|
|
2324
2391
|
if (entries.length === 0) {
|
|
2325
2392
|
ctx.ui.notify("No active avenor runs", "info");
|
|
2326
2393
|
return;
|
|
@@ -2328,12 +2395,28 @@ function createExtension(deps = defaultDeps) {
|
|
|
2328
2395
|
ctx.ui.setWidget("avenor-status-detail", entries.map((entry) => formatRunLine(entry)));
|
|
2329
2396
|
}
|
|
2330
2397
|
});
|
|
2398
|
+
pi.registerCommand("avenor-errors", {
|
|
2399
|
+
description: "Show and clear recent Avenor polling errors",
|
|
2400
|
+
handler: async (_args, ctx) => {
|
|
2401
|
+
if (pollingErrors.length === 0) {
|
|
2402
|
+
ctx.ui.setWidget("avenor-errors", void 0);
|
|
2403
|
+
ctx.ui.notify("No recorded avenor polling errors", "info");
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
const errorLines = pollingErrors.map((error) => {
|
|
2407
|
+
const run = error.runId ? ` [${error.runId}]` : "";
|
|
2408
|
+
return `${new Date(error.timestamp).toLocaleTimeString()} ${error.message}${run}: ${error.error}`;
|
|
2409
|
+
});
|
|
2410
|
+
ctx.ui.setWidget("avenor-errors", errorLines);
|
|
2411
|
+
clearPollingErrors();
|
|
2412
|
+
}
|
|
2413
|
+
});
|
|
2331
2414
|
pi.registerCommand("avenor-watch", {
|
|
2332
2415
|
description: "Open a live run inspector by run ID, label, or agent name",
|
|
2333
2416
|
getArgumentCompletions: (prefix) => {
|
|
2334
|
-
const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.agent
|
|
2417
|
+
const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.agent?.startsWith(prefix) || run.runId.startsWith(prefix)).map((run) => ({
|
|
2335
2418
|
value: run.runId,
|
|
2336
|
-
label: `${sanitizeText(run.label)} (${sanitizeText(run.agent)}, ${sanitizeText(run.runId).slice(0, 8)})`
|
|
2419
|
+
label: `${sanitizeText(run.label)} (${sanitizeText(run.agent ?? "roster")}, ${sanitizeText(run.runId).slice(0, 8)})`
|
|
2337
2420
|
}));
|
|
2338
2421
|
return items.length > 0 ? items : null;
|
|
2339
2422
|
},
|
|
@@ -2355,9 +2438,9 @@ function createExtension(deps = defaultDeps) {
|
|
|
2355
2438
|
pi.registerCommand("avenor-cancel", {
|
|
2356
2439
|
description: "Cancel a running avenor sub-agent",
|
|
2357
2440
|
getArgumentCompletions: (prefix) => {
|
|
2358
|
-
const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.agent
|
|
2441
|
+
const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.agent?.startsWith(prefix) || run.runId.startsWith(prefix)).map((run) => ({
|
|
2359
2442
|
value: run.runId,
|
|
2360
|
-
label: `${sanitizeText(run.label)} (${sanitizeText(run.agent)}, ${sanitizeText(run.runId).slice(0, 8)})`
|
|
2443
|
+
label: `${sanitizeText(run.label)} (${sanitizeText(run.agent ?? "roster")}, ${sanitizeText(run.runId).slice(0, 8)})`
|
|
2361
2444
|
}));
|
|
2362
2445
|
return items.length > 0 ? items : null;
|
|
2363
2446
|
},
|
|
@@ -2392,4 +2475,4 @@ function createExtension(deps = defaultDeps) {
|
|
|
2392
2475
|
}
|
|
2393
2476
|
var src_default = createExtension();
|
|
2394
2477
|
//#endregion
|
|
2395
|
-
export { CHANNEL_POLL_COMPLETED, CHANNEL_RUN_TERMINAL, buildCompletionText, buildInspectPayload, compactWhitespace, createExtension, createPollCompletedPayload, createRunTerminalPayload, src_default as default, emitAvenorEvent, isTerminalStatus, onAvenorEvent, renderStatusLines, statusSupervisorId };
|
|
2478
|
+
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.0",
|
|
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.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@earendil-works/pi-coding-agent": ">=0.74.0",
|