@dougbots/avenor-pi 0.16.1 → 0.18.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 +57 -2
- package/dist/index.d.ts +41 -4
- package/dist/index.js +441 -122
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -14,6 +14,10 @@ which avenor
|
|
|
14
14
|
export AVENOR_BIN=~/.botfiles/bin/avenor
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
You also need Pi 1.4.0 or newer (`pi --version`). Busy-session delivery depends on
|
|
18
|
+
the `agent_before_settle` hook, so a notice raised while you were busy needs that
|
|
19
|
+
version to reach you. A missing notification is not a lost run.
|
|
20
|
+
|
|
17
21
|
## Installation
|
|
18
22
|
|
|
19
23
|
### From npm (once published)
|
|
@@ -144,8 +148,9 @@ Each roster entry must contain `backend` and at least one of `agent` or `model`.
|
|
|
144
148
|
Beyond the tools, the extension integrates with Pi's TUI and event system:
|
|
145
149
|
|
|
146
150
|
- **Status widget** — persistent widget showing all active runs with status, phase, and permission state
|
|
147
|
-
- **Footer status** — active runs
|
|
148
|
-
- **
|
|
151
|
+
- **Footer status** — the `avenor` status key carries active runs, the live agent-tree total, the unread notice count, and the current poll-failure count (see [Completion notices](#completion-notices))
|
|
152
|
+
- **Completion notices** — one notice per finished run, delivered immediately when your session is idle and batched into a single continuation when it was busy
|
|
153
|
+
- **Polling diagnostics** — bounded polling errors are emitted on `avenor:poll:error` for companion extensions; a completion-notice delivery that exhausts its retries is emitted separately on `avenor:delivery:error`
|
|
149
154
|
- **Live progress** — blocking `avenor_spawn` calls stream progress updates via `onUpdate`
|
|
150
155
|
- **Context enrichment** — active sub-agents are automatically surfaced in the system prompt via `before_agent_start`
|
|
151
156
|
- **Custom rendering** — The Pi extension renders all Avenor tool calls and results as themed, bounded summaries. Results have collapsed and expanded views.
|
|
@@ -155,6 +160,56 @@ Beyond the tools, the extension integrates with Pi's TUI and event system:
|
|
|
155
160
|
- `/avenor-watch <run_id>` — open a live event feed overlay
|
|
156
161
|
- `/avenor-cancel <run_id>` — cancel a running sub-agent
|
|
157
162
|
|
|
163
|
+
### Completion notices
|
|
164
|
+
|
|
165
|
+
A fire-and-forget spawn (`wait: false`) promises one thing: you hear about the
|
|
166
|
+
run when it ends. Each finished run produces one notice — label, terminal
|
|
167
|
+
status, bounded output preview — and where it lands depends on your session at
|
|
168
|
+
that moment.
|
|
169
|
+
|
|
170
|
+
- **Idle.** The notice is sent as a user message and starts its own turn. It
|
|
171
|
+
counts as unread until Pi confirms the message landed, and while compaction is
|
|
172
|
+
running the send waits for it to finish. A send that is accepted but never
|
|
173
|
+
confirmed is sent at most three times; once that budget is spent the notice
|
|
174
|
+
stays unread for the next settlement and the failure is emitted on
|
|
175
|
+
`avenor:delivery:error`.
|
|
176
|
+
- **Busy.** The notice is queued and the footer shows `unread:N`. The next
|
|
177
|
+
settlement that completes normally appends each queued notice as an
|
|
178
|
+
`avenor-completion` custom message under a single continuation, so three runs
|
|
179
|
+
finishing while you work mean three messages and one wake-up. A settlement
|
|
180
|
+
that ends in abort or error delivers nothing; the notices wait.
|
|
181
|
+
|
|
182
|
+
Every terminal status queues a notice — `done`, `failed`, `timeout`, `killed`.
|
|
183
|
+
|
|
184
|
+
`avenor_result` and a blocking `avenor_spawn` (`wait: true`) own delivery while
|
|
185
|
+
they run; `avenor_result` takes the label you spawned with, so you need not keep
|
|
186
|
+
the id. A `ready` result consumes the run: the queued notice is dropped,
|
|
187
|
+
`unread:N` falls by one, and nothing arrives for it later. A result that is not
|
|
188
|
+
`ready` — interrupted by a sibling's stopping point, timed out, or reporting a
|
|
189
|
+
pending permission — consumes nothing, and its notice arrives on the normal
|
|
190
|
+
schedule. A blocking spawn that reaches completion returns the result inline and
|
|
191
|
+
removes the notice; the footer can show a transient `unread:N` for that run while
|
|
192
|
+
the wait is in flight.
|
|
193
|
+
|
|
194
|
+
#### Footer status
|
|
195
|
+
|
|
196
|
+
One string under the status key `avenor`, in this order:
|
|
197
|
+
|
|
198
|
+
| Token | Meaning |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `🟢 label — running [phase] 🔒 +2` | one per non-terminal run: status emoji, label, status, phase label, `🔒` for a pending permission, `+N` for that run's active descendants |
|
|
201
|
+
| `total:N` | direct active runs plus discovered active descendants; present only when active descendants exist |
|
|
202
|
+
| `unread:N` | notices not yet delivered: queued for a settlement, or in flight for an idle session |
|
|
203
|
+
| `errors:N` | recent poll failures; cleared by a cycle that adds no new error |
|
|
204
|
+
|
|
205
|
+
Missing `unread:N` or `errors:N` means zero. Without `total:N`, count the active run lines. `unread:N` counts notices,
|
|
206
|
+
not runs, and can stay set after the last run exits — an otherwise idle session
|
|
207
|
+
can still advertise `unread:1` until you consume or receive that notice.
|
|
208
|
+
`total:N` tracks live runs and disappears with them. `errors:N` outlives them
|
|
209
|
+
too: it clears when a later poll cycle adds no new error, so it counts recent
|
|
210
|
+
failures rather than a consecutive streak. The bounded history behind
|
|
211
|
+
`/avenor-errors` is longer than `errors:N`; running that command clears both.
|
|
212
|
+
|
|
158
213
|
### Tool-result channels
|
|
159
214
|
|
|
160
215
|
Pi returns JSON model content in `content[0].text` and structured `details` for
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InspectResult, MessageProvider, StatusResult, Supervisor, answerPermissionTool, attachOwner, brokerReceiveTool, dial, eventsTool, getSupervisorClient, inspectTool, killTool, messageTool, observeRun, resultTool, shutdownTool, spawnTool, statusTool, workflowCancelTool, workflowCompleteTool, workflowControllerStatusTool, workflowEventsTool, workflowGateTool, workflowInspectTool, workflowReadyTool, workflowStatusTool, workflowWaitTool } from "@dougbots/avenor-core";
|
|
1
|
+
import { InspectResult, MessageProvider, RunObserver, StatusResult, Supervisor, answerPermissionTool, attachOwner, brokerReceiveTool, dial, eventsTool, getSupervisorClient, inspectTool, killTool, messageTool, observeRun, resultTool, shutdownTool, spawnTool, statusTool, workflowCancelTool, workflowCompleteTool, workflowControllerStatusTool, workflowEventsTool, workflowGateTool, workflowInspectTool, workflowReadyTool, workflowStatusTool, workflowWaitTool } from "@dougbots/avenor-core";
|
|
2
2
|
import { EventBus, ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
|
|
4
4
|
//#region src/subagent-broker.d.ts
|
|
@@ -37,7 +37,11 @@ declare const CHANNEL_POLL_COMPLETED: "avenor:poll:completed";
|
|
|
37
37
|
declare const CHANNEL_RUN_TERMINAL: "avenor:run:terminal";
|
|
38
38
|
/** Emitted when a best-effort polling operation fails. */
|
|
39
39
|
declare const CHANNEL_POLL_ERROR: "avenor:poll:error";
|
|
40
|
+
/** Emitted when a completion-notice delivery fails permanently. */
|
|
41
|
+
declare const CHANNEL_DELIVERY_ERROR: "avenor:delivery:error";
|
|
40
42
|
type PollErrorSource = 'singleton-list' | 'run-status' | 'spawn-status';
|
|
43
|
+
/** Delivery paths whose retries can be exhausted. */
|
|
44
|
+
type DeliveryErrorSource = 'idle-send';
|
|
41
45
|
interface PollErrorPayload {
|
|
42
46
|
/** Polling operation that failed. */
|
|
43
47
|
readonly source: PollErrorSource;
|
|
@@ -60,6 +64,31 @@ interface PollErrorPayloadInput {
|
|
|
60
64
|
readonly count: number;
|
|
61
65
|
readonly timestamp: number;
|
|
62
66
|
}
|
|
67
|
+
interface DeliveryErrorPayload {
|
|
68
|
+
/** Delivery path that failed. */
|
|
69
|
+
readonly source: DeliveryErrorSource;
|
|
70
|
+
/** Run whose completion notice is stuck unread. */
|
|
71
|
+
readonly runId: string;
|
|
72
|
+
/** Label of the run, as shown in completion notices. */
|
|
73
|
+
readonly label: string;
|
|
74
|
+
/** Human-readable failure description. */
|
|
75
|
+
readonly message: string;
|
|
76
|
+
/** Bounded, sanitized error text suitable for debug consumers. */
|
|
77
|
+
readonly error: string;
|
|
78
|
+
/** Delivery attempts made for this notice. */
|
|
79
|
+
readonly attempts: number;
|
|
80
|
+
/** Wall-clock timestamp in epoch milliseconds. */
|
|
81
|
+
readonly timestamp: number;
|
|
82
|
+
}
|
|
83
|
+
interface DeliveryErrorPayloadInput {
|
|
84
|
+
readonly source: DeliveryErrorSource;
|
|
85
|
+
readonly runId: string;
|
|
86
|
+
readonly label: string;
|
|
87
|
+
readonly message: string;
|
|
88
|
+
readonly error: unknown;
|
|
89
|
+
readonly attempts: number;
|
|
90
|
+
readonly timestamp: number;
|
|
91
|
+
}
|
|
63
92
|
type AvenorRunStatus = Readonly<Omit<RunStatusEntry, 'supervisorId' | 'pid' | 'permissionDescription'> & {
|
|
64
93
|
supervisorKey: string;
|
|
65
94
|
}>;
|
|
@@ -79,7 +108,7 @@ interface RunTerminalPayload {
|
|
|
79
108
|
readonly label: string;
|
|
80
109
|
readonly agent: string;
|
|
81
110
|
readonly status: string;
|
|
82
|
-
/** Number of active descendants
|
|
111
|
+
/** Number of active descendants last seen for the run, if available. */
|
|
83
112
|
readonly nestedCount?: number;
|
|
84
113
|
readonly backend?: string;
|
|
85
114
|
}
|
|
@@ -98,6 +127,7 @@ interface AvenorEventMap {
|
|
|
98
127
|
[CHANNEL_POLL_COMPLETED]: PollCompletedPayload;
|
|
99
128
|
[CHANNEL_RUN_TERMINAL]: RunTerminalPayload;
|
|
100
129
|
[CHANNEL_POLL_ERROR]: PollErrorPayload;
|
|
130
|
+
[CHANNEL_DELIVERY_ERROR]: DeliveryErrorPayload;
|
|
101
131
|
}
|
|
102
132
|
type AvenorEventChannel = keyof AvenorEventMap;
|
|
103
133
|
type AvenorEventBus = Pick<EventBus, 'emit' | 'on'>;
|
|
@@ -113,6 +143,8 @@ declare function onAvenorEvent<K extends AvenorEventChannel>(bus: Pick<EventBus,
|
|
|
113
143
|
declare function createPollCompletedPayload(entries: readonly RunStatusEntry[], timestamp: number, generation: number): PollCompletedPayload;
|
|
114
144
|
/** Copy and freeze a terminal payload before publishing it. */
|
|
115
145
|
declare function createRunTerminalPayload(payload: RunTerminalPayloadInput): RunTerminalPayload;
|
|
146
|
+
/** Copy, bound, sanitize, and freeze a delivery failure before publishing it. */
|
|
147
|
+
declare function createDeliveryErrorPayload(payload: DeliveryErrorPayloadInput): DeliveryErrorPayload;
|
|
116
148
|
/** Copy, bound, sanitize, and freeze a polling error before publishing it. */
|
|
117
149
|
declare function createPollErrorPayload(payload: PollErrorPayloadInput): PollErrorPayload;
|
|
118
150
|
//#endregion
|
|
@@ -141,7 +173,9 @@ interface ExtensionDeps {
|
|
|
141
173
|
hostProviderFor?: (supervisorId: string | undefined) => Promise<MessageProvider>;
|
|
142
174
|
/** Dial a supervisor for the default host provider; tests inject a fake client. */
|
|
143
175
|
getSupervisorClient?: typeof getSupervisorClient;
|
|
144
|
-
|
|
176
|
+
/** A watch transport may decline to observe (no runtime id yet), so a test
|
|
177
|
+
* double that only asserts the call can answer null. */
|
|
178
|
+
observeRun: (client: Parameters<typeof observeRun>[0], options?: Parameters<typeof observeRun>[1]) => RunObserver | null;
|
|
145
179
|
dial: typeof dial;
|
|
146
180
|
attachOwner: typeof attachOwner;
|
|
147
181
|
Supervisor: typeof Supervisor;
|
|
@@ -167,10 +201,13 @@ declare function buildInspectPayload(result: InspectResult): Record<string, unkn
|
|
|
167
201
|
interface ExtensionOptions {
|
|
168
202
|
/** Polling cadence; injectable so tests can drive deterministic ticks. */
|
|
169
203
|
pollIntervalMs?: number;
|
|
204
|
+
/** How long an unconfirmed idle completion send waits before re-arming;
|
|
205
|
+
* injectable so tests can exercise the timeout without fixed sleeps. */
|
|
206
|
+
idleSendTimeoutMs?: number;
|
|
170
207
|
/** Overrides broker-credential autodetection for sub-agent broker mode (tests). */
|
|
171
208
|
subAgentEnv?: SubAgentEnv | null;
|
|
172
209
|
}
|
|
173
210
|
declare function createExtension(deps?: ExtensionDeps, options?: ExtensionOptions): (pi: ExtensionAPI) => Promise<void>;
|
|
174
211
|
declare const _default: (pi: ExtensionAPI) => Promise<void>;
|
|
175
212
|
//#endregion
|
|
176
|
-
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 };
|
|
213
|
+
export { type AvenorEventBus, type AvenorEventChannel, type AvenorEventMap, type AvenorRunStatus, CHANNEL_DELIVERY_ERROR, CHANNEL_POLL_COMPLETED, CHANNEL_POLL_ERROR, CHANNEL_RUN_TERMINAL, type DeliveryErrorPayload, type DeliveryErrorPayloadInput, type DeliveryErrorSource, ExtensionDeps, ExtensionOptions, type PollCompletedPayload, type PollErrorPayload, type PollErrorPayloadInput, type PollErrorSource, type RunTerminalPayload, type RunTerminalPayloadInput, buildCompletionText, buildInspectPayload, compactWhitespace, createDeliveryErrorPayload, createExtension, createPollCompletedPayload, createPollErrorPayload, createRunTerminalPayload, _default as default, emitAvenorEvent, isTerminalStatus, onAvenorEvent, renderStatusLines, spawnIdentityMetadata, statusSupervisorId };
|
package/dist/index.js
CHANGED
|
@@ -106,6 +106,14 @@ async function postBroker(env, path, body, fetchImpl = fetch, signal) {
|
|
|
106
106
|
if (!res.ok) throw await codedError(path, res);
|
|
107
107
|
return res.json();
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Publish the harness's own user-defined session name for this run, or clear it
|
|
111
|
+
* with an empty name. The broker stores it apart from any model-set rename and
|
|
112
|
+
* prefers it, so peers see the name the human gave the session.
|
|
113
|
+
*/
|
|
114
|
+
async function publishHarnessName(env, name, fetchImpl = fetch) {
|
|
115
|
+
await postBroker(env, "/session_name", { name: name ?? "" }, fetchImpl);
|
|
116
|
+
}
|
|
109
117
|
/** Authenticated GET; broker credentials travel in headers, never the URL. */
|
|
110
118
|
async function getBroker(env, path, fetchImpl = fetch, signal) {
|
|
111
119
|
const res = await fetchImpl(`${env.brokerUrl}${path}`, {
|
|
@@ -536,7 +544,13 @@ function createBrokerChildBroker(env, fetchImpl = fetch) {
|
|
|
536
544
|
}
|
|
537
545
|
},
|
|
538
546
|
async peers() {
|
|
539
|
-
|
|
547
|
+
const body = await getBroker(env, "/sessions", fetchImpl);
|
|
548
|
+
const peers = peerRecords(body);
|
|
549
|
+
const detail = body !== null && typeof body === "object" ? body.peers_error : void 0;
|
|
550
|
+
return typeof detail === "string" && detail ? {
|
|
551
|
+
peers,
|
|
552
|
+
detail
|
|
553
|
+
} : { peers };
|
|
540
554
|
},
|
|
541
555
|
async rename(req) {
|
|
542
556
|
try {
|
|
@@ -1136,21 +1150,6 @@ function formatRunLine(entry) {
|
|
|
1136
1150
|
const phase = entry.phaseLabel ? ` (${entry.phaseLabel})` : "";
|
|
1137
1151
|
return `${emoji} ${entry.label} (${entry.agent}) — ${entry.status}${phase}${perm}`;
|
|
1138
1152
|
}
|
|
1139
|
-
/**
|
|
1140
|
-
* decideCompletion determines whether to skip or send the completion
|
|
1141
|
-
* steering message for a tracked run that has reached terminal status.
|
|
1142
|
-
*
|
|
1143
|
-
* The message is delivered on the same terminal tick — never deferred to a
|
|
1144
|
-
* later cycle — so a run that is the last live one still gets its completion.
|
|
1145
|
-
* `blocking` suppresses it while avenor_result is awaiting (it will deliver
|
|
1146
|
-
* the result itself and set `consumed`), and `consumed` suppresses it once
|
|
1147
|
-
* avenor_result has already returned the result successfully.
|
|
1148
|
-
*/
|
|
1149
|
-
function decideCompletion(run) {
|
|
1150
|
-
if (run.blocking) return "skip";
|
|
1151
|
-
if (run.consumed) return "skip";
|
|
1152
|
-
return "send";
|
|
1153
|
-
}
|
|
1154
1153
|
//#endregion
|
|
1155
1154
|
//#region src/nested-count.ts
|
|
1156
1155
|
const NESTED_DIAL_TIMEOUT_MS = 2e3;
|
|
@@ -1303,6 +1302,8 @@ const CHANNEL_POLL_COMPLETED = "avenor:poll:completed";
|
|
|
1303
1302
|
const CHANNEL_RUN_TERMINAL = "avenor:run:terminal";
|
|
1304
1303
|
/** Emitted when a best-effort polling operation fails. */
|
|
1305
1304
|
const CHANNEL_POLL_ERROR = "avenor:poll:error";
|
|
1305
|
+
/** Emitted when a completion-notice delivery fails permanently. */
|
|
1306
|
+
const CHANNEL_DELIVERY_ERROR = "avenor:delivery:error";
|
|
1306
1307
|
/** Maximum sanitized error text exposed to companion extensions. */
|
|
1307
1308
|
const POLL_ERROR_TEXT_CHARS = 600;
|
|
1308
1309
|
function sanitizeAndBoundText(value) {
|
|
@@ -1360,6 +1361,18 @@ function createRunTerminalPayload(payload) {
|
|
|
1360
1361
|
...payload.backend !== void 0 && { backend: payload.backend }
|
|
1361
1362
|
});
|
|
1362
1363
|
}
|
|
1364
|
+
/** Copy, bound, sanitize, and freeze a delivery failure before publishing it. */
|
|
1365
|
+
function createDeliveryErrorPayload(payload) {
|
|
1366
|
+
return Object.freeze({
|
|
1367
|
+
source: payload.source,
|
|
1368
|
+
runId: payload.runId,
|
|
1369
|
+
label: payload.label,
|
|
1370
|
+
message: sanitizeAndBoundText(payload.message),
|
|
1371
|
+
error: sanitizeAndBoundText(payload.error),
|
|
1372
|
+
attempts: payload.attempts,
|
|
1373
|
+
timestamp: payload.timestamp
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1363
1376
|
/** Copy, bound, sanitize, and freeze a polling error before publishing it. */
|
|
1364
1377
|
function createPollErrorPayload(payload) {
|
|
1365
1378
|
return Object.freeze({
|
|
@@ -1920,6 +1933,8 @@ function asStatusResult(raw) {
|
|
|
1920
1933
|
return raw;
|
|
1921
1934
|
}
|
|
1922
1935
|
const POLL_INTERVAL_MS = 3e3;
|
|
1936
|
+
const IDLE_SEND_TIMEOUT_MS = 1e4;
|
|
1937
|
+
const MAX_IDLE_SEND_ATTEMPTS = 3;
|
|
1923
1938
|
const INSPECT_LIMIT = 128;
|
|
1924
1939
|
const COMPLETION_OUTPUT_CHARS = 600;
|
|
1925
1940
|
const INSPECT_JSON_STRING_CHARS = 600;
|
|
@@ -1972,6 +1987,18 @@ function spawnIdentityMetadata(params, status) {
|
|
|
1972
1987
|
function sleep(ms) {
|
|
1973
1988
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1974
1989
|
}
|
|
1990
|
+
/** The text of a tool result's first text part; image parts carry none. */
|
|
1991
|
+
function firstTextPart(content) {
|
|
1992
|
+
return content?.find((part) => part.type === "text")?.text ?? "";
|
|
1993
|
+
}
|
|
1994
|
+
/** The status a tool result records; a stored transcript record may carry no details. */
|
|
1995
|
+
function resultStatus(details) {
|
|
1996
|
+
return details && typeof details === "object" && "status" in details ? String(details.status ?? "") : "";
|
|
1997
|
+
}
|
|
1998
|
+
/** Close an observer a concurrent watch assigned, ignoring a close failure. */
|
|
1999
|
+
async function closeObserverQuietly(observer) {
|
|
2000
|
+
await observer?.close().catch(() => {});
|
|
2001
|
+
}
|
|
1975
2002
|
function normalizeMultilinePreview(text, limit = COMPLETION_OUTPUT_CHARS) {
|
|
1976
2003
|
if (!text) return void 0;
|
|
1977
2004
|
const clean = sanitizeText(text).trim();
|
|
@@ -2092,6 +2119,7 @@ function trackedRunKey(supervisorId, runId) {
|
|
|
2092
2119
|
function createExtension(deps = defaultDeps, options = {}) {
|
|
2093
2120
|
return async function(pi) {
|
|
2094
2121
|
const pollIntervalMs = options.pollIntervalMs ?? POLL_INTERVAL_MS;
|
|
2122
|
+
const idleSendTimeoutMs = options.idleSendTimeoutMs ?? IDLE_SEND_TIMEOUT_MS;
|
|
2095
2123
|
let telemetryGeneration = 0;
|
|
2096
2124
|
const trackedRuns = /* @__PURE__ */ new Map();
|
|
2097
2125
|
let pollingActive = false;
|
|
@@ -2101,6 +2129,8 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2101
2129
|
let pollingErrorCount = 0;
|
|
2102
2130
|
const pollingErrors = [];
|
|
2103
2131
|
let lastStatusEntries = [];
|
|
2132
|
+
let sessionGeneration = 0;
|
|
2133
|
+
let compacting = false;
|
|
2104
2134
|
let lastTurnSocketClosed = false;
|
|
2105
2135
|
const pendingWaits = /* @__PURE__ */ new Set();
|
|
2106
2136
|
/** The agent-facing prose clause for a stopping point. A synthetic status
|
|
@@ -2174,12 +2204,16 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2174
2204
|
};
|
|
2175
2205
|
}
|
|
2176
2206
|
/** Build the early return for a wait interrupted by a sibling stopping
|
|
2177
|
-
* point. When the awaited run itself has since reached a stopping point
|
|
2178
|
-
*
|
|
2179
|
-
*
|
|
2207
|
+
* point. When the awaited run itself has since reached a stopping point,
|
|
2208
|
+
* deliver its real state instead of an interruption notice. The probe and
|
|
2209
|
+
* redelivery use the caller's original run reference, matching the non-
|
|
2210
|
+
* interrupted path: a label may resolve canonically to a newer run than
|
|
2211
|
+
* the one this call first bound to, and the reconciliation below handles
|
|
2212
|
+
* that divergence the same way. A caller that omitted supervisor_id still
|
|
2213
|
+
* probes through the bound run's supervisor. */
|
|
2180
2214
|
async function resultAfterInterrupt(params, stop, tracked) {
|
|
2181
|
-
const runId =
|
|
2182
|
-
const supervisorId = tracked?.supervisorId
|
|
2215
|
+
const runId = params.run_id;
|
|
2216
|
+
const supervisorId = params.supervisor_id ?? tracked?.supervisorId;
|
|
2183
2217
|
let current;
|
|
2184
2218
|
try {
|
|
2185
2219
|
current = asStatusResult(await deps.statusTool({
|
|
@@ -2229,6 +2263,35 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2229
2263
|
subAgentPollTimer = null;
|
|
2230
2264
|
}
|
|
2231
2265
|
}
|
|
2266
|
+
let publishedSessionName = null;
|
|
2267
|
+
let publishChain = Promise.resolve();
|
|
2268
|
+
let refusedSessionName = null;
|
|
2269
|
+
function publishSessionName(ctx) {
|
|
2270
|
+
if (!subAgentEnv) return publishChain;
|
|
2271
|
+
const env = subAgentEnv;
|
|
2272
|
+
const manager = ctx.sessionManager;
|
|
2273
|
+
if (!manager || typeof manager.getSessionName !== "function") return publishChain;
|
|
2274
|
+
const name = manager.getSessionName();
|
|
2275
|
+
if (publishedSessionName && publishedSessionName.name === name) return publishChain;
|
|
2276
|
+
if (refusedSessionName === name) return publishChain;
|
|
2277
|
+
const attempt = { name };
|
|
2278
|
+
publishedSessionName = attempt;
|
|
2279
|
+
publishChain = publishChain.then(async () => {
|
|
2280
|
+
try {
|
|
2281
|
+
await publishHarnessName(env, name);
|
|
2282
|
+
refusedSessionName = null;
|
|
2283
|
+
} catch (error) {
|
|
2284
|
+
if (error instanceof BrokerHttpError && error.status === 400 && error.code === "invalid-name") {
|
|
2285
|
+
refusedSessionName = name ?? null;
|
|
2286
|
+
console.error("[avenor] session_name_set refused:", error.message);
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
if (publishedSessionName === attempt) publishedSessionName = null;
|
|
2290
|
+
console.error("[avenor] session_name_set failed:", error instanceof Error ? error.message : error);
|
|
2291
|
+
}
|
|
2292
|
+
});
|
|
2293
|
+
return publishChain;
|
|
2294
|
+
}
|
|
2232
2295
|
async function startSubAgentPoll() {
|
|
2233
2296
|
if (!subAgentEnv) return;
|
|
2234
2297
|
stopSubAgentPoll();
|
|
@@ -2313,6 +2376,40 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2313
2376
|
if (pollingErrors.length > MAX_POLL_ERRORS) pollingErrors.shift();
|
|
2314
2377
|
emitAvenorEvent(pi.events, CHANNEL_POLL_ERROR, payload);
|
|
2315
2378
|
}
|
|
2379
|
+
/** A delivery that exhausted its retries is not a polling failure: emit
|
|
2380
|
+
* it on its own channel so `avenor:poll:error` consumers keep a pure
|
|
2381
|
+
* polling contract. The unread notice is the persistent user-facing
|
|
2382
|
+
* surface; this event is the companion-extension signal. */
|
|
2383
|
+
function recordDeliveryError(run, attempts) {
|
|
2384
|
+
emitAvenorEvent(pi.events, CHANNEL_DELIVERY_ERROR, createDeliveryErrorPayload({
|
|
2385
|
+
source: "idle-send",
|
|
2386
|
+
runId: run.runId,
|
|
2387
|
+
label: run.label,
|
|
2388
|
+
message: "idle completion send was never confirmed",
|
|
2389
|
+
error: /* @__PURE__ */ new Error(`no message_end after ${attempts} attempts`),
|
|
2390
|
+
attempts,
|
|
2391
|
+
timestamp: Date.now()
|
|
2392
|
+
}));
|
|
2393
|
+
console.error(`[avenor] idle completion send for run "${run.label}" was never confirmed after ${attempts} attempts; the notice stays unread`);
|
|
2394
|
+
}
|
|
2395
|
+
/** Latch the per-notice retry budget as spent and report the exhausted
|
|
2396
|
+
* delivery. Idempotent, so the timeout re-arm and the compaction
|
|
2397
|
+
* hand-back cannot double-report the same notice. */
|
|
2398
|
+
function capIdleSend(run) {
|
|
2399
|
+
if (!run.idleSend || run.idleSend.capped) return;
|
|
2400
|
+
run.idleSend.capped = true;
|
|
2401
|
+
recordDeliveryError(run, run.idleSend.attempts);
|
|
2402
|
+
}
|
|
2403
|
+
/** Hand an unconfirmed idle send back to the poll: clear the in-flight
|
|
2404
|
+
* mark and latch the budget when it is spent. Shared by the poll-tick
|
|
2405
|
+
* timeout and the compaction hand-back, which must stay in lockstep. */
|
|
2406
|
+
function rearmIdleSend(run) {
|
|
2407
|
+
const idle = run.idleSend;
|
|
2408
|
+
if (!idle) return;
|
|
2409
|
+
idle.inFlight = false;
|
|
2410
|
+
idle.sentAt = void 0;
|
|
2411
|
+
if (idle.attempts >= MAX_IDLE_SEND_ATTEMPTS) capIdleSend(run);
|
|
2412
|
+
}
|
|
2316
2413
|
function clearPollingErrors() {
|
|
2317
2414
|
pollingErrorCount = 0;
|
|
2318
2415
|
pollingErrors.length = 0;
|
|
@@ -2321,38 +2418,75 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2321
2418
|
function upsertTrackedRun(run, status, blocking = false) {
|
|
2322
2419
|
const key = trackedRunKey(run.supervisorId, run.runId);
|
|
2323
2420
|
const current = trackedRuns.get(key);
|
|
2324
|
-
|
|
2421
|
+
const tracked = current ?? {
|
|
2325
2422
|
runId: run.runId,
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2423
|
+
startTime: Date.now(),
|
|
2424
|
+
agent: "unknown",
|
|
2425
|
+
label: run.label ?? run.runId
|
|
2426
|
+
};
|
|
2427
|
+
tracked.runId = run.runId;
|
|
2428
|
+
tracked.agent = run.agent ?? status?.agent ?? current?.agent ?? tracked.agent;
|
|
2429
|
+
tracked.label = run.label ?? status?.label ?? current?.label ?? tracked.label;
|
|
2430
|
+
tracked.supervisorId = run.supervisorId ?? current?.supervisorId;
|
|
2431
|
+
tracked.runtimeId = run.runtimeId ?? status?.runtime_id ?? current?.runtimeId;
|
|
2432
|
+
if (!current) tracked.startTime = Date.now();
|
|
2433
|
+
tracked.lastStatus = status ?? current?.lastStatus;
|
|
2434
|
+
tracked.blocking = current?.blocking ?? blocking;
|
|
2435
|
+
trackedRuns.set(key, tracked);
|
|
2436
|
+
}
|
|
2437
|
+
function findTrackedByLabel(label, supervisorId) {
|
|
2438
|
+
let match;
|
|
2439
|
+
for (const run of trackedRuns.values()) {
|
|
2440
|
+
if (supervisorId !== void 0 && run.supervisorId !== supervisorId) continue;
|
|
2441
|
+
if (run.label !== label) continue;
|
|
2442
|
+
if (match) return void 0;
|
|
2443
|
+
match = run;
|
|
2444
|
+
}
|
|
2445
|
+
return match;
|
|
2336
2446
|
}
|
|
2337
2447
|
/**
|
|
2338
|
-
*
|
|
2339
|
-
* supervisor is supplied, resolve exactly. Without one, prefer the
|
|
2448
|
+
* Resolve a canonical run id in the per-supervisor tracking namespace.
|
|
2449
|
+
* When a supervisor is supplied, resolve exactly. Without one, prefer the
|
|
2340
2450
|
* singleton namespace and only fall back to a run id walk when it is
|
|
2341
2451
|
* unambiguous across supervisors. Duplicate run ids across supervisors
|
|
2342
2452
|
* without an explicit supervisor return undefined so callers never bind
|
|
2343
2453
|
* flags or telemetry to the wrong supervisor's run.
|
|
2344
2454
|
*/
|
|
2345
|
-
function
|
|
2346
|
-
if (supervisorId !== void 0)
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
let match;
|
|
2350
|
-
for (const run of trackedRuns.values()) {
|
|
2351
|
-
if (run.runId !== runId) continue;
|
|
2352
|
-
if (match) return void 0;
|
|
2353
|
-
match = run;
|
|
2455
|
+
function trackedRunsById(runId, supervisorId) {
|
|
2456
|
+
if (supervisorId !== void 0) {
|
|
2457
|
+
const exact = trackedRuns.get(trackedRunKey(supervisorId, runId));
|
|
2458
|
+
return exact ? [exact] : [];
|
|
2354
2459
|
}
|
|
2355
|
-
|
|
2460
|
+
const singleton = trackedRuns.get(trackedRunKey(void 0, runId));
|
|
2461
|
+
if (singleton) return [singleton];
|
|
2462
|
+
const matches = [];
|
|
2463
|
+
for (const run of trackedRuns.values()) if (run.runId === runId) matches.push(run);
|
|
2464
|
+
return matches;
|
|
2465
|
+
}
|
|
2466
|
+
function getTrackedRunById(runId, supervisorId) {
|
|
2467
|
+
const matches = trackedRunsById(runId, supervisorId);
|
|
2468
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Resolve a user-supplied run reference. A canonical identity always wins;
|
|
2472
|
+
* a label is only a fallback, and an ambiguous canonical id never falls
|
|
2473
|
+
* through to a label.
|
|
2474
|
+
*/
|
|
2475
|
+
function getTrackedRun(runId, supervisorId) {
|
|
2476
|
+
const exact = getTrackedRunById(runId, supervisorId);
|
|
2477
|
+
if (exact) return exact;
|
|
2478
|
+
if (trackedRunsById(runId, supervisorId).length > 1) return void 0;
|
|
2479
|
+
return findTrackedByLabel(runId, supervisorId);
|
|
2480
|
+
}
|
|
2481
|
+
/** Resolve the tracked run a successful result belongs to from the
|
|
2482
|
+
* canonical run id the result tool reports, accepting a runtime-id alias
|
|
2483
|
+
* for the label-resolved run. */
|
|
2484
|
+
function reconcileResultRun(tracked, result, supervisorId) {
|
|
2485
|
+
const runId = result.run_id;
|
|
2486
|
+
if (!runId) return tracked;
|
|
2487
|
+
if (tracked && (tracked.runId === runId || tracked.runtimeId === runId)) return tracked;
|
|
2488
|
+
const namespace = tracked?.supervisorId ?? supervisorId;
|
|
2489
|
+
return trackedRuns.get(trackedRunKey(namespace, runId));
|
|
2356
2490
|
}
|
|
2357
2491
|
async function resolveFinalOutput(run, status) {
|
|
2358
2492
|
const preview = finalOutputPreview(status);
|
|
@@ -2368,6 +2502,67 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2368
2502
|
return;
|
|
2369
2503
|
}
|
|
2370
2504
|
}
|
|
2505
|
+
/**
|
|
2506
|
+
* Fire-and-forget delivery. Pi's extension-facing `sendUserMessage` returns
|
|
2507
|
+
* void and the runtime swallows async rejections, so a send is only known to
|
|
2508
|
+
* have landed when the user message comes back through `message_end`. Only a
|
|
2509
|
+
* synchronous throw is observable here.
|
|
2510
|
+
*/
|
|
2511
|
+
function sendUserMessageLogged(text, failureLog) {
|
|
2512
|
+
try {
|
|
2513
|
+
pi.sendUserMessage(text, { deliverAs: "followUp" });
|
|
2514
|
+
} catch (err) {
|
|
2515
|
+
console.error(failureLog, err);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
function clearPendingNotices() {
|
|
2519
|
+
for (const run of trackedRuns.values()) {
|
|
2520
|
+
run.pendingNotice = void 0;
|
|
2521
|
+
run.idleSend = void 0;
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
/**
|
|
2525
|
+
* Emit the single terminal event for a tracked run. Every terminal path
|
|
2526
|
+
* (poll, blocking spawn, avenor_result) goes through here so the event-once
|
|
2527
|
+
* guard and payload shape stay identical. A tracked run emits at most once;
|
|
2528
|
+
* an untracked run emits with the canonical result identity, unguarded.
|
|
2529
|
+
*
|
|
2530
|
+
* @returns whether the event was emitted, so callers can gate one-time
|
|
2531
|
+
* side effects.
|
|
2532
|
+
*/
|
|
2533
|
+
function emitRunTerminal(input) {
|
|
2534
|
+
const { run } = input;
|
|
2535
|
+
if (!run) {
|
|
2536
|
+
if (!input.runId) return false;
|
|
2537
|
+
emitAvenorEvent(pi.events, CHANNEL_RUN_TERMINAL, createRunTerminalPayload({
|
|
2538
|
+
runId: input.runId,
|
|
2539
|
+
supervisorId: input.supervisorId,
|
|
2540
|
+
runtimeId: input.runtimeId,
|
|
2541
|
+
label: input.label,
|
|
2542
|
+
agent: input.agent,
|
|
2543
|
+
status: input.status,
|
|
2544
|
+
nestedCount: input.nestedCount,
|
|
2545
|
+
backend: input.backend
|
|
2546
|
+
}));
|
|
2547
|
+
return true;
|
|
2548
|
+
}
|
|
2549
|
+
if (run.terminalEventEmitted) return false;
|
|
2550
|
+
run.terminalEventEmitted = true;
|
|
2551
|
+
emitAvenorEvent(pi.events, CHANNEL_RUN_TERMINAL, createRunTerminalPayload({
|
|
2552
|
+
runId: run.runId,
|
|
2553
|
+
supervisorId: run.supervisorId,
|
|
2554
|
+
runtimeId: input.runtimeId,
|
|
2555
|
+
label: input.label,
|
|
2556
|
+
agent: input.agent,
|
|
2557
|
+
status: input.status,
|
|
2558
|
+
nestedCount: input.nestedCount,
|
|
2559
|
+
backend: input.backend
|
|
2560
|
+
}));
|
|
2561
|
+
return true;
|
|
2562
|
+
}
|
|
2563
|
+
function sendCompletionNotice(notice) {
|
|
2564
|
+
sendUserMessageLogged(notice.text, "avenor tick: sendUserMessage failed");
|
|
2565
|
+
}
|
|
2371
2566
|
async function pollRuns() {
|
|
2372
2567
|
const errorCountAtPollStart = pollingErrorCount;
|
|
2373
2568
|
const liveMap = /* @__PURE__ */ new Map();
|
|
@@ -2460,7 +2655,11 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2460
2655
|
if (nestedPids.length > 0) {
|
|
2461
2656
|
const nestedCounts = await Promise.all(nestedPids.map((pid) => countNestedRuns(pid, deps.dial)));
|
|
2462
2657
|
let idx = 0;
|
|
2463
|
-
for (const entry of entries) if (entry.backend === "pi" && entry.pid && !isTerminalStatus(entry.status))
|
|
2658
|
+
for (const entry of entries) if (entry.backend === "pi" && entry.pid && !isTerminalStatus(entry.status)) {
|
|
2659
|
+
entry.nestedCount = nestedCounts[idx++] ?? 0;
|
|
2660
|
+
const tracked = trackedRuns.get(trackedRunKey(entry.supervisorId, entry.runId));
|
|
2661
|
+
if (tracked) tracked.nestedCount = entry.nestedCount;
|
|
2662
|
+
}
|
|
2464
2663
|
}
|
|
2465
2664
|
if (pollingErrorCount === errorCountAtPollStart) pollingErrorCount = 0;
|
|
2466
2665
|
return entries;
|
|
@@ -2469,17 +2668,24 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2469
2668
|
lastStatusEntries = entries;
|
|
2470
2669
|
if (!sessionCtx) return;
|
|
2471
2670
|
const lines = renderStatusLines(entries);
|
|
2671
|
+
let unread = 0;
|
|
2672
|
+
for (const run of trackedRuns.values()) if (run.pendingNotice) unread++;
|
|
2472
2673
|
const errorSuffix = pollingErrorCount > 0 ? `errors:${pollingErrorCount}` : "";
|
|
2674
|
+
const unreadSuffix = unread > 0 ? `unread:${unread}` : "";
|
|
2473
2675
|
if (lines.length > 0) {
|
|
2474
2676
|
sessionCtx.ui.setWidget("avenor-status", lines);
|
|
2475
2677
|
const statusStr = lines.map((line) => line.slice(0, 60)).join(" ");
|
|
2476
2678
|
const nestedTotal = entries.filter((e) => !isTerminalStatus(e.status)).reduce((sum, e) => sum + (e.nestedCount ?? 0), 0);
|
|
2477
2679
|
const total = lines.length + nestedTotal;
|
|
2478
|
-
const statusWithTotal = [
|
|
2680
|
+
const statusWithTotal = [
|
|
2681
|
+
nestedTotal > 0 ? `${statusStr} total:${total}` : statusStr,
|
|
2682
|
+
unreadSuffix,
|
|
2683
|
+
errorSuffix
|
|
2684
|
+
].filter(Boolean).join(" ");
|
|
2479
2685
|
sessionCtx.ui.setStatus("avenor", statusWithTotal);
|
|
2480
2686
|
} else {
|
|
2481
2687
|
sessionCtx.ui.setWidget("avenor-status", void 0);
|
|
2482
|
-
sessionCtx.ui.setStatus("avenor", errorSuffix);
|
|
2688
|
+
sessionCtx.ui.setStatus("avenor", [unreadSuffix, errorSuffix].filter(Boolean).join(" "));
|
|
2483
2689
|
}
|
|
2484
2690
|
}
|
|
2485
2691
|
function startPolling() {
|
|
@@ -2500,12 +2706,20 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2500
2706
|
});
|
|
2501
2707
|
if (!pollingActive || generation !== pollingGeneration) return;
|
|
2502
2708
|
updateWidget(entries);
|
|
2709
|
+
let widgetDirty = false;
|
|
2710
|
+
const sessionAtPollStart = sessionGeneration;
|
|
2503
2711
|
emitAvenorEvent(pi.events, CHANNEL_POLL_COMPLETED, createPollCompletedPayload(entries, Date.now(), ++telemetryGeneration));
|
|
2504
2712
|
for (const entry of entries) {
|
|
2505
2713
|
const entryKey = trackedRunKey(entry.supervisorId, entry.runId);
|
|
2506
2714
|
const run = trackedRuns.get(entryKey);
|
|
2507
2715
|
if (!run) continue;
|
|
2508
2716
|
const prevStatus = prevStatuses.get(entryKey);
|
|
2717
|
+
const idle = run.idleSend;
|
|
2718
|
+
if (idle?.inFlight && idle.sentAt !== void 0 && Date.now() - idle.sentAt >= idleSendTimeoutMs) {
|
|
2719
|
+
rearmIdleSend(run);
|
|
2720
|
+
widgetDirty = true;
|
|
2721
|
+
}
|
|
2722
|
+
if (run.idleSend?.inFlight && isTerminalStatus(entry.status)) continue;
|
|
2509
2723
|
const wasStopped = prevStatus?.status !== void 0 && isTerminalStatus(prevStatus.status) || !!prevStatus?.pendingPermission;
|
|
2510
2724
|
if ((entry.pendingPermission || isTerminalStatus(entry.status)) && !wasStopped) interruptPendingWaits({
|
|
2511
2725
|
runId: entry.runId,
|
|
@@ -2513,45 +2727,61 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2513
2727
|
status: isTerminalStatus(entry.status) ? entry.status : "waiting"
|
|
2514
2728
|
}, entryKey);
|
|
2515
2729
|
if (isTerminalStatus(entry.status)) {
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
if (
|
|
2521
|
-
|
|
2522
|
-
try {
|
|
2523
|
-
pi.sendUserMessage(buildCompletionText({
|
|
2524
|
-
runId: run.runId,
|
|
2525
|
-
label: run.label
|
|
2526
|
-
}, run.lastStatus, finalOutput), { deliverAs: "followUp" });
|
|
2527
|
-
} catch (err) {
|
|
2528
|
-
console.error("avenor tick: sendUserMessage failed", err);
|
|
2529
|
-
}
|
|
2530
|
-
emitAvenorEvent(pi.events, CHANNEL_RUN_TERMINAL, createRunTerminalPayload({
|
|
2531
|
-
runId: entry.runId,
|
|
2532
|
-
supervisorId: entry.supervisorId,
|
|
2730
|
+
const finalOutput = run.pendingNotice ? void 0 : await resolveFinalOutput(run, run.lastStatus);
|
|
2731
|
+
if (sessionAtPollStart !== sessionGeneration) continue;
|
|
2732
|
+
if (trackedRuns.get(entryKey) !== run) continue;
|
|
2733
|
+
const wasTerminalBefore = prevStatus?.status !== void 0 && isTerminalStatus(prevStatus.status);
|
|
2734
|
+
if (emitRunTerminal({
|
|
2735
|
+
run,
|
|
2533
2736
|
runtimeId: entry.runtimeId,
|
|
2534
2737
|
label: entry.label,
|
|
2535
2738
|
agent: entry.agent,
|
|
2536
2739
|
status: entry.status,
|
|
2537
|
-
nestedCount: entry.nestedCount,
|
|
2740
|
+
nestedCount: entry.nestedCount ?? run.nestedCount,
|
|
2538
2741
|
backend: entry.backend
|
|
2539
|
-
}))
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
run.
|
|
2543
|
-
|
|
2544
|
-
|
|
2742
|
+
})) {
|
|
2743
|
+
if (!wasTerminalBefore && !run.blocking) sessionCtx?.ui.notify(`Sub-agent "${entry.label}" finished: ${entry.status}`, entry.status === "done" ? "info" : "warning");
|
|
2744
|
+
}
|
|
2745
|
+
const notice = run.pendingNotice ?? {
|
|
2746
|
+
status: entry.status,
|
|
2747
|
+
text: buildCompletionText({
|
|
2545
2748
|
runId: run.runId,
|
|
2546
|
-
label: run.label
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2749
|
+
label: run.label
|
|
2750
|
+
}, run.lastStatus, finalOutput)
|
|
2751
|
+
};
|
|
2752
|
+
if (run.blocking) {
|
|
2753
|
+
if (!run.pendingNotice) {
|
|
2754
|
+
run.pendingNotice = notice;
|
|
2755
|
+
widgetDirty = true;
|
|
2756
|
+
}
|
|
2757
|
+
continue;
|
|
2551
2758
|
}
|
|
2759
|
+
if (sessionCtx?.isIdle() === true && !compacting && !run.idleSend?.capped) {
|
|
2760
|
+
run.pendingNotice = notice;
|
|
2761
|
+
run.idleSend = {
|
|
2762
|
+
inFlight: true,
|
|
2763
|
+
sentAt: Date.now(),
|
|
2764
|
+
attempts: (run.idleSend?.attempts ?? 0) + 1,
|
|
2765
|
+
capped: false
|
|
2766
|
+
};
|
|
2767
|
+
widgetDirty = true;
|
|
2768
|
+
sendCompletionNotice(notice);
|
|
2769
|
+
continue;
|
|
2770
|
+
} else if (!run.pendingNotice) {
|
|
2771
|
+
run.pendingNotice = notice;
|
|
2772
|
+
widgetDirty = true;
|
|
2773
|
+
}
|
|
2774
|
+
} else if (entry.pendingPermission && !run.blocking && !run.permissionNotified) {
|
|
2775
|
+
run.permissionNotified = true;
|
|
2776
|
+
if (run.lastStatus) sendUserMessageLogged(buildWaitingText({
|
|
2777
|
+
runId: run.runId,
|
|
2778
|
+
label: run.label,
|
|
2779
|
+
agent: run.agent
|
|
2780
|
+
}, run.lastStatus), "avenor tick: sendUserMessage for permission failed");
|
|
2552
2781
|
} else if (!entry.pendingPermission && run.permissionNotified) run.permissionNotified = false;
|
|
2553
2782
|
}
|
|
2554
|
-
if (
|
|
2783
|
+
if (widgetDirty) updateWidget(entries);
|
|
2784
|
+
if ((entries.some((entry) => !isTerminalStatus(entry.status)) || Array.from(trackedRuns.values()).some((run) => run.idleSend?.inFlight)) && pollingActive && generation === pollingGeneration) setTimeout(() => {
|
|
2555
2785
|
tick().catch((err) => {
|
|
2556
2786
|
pollingActive = false;
|
|
2557
2787
|
console.error("avenor polling loop failed", err);
|
|
@@ -2693,7 +2923,7 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2693
2923
|
const outcome = abortPromise ? await Promise.race([waitPromise, abortPromise]) : await waitPromise;
|
|
2694
2924
|
if (signal && abortListener) signal.removeEventListener("abort", abortListener);
|
|
2695
2925
|
unsubscribe?.();
|
|
2696
|
-
await observer
|
|
2926
|
+
await closeObserverQuietly(observer);
|
|
2697
2927
|
if (outcome === "aborted") return { aborted: true };
|
|
2698
2928
|
try {
|
|
2699
2929
|
return {
|
|
@@ -2846,10 +3076,16 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2846
3076
|
});
|
|
2847
3077
|
pi.on("session_start", async (_event, ctx) => {
|
|
2848
3078
|
sessionCtx = ctx;
|
|
3079
|
+
sessionGeneration++;
|
|
3080
|
+
compacting = false;
|
|
3081
|
+
clearPendingNotices();
|
|
3082
|
+
if (trackedRuns.size > 0) startPolling();
|
|
2849
3083
|
lastStatusEntries = [];
|
|
2850
3084
|
lastTurnSocketClosed = false;
|
|
2851
3085
|
pollingErrorCount = 0;
|
|
2852
3086
|
pollingErrors.length = 0;
|
|
3087
|
+
publishedSessionName = null;
|
|
3088
|
+
publishSessionName(ctx);
|
|
2853
3089
|
if (!isSubAgentBrokerMode) startHostAskPoll();
|
|
2854
3090
|
startSubAgentPoll();
|
|
2855
3091
|
});
|
|
@@ -2859,6 +3095,8 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2859
3095
|
disposeHostProviders();
|
|
2860
3096
|
hostPendingAsks.clear();
|
|
2861
3097
|
subAgentPendingAsks.clear();
|
|
3098
|
+
sessionGeneration++;
|
|
3099
|
+
clearPendingNotices();
|
|
2862
3100
|
await stopPolling();
|
|
2863
3101
|
for (const wait of [...pendingWaits]) {
|
|
2864
3102
|
pendingWaits.delete(wait);
|
|
@@ -2870,7 +3108,36 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2870
3108
|
}
|
|
2871
3109
|
sessionCtx = null;
|
|
2872
3110
|
});
|
|
2873
|
-
pi.on("
|
|
3111
|
+
pi.on("message_end", (event) => {
|
|
3112
|
+
if (event.message?.role !== "user") return;
|
|
3113
|
+
const text = extractTurnText([event.message]).text;
|
|
3114
|
+
if (text === "") return;
|
|
3115
|
+
const matches = [...trackedRuns].filter(([, run]) => run.pendingNotice?.text === text);
|
|
3116
|
+
let targets = matches.filter(([, run]) => run.idleSend?.inFlight);
|
|
3117
|
+
if (targets.length === 0) targets = matches.filter(([, run]) => run.idleSend !== void 0);
|
|
3118
|
+
for (const [key, run] of targets) {
|
|
3119
|
+
run.pendingNotice = void 0;
|
|
3120
|
+
run.idleSend = void 0;
|
|
3121
|
+
trackedRuns.delete(key);
|
|
3122
|
+
}
|
|
3123
|
+
if (targets.length > 0) updateWidget(lastStatusEntries);
|
|
3124
|
+
});
|
|
3125
|
+
pi.on("session_before_compact", () => {
|
|
3126
|
+
compacting = true;
|
|
3127
|
+
});
|
|
3128
|
+
const resumeAfterCompaction = () => {
|
|
3129
|
+
compacting = false;
|
|
3130
|
+
for (const run of trackedRuns.values()) {
|
|
3131
|
+
if (run.permissionNotified && run.lastStatus?.pending_permission) run.permissionNotified = false;
|
|
3132
|
+
if (!run.idleSend) continue;
|
|
3133
|
+
rearmIdleSend(run);
|
|
3134
|
+
}
|
|
3135
|
+
if (Array.from(trackedRuns.values()).some((run) => run.pendingNotice || !run.permissionNotified && !!run.lastStatus?.pending_permission)) startPolling();
|
|
3136
|
+
};
|
|
3137
|
+
pi.on("session_compact", resumeAfterCompaction);
|
|
3138
|
+
pi.on("before_agent_start", async (_event, ctx) => {
|
|
3139
|
+
publishSessionName(ctx);
|
|
3140
|
+
compacting = false;
|
|
2874
3141
|
if (trackedRuns.size === 0) return;
|
|
2875
3142
|
const active = (await pollRuns()).filter((entry) => !isTerminalStatus(entry.status));
|
|
2876
3143
|
if (active.length === 0) return;
|
|
@@ -2884,6 +3151,38 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2884
3151
|
display: true
|
|
2885
3152
|
} };
|
|
2886
3153
|
});
|
|
3154
|
+
pi.on("agent_before_settle", async (event) => {
|
|
3155
|
+
if (event.outcome !== "completed") return;
|
|
3156
|
+
const deliverable = [];
|
|
3157
|
+
for (const run of trackedRuns.values()) {
|
|
3158
|
+
if (!run.pendingNotice) continue;
|
|
3159
|
+
if (run.idleSend?.inFlight) continue;
|
|
3160
|
+
deliverable.push({
|
|
3161
|
+
run,
|
|
3162
|
+
notice: run.pendingNotice
|
|
3163
|
+
});
|
|
3164
|
+
}
|
|
3165
|
+
if (deliverable.length === 0) return;
|
|
3166
|
+
for (const { run } of deliverable) {
|
|
3167
|
+
run.pendingNotice = void 0;
|
|
3168
|
+
trackedRuns.delete(trackedRunKey(run.supervisorId, run.runId));
|
|
3169
|
+
}
|
|
3170
|
+
updateWidget(lastStatusEntries);
|
|
3171
|
+
return {
|
|
3172
|
+
entries: [...event.entries, ...deliverable.map(({ run, notice }) => ({
|
|
3173
|
+
type: "custom_message",
|
|
3174
|
+
customType: "avenor-completion",
|
|
3175
|
+
content: notice.text,
|
|
3176
|
+
display: true,
|
|
3177
|
+
details: {
|
|
3178
|
+
run_id: run.runId,
|
|
3179
|
+
supervisor_id: run.supervisorId,
|
|
3180
|
+
status: notice.status
|
|
3181
|
+
}
|
|
3182
|
+
}))],
|
|
3183
|
+
continue: true
|
|
3184
|
+
};
|
|
3185
|
+
});
|
|
2887
3186
|
pi.registerMessageRenderer("avenor-active-runs", (message, _options, theme) => {
|
|
2888
3187
|
const lines = message.content.split("\n");
|
|
2889
3188
|
let text = "";
|
|
@@ -2934,10 +3233,13 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2934
3233
|
wait: Type.Optional(Type.Boolean({ description: "Block until complete (default true)" }))
|
|
2935
3234
|
}),
|
|
2936
3235
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
2937
|
-
if (signal?.aborted) return {
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
3236
|
+
if (signal?.aborted) return {
|
|
3237
|
+
content: [{
|
|
3238
|
+
type: "text",
|
|
3239
|
+
text: "Cancelled"
|
|
3240
|
+
}],
|
|
3241
|
+
details: {}
|
|
3242
|
+
};
|
|
2941
3243
|
validateSpawnSelection({
|
|
2942
3244
|
agent: params.agent,
|
|
2943
3245
|
model: params.model,
|
|
@@ -3016,13 +3318,14 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
3016
3318
|
if (waitResult.aborted) {
|
|
3017
3319
|
const stop = blockingWait.stopped;
|
|
3018
3320
|
if (stop && !stop.synthetic) {
|
|
3019
|
-
const
|
|
3321
|
+
const raw = await deps.statusTool({
|
|
3020
3322
|
runId: result.run_id,
|
|
3021
3323
|
supervisorId
|
|
3022
|
-
}).catch(() => void 0)
|
|
3324
|
+
}).catch(() => void 0);
|
|
3325
|
+
const probed = raw ? asStatusResult(raw) : void 0;
|
|
3023
3326
|
if (probed && (isTerminalStatus(probed.status) || probed.status === "waiting")) waitStatus = probed;
|
|
3024
3327
|
else {
|
|
3025
|
-
const tracked =
|
|
3328
|
+
const tracked = getTrackedRunById(result.run_id, supervisorId);
|
|
3026
3329
|
if (tracked) tracked.blocking = false;
|
|
3027
3330
|
return {
|
|
3028
3331
|
content: [{
|
|
@@ -3041,7 +3344,8 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
3041
3344
|
};
|
|
3042
3345
|
}
|
|
3043
3346
|
} else {
|
|
3044
|
-
|
|
3347
|
+
const runKey = trackedRunKey(supervisorId, result.run_id);
|
|
3348
|
+
trackedRuns.delete(runKey);
|
|
3045
3349
|
return {
|
|
3046
3350
|
content: [{
|
|
3047
3351
|
type: "text",
|
|
@@ -3066,10 +3370,13 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
3066
3370
|
...spawnIdentityMetadata(hostParams)
|
|
3067
3371
|
}
|
|
3068
3372
|
};
|
|
3069
|
-
upsertTrackedRun(runRef, status, wait);
|
|
3373
|
+
if (trackedRuns.has(trackedRunKey(supervisorId, result.run_id))) upsertTrackedRun(runRef, status, wait);
|
|
3070
3374
|
if (status.status === "waiting") {
|
|
3071
|
-
const tracked =
|
|
3072
|
-
if (tracked)
|
|
3375
|
+
const tracked = getTrackedRunById(result.run_id, supervisorId);
|
|
3376
|
+
if (tracked) {
|
|
3377
|
+
tracked.permissionNotified = true;
|
|
3378
|
+
tracked.blocking = false;
|
|
3379
|
+
}
|
|
3073
3380
|
return {
|
|
3074
3381
|
content: [{
|
|
3075
3382
|
type: "text",
|
|
@@ -3093,16 +3400,19 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
3093
3400
|
runId: result.run_id,
|
|
3094
3401
|
supervisorId
|
|
3095
3402
|
}, status);
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3403
|
+
const runKey = trackedRunKey(supervisorId, result.run_id);
|
|
3404
|
+
const tracked = trackedRuns.get(runKey);
|
|
3405
|
+
emitRunTerminal({
|
|
3406
|
+
run: tracked,
|
|
3099
3407
|
runtimeId: status.runtime_id ?? runRef.runtimeId,
|
|
3100
3408
|
label,
|
|
3101
|
-
agent: params.agent,
|
|
3409
|
+
agent: tracked?.agent ?? params.agent ?? "unknown",
|
|
3102
3410
|
status: status.status,
|
|
3411
|
+
nestedCount: tracked?.nestedCount,
|
|
3103
3412
|
backend: status.backend
|
|
3104
|
-
})
|
|
3105
|
-
trackedRuns.delete(
|
|
3413
|
+
});
|
|
3414
|
+
trackedRuns.delete(runKey);
|
|
3415
|
+
updateWidget(lastStatusEntries);
|
|
3106
3416
|
return {
|
|
3107
3417
|
content: [{
|
|
3108
3418
|
type: "text",
|
|
@@ -3120,6 +3430,8 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
3120
3430
|
}
|
|
3121
3431
|
};
|
|
3122
3432
|
}
|
|
3433
|
+
const fallthrough = getTrackedRunById(result.run_id, supervisorId);
|
|
3434
|
+
if (fallthrough) fallthrough.blocking = false;
|
|
3123
3435
|
return {
|
|
3124
3436
|
content: [{
|
|
3125
3437
|
type: "text",
|
|
@@ -3140,17 +3452,11 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
3140
3452
|
return new Text(agent + agentName + label + wait, 0, 0);
|
|
3141
3453
|
},
|
|
3142
3454
|
renderResult(result, { isPartial }, theme) {
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
const status = String(result.details?.status ?? "");
|
|
3149
|
-
if (status && isTerminalStatus(status)) {
|
|
3150
|
-
const emoji = statusEmoji(status);
|
|
3151
|
-
return new Text(`${theme.fg(status === "done" ? "success" : "error", `${emoji} ${status}`)}\n${(result.content?.[0]?.text ?? "").split("\n").map((line) => theme.fg("muted", ` ${line}`)).join("\n")}`, 0, 0);
|
|
3152
|
-
}
|
|
3153
|
-
return new Text((result.content?.[0]?.text ?? "").split("\n").map((line) => theme.fg("muted", line)).join("\n"), 0, 0);
|
|
3455
|
+
const status = resultStatus(result.details);
|
|
3456
|
+
const text = firstTextPart(result.content);
|
|
3457
|
+
if (isPartial) return new Text(theme.fg("accent", `${statusEmoji(status || "running")} ${text || "Running…"}`), 0, 0);
|
|
3458
|
+
if (status && isTerminalStatus(status)) return new Text(`${theme.fg(status === "done" ? "success" : "error", `${statusEmoji(status)} ${status}`)}\n${text.split("\n").map((line) => theme.fg("muted", ` ${line}`)).join("\n")}`, 0, 0);
|
|
3459
|
+
return new Text(text.split("\n").map((line) => theme.fg("muted", line)).join("\n"), 0, 0);
|
|
3154
3460
|
}
|
|
3155
3461
|
});
|
|
3156
3462
|
pi.registerTool({
|
|
@@ -3205,6 +3511,7 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
3205
3511
|
const previousBlocking = tracked?.blocking;
|
|
3206
3512
|
if (tracked) tracked.blocking = true;
|
|
3207
3513
|
let completed = false;
|
|
3514
|
+
let resultRun = tracked;
|
|
3208
3515
|
const blockingWait = params.wait ?? true ? createInterruptibleWait(trackedRunKey(tracked?.supervisorId ?? params.supervisor_id, tracked?.runId ?? params.run_id), signal) : void 0;
|
|
3209
3516
|
try {
|
|
3210
3517
|
let result;
|
|
@@ -3221,18 +3528,30 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
3221
3528
|
result = await resultAfterInterrupt(params, blockingWait.stopped, tracked);
|
|
3222
3529
|
}
|
|
3223
3530
|
completed = result.ready;
|
|
3531
|
+
resultRun = reconcileResultRun(tracked, result, params.supervisor_id);
|
|
3224
3532
|
if (completed) {
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3533
|
+
const idMatches = trackedRunsById(params.run_id, params.supervisor_id).length;
|
|
3534
|
+
const labelMatches = [...trackedRuns.values()].filter((run) => (params.supervisor_id === void 0 || run.supervisorId === params.supervisor_id) && run.label === params.run_id).length;
|
|
3535
|
+
emitRunTerminal({
|
|
3536
|
+
run: resultRun,
|
|
3537
|
+
runId: idMatches > 1 || idMatches === 0 && labelMatches > 1 ? void 0 : result.run_id ?? params.run_id,
|
|
3538
|
+
supervisorId: params.supervisor_id,
|
|
3539
|
+
runtimeId: resultRun?.runtimeId ?? result.runtime_id,
|
|
3540
|
+
label: resultRun?.label ?? result.label ?? params.run_id,
|
|
3541
|
+
agent: resultRun?.agent ?? "unknown",
|
|
3542
|
+
status: result.status ?? "done",
|
|
3543
|
+
nestedCount: resultRun?.nestedCount,
|
|
3544
|
+
backend: resultRun?.lastStatus?.backend
|
|
3545
|
+
});
|
|
3546
|
+
if (resultRun) {
|
|
3547
|
+
trackedRuns.delete(trackedRunKey(resultRun.supervisorId, resultRun.runId));
|
|
3548
|
+
updateWidget(lastStatusEntries);
|
|
3549
|
+
if (tracked && resultRun !== tracked) {
|
|
3550
|
+
const trackedKey = trackedRunKey(tracked.supervisorId, tracked.runId);
|
|
3551
|
+
if (trackedRuns.get(trackedKey) === tracked) tracked.blocking = previousBlocking ?? false;
|
|
3552
|
+
}
|
|
3553
|
+
} else if (tracked && trackedRuns.get(trackedRunKey(tracked.supervisorId, tracked.runId)) === tracked) tracked.blocking = previousBlocking ?? false;
|
|
3554
|
+
} else if (resultRun && result.status === "waiting") resultRun.permissionNotified = true;
|
|
3236
3555
|
return {
|
|
3237
3556
|
content: [{
|
|
3238
3557
|
type: "text",
|
|
@@ -3242,7 +3561,7 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
3242
3561
|
};
|
|
3243
3562
|
} finally {
|
|
3244
3563
|
blockingWait?.dispose();
|
|
3245
|
-
if (!completed && tracked &&
|
|
3564
|
+
if (!completed && tracked && trackedRuns.get(trackedRunKey(tracked.supervisorId, tracked.runId)) === tracked) tracked.blocking = previousBlocking ?? false;
|
|
3246
3565
|
}
|
|
3247
3566
|
},
|
|
3248
3567
|
renderCall(args, theme, context) {
|
|
@@ -3330,7 +3649,7 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
3330
3649
|
pi.registerTool({
|
|
3331
3650
|
name: "avenor_message",
|
|
3332
3651
|
label: "Avenor Message",
|
|
3333
|
-
description: "Send a message to another agent run, answer an inbound ask, withdraw a pending ask, or resume a completed run. Every action is explicit and the result states whether a failed ask can still be withdrawn. `control_reply` is
|
|
3652
|
+
description: "Send a message to another agent run, answer an inbound ask, withdraw a pending ask, or resume a completed run. Every action is explicit and the result states whether a failed ask can still be withdrawn. `control_reply` is retained in the contract but no surface can deliver one.",
|
|
3334
3653
|
parameters: Type.Object({
|
|
3335
3654
|
action: Type.Union([
|
|
3336
3655
|
Type.Literal("tell"),
|
|
@@ -3410,7 +3729,7 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
3410
3729
|
workflow_id: params.workflow_id,
|
|
3411
3730
|
actor: params.actor,
|
|
3412
3731
|
reason: params.reason,
|
|
3413
|
-
|
|
3732
|
+
supervisorId: params.supervisor_id
|
|
3414
3733
|
});
|
|
3415
3734
|
return {
|
|
3416
3735
|
content: [{
|
|
@@ -3832,4 +4151,4 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
3832
4151
|
}
|
|
3833
4152
|
var src_default = createExtension();
|
|
3834
4153
|
//#endregion
|
|
3835
|
-
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 };
|
|
4154
|
+
export { CHANNEL_DELIVERY_ERROR, CHANNEL_POLL_COMPLETED, CHANNEL_POLL_ERROR, CHANNEL_RUN_TERMINAL, buildCompletionText, buildInspectPayload, compactWhitespace, createDeliveryErrorPayload, 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.18.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -31,19 +31,20 @@
|
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "tsdown",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
34
35
|
"test": "bun test",
|
|
35
36
|
"dev": "tsdown --watch"
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
|
-
"@dougbots/avenor-core": "^0.
|
|
39
|
+
"@dougbots/avenor-core": "^0.18.0"
|
|
39
40
|
},
|
|
40
41
|
"peerDependencies": {
|
|
41
|
-
"@earendil-works/pi-coding-agent": ">=
|
|
42
|
+
"@earendil-works/pi-coding-agent": ">=1.4.0",
|
|
42
43
|
"@earendil-works/pi-tui": "*"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
|
-
"@earendil-works/pi-coding-agent": "
|
|
46
|
-
"@earendil-works/pi-tui": "
|
|
46
|
+
"@earendil-works/pi-coding-agent": "^1.4.0",
|
|
47
|
+
"@earendil-works/pi-tui": "^1.4.0",
|
|
47
48
|
"@types/bun": "^1.3.12",
|
|
48
49
|
"@types/node": "^25.8.0",
|
|
49
50
|
"tsdown": "^0.22.0",
|