@dougbots/avenor-pi 0.3.7 → 0.4.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/dist/index.d.ts +36 -2
- package/dist/index.js +1339 -598
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,56 +1,500 @@
|
|
|
1
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
1
|
+
import { Input, Key, Text, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { answerPermissionTool, eventsTool, followUpTool, shutdownTool, spawnTool, statusTool } from "@dougbots/avenor-core";
|
|
3
|
+
import { Supervisor, answerPermissionTool, dial, eventsTool, followUpTool, inspectTool, observeRun, shutdownTool, spawnTool, statusTool } from "@dougbots/avenor-core";
|
|
4
4
|
//#region src/watch.ts
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
const ANSI_PATTERN = /[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g;
|
|
6
|
+
const TRANSCRIPT_SCROLL_STEP = 4;
|
|
7
|
+
const ACTION_STATUS_CHARS = 180;
|
|
8
|
+
const PREVIEW_CHARS = 240;
|
|
9
|
+
const TOOL_PREVIEW_CHARS = 120;
|
|
10
|
+
const FINAL_OUTPUT_CHARS = 600;
|
|
11
|
+
const TRANSCRIPT_PADDING = 2;
|
|
12
|
+
const INSPECT_ERROR_PREFIX = "error: ";
|
|
13
|
+
const TERMINAL_STATUSES$1 = new Set([
|
|
14
|
+
"done",
|
|
15
|
+
"failed",
|
|
16
|
+
"timeout",
|
|
17
|
+
"killed"
|
|
18
|
+
]);
|
|
19
|
+
function emptySnapshot() {
|
|
20
|
+
return {
|
|
21
|
+
identity: {},
|
|
22
|
+
limits: {
|
|
23
|
+
transcript_entries: 64,
|
|
24
|
+
text_chars: 4e3,
|
|
25
|
+
tool_preview_chars: 1e3,
|
|
26
|
+
tools: 16,
|
|
27
|
+
permissions: 16,
|
|
28
|
+
legacy_keys: 128
|
|
29
|
+
},
|
|
30
|
+
ended: false,
|
|
31
|
+
assistant_text: "",
|
|
32
|
+
thought_text: "",
|
|
33
|
+
user_text: "",
|
|
34
|
+
transcript: [],
|
|
35
|
+
tools: [],
|
|
36
|
+
live_tools: [],
|
|
37
|
+
permissions: [],
|
|
38
|
+
_seen_seq_by_scope: {},
|
|
39
|
+
_recent_legacy_keys: []
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function sanitizeText(text) {
|
|
43
|
+
return text.replace(ANSI_PATTERN, "").replaceAll(" ", " ").replace(/[\u0000-\u0008\u000b-\u001f\u007f]/g, "");
|
|
44
|
+
}
|
|
45
|
+
function clipText(text, limit) {
|
|
46
|
+
if (!text) return void 0;
|
|
47
|
+
const clean = sanitizeText(text).trim();
|
|
48
|
+
if (!clean) return void 0;
|
|
49
|
+
if (clean.length <= limit) return clean;
|
|
50
|
+
return `${clean.slice(0, Math.max(0, limit - 1))}…`;
|
|
51
|
+
}
|
|
52
|
+
function compactWhitespace$1(text, limit) {
|
|
53
|
+
const clean = clipText(text, limit);
|
|
54
|
+
if (!clean) return void 0;
|
|
55
|
+
return clean.replace(/\s+/g, " ").trim() || void 0;
|
|
56
|
+
}
|
|
57
|
+
function padLine(text, width) {
|
|
58
|
+
const clipped = truncateToWidth(text, width);
|
|
59
|
+
return clipped + " ".repeat(Math.max(0, width - visibleWidth(clipped)));
|
|
60
|
+
}
|
|
61
|
+
function wrapPrefixed(text, width, firstPrefix, nextPrefix = " ".repeat(Math.max(0, visibleWidth(firstPrefix)))) {
|
|
62
|
+
const clean = clipText(text, PREVIEW_CHARS);
|
|
63
|
+
if (!clean) return [];
|
|
64
|
+
return wrapTextWithAnsi(clean, Math.max(10, width - Math.max(visibleWidth(firstPrefix), visibleWidth(nextPrefix)))).map((line, index) => truncateToWidth(`${index === 0 ? firstPrefix : nextPrefix}${line}`, width));
|
|
65
|
+
}
|
|
66
|
+
function renderKv(theme, label, value, width) {
|
|
67
|
+
if (!value) return [];
|
|
68
|
+
return wrapPrefixed(value, width, theme.fg("dim", `${label}: `));
|
|
69
|
+
}
|
|
70
|
+
function usageSummary(usage) {
|
|
71
|
+
if (!usage) return void 0;
|
|
72
|
+
const order = [
|
|
73
|
+
"input_tokens",
|
|
74
|
+
"output_tokens",
|
|
75
|
+
"total_tokens",
|
|
76
|
+
"tokens"
|
|
77
|
+
];
|
|
78
|
+
const parts = [];
|
|
79
|
+
for (const key of order) {
|
|
80
|
+
const value = usage[key];
|
|
81
|
+
if (typeof value === "number" && Number.isFinite(value)) parts.push(`${key}=${value}`);
|
|
13
82
|
}
|
|
14
|
-
|
|
15
|
-
|
|
83
|
+
if (parts.length === 0) {
|
|
84
|
+
const entries = Object.entries(usage).filter(([, value]) => typeof value === "number" || typeof value === "string" || typeof value === "boolean").slice(0, 4).map(([key, value]) => `${key}=${String(value)}`);
|
|
85
|
+
if (entries.length === 0) return void 0;
|
|
86
|
+
return entries.join(" · ");
|
|
16
87
|
}
|
|
17
|
-
|
|
18
|
-
|
|
88
|
+
return parts.join(" · ");
|
|
89
|
+
}
|
|
90
|
+
function finalOutputPreview(status, snapshot) {
|
|
91
|
+
return clipText(status?.final_output ?? snapshot?.final_output ?? snapshot?.assistant_text, FINAL_OUTPUT_CHARS);
|
|
92
|
+
}
|
|
93
|
+
function currentStatusWord(status, snapshot) {
|
|
94
|
+
const raw = status?.status?.toLowerCase();
|
|
95
|
+
if (raw && TERMINAL_STATUSES$1.has(raw)) return raw;
|
|
96
|
+
if (snapshot.pending_permission) return "waiting";
|
|
97
|
+
if (snapshot.ended) {
|
|
98
|
+
const stop = snapshot.stop_reason?.toLowerCase();
|
|
99
|
+
if (stop && TERMINAL_STATUSES$1.has(stop)) return stop;
|
|
100
|
+
return raw && raw !== "running" ? raw : "done";
|
|
19
101
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
102
|
+
return raw ?? "running";
|
|
103
|
+
}
|
|
104
|
+
function isPromptable(status, snapshot) {
|
|
105
|
+
const current = currentStatusWord(status, snapshot);
|
|
106
|
+
return !TERMINAL_STATUSES$1.has(current) && current !== "unavailable";
|
|
107
|
+
}
|
|
108
|
+
function isFollowUpable(status, snapshot) {
|
|
109
|
+
return TERMINAL_STATUSES$1.has(currentStatusWord(status, snapshot)) || snapshot.ended;
|
|
110
|
+
}
|
|
111
|
+
function transcriptLabel(entry, theme) {
|
|
112
|
+
switch (entry.kind) {
|
|
113
|
+
case "assistant": return theme.fg("text", "A ");
|
|
114
|
+
case "thought": return theme.fg("muted", "~ ");
|
|
115
|
+
case "user": return theme.fg("accent", "> ");
|
|
116
|
+
case "tool": return theme.fg("toolTitle", `→ ${entry.title ?? "tool"}${entry.status ? ` [${entry.status}]` : ""} `);
|
|
117
|
+
case "permission": return theme.fg("warning", "🔒 ");
|
|
118
|
+
case "session": return theme.fg("success", "■ ");
|
|
119
|
+
case "status": return theme.fg("dim", "· ");
|
|
23
120
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
121
|
+
}
|
|
122
|
+
function transcriptText(entry) {
|
|
123
|
+
if (entry.kind === "tool") return compactWhitespace$1(entry.text, TOOL_PREVIEW_CHARS) ?? entry.title;
|
|
124
|
+
if (entry.kind === "session") return compactWhitespace$1(entry.text, PREVIEW_CHARS) ?? entry.status;
|
|
125
|
+
return compactWhitespace$1(entry.text, PREVIEW_CHARS);
|
|
126
|
+
}
|
|
127
|
+
function renderTranscriptLines(snapshot, width, theme) {
|
|
128
|
+
const out = [];
|
|
129
|
+
for (const entry of snapshot.transcript) {
|
|
130
|
+
const text = transcriptText(entry);
|
|
131
|
+
if (!text) continue;
|
|
132
|
+
const prefix = transcriptLabel(entry, theme);
|
|
133
|
+
out.push(...wrapPrefixed(text, width, prefix));
|
|
134
|
+
}
|
|
135
|
+
if (snapshot.live_tools.length > 0) {
|
|
136
|
+
if (out.length > 0) out.push("");
|
|
137
|
+
out.push(theme.fg("dim", "live tools:"));
|
|
138
|
+
for (const tool of snapshot.live_tools) out.push(...renderToolState(tool, width, theme));
|
|
139
|
+
}
|
|
140
|
+
if (snapshot.pending_permission) {
|
|
141
|
+
if (out.length > 0) out.push("");
|
|
142
|
+
out.push(...wrapPrefixed(snapshot.pending_permission.description, width, theme.fg("warning", "pending: ")));
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
function renderToolState(tool, width, theme) {
|
|
147
|
+
const preview = compactWhitespace$1(tool.preview, TOOL_PREVIEW_CHARS);
|
|
148
|
+
const firstPrefix = `${theme.fg("toolTitle", `→ ${tool.title ?? tool.kind ?? "tool"} `)}${(tool.live ? theme.fg("warning", "[running] ") : tool.status && `${theme.fg("dim", `[${tool.status}] `)}`) ?? ""}`;
|
|
149
|
+
return wrapPrefixed(preview ?? "(no preview)", width, firstPrefix);
|
|
150
|
+
}
|
|
151
|
+
function helpText(state) {
|
|
152
|
+
const actions = [
|
|
153
|
+
"↑/↓ scroll",
|
|
154
|
+
"pgup/pgdn page",
|
|
155
|
+
"home/end jump",
|
|
156
|
+
"esc close"
|
|
157
|
+
];
|
|
158
|
+
if (state.status && isPromptable(state.status, state.snapshot)) actions.unshift("enter send prompt", "ctrl-c cancel run");
|
|
159
|
+
else if (isFollowUpable(state.status, state.snapshot)) actions.unshift("enter follow up");
|
|
160
|
+
return actions.join(" · ");
|
|
161
|
+
}
|
|
162
|
+
var RunInspectorOverlay = class {
|
|
163
|
+
tui;
|
|
164
|
+
theme;
|
|
165
|
+
keybindings;
|
|
166
|
+
deps;
|
|
167
|
+
done;
|
|
168
|
+
onTrackRun;
|
|
169
|
+
input = new Input();
|
|
170
|
+
state;
|
|
171
|
+
observer = null;
|
|
172
|
+
observerUnsubscribe = null;
|
|
173
|
+
refreshPending = false;
|
|
174
|
+
scrollOffset = 0;
|
|
175
|
+
closed = false;
|
|
176
|
+
actionPending = false;
|
|
177
|
+
actionError;
|
|
178
|
+
_focused = false;
|
|
179
|
+
get focused() {
|
|
180
|
+
return this._focused;
|
|
181
|
+
}
|
|
182
|
+
set focused(value) {
|
|
183
|
+
this._focused = value;
|
|
184
|
+
this.input.focused = value;
|
|
185
|
+
}
|
|
186
|
+
constructor(tui, theme, keybindings, deps, run, done, onTrackRun) {
|
|
187
|
+
this.tui = tui;
|
|
188
|
+
this.theme = theme;
|
|
189
|
+
this.keybindings = keybindings;
|
|
190
|
+
this.deps = deps;
|
|
191
|
+
this.done = done;
|
|
192
|
+
this.onTrackRun = onTrackRun;
|
|
193
|
+
this.state = {
|
|
194
|
+
run,
|
|
195
|
+
snapshot: emptySnapshot(),
|
|
196
|
+
loading: true
|
|
197
|
+
};
|
|
198
|
+
this.input.onSubmit = (value) => {
|
|
199
|
+
const text = value.trim();
|
|
200
|
+
if (!text) return;
|
|
201
|
+
this.input.setValue("");
|
|
202
|
+
this.submit(text);
|
|
203
|
+
};
|
|
204
|
+
this.reload(true);
|
|
205
|
+
}
|
|
206
|
+
requestRender() {
|
|
207
|
+
this.tui.requestRender();
|
|
208
|
+
}
|
|
209
|
+
close() {
|
|
210
|
+
if (this.closed) return;
|
|
211
|
+
this.closed = true;
|
|
212
|
+
this.cleanup();
|
|
213
|
+
this.done();
|
|
214
|
+
}
|
|
215
|
+
dispose() {
|
|
216
|
+
if (this.closed) return;
|
|
217
|
+
this.closed = true;
|
|
218
|
+
this.cleanup();
|
|
219
|
+
}
|
|
220
|
+
invalidate() {
|
|
221
|
+
this.input.invalidate();
|
|
222
|
+
}
|
|
223
|
+
async cleanup() {
|
|
224
|
+
this.observerUnsubscribe?.();
|
|
225
|
+
this.observerUnsubscribe = null;
|
|
226
|
+
if (this.observer) {
|
|
227
|
+
const observer = this.observer;
|
|
228
|
+
this.observer = null;
|
|
229
|
+
await observer.close().catch(() => {});
|
|
35
230
|
}
|
|
36
|
-
|
|
231
|
+
}
|
|
232
|
+
async reload(withObserver) {
|
|
233
|
+
this.state = {
|
|
234
|
+
...this.state,
|
|
235
|
+
loading: true,
|
|
236
|
+
error: void 0,
|
|
237
|
+
actionMessage: void 0
|
|
238
|
+
};
|
|
239
|
+
this.requestRender();
|
|
240
|
+
try {
|
|
241
|
+
const result = await this.deps.inspectRun(this.state.run);
|
|
242
|
+
if (this.closed) return;
|
|
243
|
+
const nextRun = {
|
|
244
|
+
...this.state.run,
|
|
245
|
+
runId: result.status.run_id ?? this.state.run.runId,
|
|
246
|
+
label: result.label ?? this.state.run.label,
|
|
247
|
+
agent: result.status.agent ?? this.state.run.agent,
|
|
248
|
+
runtimeId: result.status.runtime_id ?? result.snapshot.identity.runtime_id ?? this.state.run.runtimeId
|
|
249
|
+
};
|
|
250
|
+
this.state = {
|
|
251
|
+
run: nextRun,
|
|
252
|
+
status: result.status,
|
|
253
|
+
snapshot: result.snapshot,
|
|
254
|
+
loading: false
|
|
255
|
+
};
|
|
256
|
+
this.onTrackRun?.(nextRun, result.status);
|
|
257
|
+
this.requestRender();
|
|
258
|
+
await this.cleanup();
|
|
259
|
+
if (!withObserver) return;
|
|
260
|
+
if (TERMINAL_STATUSES$1.has(currentStatusWord(result.status, result.snapshot))) return;
|
|
261
|
+
if (!(nextRun.runtimeId ?? result.snapshot.identity.runtime_id)) return;
|
|
262
|
+
const observer = await this.deps.observeRun(nextRun, result.snapshot.latest_seq, result.snapshot);
|
|
263
|
+
if (this.closed || !observer) {
|
|
264
|
+
await observer?.close().catch(() => {});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const applySnapshot = (snapshot) => {
|
|
268
|
+
this.state = {
|
|
269
|
+
...this.state,
|
|
270
|
+
snapshot: {
|
|
271
|
+
...snapshot,
|
|
272
|
+
identity: {
|
|
273
|
+
...snapshot.identity,
|
|
274
|
+
run_id: snapshot.identity.run_id ?? this.state.status?.run_id ?? this.state.run.runId
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
if (snapshot.ended && !this.refreshPending) {
|
|
279
|
+
this.refreshPending = true;
|
|
280
|
+
this.reload(false).finally(() => {
|
|
281
|
+
this.refreshPending = false;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
this.requestRender();
|
|
285
|
+
};
|
|
286
|
+
this.observer = observer;
|
|
287
|
+
this.observerUnsubscribe = observer.subscribe((snapshot) => {
|
|
288
|
+
applySnapshot(snapshot);
|
|
289
|
+
});
|
|
290
|
+
const current = observer.snapshot();
|
|
291
|
+
if (current.last_event || current.transcript.length > 0 || current.pending_permission || current.ended) applySnapshot(current);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (this.closed) return;
|
|
294
|
+
this.state = {
|
|
295
|
+
...this.state,
|
|
296
|
+
loading: false,
|
|
297
|
+
error: error instanceof Error ? error.message : String(error)
|
|
298
|
+
};
|
|
299
|
+
this.requestRender();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
runAction(work, message) {
|
|
303
|
+
if (this.actionPending) return;
|
|
304
|
+
this.actionPending = true;
|
|
305
|
+
this.actionError = void 0;
|
|
306
|
+
if (message) this.state = {
|
|
307
|
+
...this.state,
|
|
308
|
+
actionMessage: clipText(message, ACTION_STATUS_CHARS)
|
|
309
|
+
};
|
|
310
|
+
this.requestRender();
|
|
311
|
+
work().catch((error) => {
|
|
312
|
+
this.actionError = error instanceof Error ? error.message : String(error);
|
|
313
|
+
}).finally(() => {
|
|
314
|
+
this.actionPending = false;
|
|
315
|
+
this.requestRender();
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
submit(text) {
|
|
319
|
+
const runtimeId = this.state.run.runtimeId ?? this.state.status?.runtime_id ?? this.state.snapshot.identity.runtime_id;
|
|
320
|
+
if (this.state.status && isPromptable(this.state.status, this.state.snapshot) && runtimeId) {
|
|
321
|
+
this.runAction(async () => {
|
|
322
|
+
await this.deps.promptRun(this.state.run, runtimeId, text);
|
|
323
|
+
this.state = {
|
|
324
|
+
...this.state,
|
|
325
|
+
actionMessage: clipText(`Queued prompt: ${text}`, ACTION_STATUS_CHARS)
|
|
326
|
+
};
|
|
327
|
+
}, "sending prompt…");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (!isFollowUpable(this.state.status, this.state.snapshot)) return;
|
|
331
|
+
this.runAction(async () => {
|
|
332
|
+
const next = await this.deps.followUpRun(this.state.run, text);
|
|
333
|
+
this.state = {
|
|
334
|
+
...this.state,
|
|
335
|
+
run: {
|
|
336
|
+
...this.state.run,
|
|
337
|
+
runId: next.run_id,
|
|
338
|
+
label: next.label,
|
|
339
|
+
runtimeId: void 0
|
|
340
|
+
},
|
|
341
|
+
actionMessage: clipText(`Started follow-up run ${next.label}`, ACTION_STATUS_CHARS)
|
|
342
|
+
};
|
|
343
|
+
this.scrollOffset = 0;
|
|
344
|
+
await this.reload(true);
|
|
345
|
+
}, "starting follow-up…");
|
|
346
|
+
}
|
|
347
|
+
cancelCurrentRun() {
|
|
348
|
+
const runtimeId = this.state.run.runtimeId ?? this.state.status?.runtime_id ?? this.state.snapshot.identity.runtime_id;
|
|
349
|
+
if (!runtimeId) return;
|
|
350
|
+
this.runAction(async () => {
|
|
351
|
+
await this.deps.cancelRun(this.state.run, runtimeId);
|
|
352
|
+
this.state = {
|
|
353
|
+
...this.state,
|
|
354
|
+
actionMessage: "cancel requested"
|
|
355
|
+
};
|
|
356
|
+
}, "cancelling run…");
|
|
37
357
|
}
|
|
38
358
|
handleInput(data) {
|
|
39
|
-
if (data
|
|
359
|
+
if (matchesKey(data, Key.escape)) {
|
|
360
|
+
this.close();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (matchesKey(data, Key.up)) {
|
|
364
|
+
this.scrollOffset += TRANSCRIPT_SCROLL_STEP;
|
|
365
|
+
this.requestRender();
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (matchesKey(data, Key.down)) {
|
|
369
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - TRANSCRIPT_SCROLL_STEP);
|
|
370
|
+
this.requestRender();
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (matchesKey(data, Key.pageUp)) {
|
|
374
|
+
this.scrollOffset += this.viewportHeight();
|
|
375
|
+
this.requestRender();
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (matchesKey(data, Key.pageDown)) {
|
|
379
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - this.viewportHeight());
|
|
380
|
+
this.requestRender();
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (matchesKey(data, Key.home)) {
|
|
384
|
+
this.scrollOffset = Number.MAX_SAFE_INTEGER;
|
|
385
|
+
this.requestRender();
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (matchesKey(data, Key.end)) {
|
|
389
|
+
this.scrollOffset = 0;
|
|
390
|
+
this.requestRender();
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (matchesKey(data, Key.ctrl("c")) && this.state.status && isPromptable(this.state.status, this.state.snapshot)) {
|
|
394
|
+
this.cancelCurrentRun();
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
this.input.handleInput(data);
|
|
398
|
+
this.requestRender();
|
|
40
399
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
400
|
+
viewportHeight() {
|
|
401
|
+
const rows = this.tui.terminal.rows || 30;
|
|
402
|
+
return Math.max(8, rows - 15);
|
|
403
|
+
}
|
|
404
|
+
render(width) {
|
|
405
|
+
const theme = this.theme;
|
|
406
|
+
const lines = [];
|
|
407
|
+
const horizontal = theme.fg("borderAccent", "─".repeat(Math.max(1, width)));
|
|
408
|
+
const innerWidth = Math.max(10, width);
|
|
409
|
+
lines.push(horizontal);
|
|
410
|
+
const title = this.state.run.label ?? this.state.run.runId;
|
|
411
|
+
const statusWord = currentStatusWord(this.state.status, this.state.snapshot);
|
|
412
|
+
const heading = `${theme.fg("accent", theme.bold(title))} ${theme.fg("dim", `(${this.state.run.runId})`)} ${theme.fg(statusWord === "done" ? "success" : statusWord === "failed" ? "error" : statusWord === "waiting" ? "warning" : "muted", statusWord)}`;
|
|
413
|
+
lines.push(truncateToWidth(heading, innerWidth));
|
|
414
|
+
const phase = this.state.snapshot.phase_label ?? this.state.status?.phase_label ?? this.state.snapshot.phase ?? this.state.status?.phase;
|
|
415
|
+
const summary = [
|
|
416
|
+
phase ? `phase=${phase}` : void 0,
|
|
417
|
+
this.state.snapshot.latest_seq !== void 0 ? `seq=${this.state.snapshot.latest_seq}` : void 0,
|
|
418
|
+
this.state.snapshot.identity.agent ?? this.state.status?.agent,
|
|
419
|
+
this.state.snapshot.identity.model ?? this.state.status?.model,
|
|
420
|
+
this.state.snapshot.identity.backend ?? this.state.status?.backend
|
|
421
|
+
].filter((value) => !!value).join(" · ");
|
|
422
|
+
lines.push(...renderKv(theme, "status", summary || void 0, innerWidth));
|
|
423
|
+
const identity = [
|
|
424
|
+
this.state.snapshot.identity.runtime_id ?? this.state.status?.runtime_id,
|
|
425
|
+
this.state.snapshot.identity.session_id ?? this.state.status?.session_id,
|
|
426
|
+
this.state.snapshot.identity.dir ?? this.state.status?.dir
|
|
427
|
+
].filter((value) => !!value).join(" · ");
|
|
428
|
+
lines.push(...renderKv(theme, "identity", identity || void 0, innerWidth));
|
|
429
|
+
lines.push(...renderKv(theme, "usage", usageSummary(this.state.snapshot.usage ?? this.state.status?.usage), innerWidth));
|
|
430
|
+
const finalOutput = finalOutputPreview(this.state.status, this.state.snapshot);
|
|
431
|
+
if (finalOutput && (this.state.snapshot.ended || TERMINAL_STATUSES$1.has(statusWord))) lines.push(...wrapPrefixed(finalOutput, innerWidth, theme.fg("success", "final: ")));
|
|
432
|
+
if (this.state.snapshot.pending_permission) {
|
|
433
|
+
lines.push(...wrapPrefixed(this.state.snapshot.pending_permission.description, innerWidth, theme.fg("warning", "permission: ")));
|
|
434
|
+
const optionText = this.state.snapshot.pending_permission.options.map((option) => `${option.option_id} (${option.label})`).join(", ");
|
|
435
|
+
lines.push(...wrapPrefixed(optionText, innerWidth, theme.fg("dim", "options: ")));
|
|
436
|
+
} else if (this.state.status?.pending_permission) {
|
|
437
|
+
lines.push(...wrapPrefixed(this.state.status.pending_permission.description, innerWidth, theme.fg("warning", "permission: ")));
|
|
438
|
+
const optionText = this.state.status.pending_permission.options.map((option) => `${option.option_id} (${option.label})`).join(", ");
|
|
439
|
+
lines.push(...wrapPrefixed(optionText, innerWidth, theme.fg("dim", "options: ")));
|
|
49
440
|
}
|
|
50
|
-
|
|
441
|
+
const liveTools = this.state.snapshot.live_tools.slice(-4);
|
|
442
|
+
if (liveTools.length > 0) {
|
|
443
|
+
lines.push(theme.fg("dim", "live tools:"));
|
|
444
|
+
for (const tool of liveTools) lines.push(...renderToolState(tool, innerWidth, theme));
|
|
445
|
+
}
|
|
446
|
+
if (this.state.loading) lines.push(theme.fg("muted", "loading…"));
|
|
447
|
+
if (this.state.actionMessage) lines.push(...wrapPrefixed(this.state.actionMessage, innerWidth, theme.fg("accent", "action: ")));
|
|
448
|
+
if (this.actionError) lines.push(...wrapPrefixed(this.actionError, innerWidth, theme.fg("error", INSPECT_ERROR_PREFIX)));
|
|
449
|
+
else if (this.state.error) lines.push(...wrapPrefixed(this.state.error, innerWidth, theme.fg("error", INSPECT_ERROR_PREFIX)));
|
|
450
|
+
lines.push(horizontal);
|
|
451
|
+
lines.push(theme.fg("dim", "transcript:"));
|
|
452
|
+
const transcript = renderTranscriptLines(this.state.snapshot, Math.max(10, innerWidth - TRANSCRIPT_PADDING), theme).map((line) => truncateToWidth(` ${line}`, innerWidth));
|
|
453
|
+
const viewportHeight = this.viewportHeight();
|
|
454
|
+
const scrollNotice = this.scrollOffset > 0 ? truncateToWidth(theme.fg("dim", `… ${this.scrollOffset} line${this.scrollOffset === 1 ? "" : "s"} below`), innerWidth) : void 0;
|
|
455
|
+
const transcriptCapacity = Math.max(1, viewportHeight - (scrollNotice ? 1 : 0));
|
|
456
|
+
const maxOffset = Math.max(0, transcript.length - transcriptCapacity);
|
|
457
|
+
if (this.scrollOffset > maxOffset) this.scrollOffset = maxOffset;
|
|
458
|
+
const end = transcript.length - this.scrollOffset;
|
|
459
|
+
const start = Math.max(0, end - transcriptCapacity);
|
|
460
|
+
const body = transcript.slice(start, end);
|
|
461
|
+
while (body.length < transcriptCapacity) body.push("");
|
|
462
|
+
if (scrollNotice) body.push(scrollNotice);
|
|
463
|
+
for (const line of body) lines.push(padLine(line, innerWidth));
|
|
464
|
+
lines.push(horizontal);
|
|
465
|
+
lines.push(...this.input.render(innerWidth).map((line) => truncateToWidth(line, innerWidth)));
|
|
466
|
+
const promptHint = this.state.status && isPromptable(this.state.status, this.state.snapshot) ? "send prompt to running/waiting run" : isFollowUpable(this.state.status, this.state.snapshot) ? "start a follow-up run from this transcript" : "read-only view";
|
|
467
|
+
lines.push(truncateToWidth(theme.fg("dim", `${promptHint} · ${helpText(this.state)}`), innerWidth));
|
|
468
|
+
lines.push(horizontal);
|
|
469
|
+
return lines.map((line) => truncateToWidth(line, innerWidth));
|
|
51
470
|
}
|
|
52
|
-
invalidate() {}
|
|
53
471
|
};
|
|
472
|
+
async function openRunInspector(ctx, options) {
|
|
473
|
+
if (!ctx.hasUI) {
|
|
474
|
+
ctx.ui.notify("avenor-watch requires TUI mode", "warning");
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
let runId = options.initialRunId?.trim();
|
|
478
|
+
if (!runId) {
|
|
479
|
+
if (options.trackedRuns.length === 0) {
|
|
480
|
+
ctx.ui.notify("No tracked runs", "warning");
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const selected = await ctx.ui.select("Watch run:", options.trackedRuns.map((run) => run.runId));
|
|
484
|
+
if (!selected) return;
|
|
485
|
+
runId = selected;
|
|
486
|
+
}
|
|
487
|
+
const tracked = options.trackedRuns.find((run) => run.runId === runId);
|
|
488
|
+
await ctx.ui.custom((tui, theme, keybindings, done) => new RunInspectorOverlay(tui, theme, keybindings, options.deps, tracked ?? { runId }, () => done(), options.onTrackRun), {
|
|
489
|
+
overlay: true,
|
|
490
|
+
overlayOptions: {
|
|
491
|
+
anchor: "center",
|
|
492
|
+
width: "95%",
|
|
493
|
+
maxHeight: "95%",
|
|
494
|
+
minWidth: 80
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
}
|
|
54
498
|
//#endregion
|
|
55
499
|
//#region src/types.ts
|
|
56
500
|
const TERMINAL_STATUSES = new Set([
|
|
@@ -80,17 +524,48 @@ function formatRunLine(entry) {
|
|
|
80
524
|
//#endregion
|
|
81
525
|
//#region src/index.ts
|
|
82
526
|
const POLL_INTERVAL_MS = 3e3;
|
|
527
|
+
const INSPECT_LIMIT = 128;
|
|
528
|
+
const COMPLETION_OUTPUT_CHARS = 600;
|
|
529
|
+
const INSPECT_JSON_STRING_CHARS = 600;
|
|
530
|
+
const INSPECT_JSON_ARRAY_ITEMS = 24;
|
|
531
|
+
const INSPECT_JSON_OBJECT_KEYS = 24;
|
|
532
|
+
const INSPECT_JSON_DEPTH = 8;
|
|
533
|
+
const defaultDeps = {
|
|
534
|
+
spawnTool,
|
|
535
|
+
statusTool,
|
|
536
|
+
eventsTool,
|
|
537
|
+
answerPermissionTool,
|
|
538
|
+
followUpTool,
|
|
539
|
+
inspectTool,
|
|
540
|
+
shutdownTool,
|
|
541
|
+
observeRun,
|
|
542
|
+
dial,
|
|
543
|
+
Supervisor
|
|
544
|
+
};
|
|
83
545
|
function statusSupervisorId(requestedSupervisorId, spawnedSupervisorId) {
|
|
84
546
|
return requestedSupervisorId ? spawnedSupervisorId : void 0;
|
|
85
547
|
}
|
|
86
548
|
function sleep(ms) {
|
|
87
549
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
88
550
|
}
|
|
89
|
-
function
|
|
551
|
+
function normalizeMultilinePreview(text, limit = COMPLETION_OUTPUT_CHARS) {
|
|
552
|
+
if (!text) return void 0;
|
|
553
|
+
const clean = sanitizeText(text).trim();
|
|
554
|
+
if (!clean) return void 0;
|
|
555
|
+
return clean.length <= limit ? clean : `${clean.slice(0, Math.max(0, limit - 1))}…`;
|
|
556
|
+
}
|
|
557
|
+
function quotedLines(text) {
|
|
558
|
+
const preview = normalizeMultilinePreview(text);
|
|
559
|
+
if (!preview) return [];
|
|
560
|
+
return preview.split("\n").slice(0, 8).map((line) => `> ${line}`);
|
|
561
|
+
}
|
|
562
|
+
function buildCompletionText(run, result, finalOutput) {
|
|
90
563
|
const lines = [`Sub-agent "${run.label}" finished with status **${result.status}**.`];
|
|
564
|
+
const output = normalizeMultilinePreview(finalOutput ?? result.final_output);
|
|
565
|
+
if (output) lines.push("", "Final output:", ...quotedLines(output));
|
|
91
566
|
if (result.stop_reason && result.stop_reason.toUpperCase() !== result.status.toUpperCase()) lines.push(`Stop reason: ${result.stop_reason}`);
|
|
92
567
|
if (result.session_id) lines.push(`Session: \`${result.session_id}\``);
|
|
93
|
-
lines.push(
|
|
568
|
+
lines.push("", `Call \`avenor_inspect\` with \`run_id: "${run.runId}"\` for the bounded transcript/snapshot, or \`avenor_follow_up\` to iterate.`);
|
|
94
569
|
return lines.join("\n");
|
|
95
570
|
}
|
|
96
571
|
function buildWaitingText(run, status) {
|
|
@@ -98,342 +573,598 @@ function buildWaitingText(run, status) {
|
|
|
98
573
|
const lines = [`Sub-agent "${run.label}" (${run.agent}) is waiting for input.`];
|
|
99
574
|
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"`);
|
|
100
575
|
else if (status.phase_label) lines.push(`Question: ${status.phase_label}`);
|
|
101
|
-
if (!perm) lines.push(`Use \`avenor_follow_up\` with run_id "${run.runId}" to respond, or \`
|
|
576
|
+
if (!perm) lines.push(`Use \`avenor_follow_up\` with run_id "${run.runId}" to respond, or \`avenor_inspect\` to review context first.`);
|
|
102
577
|
return lines.join("\n");
|
|
103
578
|
}
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
579
|
+
function isTerminalStatus(status) {
|
|
580
|
+
return !!status && TERMINAL_STATUSES.has(status);
|
|
581
|
+
}
|
|
582
|
+
function renderStatusLines(entries) {
|
|
583
|
+
const active = entries.filter((e) => !isTerminalStatus(e.status));
|
|
584
|
+
if (active.length === 0) return [];
|
|
585
|
+
return active.map((e) => {
|
|
586
|
+
const emoji = statusEmoji(e.status);
|
|
587
|
+
const perm = e.pendingPermission ? " 🔒" : "";
|
|
588
|
+
const phase = e.phaseLabel ? ` [${e.phaseLabel}]` : "";
|
|
589
|
+
return `${emoji} ${e.label} — ${e.status}${phase}${perm}`;
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
function compactWhitespace(text, limit = 100) {
|
|
593
|
+
const clipped = clipText(text, limit);
|
|
594
|
+
return clipped ? clipped.replace(/\s+/g, " ").trim() : void 0;
|
|
595
|
+
}
|
|
596
|
+
function snapshotProgressText(label, snapshot) {
|
|
597
|
+
if (snapshot.pending_permission) return `[blocked] ${label} — permission: ${snapshot.pending_permission.description}`;
|
|
598
|
+
const phase = snapshot.phase_label ? ` (${snapshot.phase_label})` : snapshot.phase ? ` (${snapshot.phase})` : "";
|
|
599
|
+
const liveTool = snapshot.live_tools[snapshot.live_tools.length - 1];
|
|
600
|
+
if (liveTool) {
|
|
601
|
+
const toolName = liveTool.title ?? liveTool.kind ?? "tool";
|
|
602
|
+
const preview = compactWhitespace(liveTool.preview, 80);
|
|
603
|
+
return `${label}${phase} — ${toolName}${preview ? `: ${preview}` : ""}`;
|
|
604
|
+
}
|
|
605
|
+
const last = snapshot.transcript[snapshot.transcript.length - 1];
|
|
606
|
+
const transcriptWidth = 160;
|
|
607
|
+
const rendered = last ? compactWhitespace(renderTranscriptLines({
|
|
608
|
+
...snapshot,
|
|
609
|
+
transcript: [last],
|
|
610
|
+
live_tools: [],
|
|
611
|
+
pending_permission: void 0
|
|
612
|
+
}, transcriptWidth, {
|
|
613
|
+
fg: (_color, text) => text,
|
|
614
|
+
bg: (_color, text) => text,
|
|
615
|
+
bold: (text) => text,
|
|
616
|
+
italic: (text) => text
|
|
617
|
+
})[0], 80) : void 0;
|
|
618
|
+
return `${label}${phase}${rendered ? ` — ${rendered}` : ""}`;
|
|
619
|
+
}
|
|
620
|
+
function boundJsonValue(value, depth = 0) {
|
|
621
|
+
if (depth >= INSPECT_JSON_DEPTH) return "[truncated]";
|
|
622
|
+
if (typeof value === "string") {
|
|
623
|
+
const clean = sanitizeText(value);
|
|
624
|
+
return clean.length <= INSPECT_JSON_STRING_CHARS ? clean : `${clean.slice(0, INSPECT_JSON_STRING_CHARS - 1)}…`;
|
|
625
|
+
}
|
|
626
|
+
if (Array.isArray(value)) return value.slice(Math.max(0, value.length - INSPECT_JSON_ARRAY_ITEMS)).map((item) => boundJsonValue(item, depth + 1));
|
|
627
|
+
if (!value || typeof value !== "object") return value;
|
|
628
|
+
const result = {};
|
|
629
|
+
for (const [key, item] of Object.entries(value).slice(0, INSPECT_JSON_OBJECT_KEYS)) result[key] = boundJsonValue(item, depth + 1);
|
|
630
|
+
return result;
|
|
631
|
+
}
|
|
632
|
+
function buildInspectPayload(result) {
|
|
633
|
+
const finalOutput = finalOutputPreview(result.status, result.snapshot);
|
|
634
|
+
return boundJsonValue({
|
|
635
|
+
run_id: result.run_id,
|
|
636
|
+
label: result.label,
|
|
637
|
+
status: result.status,
|
|
638
|
+
snapshot: result.snapshot,
|
|
639
|
+
transcript: result.transcript,
|
|
640
|
+
tools: result.tools,
|
|
641
|
+
live_tools: result.live_tools,
|
|
642
|
+
permissions: result.permissions,
|
|
643
|
+
pending_permission: result.pending_permission,
|
|
644
|
+
final_output: finalOutput
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
function toWatchRunRef(run) {
|
|
648
|
+
return {
|
|
649
|
+
runId: run.runId,
|
|
650
|
+
label: run.label,
|
|
651
|
+
agent: run.agent,
|
|
652
|
+
runtimeId: run.runtimeId,
|
|
653
|
+
supervisorId: run.supervisorId
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
function createExtension(deps = defaultDeps) {
|
|
657
|
+
return async function(pi) {
|
|
658
|
+
const trackedRuns = /* @__PURE__ */ new Map();
|
|
659
|
+
let pollingActive = false;
|
|
660
|
+
let pollingGeneration = 0;
|
|
661
|
+
let pollInFlight = null;
|
|
662
|
+
let sessionCtx = null;
|
|
663
|
+
function upsertTrackedRun(run, status, blocking = false) {
|
|
664
|
+
const current = trackedRuns.get(run.runId);
|
|
665
|
+
trackedRuns.set(run.runId, {
|
|
666
|
+
runId: run.runId,
|
|
667
|
+
agent: run.agent ?? status?.agent ?? current?.agent ?? "unknown",
|
|
668
|
+
label: run.label ?? status?.label ?? current?.label ?? run.runId,
|
|
669
|
+
supervisorId: run.supervisorId ?? current?.supervisorId,
|
|
670
|
+
runtimeId: run.runtimeId ?? status?.runtime_id ?? current?.runtimeId,
|
|
671
|
+
startTime: current?.startTime ?? Date.now(),
|
|
672
|
+
lastStatus: status ?? current?.lastStatus,
|
|
673
|
+
blocking: current?.blocking ?? blocking,
|
|
674
|
+
permissionNotified: current?.permissionNotified
|
|
675
|
+
});
|
|
125
676
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
run.lastStatus = live;
|
|
132
|
-
entries.push({
|
|
677
|
+
async function resolveFinalOutput(run, status) {
|
|
678
|
+
const preview = finalOutputPreview(status);
|
|
679
|
+
if (preview) return preview;
|
|
680
|
+
try {
|
|
681
|
+
const inspected = await deps.inspectTool({
|
|
133
682
|
runId: run.runId,
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
phase: live.phase,
|
|
137
|
-
phaseLabel: live.phase_label,
|
|
138
|
-
agent: run.agent,
|
|
139
|
-
pendingPermission: !!live.pending_permission,
|
|
140
|
-
permissionDescription: live.pending_permission?.description
|
|
683
|
+
supervisorId: run.supervisorId,
|
|
684
|
+
limit: INSPECT_LIMIT
|
|
141
685
|
});
|
|
142
|
-
|
|
686
|
+
return finalOutputPreview(inspected.status, inspected.snapshot);
|
|
687
|
+
} catch {
|
|
688
|
+
return;
|
|
143
689
|
}
|
|
690
|
+
}
|
|
691
|
+
async function pollRuns() {
|
|
692
|
+
const liveMap = /* @__PURE__ */ new Map();
|
|
144
693
|
try {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
});
|
|
149
|
-
const result = Array.isArray(raw) ? raw[0] : raw;
|
|
150
|
-
if (!result) throw new Error("status response was empty");
|
|
151
|
-
run.lastStatus = result;
|
|
152
|
-
entries.push({
|
|
153
|
-
runId: run.runId,
|
|
154
|
-
label: run.label,
|
|
155
|
-
status: result.status,
|
|
156
|
-
phase: result.phase,
|
|
157
|
-
phaseLabel: result.phase_label,
|
|
158
|
-
agent: run.agent,
|
|
159
|
-
pendingPermission: !!result.pending_permission,
|
|
160
|
-
permissionDescription: result.pending_permission?.description
|
|
161
|
-
});
|
|
694
|
+
const allLive = await deps.statusTool({});
|
|
695
|
+
const liveList = Array.isArray(allLive) ? allLive : [allLive];
|
|
696
|
+
for (const result of liveList) liveMap.set(result.run_id, result);
|
|
162
697
|
} catch (err) {
|
|
163
|
-
console.error(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
698
|
+
console.error("avenor pollRuns: failed to list singleton runs", err);
|
|
699
|
+
}
|
|
700
|
+
const entries = [];
|
|
701
|
+
for (const run of trackedRuns.values()) {
|
|
702
|
+
const live = liveMap.get(run.runId);
|
|
703
|
+
if (live) {
|
|
704
|
+
liveMap.delete(live.run_id);
|
|
705
|
+
run.lastStatus = live;
|
|
706
|
+
entries.push({
|
|
707
|
+
runId: run.runId,
|
|
708
|
+
label: run.label,
|
|
709
|
+
status: live.status,
|
|
710
|
+
phase: live.phase,
|
|
711
|
+
phaseLabel: live.phase_label,
|
|
712
|
+
agent: run.agent,
|
|
713
|
+
pendingPermission: !!live.pending_permission,
|
|
714
|
+
permissionDescription: live.pending_permission?.description
|
|
715
|
+
});
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
try {
|
|
719
|
+
const raw = await deps.statusTool({
|
|
720
|
+
runId: run.runId,
|
|
721
|
+
supervisorId: run.supervisorId
|
|
722
|
+
});
|
|
723
|
+
const result = Array.isArray(raw) ? raw[0] : raw;
|
|
724
|
+
if (!result) throw new Error("status response was empty");
|
|
725
|
+
run.lastStatus = result;
|
|
726
|
+
entries.push({
|
|
727
|
+
runId: run.runId,
|
|
728
|
+
label: run.label,
|
|
729
|
+
status: result.status,
|
|
730
|
+
phase: result.phase,
|
|
731
|
+
phaseLabel: result.phase_label,
|
|
732
|
+
agent: run.agent,
|
|
733
|
+
pendingPermission: !!result.pending_permission,
|
|
734
|
+
permissionDescription: result.pending_permission?.description
|
|
735
|
+
});
|
|
736
|
+
} catch (err) {
|
|
737
|
+
console.error(`avenor pollRuns: statusTool failed for ${run.runId}`, err);
|
|
738
|
+
const previous = run.lastStatus;
|
|
739
|
+
entries.push({
|
|
740
|
+
runId: run.runId,
|
|
741
|
+
label: run.label,
|
|
742
|
+
status: previous?.status ?? "unavailable",
|
|
743
|
+
phase: previous?.phase,
|
|
744
|
+
phaseLabel: previous?.phase_label,
|
|
745
|
+
agent: run.agent,
|
|
746
|
+
pendingPermission: !!previous?.pending_permission,
|
|
747
|
+
permissionDescription: previous?.pending_permission?.description
|
|
748
|
+
});
|
|
749
|
+
}
|
|
175
750
|
}
|
|
751
|
+
for (const live of liveMap.values()) entries.push({
|
|
752
|
+
runId: live.run_id,
|
|
753
|
+
label: live.label,
|
|
754
|
+
status: live.status,
|
|
755
|
+
phase: live.phase,
|
|
756
|
+
phaseLabel: live.phase_label,
|
|
757
|
+
agent: live.agent ?? "unknown",
|
|
758
|
+
pendingPermission: !!live.pending_permission,
|
|
759
|
+
permissionDescription: live.pending_permission?.description
|
|
760
|
+
});
|
|
761
|
+
return entries;
|
|
176
762
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return entries;
|
|
188
|
-
}
|
|
189
|
-
function renderStatusLines(entries) {
|
|
190
|
-
const active = entries.filter((e) => !TERMINAL_STATUSES.has(e.status));
|
|
191
|
-
if (active.length === 0) return [];
|
|
192
|
-
return active.map((e) => {
|
|
193
|
-
const emoji = statusEmoji(e.status);
|
|
194
|
-
const perm = e.pendingPermission ? ` 🔒` : "";
|
|
195
|
-
const phase = e.phaseLabel ? ` [${e.phaseLabel}]` : "";
|
|
196
|
-
return `${emoji} ${e.label} — ${e.status}${phase}${perm}`;
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
function updateWidget(entries) {
|
|
200
|
-
if (!sessionCtx) return;
|
|
201
|
-
const lines = renderStatusLines(entries);
|
|
202
|
-
if (lines.length > 0) {
|
|
203
|
-
sessionCtx.ui.setWidget("avenor-status", lines);
|
|
204
|
-
const parts = lines.map((l) => l.slice(0, 60));
|
|
205
|
-
sessionCtx.ui.setStatus("avenor", parts.join(" "));
|
|
206
|
-
} else {
|
|
207
|
-
sessionCtx.ui.setWidget("avenor-status", void 0);
|
|
208
|
-
sessionCtx.ui.setStatus("avenor", "");
|
|
763
|
+
function updateWidget(entries) {
|
|
764
|
+
if (!sessionCtx) return;
|
|
765
|
+
const lines = renderStatusLines(entries);
|
|
766
|
+
if (lines.length > 0) {
|
|
767
|
+
sessionCtx.ui.setWidget("avenor-status", lines);
|
|
768
|
+
sessionCtx.ui.setStatus("avenor", lines.map((line) => line.slice(0, 60)).join(" "));
|
|
769
|
+
} else {
|
|
770
|
+
sessionCtx.ui.setWidget("avenor-status", void 0);
|
|
771
|
+
sessionCtx.ui.setStatus("avenor", "");
|
|
772
|
+
}
|
|
209
773
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
774
|
+
function startPolling() {
|
|
775
|
+
if (pollingActive) return;
|
|
776
|
+
pollingActive = true;
|
|
777
|
+
const generation = ++pollingGeneration;
|
|
778
|
+
const tick = async () => {
|
|
779
|
+
if (!pollingActive || generation !== pollingGeneration) return;
|
|
780
|
+
const prevStatuses = /* @__PURE__ */ new Map();
|
|
781
|
+
for (const [id, run] of trackedRuns) prevStatuses.set(id, run.lastStatus?.status);
|
|
782
|
+
const currentPoll = pollRuns();
|
|
783
|
+
pollInFlight = currentPoll;
|
|
784
|
+
const entries = await currentPoll.finally(() => {
|
|
785
|
+
if (pollInFlight === currentPoll) pollInFlight = null;
|
|
786
|
+
});
|
|
787
|
+
if (!pollingActive || generation !== pollingGeneration) return;
|
|
788
|
+
updateWidget(entries);
|
|
789
|
+
for (const entry of entries) {
|
|
790
|
+
const run = trackedRuns.get(entry.runId);
|
|
791
|
+
if (!run) continue;
|
|
792
|
+
if (isTerminalStatus(entry.status)) {
|
|
793
|
+
const prevStatus = prevStatuses.get(entry.runId);
|
|
794
|
+
if (!prevStatus || !isTerminalStatus(prevStatus)) {
|
|
795
|
+
sessionCtx?.ui.notify(`Sub-agent "${entry.label}" finished: ${entry.status}`, entry.status === "done" ? "info" : "warning");
|
|
796
|
+
if (!run.blocking && run.lastStatus) {
|
|
797
|
+
const finalOutput = await resolveFinalOutput(run, run.lastStatus);
|
|
798
|
+
try {
|
|
799
|
+
pi.sendUserMessage(buildCompletionText({
|
|
800
|
+
runId: run.runId,
|
|
801
|
+
label: run.label
|
|
802
|
+
}, run.lastStatus, finalOutput), { deliverAs: "followUp" });
|
|
803
|
+
} catch (err) {
|
|
804
|
+
console.error("avenor tick: sendUserMessage failed", err);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
trackedRuns.delete(entry.runId);
|
|
809
|
+
} else if (entry.pendingPermission && !run.blocking && !run.permissionNotified) {
|
|
810
|
+
run.permissionNotified = true;
|
|
811
|
+
if (run.lastStatus) try {
|
|
812
|
+
pi.sendUserMessage(buildWaitingText({
|
|
235
813
|
runId: run.runId,
|
|
236
|
-
label: run.label
|
|
814
|
+
label: run.label,
|
|
815
|
+
agent: run.agent
|
|
237
816
|
}, run.lastStatus), { deliverAs: "followUp" });
|
|
238
817
|
} catch (err) {
|
|
239
|
-
console.error("avenor tick: sendUserMessage failed", err);
|
|
818
|
+
console.error("avenor tick: sendUserMessage for permission failed", err);
|
|
240
819
|
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}, run.lastStatus), { deliverAs: "followUp" });
|
|
251
|
-
} catch (err) {
|
|
252
|
-
console.error("avenor tick: sendUserMessage for permission failed", err);
|
|
253
|
-
}
|
|
254
|
-
} else if (!entry.pendingPermission && run.permissionNotified) run.permissionNotified = false;
|
|
255
|
-
}
|
|
256
|
-
if (entries.some((e) => !TERMINAL_STATUSES.has(e.status)) && pollingActive && generation === pollingGeneration) setTimeout(() => {
|
|
257
|
-
tick().catch((err) => {
|
|
820
|
+
} else if (!entry.pendingPermission && run.permissionNotified) run.permissionNotified = false;
|
|
821
|
+
}
|
|
822
|
+
if (entries.some((entry) => !isTerminalStatus(entry.status)) && pollingActive && generation === pollingGeneration) setTimeout(() => {
|
|
823
|
+
tick().catch((err) => {
|
|
824
|
+
pollingActive = false;
|
|
825
|
+
console.error("avenor polling loop failed", err);
|
|
826
|
+
});
|
|
827
|
+
}, POLL_INTERVAL_MS);
|
|
828
|
+
else {
|
|
258
829
|
pollingActive = false;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
830
|
+
sessionCtx?.ui.setStatus("avenor", "");
|
|
831
|
+
sessionCtx?.ui.setWidget("avenor-status", void 0);
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
tick().catch((err) => {
|
|
263
835
|
pollingActive = false;
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
tick().catch((err) => {
|
|
836
|
+
console.error("avenor polling loop failed", err);
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
async function stopPolling() {
|
|
269
840
|
pollingActive = false;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
pollingActive = false;
|
|
275
|
-
pollingGeneration++;
|
|
276
|
-
await pollInFlight?.catch(() => {});
|
|
277
|
-
sessionCtx?.ui.setStatus("avenor", "");
|
|
278
|
-
sessionCtx?.ui.setWidget("avenor-status", void 0);
|
|
279
|
-
}
|
|
280
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
281
|
-
sessionCtx = ctx;
|
|
282
|
-
});
|
|
283
|
-
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
284
|
-
await stopPolling();
|
|
285
|
-
sessionCtx = null;
|
|
286
|
-
});
|
|
287
|
-
pi.on("before_agent_start", async (event, _ctx) => {
|
|
288
|
-
if (trackedRuns.size === 0) return;
|
|
289
|
-
const active = (await pollRuns()).filter((e) => !TERMINAL_STATUSES.has(e.status));
|
|
290
|
-
if (active.length === 0) return;
|
|
291
|
-
return { message: {
|
|
292
|
-
customType: "avenor-active-runs",
|
|
293
|
-
content: `Active sub-agents:\n${active.map((e) => {
|
|
294
|
-
const perm = e.pendingPermission ? ` [blocked: ${e.permissionDescription ?? "permission"}]` : "";
|
|
295
|
-
const phase = e.phaseLabel ? ` (${e.phaseLabel})` : "";
|
|
296
|
-
return ` • ${e.label} (${e.agent}): ${e.status}${phase}${perm}`;
|
|
297
|
-
}).join("\n")}`,
|
|
298
|
-
display: true
|
|
299
|
-
} };
|
|
300
|
-
});
|
|
301
|
-
pi.registerMessageRenderer("avenor-active-runs", (message, options, theme) => {
|
|
302
|
-
const lines = message.content.split("\n");
|
|
303
|
-
let text = "";
|
|
304
|
-
for (let i = 0; i < lines.length; i++) {
|
|
305
|
-
if (i === 0) text += theme.fg("info", theme.bold(lines[i]));
|
|
306
|
-
else text += theme.fg("muted", lines[i]);
|
|
307
|
-
if (i < lines.length - 1) text += "\n";
|
|
841
|
+
pollingGeneration++;
|
|
842
|
+
await pollInFlight?.catch(() => {});
|
|
843
|
+
sessionCtx?.ui.setStatus("avenor", "");
|
|
844
|
+
sessionCtx?.ui.setWidget("avenor-status", void 0);
|
|
308
845
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
"Use avenor_shutdown when done delegating work."
|
|
322
|
-
],
|
|
323
|
-
parameters: Type.Object({
|
|
324
|
-
agent: Type.String({ description: "Agent name (required, no default)" }),
|
|
325
|
-
prompt: Type.Optional(Type.String({ description: "Prompt text" })),
|
|
326
|
-
prompt_file: Type.Optional(Type.String({ description: "Path to prompt file" })),
|
|
327
|
-
dir: Type.Optional(Type.String({ description: "Working directory (defaults to session directory)" })),
|
|
328
|
-
label: Type.Optional(Type.String({ description: "Human-readable label for the run" })),
|
|
329
|
-
timeout: Type.Optional(Type.String({ description: "Timeout duration (e.g. 3600s)" })),
|
|
330
|
-
model: Type.Optional(Type.String({ description: "Model override" })),
|
|
331
|
-
backend: Type.Optional(Type.String({ description: "Backend override" })),
|
|
332
|
-
server_url: Type.Optional(Type.String({ description: "Backend server URL" })),
|
|
333
|
-
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" })),
|
|
334
|
-
wait: Type.Optional(Type.Boolean({ description: "Block until complete (default true)" }))
|
|
335
|
-
}),
|
|
336
|
-
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
337
|
-
if (signal?.aborted) return { content: [{
|
|
338
|
-
type: "text",
|
|
339
|
-
text: "Cancelled"
|
|
340
|
-
}] };
|
|
341
|
-
const wait = params.wait ?? true;
|
|
342
|
-
const dir = params.dir ?? ctx.cwd;
|
|
343
|
-
const label = params.label ?? `${params.agent}-${Date.now()}`;
|
|
344
|
-
const result = await spawnTool({
|
|
345
|
-
agent: params.agent,
|
|
346
|
-
prompt: params.prompt,
|
|
347
|
-
promptFile: params.prompt_file,
|
|
348
|
-
label,
|
|
349
|
-
dir,
|
|
350
|
-
timeout: params.timeout,
|
|
351
|
-
model: params.model,
|
|
352
|
-
backend: params.backend,
|
|
353
|
-
serverUrl: params.server_url,
|
|
354
|
-
supervisorId: params.supervisor_id
|
|
355
|
-
});
|
|
356
|
-
const supervisorId = statusSupervisorId(params.supervisor_id, result.supervisor_id);
|
|
357
|
-
trackedRuns.set(result.run_id, {
|
|
358
|
-
runId: result.run_id,
|
|
359
|
-
agent: params.agent,
|
|
360
|
-
label,
|
|
361
|
-
supervisorId,
|
|
362
|
-
runtimeId: result.runtime_id,
|
|
363
|
-
startTime: Date.now(),
|
|
364
|
-
blocking: wait
|
|
365
|
-
});
|
|
366
|
-
startPolling();
|
|
367
|
-
if (!wait) return {
|
|
368
|
-
content: [{
|
|
369
|
-
type: "text",
|
|
370
|
-
text: `Dispatched "${label}" (run_id: ${result.run_id}). Call avenor_status to check progress.`
|
|
371
|
-
}],
|
|
372
|
-
details: {
|
|
373
|
-
run_id: result.run_id,
|
|
374
|
-
label
|
|
846
|
+
function createWatchDeps() {
|
|
847
|
+
async function resolveSupervisorSocket(run) {
|
|
848
|
+
if (run.supervisorId) return run.supervisorId;
|
|
849
|
+
return (await deps.Supervisor.get()).supervisorId;
|
|
850
|
+
}
|
|
851
|
+
async function withClient(run, work) {
|
|
852
|
+
const socket = await resolveSupervisorSocket(run);
|
|
853
|
+
const client = await deps.dial(socket);
|
|
854
|
+
try {
|
|
855
|
+
return await work(client);
|
|
856
|
+
} finally {
|
|
857
|
+
client.close();
|
|
375
858
|
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
859
|
+
}
|
|
860
|
+
return {
|
|
861
|
+
inspectRun(run) {
|
|
862
|
+
return deps.inspectTool({
|
|
863
|
+
runId: run.runId,
|
|
864
|
+
supervisorId: run.supervisorId,
|
|
865
|
+
limit: INSPECT_LIMIT
|
|
866
|
+
});
|
|
867
|
+
},
|
|
868
|
+
async observeRun(run, afterSeq, initialSnapshot) {
|
|
869
|
+
const runtimeId = run.runtimeId;
|
|
870
|
+
if (!runtimeId) return null;
|
|
871
|
+
const socket = await resolveSupervisorSocket(run);
|
|
872
|
+
const client = await deps.dial(socket);
|
|
873
|
+
return deps.observeRun(client, {
|
|
874
|
+
runtime_id: runtimeId,
|
|
875
|
+
replay: true,
|
|
876
|
+
after_seq: afterSeq,
|
|
877
|
+
initial_snapshot: initialSnapshot,
|
|
878
|
+
close_client: true
|
|
879
|
+
});
|
|
880
|
+
},
|
|
881
|
+
cancelRun(run, runtimeId) {
|
|
882
|
+
return withClient(run, async (client) => {
|
|
883
|
+
await client.cancel(runtimeId);
|
|
884
|
+
});
|
|
885
|
+
},
|
|
886
|
+
promptRun(run, runtimeId, text) {
|
|
887
|
+
return withClient(run, async (client) => {
|
|
888
|
+
await client.prompt(runtimeId, text);
|
|
889
|
+
});
|
|
890
|
+
},
|
|
891
|
+
followUpRun(run, text) {
|
|
892
|
+
return deps.followUpTool({
|
|
893
|
+
runId: run.runId,
|
|
894
|
+
message: text,
|
|
895
|
+
supervisorId: run.supervisorId
|
|
896
|
+
});
|
|
385
897
|
}
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
async function followUpTrackedRun(run, text) {
|
|
901
|
+
const result = await deps.followUpTool({
|
|
902
|
+
runId: run.runId,
|
|
903
|
+
message: text,
|
|
904
|
+
supervisorId: run.supervisorId
|
|
386
905
|
});
|
|
906
|
+
try {
|
|
907
|
+
const rawStatus = await deps.statusTool({
|
|
908
|
+
runId: result.run_id,
|
|
909
|
+
supervisorId: run.supervisorId
|
|
910
|
+
});
|
|
911
|
+
const status = Array.isArray(rawStatus) ? rawStatus[0] : rawStatus;
|
|
912
|
+
upsertTrackedRun({
|
|
913
|
+
runId: result.run_id,
|
|
914
|
+
label: result.label,
|
|
915
|
+
agent: status?.agent ?? run.agent,
|
|
916
|
+
runtimeId: status?.runtime_id,
|
|
917
|
+
supervisorId: run.supervisorId
|
|
918
|
+
}, status, false);
|
|
919
|
+
startPolling();
|
|
920
|
+
} catch {
|
|
921
|
+
upsertTrackedRun({
|
|
922
|
+
runId: result.run_id,
|
|
923
|
+
label: result.label,
|
|
924
|
+
agent: run.agent,
|
|
925
|
+
supervisorId: run.supervisorId
|
|
926
|
+
}, void 0, false);
|
|
927
|
+
startPolling();
|
|
928
|
+
}
|
|
929
|
+
return result;
|
|
930
|
+
}
|
|
931
|
+
async function waitForRunWithObserver(run, signal, onUpdate) {
|
|
932
|
+
let observer = null;
|
|
933
|
+
let unsubscribe;
|
|
934
|
+
let lastUpdate = "";
|
|
935
|
+
let abortListener;
|
|
936
|
+
const emit = (snapshot) => {
|
|
937
|
+
const text = snapshotProgressText(run.label ?? run.runId, snapshot);
|
|
938
|
+
if (text === lastUpdate) return;
|
|
939
|
+
lastUpdate = text;
|
|
940
|
+
onUpdate?.({
|
|
941
|
+
content: [{
|
|
942
|
+
type: "text",
|
|
943
|
+
text
|
|
944
|
+
}],
|
|
945
|
+
details: {
|
|
946
|
+
status: snapshot.pending_permission ? "waiting" : snapshot.ended ? "done" : "running",
|
|
947
|
+
run_id: run.runId
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
};
|
|
951
|
+
const waitPromise = new Promise((resolve) => {
|
|
952
|
+
const complete = () => resolve("settled");
|
|
953
|
+
(async () => {
|
|
954
|
+
try {
|
|
955
|
+
observer = await createWatchDeps().observeRun(run);
|
|
956
|
+
if (!observer) {
|
|
957
|
+
complete();
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
const maybeComplete = (snapshot) => {
|
|
961
|
+
emit(snapshot);
|
|
962
|
+
if (snapshot.pending_permission || snapshot.ended) complete();
|
|
963
|
+
};
|
|
964
|
+
unsubscribe = observer.subscribe((snapshot) => {
|
|
965
|
+
maybeComplete(snapshot);
|
|
966
|
+
});
|
|
967
|
+
const current = observer.snapshot();
|
|
968
|
+
if (current.last_event || current.transcript.length > 0 || current.pending_permission || current.ended) maybeComplete(current);
|
|
969
|
+
} catch {
|
|
970
|
+
complete();
|
|
971
|
+
}
|
|
972
|
+
})();
|
|
973
|
+
});
|
|
974
|
+
const abortPromise = signal ? new Promise((resolve) => {
|
|
975
|
+
if (signal.aborted) {
|
|
976
|
+
resolve("aborted");
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
const onAbort = () => resolve("aborted");
|
|
980
|
+
abortListener = onAbort;
|
|
981
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
982
|
+
}) : void 0;
|
|
983
|
+
const outcome = abortPromise ? await Promise.race([waitPromise, abortPromise]) : await waitPromise;
|
|
984
|
+
if (signal && abortListener) signal.removeEventListener("abort", abortListener);
|
|
985
|
+
unsubscribe?.();
|
|
986
|
+
await observer?.close().catch(() => {});
|
|
987
|
+
if (outcome === "aborted") return { aborted: true };
|
|
988
|
+
try {
|
|
989
|
+
const raw = await deps.statusTool({
|
|
990
|
+
runId: run.runId,
|
|
991
|
+
supervisorId: run.supervisorId
|
|
992
|
+
});
|
|
993
|
+
return {
|
|
994
|
+
status: Array.isArray(raw) ? raw[0] : raw,
|
|
995
|
+
aborted: false
|
|
996
|
+
};
|
|
997
|
+
} catch {
|
|
998
|
+
return { aborted: false };
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
async function waitForRunByPolling(run, signal, onUpdate) {
|
|
387
1002
|
let firstPoll = true;
|
|
388
1003
|
while (!signal?.aborted) {
|
|
389
1004
|
if (!firstPoll) await sleep(POLL_INTERVAL_MS);
|
|
390
1005
|
firstPoll = false;
|
|
391
|
-
let raw;
|
|
392
1006
|
try {
|
|
393
|
-
raw = await statusTool({
|
|
394
|
-
runId:
|
|
395
|
-
supervisorId
|
|
1007
|
+
const raw = await deps.statusTool({
|
|
1008
|
+
runId: run.runId,
|
|
1009
|
+
supervisorId: run.supervisorId
|
|
1010
|
+
});
|
|
1011
|
+
const status = Array.isArray(raw) ? raw[0] : raw;
|
|
1012
|
+
if (!status) continue;
|
|
1013
|
+
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})` : ""}`;
|
|
1014
|
+
onUpdate?.({
|
|
1015
|
+
content: [{
|
|
1016
|
+
type: "text",
|
|
1017
|
+
text
|
|
1018
|
+
}],
|
|
1019
|
+
details: {
|
|
1020
|
+
status: status.status,
|
|
1021
|
+
run_id: run.runId
|
|
1022
|
+
}
|
|
396
1023
|
});
|
|
1024
|
+
if (status.status === "waiting" || isTerminalStatus(status.status)) return {
|
|
1025
|
+
status,
|
|
1026
|
+
aborted: false
|
|
1027
|
+
};
|
|
397
1028
|
} catch (err) {
|
|
398
1029
|
console.error("avenor spawn poll: statusTool failed", err);
|
|
399
|
-
continue;
|
|
400
1030
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
1031
|
+
}
|
|
1032
|
+
return { aborted: true };
|
|
1033
|
+
}
|
|
1034
|
+
async function waitForRun(run, signal, onUpdate) {
|
|
1035
|
+
const observed = await waitForRunWithObserver(run, signal, onUpdate);
|
|
1036
|
+
if (observed.status || observed.aborted) return observed;
|
|
1037
|
+
return waitForRunByPolling(run, signal, onUpdate);
|
|
1038
|
+
}
|
|
1039
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
1040
|
+
sessionCtx = ctx;
|
|
1041
|
+
});
|
|
1042
|
+
pi.on("session_shutdown", async () => {
|
|
1043
|
+
await stopPolling();
|
|
1044
|
+
sessionCtx = null;
|
|
1045
|
+
});
|
|
1046
|
+
pi.on("before_agent_start", async () => {
|
|
1047
|
+
if (trackedRuns.size === 0) return;
|
|
1048
|
+
const active = (await pollRuns()).filter((entry) => !isTerminalStatus(entry.status));
|
|
1049
|
+
if (active.length === 0) return;
|
|
1050
|
+
return { message: {
|
|
1051
|
+
customType: "avenor-active-runs",
|
|
1052
|
+
content: `Active sub-agents:\n${active.map((entry) => {
|
|
1053
|
+
const perm = entry.pendingPermission ? ` [blocked: ${entry.permissionDescription ?? "permission"}]` : "";
|
|
1054
|
+
const phase = entry.phaseLabel ? ` (${entry.phaseLabel})` : "";
|
|
1055
|
+
return ` • ${entry.label} (${entry.agent}): ${entry.status}${phase}${perm}`;
|
|
1056
|
+
}).join("\n")}`,
|
|
1057
|
+
display: true
|
|
1058
|
+
} };
|
|
1059
|
+
});
|
|
1060
|
+
pi.registerMessageRenderer("avenor-active-runs", (message, _options, theme) => {
|
|
1061
|
+
const lines = message.content.split("\n");
|
|
1062
|
+
let text = "";
|
|
1063
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1064
|
+
text += i === 0 ? theme.fg("accent", theme.bold(lines[i] ?? "")) : theme.fg("muted", lines[i] ?? "");
|
|
1065
|
+
if (i < lines.length - 1) text += "\n";
|
|
1066
|
+
}
|
|
1067
|
+
return new Text(text, 0, 0);
|
|
1068
|
+
});
|
|
1069
|
+
pi.registerTool({
|
|
1070
|
+
name: "avenor_spawn",
|
|
1071
|
+
label: "Avenor Spawn",
|
|
1072
|
+
description: "Dispatch an agent run via avenor. Blocks by default, showing live progress. Set wait=false for fire-and-forget.",
|
|
1073
|
+
promptSnippet: "Dispatch sub-agent runs via avenor (spawn, status, inspect, events, follow-up, shutdown)",
|
|
1074
|
+
promptGuidelines: [
|
|
1075
|
+
"Use avenor_spawn to delegate well-defined tasks to sub-agents.",
|
|
1076
|
+
"Use avenor_status to check progress of fire-and-forget runs.",
|
|
1077
|
+
"Use avenor_inspect to review a bounded transcript, tools, and final output from a run.",
|
|
1078
|
+
"Use avenor_events only when raw event payloads are specifically needed.",
|
|
1079
|
+
"Use avenor_follow_up to iterate on a completed run.",
|
|
1080
|
+
"Use avenor_shutdown when done delegating work."
|
|
1081
|
+
],
|
|
1082
|
+
parameters: Type.Object({
|
|
1083
|
+
agent: Type.String({ description: "Agent name (required, no default)" }),
|
|
1084
|
+
prompt: Type.Optional(Type.String({ description: "Prompt text" })),
|
|
1085
|
+
prompt_file: Type.Optional(Type.String({ description: "Path to prompt file" })),
|
|
1086
|
+
dir: Type.Optional(Type.String({ description: "Working directory (defaults to session directory)" })),
|
|
1087
|
+
label: Type.Optional(Type.String({ description: "Human-readable label for the run" })),
|
|
1088
|
+
timeout: Type.Optional(Type.String({ description: "Timeout duration (e.g. 3600s)" })),
|
|
1089
|
+
model: Type.Optional(Type.String({ description: "Model override" })),
|
|
1090
|
+
backend: Type.Optional(Type.String({ description: "Backend override" })),
|
|
1091
|
+
server_url: Type.Optional(Type.String({ description: "Backend server URL" })),
|
|
1092
|
+
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" })),
|
|
1093
|
+
wait: Type.Optional(Type.Boolean({ description: "Block until complete (default true)" }))
|
|
1094
|
+
}),
|
|
1095
|
+
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
1096
|
+
if (signal?.aborted) return { content: [{
|
|
1097
|
+
type: "text",
|
|
1098
|
+
text: "Cancelled"
|
|
1099
|
+
}] };
|
|
1100
|
+
const wait = params.wait ?? true;
|
|
1101
|
+
const dir = params.dir ?? ctx.cwd;
|
|
1102
|
+
const label = params.label ?? `${params.agent}-${Date.now()}`;
|
|
1103
|
+
const result = await deps.spawnTool({
|
|
1104
|
+
agent: params.agent,
|
|
1105
|
+
prompt: params.prompt,
|
|
1106
|
+
promptFile: params.prompt_file,
|
|
1107
|
+
label,
|
|
1108
|
+
dir,
|
|
1109
|
+
timeout: params.timeout,
|
|
1110
|
+
model: params.model,
|
|
1111
|
+
backend: params.backend,
|
|
1112
|
+
serverUrl: params.server_url,
|
|
1113
|
+
supervisorId: params.supervisor_id
|
|
1114
|
+
});
|
|
1115
|
+
const supervisorId = statusSupervisorId(params.supervisor_id, result.supervisor_id);
|
|
1116
|
+
const runRef = {
|
|
1117
|
+
runId: result.run_id,
|
|
1118
|
+
agent: params.agent,
|
|
1119
|
+
label,
|
|
1120
|
+
supervisorId,
|
|
1121
|
+
runtimeId: result.runtime_id
|
|
1122
|
+
};
|
|
1123
|
+
upsertTrackedRun(runRef, void 0, wait);
|
|
1124
|
+
startPolling();
|
|
1125
|
+
if (!wait) return {
|
|
404
1126
|
content: [{
|
|
405
1127
|
type: "text",
|
|
406
|
-
text: `
|
|
1128
|
+
text: `Dispatched "${label}" (run_id: ${result.run_id}). Call avenor_status to check progress or avenor_inspect for a bounded transcript snapshot.`
|
|
407
1129
|
}],
|
|
408
1130
|
details: {
|
|
409
|
-
|
|
1131
|
+
run_id: result.run_id,
|
|
1132
|
+
label
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
onUpdate?.({
|
|
1136
|
+
content: [{
|
|
1137
|
+
type: "text",
|
|
1138
|
+
text: `Starting ${label}…`
|
|
1139
|
+
}],
|
|
1140
|
+
details: {
|
|
1141
|
+
status: "running",
|
|
410
1142
|
run_id: result.run_id
|
|
411
1143
|
}
|
|
412
1144
|
});
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
runId: result.run_id,
|
|
418
|
-
limit: 3
|
|
419
|
-
});
|
|
420
|
-
const last = eventsResult.events[eventsResult.events.length - 1];
|
|
421
|
-
if (last) lastAction = summarizeEvent(last);
|
|
422
|
-
} catch (err) {
|
|
423
|
-
console.error("avenor spawn poll: eventsTool failed", err);
|
|
424
|
-
}
|
|
425
|
-
onUpdate?.({
|
|
1145
|
+
const waitResult = await waitForRun(runRef, signal, onUpdate);
|
|
1146
|
+
if (waitResult.aborted) {
|
|
1147
|
+
trackedRuns.delete(result.run_id);
|
|
1148
|
+
return {
|
|
426
1149
|
content: [{
|
|
427
1150
|
type: "text",
|
|
428
|
-
text:
|
|
1151
|
+
text: `Monitoring of "${label}" (run_id: ${result.run_id}) was interrupted. Use avenor_status or avenor_inspect to check.`
|
|
429
1152
|
}],
|
|
430
|
-
details: {
|
|
431
|
-
|
|
432
|
-
run_id: result.run_id,
|
|
433
|
-
lastAction
|
|
434
|
-
}
|
|
435
|
-
});
|
|
1153
|
+
details: { run_id: result.run_id }
|
|
1154
|
+
};
|
|
436
1155
|
}
|
|
1156
|
+
const status = waitResult.status;
|
|
1157
|
+
if (!status) return {
|
|
1158
|
+
content: [{
|
|
1159
|
+
type: "text",
|
|
1160
|
+
text: `Sub-agent "${label}" is still running. Use avenor_status or avenor_inspect to check progress.`
|
|
1161
|
+
}],
|
|
1162
|
+
details: {
|
|
1163
|
+
status: "running",
|
|
1164
|
+
run_id: result.run_id
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
upsertTrackedRun(runRef, status, wait);
|
|
437
1168
|
if (status.status === "waiting") {
|
|
438
1169
|
const tracked = trackedRuns.get(result.run_id);
|
|
439
1170
|
if (tracked) tracked.permissionNotified = true;
|
|
@@ -454,7 +1185,11 @@ async function src_default(pi) {
|
|
|
454
1185
|
}
|
|
455
1186
|
};
|
|
456
1187
|
}
|
|
457
|
-
if (
|
|
1188
|
+
if (isTerminalStatus(status.status)) {
|
|
1189
|
+
const finalOutput = await resolveFinalOutput({
|
|
1190
|
+
runId: result.run_id,
|
|
1191
|
+
supervisorId
|
|
1192
|
+
}, status);
|
|
458
1193
|
trackedRuns.delete(result.run_id);
|
|
459
1194
|
return {
|
|
460
1195
|
content: [{
|
|
@@ -462,286 +1197,292 @@ async function src_default(pi) {
|
|
|
462
1197
|
text: buildCompletionText({
|
|
463
1198
|
runId: result.run_id,
|
|
464
1199
|
label
|
|
465
|
-
}, status)
|
|
1200
|
+
}, status, finalOutput)
|
|
466
1201
|
}],
|
|
467
1202
|
details: {
|
|
468
1203
|
status: status.status,
|
|
469
1204
|
run_id: result.run_id,
|
|
470
|
-
session_id: status.session_id
|
|
1205
|
+
session_id: status.session_id,
|
|
1206
|
+
final_output: finalOutput
|
|
471
1207
|
}
|
|
472
1208
|
};
|
|
473
1209
|
}
|
|
1210
|
+
return {
|
|
1211
|
+
content: [{
|
|
1212
|
+
type: "text",
|
|
1213
|
+
text: `${label} is ${status.status}. Use avenor_status or avenor_inspect to check progress.`
|
|
1214
|
+
}],
|
|
1215
|
+
details: {
|
|
1216
|
+
status: status.status,
|
|
1217
|
+
run_id: result.run_id
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
},
|
|
1221
|
+
renderCall(args, theme) {
|
|
1222
|
+
const agent = theme.fg("toolTitle", theme.bold("avenor_spawn "));
|
|
1223
|
+
const agentName = theme.fg("accent", args.agent);
|
|
1224
|
+
const label = args.label ? theme.fg("dim", ` "${args.label}"`) : "";
|
|
1225
|
+
const wait = args.wait === false ? theme.fg("warning", " [async]") : "";
|
|
1226
|
+
return new Text(agent + agentName + label + wait, 0, 0);
|
|
1227
|
+
},
|
|
1228
|
+
renderResult(result, { isPartial }, theme) {
|
|
1229
|
+
if (isPartial) {
|
|
1230
|
+
const status = result.details?.status ?? "running";
|
|
1231
|
+
const emoji = statusEmoji(String(status));
|
|
1232
|
+
return new Text(theme.fg("accent", `${emoji} ${result.content?.[0]?.text ?? "Running…"}`), 0, 0);
|
|
1233
|
+
}
|
|
1234
|
+
const status = String(result.details?.status ?? "");
|
|
1235
|
+
if (status && isTerminalStatus(status)) {
|
|
1236
|
+
const emoji = statusEmoji(status);
|
|
1237
|
+
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);
|
|
1238
|
+
}
|
|
1239
|
+
return new Text((result.content?.[0]?.text ?? "").split("\n").map((line) => theme.fg("muted", line)).join("\n"), 0, 0);
|
|
474
1240
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
const status = result.details?.status;
|
|
497
|
-
if (status && TERMINAL_STATUSES.has(status)) {
|
|
498
|
-
const emoji = statusEmoji(status);
|
|
499
|
-
const header = theme.fg(status === "done" ? "success" : "error", `${emoji} ${status}`);
|
|
500
|
-
const lines = (result.content?.[0]?.text ?? "").split("\n");
|
|
501
|
-
return new Text(header + "\n" + lines.map((l) => theme.fg("muted", ` ${l}`)).join("\n"), 0, 0);
|
|
1241
|
+
});
|
|
1242
|
+
pi.registerTool({
|
|
1243
|
+
name: "avenor_status",
|
|
1244
|
+
label: "Avenor Status",
|
|
1245
|
+
description: "Get status of a run or all runs. Surfaces pending permission requests.",
|
|
1246
|
+
parameters: Type.Object({
|
|
1247
|
+
run_id: Type.Optional(Type.String({ description: "Specific run ID to query" })),
|
|
1248
|
+
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
|
|
1249
|
+
}),
|
|
1250
|
+
async execute(_toolCallId, params) {
|
|
1251
|
+
const result = await deps.statusTool({
|
|
1252
|
+
runId: params.run_id,
|
|
1253
|
+
supervisorId: params.supervisor_id
|
|
1254
|
+
});
|
|
1255
|
+
return {
|
|
1256
|
+
content: [{
|
|
1257
|
+
type: "text",
|
|
1258
|
+
text: JSON.stringify(result, null, 2)
|
|
1259
|
+
}],
|
|
1260
|
+
details: result
|
|
1261
|
+
};
|
|
502
1262
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
pi.registerTool({
|
|
529
|
-
name: "avenor_answer_permission",
|
|
530
|
-
label: "Avenor Answer Permission",
|
|
531
|
-
description: "Answer a pending permission request for a sub-agent. Pass option_id from avenor_status pending_permission.options.",
|
|
532
|
-
parameters: Type.Object({
|
|
533
|
-
run_id: Type.String({ description: "Run ID with the pending permission" }),
|
|
534
|
-
option_id: Type.String({ description: "allow_once | allow_always | deny" }),
|
|
535
|
-
request_id: Type.Optional(Type.String({ description: "Permission request ID (auto-discovered if omitted)" })),
|
|
536
|
-
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
|
|
537
|
-
}),
|
|
538
|
-
async execute(_toolCallId, params) {
|
|
539
|
-
const result = await answerPermissionTool({
|
|
540
|
-
runId: params.run_id,
|
|
541
|
-
optionId: params.option_id,
|
|
542
|
-
requestId: params.request_id,
|
|
543
|
-
supervisorId: params.supervisor_id
|
|
544
|
-
});
|
|
545
|
-
return {
|
|
546
|
-
content: [{
|
|
547
|
-
type: "text",
|
|
548
|
-
text: JSON.stringify(result, null, 2)
|
|
549
|
-
}],
|
|
550
|
-
details: result
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
});
|
|
554
|
-
pi.registerTool({
|
|
555
|
-
name: "avenor_follow_up",
|
|
556
|
-
label: "Avenor Follow Up",
|
|
557
|
-
description: "Resume a completed run with a follow-up message.",
|
|
558
|
-
parameters: Type.Object({
|
|
559
|
-
run_id: Type.String({ description: "Completed run ID to resume" }),
|
|
560
|
-
message: Type.String({ description: "Follow-up message text" }),
|
|
561
|
-
label: Type.Optional(Type.String({ description: "Override label (defaults to <original>-followup)" })),
|
|
562
|
-
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
|
|
563
|
-
}),
|
|
564
|
-
async execute(_toolCallId, params) {
|
|
565
|
-
const result = await followUpTool({
|
|
566
|
-
runId: params.run_id,
|
|
567
|
-
message: params.message,
|
|
568
|
-
label: params.label,
|
|
569
|
-
supervisorId: params.supervisor_id
|
|
570
|
-
});
|
|
571
|
-
return {
|
|
572
|
-
content: [{
|
|
573
|
-
type: "text",
|
|
574
|
-
text: JSON.stringify(result, null, 2)
|
|
575
|
-
}],
|
|
576
|
-
details: result
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
});
|
|
580
|
-
pi.registerTool({
|
|
581
|
-
name: "avenor_events",
|
|
582
|
-
label: "Avenor Events",
|
|
583
|
-
description: "Read events from a run. Filter by type. Returns last N events.",
|
|
584
|
-
parameters: Type.Object({
|
|
585
|
-
run_id: Type.String({ description: "Run ID to read events from" }),
|
|
586
|
-
types: Type.Optional(Type.Array(Type.String(), { description: "Filter by event types" })),
|
|
587
|
-
limit: Type.Optional(Type.Number({ description: "Max events to return (default 50)" })),
|
|
588
|
-
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
|
|
589
|
-
}),
|
|
590
|
-
async execute(_toolCallId, params) {
|
|
591
|
-
const result = await eventsTool({
|
|
592
|
-
runId: params.run_id,
|
|
593
|
-
types: params.types,
|
|
594
|
-
limit: params.limit,
|
|
595
|
-
supervisorId: params.supervisor_id
|
|
596
|
-
});
|
|
597
|
-
return {
|
|
598
|
-
content: [{
|
|
599
|
-
type: "text",
|
|
600
|
-
text: JSON.stringify(result, null, 2)
|
|
601
|
-
}],
|
|
602
|
-
details: result
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
});
|
|
606
|
-
pi.registerTool({
|
|
607
|
-
name: "avenor_shutdown",
|
|
608
|
-
label: "Avenor Shutdown",
|
|
609
|
-
description: "Shut down the avenor supervisor and clean up temp files.",
|
|
610
|
-
parameters: Type.Object({
|
|
611
|
-
supervisor_id: Type.Optional(Type.String({ description: "Supervisor to shut down (defaults to singleton)" })),
|
|
612
|
-
force: Type.Optional(Type.Boolean({ description: "Force shutdown instead of graceful" }))
|
|
613
|
-
}),
|
|
614
|
-
async execute(_toolCallId, params) {
|
|
615
|
-
await stopPolling();
|
|
616
|
-
trackedRuns.clear();
|
|
617
|
-
const result = await shutdownTool({
|
|
618
|
-
supervisorId: params.supervisor_id,
|
|
619
|
-
force: params.force
|
|
620
|
-
});
|
|
621
|
-
return {
|
|
622
|
-
content: [{
|
|
623
|
-
type: "text",
|
|
624
|
-
text: JSON.stringify(result, null, 2)
|
|
625
|
-
}],
|
|
626
|
-
details: result
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
});
|
|
630
|
-
pi.registerCommand("avenor-status", {
|
|
631
|
-
description: "Show status of all avenor runs",
|
|
632
|
-
handler: async (_args, ctx) => {
|
|
633
|
-
const entries = await pollRuns();
|
|
634
|
-
if (entries.length === 0) {
|
|
635
|
-
ctx.ui.notify("No active avenor runs", "info");
|
|
636
|
-
return;
|
|
1263
|
+
});
|
|
1264
|
+
pi.registerTool({
|
|
1265
|
+
name: "avenor_inspect",
|
|
1266
|
+
label: "Avenor Inspect",
|
|
1267
|
+
description: "Inspect a run with bounded snapshot, transcript, tool, permission, and final output details.",
|
|
1268
|
+
parameters: Type.Object({
|
|
1269
|
+
run_id: Type.String({ description: "Run ID to inspect" }),
|
|
1270
|
+
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" })),
|
|
1271
|
+
limit: Type.Optional(Type.Number({ description: "Replay event limit (default 128)" })),
|
|
1272
|
+
after_seq: Type.Optional(Type.Number({ description: "Only include events after this sequence number" }))
|
|
1273
|
+
}),
|
|
1274
|
+
async execute(_toolCallId, params) {
|
|
1275
|
+
const payload = buildInspectPayload(await deps.inspectTool({
|
|
1276
|
+
runId: params.run_id,
|
|
1277
|
+
supervisorId: params.supervisor_id,
|
|
1278
|
+
limit: params.limit,
|
|
1279
|
+
after_seq: params.after_seq
|
|
1280
|
+
}));
|
|
1281
|
+
return {
|
|
1282
|
+
content: [{
|
|
1283
|
+
type: "text",
|
|
1284
|
+
text: JSON.stringify(payload, null, 2)
|
|
1285
|
+
}],
|
|
1286
|
+
details: payload
|
|
1287
|
+
};
|
|
637
1288
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
})
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
1289
|
+
});
|
|
1290
|
+
pi.registerTool({
|
|
1291
|
+
name: "avenor_answer_permission",
|
|
1292
|
+
label: "Avenor Answer Permission",
|
|
1293
|
+
description: "Answer a pending permission request for a sub-agent. Pass option_id from avenor_status pending_permission.options.",
|
|
1294
|
+
parameters: Type.Object({
|
|
1295
|
+
run_id: Type.String({ description: "Run ID with the pending permission" }),
|
|
1296
|
+
option_id: Type.String({ description: "allow_once | allow_always | deny" }),
|
|
1297
|
+
request_id: Type.Optional(Type.String({ description: "Permission request ID (auto-discovered if omitted)" })),
|
|
1298
|
+
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
|
|
1299
|
+
}),
|
|
1300
|
+
async execute(_toolCallId, params) {
|
|
1301
|
+
const result = await deps.answerPermissionTool({
|
|
1302
|
+
runId: params.run_id,
|
|
1303
|
+
optionId: params.option_id,
|
|
1304
|
+
requestId: params.request_id,
|
|
1305
|
+
supervisorId: params.supervisor_id
|
|
1306
|
+
});
|
|
1307
|
+
return {
|
|
1308
|
+
content: [{
|
|
1309
|
+
type: "text",
|
|
1310
|
+
text: JSON.stringify(result, null, 2)
|
|
1311
|
+
}],
|
|
1312
|
+
details: result
|
|
1313
|
+
};
|
|
655
1314
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
1315
|
+
});
|
|
1316
|
+
pi.registerTool({
|
|
1317
|
+
name: "avenor_follow_up",
|
|
1318
|
+
label: "Avenor Follow Up",
|
|
1319
|
+
description: "Resume a completed run with a follow-up message.",
|
|
1320
|
+
parameters: Type.Object({
|
|
1321
|
+
run_id: Type.String({ description: "Completed run ID to resume" }),
|
|
1322
|
+
message: Type.String({ description: "Follow-up message text" }),
|
|
1323
|
+
label: Type.Optional(Type.String({ description: "Override label (defaults to <original>-followup)" })),
|
|
1324
|
+
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
|
|
1325
|
+
}),
|
|
1326
|
+
async execute(_toolCallId, params) {
|
|
1327
|
+
const result = await deps.followUpTool({
|
|
1328
|
+
runId: params.run_id,
|
|
1329
|
+
message: params.message,
|
|
1330
|
+
label: params.label,
|
|
1331
|
+
supervisorId: params.supervisor_id
|
|
1332
|
+
});
|
|
1333
|
+
try {
|
|
1334
|
+
const rawStatus = await deps.statusTool({
|
|
1335
|
+
runId: result.run_id,
|
|
1336
|
+
supervisorId: params.supervisor_id
|
|
1337
|
+
});
|
|
1338
|
+
const status = Array.isArray(rawStatus) ? rawStatus[0] : rawStatus;
|
|
1339
|
+
upsertTrackedRun({
|
|
1340
|
+
runId: result.run_id,
|
|
1341
|
+
label: result.label,
|
|
1342
|
+
agent: status?.agent,
|
|
1343
|
+
runtimeId: status?.runtime_id,
|
|
1344
|
+
supervisorId: params.supervisor_id
|
|
1345
|
+
}, status, false);
|
|
1346
|
+
} catch {
|
|
1347
|
+
upsertTrackedRun({
|
|
1348
|
+
runId: result.run_id,
|
|
1349
|
+
label: result.label,
|
|
1350
|
+
supervisorId: params.supervisor_id
|
|
1351
|
+
}, void 0, false);
|
|
662
1352
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
1353
|
+
startPolling();
|
|
1354
|
+
return {
|
|
1355
|
+
content: [{
|
|
1356
|
+
type: "text",
|
|
1357
|
+
text: JSON.stringify(result, null, 2)
|
|
1358
|
+
}],
|
|
1359
|
+
details: result
|
|
1360
|
+
};
|
|
668
1361
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
1362
|
+
});
|
|
1363
|
+
pi.registerTool({
|
|
1364
|
+
name: "avenor_events",
|
|
1365
|
+
label: "Avenor Events",
|
|
1366
|
+
description: "Read events from a run. Filter by type. Returns last N events.",
|
|
1367
|
+
parameters: Type.Object({
|
|
1368
|
+
run_id: Type.String({ description: "Run ID to read events from" }),
|
|
1369
|
+
types: Type.Optional(Type.Array(Type.String(), { description: "Filter by event types" })),
|
|
1370
|
+
limit: Type.Optional(Type.Number({ description: "Max events to return (default 50)" })),
|
|
1371
|
+
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
|
|
1372
|
+
}),
|
|
1373
|
+
async execute(_toolCallId, params) {
|
|
1374
|
+
const result = await deps.eventsTool({
|
|
1375
|
+
runId: params.run_id,
|
|
1376
|
+
types: params.types,
|
|
1377
|
+
limit: params.limit,
|
|
1378
|
+
supervisorId: params.supervisor_id
|
|
675
1379
|
});
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1380
|
+
return {
|
|
1381
|
+
content: [{
|
|
1382
|
+
type: "text",
|
|
1383
|
+
text: JSON.stringify(result, null, 2)
|
|
1384
|
+
}],
|
|
1385
|
+
details: result
|
|
1386
|
+
};
|
|
680
1387
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
1388
|
+
});
|
|
1389
|
+
pi.registerTool({
|
|
1390
|
+
name: "avenor_shutdown",
|
|
1391
|
+
label: "Avenor Shutdown",
|
|
1392
|
+
description: "Shut down the avenor supervisor and clean up temp files.",
|
|
1393
|
+
parameters: Type.Object({
|
|
1394
|
+
supervisor_id: Type.Optional(Type.String({ description: "Supervisor to shut down (defaults to singleton)" })),
|
|
1395
|
+
force: Type.Optional(Type.Boolean({ description: "Force shutdown instead of graceful" }))
|
|
1396
|
+
}),
|
|
1397
|
+
async execute(_toolCallId, params) {
|
|
1398
|
+
await stopPolling();
|
|
1399
|
+
trackedRuns.clear();
|
|
1400
|
+
const result = await deps.shutdownTool({
|
|
1401
|
+
supervisorId: params.supervisor_id,
|
|
1402
|
+
force: params.force
|
|
685
1403
|
});
|
|
686
|
-
return
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1404
|
+
return {
|
|
1405
|
+
content: [{
|
|
1406
|
+
type: "text",
|
|
1407
|
+
text: JSON.stringify(result, null, 2)
|
|
1408
|
+
}],
|
|
1409
|
+
details: result
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
pi.registerCommand("avenor-status", {
|
|
1414
|
+
description: "Show status of all avenor runs",
|
|
1415
|
+
handler: async (_args, ctx) => {
|
|
1416
|
+
const entries = await pollRuns();
|
|
1417
|
+
if (entries.length === 0) {
|
|
1418
|
+
ctx.ui.notify("No active avenor runs", "info");
|
|
1419
|
+
return;
|
|
697
1420
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1421
|
+
ctx.ui.setWidget("avenor-status-detail", entries.map((entry) => formatRunLine(entry)));
|
|
1422
|
+
}
|
|
1423
|
+
});
|
|
1424
|
+
pi.registerCommand("avenor-watch", {
|
|
1425
|
+
description: "Open a live run inspector for an avenor run",
|
|
1426
|
+
getArgumentCompletions: (prefix) => {
|
|
1427
|
+
const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.runId.startsWith(prefix)).map((run) => ({
|
|
1428
|
+
value: run.runId,
|
|
1429
|
+
label: `${run.label} (${run.runId.slice(0, 8)})`
|
|
1430
|
+
}));
|
|
1431
|
+
return items.length > 0 ? items : null;
|
|
1432
|
+
},
|
|
1433
|
+
handler: async (args, ctx) => {
|
|
1434
|
+
await openRunInspector(ctx, {
|
|
1435
|
+
trackedRuns: Array.from(trackedRuns.values()).map(toWatchRunRef),
|
|
1436
|
+
initialRunId: args?.trim() || void 0,
|
|
1437
|
+
deps: {
|
|
1438
|
+
...createWatchDeps(),
|
|
1439
|
+
followUpRun: (run, text) => followUpTrackedRun(run, text)
|
|
1440
|
+
},
|
|
1441
|
+
onTrackRun: (run, status) => {
|
|
1442
|
+
upsertTrackedRun(run, status, false);
|
|
1443
|
+
if (status && !isTerminalStatus(status.status)) startPolling();
|
|
712
1444
|
}
|
|
713
|
-
}
|
|
714
|
-
};
|
|
715
|
-
watchTick();
|
|
716
|
-
}
|
|
717
|
-
});
|
|
718
|
-
pi.registerCommand("avenor-cancel", {
|
|
719
|
-
description: "Cancel a running avenor sub-agent",
|
|
720
|
-
getArgumentCompletions: (prefix) => {
|
|
721
|
-
const items = Array.from(trackedRuns.values()).filter((r) => r.label.startsWith(prefix) || r.runId.startsWith(prefix)).map((r) => ({
|
|
722
|
-
value: r.runId,
|
|
723
|
-
label: `${r.label} (${r.runId.slice(0, 8)})`
|
|
724
|
-
}));
|
|
725
|
-
return items.length > 0 ? items : null;
|
|
726
|
-
},
|
|
727
|
-
handler: async (args, ctx) => {
|
|
728
|
-
if (!args?.trim()) {
|
|
729
|
-
ctx.ui.notify("Usage: /avenor-cancel <run_id>", "warning");
|
|
730
|
-
return;
|
|
1445
|
+
});
|
|
731
1446
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1447
|
+
});
|
|
1448
|
+
pi.registerCommand("avenor-cancel", {
|
|
1449
|
+
description: "Cancel a running avenor sub-agent",
|
|
1450
|
+
getArgumentCompletions: (prefix) => {
|
|
1451
|
+
const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.runId.startsWith(prefix)).map((run) => ({
|
|
1452
|
+
value: run.runId,
|
|
1453
|
+
label: `${run.label} (${run.runId.slice(0, 8)})`
|
|
1454
|
+
}));
|
|
1455
|
+
return items.length > 0 ? items : null;
|
|
1456
|
+
},
|
|
1457
|
+
handler: async (args, ctx) => {
|
|
1458
|
+
if (!args?.trim()) {
|
|
1459
|
+
ctx.ui.notify("Usage: /avenor-cancel <run_id>", "warning");
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
const runId = args.trim();
|
|
1463
|
+
const run = trackedRuns.get(runId);
|
|
1464
|
+
const label = run?.label ?? runId;
|
|
1465
|
+
const runtimeId = run?.runtimeId ?? run?.lastStatus?.runtime_id;
|
|
1466
|
+
if (!runtimeId) {
|
|
1467
|
+
ctx.ui.notify(`No runtime id available for "${label}"`, "warning");
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
if (!await ctx.ui.confirm("Cancel run", `Cancel "${label}" (${runId.slice(0, 8)})?`)) return;
|
|
1471
|
+
try {
|
|
1472
|
+
await createWatchDeps().cancelRun(toWatchRunRef(run ?? {
|
|
1473
|
+
runId,
|
|
1474
|
+
agent: "unknown",
|
|
1475
|
+
label,
|
|
1476
|
+
startTime: Date.now()
|
|
1477
|
+
}), runtimeId);
|
|
1478
|
+
ctx.ui.notify(`Cancelled "${label}"`, "info");
|
|
1479
|
+
} catch (err) {
|
|
1480
|
+
ctx.ui.notify(`Failed to cancel: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
1481
|
+
}
|
|
742
1482
|
}
|
|
743
|
-
}
|
|
744
|
-
}
|
|
1483
|
+
});
|
|
1484
|
+
};
|
|
745
1485
|
}
|
|
1486
|
+
var src_default = createExtension();
|
|
746
1487
|
//#endregion
|
|
747
|
-
export { src_default as default, statusSupervisorId };
|
|
1488
|
+
export { buildCompletionText, buildInspectPayload, compactWhitespace, createExtension, src_default as default, isTerminalStatus, renderStatusLines, statusSupervisorId };
|