@esso0428/pi-subagents 0.16.0 → 0.17.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/CHANGELOG.md +5 -0
- package/README.md +10 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -73
- package/dist/index.js.map +1 -1
- package/dist/settings.d.ts +1 -8
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +0 -5
- package/dist/settings.js.map +1 -1
- package/dist/ui/agent-widget.d.ts +55 -24
- package/dist/ui/agent-widget.d.ts.map +1 -1
- package/dist/ui/agent-widget.js +246 -121
- package/dist/ui/agent-widget.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +17 -71
- package/src/settings.ts +1 -12
- package/src/ui/agent-widget.ts +264 -114
- package/dist/ui/deferred-ui-refresh.d.ts +0 -15
- package/dist/ui/deferred-ui-refresh.d.ts.map +0 -1
- package/dist/ui/deferred-ui-refresh.js +0 -50
- package/dist/ui/deferred-ui-refresh.js.map +0 -1
- package/dist/ui/fleet-list.d.ts +0 -163
- package/dist/ui/fleet-list.d.ts.map +0 -1
- package/dist/ui/fleet-list.js +0 -509
- package/dist/ui/fleet-list.js.map +0 -1
- package/src/ui/deferred-ui-refresh.ts +0 -72
- package/src/ui/fleet-list.ts +0 -584
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-widget.d.ts","sourceRoot":"","sources":["../../src/ui/agent-widget.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-widget.d.ts","sourceRoot":"","sources":["../../src/ui/agent-widget.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/agent-widget.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Displays a tree of agents with animated spinners, live stats, and activity descriptions.
|
|
5
5
|
* Uses the callback form of setWidget for themed rendering.
|
|
6
6
|
*/
|
|
7
|
-
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
7
|
+
import { Editor, isKeyRelease, Key, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
8
8
|
import { getConfig } from "../agent-types.js";
|
|
9
9
|
import { getLifetimeTotal, getSessionContextPercent } from "../usage.js";
|
|
10
10
|
// ---- Constants ----
|
|
@@ -155,13 +155,17 @@ export class AgentWidget {
|
|
|
155
155
|
manager;
|
|
156
156
|
agentActivity;
|
|
157
157
|
mode;
|
|
158
|
+
options;
|
|
158
159
|
uiCtx;
|
|
159
160
|
widgetFrame = 0;
|
|
160
161
|
widgetInterval;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
inputUnsub;
|
|
163
|
+
/** Whether arrow keys currently navigate the agent roster. */
|
|
164
|
+
navigationActive = false;
|
|
165
|
+
/** Stable identity of the selected row, so roster changes do not jump selection. */
|
|
166
|
+
selectedAgentId;
|
|
167
|
+
/** First logical row currently represented by the bounded viewport. */
|
|
168
|
+
viewportStart = 0;
|
|
165
169
|
/** Whether the widget callback is currently registered with the TUI. */
|
|
166
170
|
widgetRegistered = false;
|
|
167
171
|
/** Cached TUI reference from widget factory callback, used for requestRender(). */
|
|
@@ -171,15 +175,15 @@ export class AgentWidget {
|
|
|
171
175
|
/** Snapshot of the state used for the last widget registration/render request. */
|
|
172
176
|
lastRenderKey;
|
|
173
177
|
constructor(manager, agentActivity,
|
|
174
|
-
/**
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
mode = () => "all") {
|
|
178
|
+
/** Read live at render time. Selects which agents the widget shows. */
|
|
179
|
+
mode = () => "all", options = {
|
|
180
|
+
canOpenHistory: (record) => record.session !== undefined || record.completedAt !== undefined,
|
|
181
|
+
onOpen: () => { },
|
|
182
|
+
}) {
|
|
180
183
|
this.manager = manager;
|
|
181
184
|
this.agentActivity = agentActivity;
|
|
182
185
|
this.mode = mode;
|
|
186
|
+
this.options = options;
|
|
183
187
|
}
|
|
184
188
|
/**
|
|
185
189
|
* Agents eligible for the widget, per the current `WidgetMode`:
|
|
@@ -204,13 +208,23 @@ export class AgentWidget {
|
|
|
204
208
|
setUICtx(ctx) {
|
|
205
209
|
if (ctx === this.uiCtx)
|
|
206
210
|
return false;
|
|
207
|
-
// UICtx changed — the widget registered on the old
|
|
208
|
-
//
|
|
211
|
+
// UICtx changed — the widget and input handler registered on the old
|
|
212
|
+
// context are gone. Re-register both on the next update().
|
|
213
|
+
this.inputUnsub?.();
|
|
214
|
+
this.inputUnsub = undefined;
|
|
209
215
|
this.uiCtx = ctx;
|
|
210
216
|
this.widgetRegistered = false;
|
|
211
217
|
this.tui = undefined;
|
|
212
218
|
this.lastStatusText = undefined;
|
|
213
219
|
this.lastRenderKey = undefined;
|
|
220
|
+
this.navigationActive = false;
|
|
221
|
+
this.selectedAgentId = undefined;
|
|
222
|
+
this.viewportStart = 0;
|
|
223
|
+
// Print/RPC tests and lightweight embedders may provide only the widget
|
|
224
|
+
// surface; real interactive contexts always implement this hook.
|
|
225
|
+
if (typeof ctx.onTerminalInput === "function") {
|
|
226
|
+
this.inputUnsub = ctx.onTerminalInput(data => this.handleKey(data));
|
|
227
|
+
}
|
|
214
228
|
return true;
|
|
215
229
|
}
|
|
216
230
|
/** Request a render on the currently registered TUI without touching input. */
|
|
@@ -220,16 +234,8 @@ export class AgentWidget {
|
|
|
220
234
|
this.tui.requestRender(force);
|
|
221
235
|
return true;
|
|
222
236
|
}
|
|
223
|
-
/**
|
|
224
|
-
* Called on each new turn (tool_execution_start).
|
|
225
|
-
* Ages finished agents and clears those that have lingered long enough.
|
|
226
|
-
*/
|
|
237
|
+
/** Called on each new turn (tool_execution_start). */
|
|
227
238
|
onTurnStart() {
|
|
228
|
-
// Age all finished agents
|
|
229
|
-
for (const [id, age] of this.finishedTurnAge) {
|
|
230
|
-
this.finishedTurnAge.set(id, age + 1);
|
|
231
|
-
}
|
|
232
|
-
// Trigger a widget refresh (will filter out expired agents)
|
|
233
239
|
this.update();
|
|
234
240
|
}
|
|
235
241
|
/** Keep the spinner/elapsed-time timer alive only while a visible agent runs. */
|
|
@@ -237,7 +243,7 @@ export class AgentWidget {
|
|
|
237
243
|
if (!this.uiCtx || !this.widgetAgents().some(a => a.status === "running"))
|
|
238
244
|
return;
|
|
239
245
|
if (!this.widgetInterval) {
|
|
240
|
-
this.widgetInterval = setInterval(() => this.update(true),
|
|
246
|
+
this.widgetInterval = setInterval(() => this.update(true), 250);
|
|
241
247
|
}
|
|
242
248
|
}
|
|
243
249
|
syncTimer(shouldRun) {
|
|
@@ -249,17 +255,111 @@ export class AgentWidget {
|
|
|
249
255
|
this.widgetInterval = undefined;
|
|
250
256
|
}
|
|
251
257
|
}
|
|
252
|
-
/**
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Retained for the lifecycle call sites. Terminal visibility is determined
|
|
260
|
+
* by the manager record and the history capability, not a one-turn timer.
|
|
261
|
+
*/
|
|
262
|
+
markFinished(_agentId) { }
|
|
263
|
+
/**
|
|
264
|
+
* Records represented by selectable rows in the above-editor widget.
|
|
265
|
+
*
|
|
266
|
+
* Keep this order as the single source of truth for both rendering and key
|
|
267
|
+
* navigation. `listAgents()` is newest-first; active rows come first so the
|
|
268
|
+
* panel exposes currently useful work before terminal history.
|
|
269
|
+
*/
|
|
270
|
+
roster() {
|
|
271
|
+
const agents = this.widgetAgents();
|
|
272
|
+
const finished = agents.filter(record => record.status !== "running" && record.status !== "queued"
|
|
273
|
+
&& record.completedAt !== undefined
|
|
274
|
+
&& this.options.canOpenHistory(record));
|
|
275
|
+
const running = agents.filter(record => record.status === "running");
|
|
276
|
+
const queued = agents.filter(record => record.status === "queued");
|
|
277
|
+
return [...running, ...queued, ...finished];
|
|
278
|
+
}
|
|
279
|
+
/** True when pi's prompt editor owns the keyboard. */
|
|
280
|
+
editorHasFocus() {
|
|
281
|
+
const focused = this.tui?.focusedComponent;
|
|
282
|
+
return focused == null || focused instanceof Editor;
|
|
283
|
+
}
|
|
284
|
+
selectedIndexOf(records) {
|
|
285
|
+
if (!this.selectedAgentId)
|
|
286
|
+
return -1;
|
|
287
|
+
return records.findIndex(record => record.id === this.selectedAgentId);
|
|
288
|
+
}
|
|
289
|
+
deactivate() {
|
|
290
|
+
this.navigationActive = false;
|
|
291
|
+
this.selectedAgentId = undefined;
|
|
292
|
+
this.viewportStart = 0;
|
|
293
|
+
this.update();
|
|
294
|
+
}
|
|
295
|
+
/** Move the selected row, activating only from an empty focused editor. */
|
|
296
|
+
moveSelection(direction) {
|
|
297
|
+
const records = this.roster();
|
|
298
|
+
const ui = this.uiCtx;
|
|
299
|
+
if (records.length === 0 || !ui)
|
|
300
|
+
return false;
|
|
301
|
+
if (!this.navigationActive) {
|
|
302
|
+
if (direction !== 1 || !this.editorHasFocus() || (ui.getEditorText?.() ?? "") !== "")
|
|
303
|
+
return false;
|
|
304
|
+
this.navigationActive = true;
|
|
305
|
+
this.selectedAgentId = records[0].id;
|
|
306
|
+
this.viewportStart = 0;
|
|
307
|
+
this.update();
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
const currentIndex = Math.max(0, this.selectedIndexOf(records));
|
|
311
|
+
if (direction === -1 && currentIndex === 0) {
|
|
312
|
+
this.deactivate();
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
const nextIndex = Math.max(0, Math.min(records.length - 1, currentIndex + direction));
|
|
316
|
+
this.selectedAgentId = records[nextIndex].id;
|
|
317
|
+
this.update();
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
openSelected() {
|
|
321
|
+
const record = this.roster().find(candidate => candidate.id === this.selectedAgentId);
|
|
322
|
+
this.deactivate();
|
|
323
|
+
if (!record)
|
|
324
|
+
return;
|
|
325
|
+
const mode = record.status === "running" || record.status === "queued" ? "live" : "history";
|
|
326
|
+
void this.options.onOpen(record, mode);
|
|
257
327
|
}
|
|
258
|
-
/**
|
|
259
|
-
|
|
260
|
-
if (!this.
|
|
261
|
-
|
|
328
|
+
/** Handle terminal input before it reaches the focused prompt editor. */
|
|
329
|
+
handleKey(data) {
|
|
330
|
+
if (!this.uiCtx || isKeyRelease(data))
|
|
331
|
+
return undefined;
|
|
332
|
+
if (!this.editorHasFocus()) {
|
|
333
|
+
if (this.navigationActive)
|
|
334
|
+
this.deactivate();
|
|
335
|
+
return undefined;
|
|
336
|
+
}
|
|
337
|
+
if (!this.navigationActive) {
|
|
338
|
+
if (!matchesKey(data, "down") || (this.uiCtx.getEditorText?.() ?? "") !== "" || this.roster().length === 0) {
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
this.navigationActive = true;
|
|
342
|
+
this.selectedAgentId = this.roster()[0]?.id;
|
|
343
|
+
this.viewportStart = 0;
|
|
344
|
+
this.update();
|
|
345
|
+
return { consume: true };
|
|
346
|
+
}
|
|
347
|
+
if (matchesKey(data, "escape")) {
|
|
348
|
+
this.deactivate();
|
|
349
|
+
return { consume: true };
|
|
262
350
|
}
|
|
351
|
+
if (matchesKey(data, "up"))
|
|
352
|
+
return this.moveSelection(-1) ? { consume: true } : undefined;
|
|
353
|
+
if (matchesKey(data, "down"))
|
|
354
|
+
return this.moveSelection(1) ? { consume: true } : undefined;
|
|
355
|
+
if (matchesKey(data, Key.enter)) {
|
|
356
|
+
this.openSelected();
|
|
357
|
+
return { consume: true };
|
|
358
|
+
}
|
|
359
|
+
// Only ↑/↓ navigate. Other keys, including j/k/←/→, flow to the editor
|
|
360
|
+
// and leave navigation mode.
|
|
361
|
+
this.deactivate();
|
|
362
|
+
return undefined;
|
|
263
363
|
}
|
|
264
364
|
/** Render a finished agent line. */
|
|
265
365
|
renderFinishedLine(a, theme) {
|
|
@@ -308,8 +408,9 @@ export class AgentWidget {
|
|
|
308
408
|
const allAgents = this.widgetAgents();
|
|
309
409
|
const running = allAgents.filter(a => a.status === "running");
|
|
310
410
|
const queued = allAgents.filter(a => a.status === "queued");
|
|
311
|
-
const finished = allAgents.filter(a => a.status !== "running" && a.status !== "queued" && a.completedAt
|
|
312
|
-
&& this.
|
|
411
|
+
const finished = allAgents.filter(a => a.status !== "running" && a.status !== "queued" && a.completedAt !== undefined
|
|
412
|
+
&& this.options.canOpenHistory(a));
|
|
413
|
+
const selectedId = this.navigationActive ? this.selectedAgentId : undefined;
|
|
313
414
|
const hasActive = running.length > 0 || queued.length > 0;
|
|
314
415
|
const hasFinished = finished.length > 0;
|
|
315
416
|
// Nothing to show — return empty (widget will be unregistered by update())
|
|
@@ -327,7 +428,11 @@ export class AgentWidget {
|
|
|
327
428
|
// Each running agent = 2 lines (header + activity), finished = 1 line, queued = 1 line.
|
|
328
429
|
const finishedLines = [];
|
|
329
430
|
for (const a of finished) {
|
|
330
|
-
|
|
431
|
+
const marker = a.id === selectedId ? theme.fg("accent", "●") : theme.fg("dim", "○");
|
|
432
|
+
finishedLines.push({
|
|
433
|
+
record: a,
|
|
434
|
+
lines: [truncate(theme.fg("dim", "├─") + ` ${marker} ` + this.renderFinishedLine(a, theme))],
|
|
435
|
+
});
|
|
331
436
|
}
|
|
332
437
|
const runningLines = []; // each entry is [header, activity]
|
|
333
438
|
for (const a of running) {
|
|
@@ -350,92 +455,99 @@ export class AgentWidget {
|
|
|
350
455
|
parts.push(elapsed);
|
|
351
456
|
const statsText = parts.join(" · ");
|
|
352
457
|
const activity = bg ? describeActivity(bg.activeTools, bg.responseText) : "thinking…";
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
458
|
+
const marker = a.id === selectedId ? theme.fg("accent", "●") : theme.fg("dim", "○");
|
|
459
|
+
runningLines.push({
|
|
460
|
+
record: a,
|
|
461
|
+
lines: [
|
|
462
|
+
truncate(theme.fg("dim", "├─") + ` ${marker} ${theme.fg("accent", frame)} ${theme.bold(name)}${modeTag} ${theme.fg("muted", truncateLine(a.description))} ${theme.fg("dim", "·")} ${fgPreservingNestedStyles(theme, "dim", statsText)}`),
|
|
463
|
+
truncate(theme.fg("dim", "│ ") + ` ${theme.fg("dim", `⎿ ${activity}`)}`),
|
|
464
|
+
],
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
const queuedLines = queued.map(a => {
|
|
468
|
+
const marker = a.id === selectedId ? theme.fg("accent", "●") : theme.fg("dim", "○");
|
|
469
|
+
return {
|
|
470
|
+
record: a,
|
|
471
|
+
lines: [truncate(theme.fg("dim", "├─") + ` ${marker} ${theme.fg("muted", "◦")} ${theme.fg("dim", `${getDisplayName(a.type)} ${truncateLine(a.description)} · queued`)}`)],
|
|
472
|
+
};
|
|
473
|
+
});
|
|
361
474
|
// Assemble with a responsive cap (heading + overflow indicator = 2
|
|
362
475
|
// reserved lines when content exceeds the available body budget).
|
|
363
476
|
const maxBody = maxLines - 1; // heading takes 1 line
|
|
364
|
-
const
|
|
365
|
-
const
|
|
477
|
+
const rows = [...runningLines, ...queuedLines, ...finishedLines];
|
|
478
|
+
const totalBody = rows.reduce((total, row) => total + row.lines.length, 0);
|
|
479
|
+
const heading = "Agents ↑↓ select · enter view · esc back";
|
|
480
|
+
const lines = [truncate(theme.fg(headingColor, headingIcon) + " " + theme.fg(headingColor, heading))];
|
|
366
481
|
if (maxLines === 1) {
|
|
367
482
|
// There is room only for the heading; do not consume the editor/input row.
|
|
368
483
|
return lines;
|
|
369
484
|
}
|
|
370
485
|
if (totalBody <= maxBody) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
lines.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
lines[last] = lines[last].replace("├─", "└─");
|
|
381
|
-
// If last item is a running agent activity line, fix indent of that line
|
|
382
|
-
// and fix the header line above it.
|
|
383
|
-
if (runningLines.length > 0 && !queuedLine) {
|
|
384
|
-
// The last two lines are the last running agent's header + activity.
|
|
385
|
-
if (last >= 2) {
|
|
386
|
-
lines[last - 1] = lines[last - 1].replace("├─", "└─");
|
|
387
|
-
lines[last] = lines[last].replace("│ ", " ");
|
|
388
|
-
}
|
|
486
|
+
this.viewportStart = 0;
|
|
487
|
+
for (const row of rows)
|
|
488
|
+
lines.push(...row.lines);
|
|
489
|
+
if (rows.length > 0) {
|
|
490
|
+
const lastRow = rows[rows.length - 1];
|
|
491
|
+
const lastStart = lines.length - lastRow.lines.length;
|
|
492
|
+
lines[lastStart] = lines[lastStart].replace("├─", "└─");
|
|
493
|
+
if (lastRow.lines.length === 2) {
|
|
494
|
+
lines[lastStart + 1] = lines[lastStart + 1].replace("│ ", " ");
|
|
389
495
|
}
|
|
390
496
|
}
|
|
391
497
|
}
|
|
392
498
|
else {
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
budget -= 2;
|
|
404
|
-
}
|
|
405
|
-
else {
|
|
406
|
-
hiddenRunning++;
|
|
499
|
+
// Reserve one line for a directional overflow summary. The viewport is
|
|
500
|
+
// a contiguous slice in roster order, so the same slice is navigable and
|
|
501
|
+
// renderable even when the selected row is currently hidden.
|
|
502
|
+
const contentBudget = Math.max(0, maxBody - 1);
|
|
503
|
+
const heightAt = (index) => rows[index]?.lines.length ?? 0;
|
|
504
|
+
const endFor = (start) => {
|
|
505
|
+
let used = 0;
|
|
506
|
+
let end = start;
|
|
507
|
+
while (end < rows.length && used + heightAt(end) <= contentBudget) {
|
|
508
|
+
used += heightAt(end++);
|
|
407
509
|
}
|
|
510
|
+
return end;
|
|
511
|
+
};
|
|
512
|
+
let start = Math.max(0, Math.min(this.viewportStart, Math.max(0, rows.length - 1)));
|
|
513
|
+
const selectedIndex = this.navigationActive && selectedId
|
|
514
|
+
? rows.findIndex(row => row.record.id === selectedId)
|
|
515
|
+
: -1;
|
|
516
|
+
if (selectedIndex >= 0) {
|
|
517
|
+
if (selectedIndex < start)
|
|
518
|
+
start = selectedIndex;
|
|
519
|
+
if (selectedIndex >= endFor(start))
|
|
520
|
+
start = selectedIndex;
|
|
408
521
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
hiddenQueued = 1;
|
|
417
|
-
}
|
|
522
|
+
this.viewportStart = start;
|
|
523
|
+
let used = 0;
|
|
524
|
+
let end = start;
|
|
525
|
+
while (end < rows.length && used + heightAt(end) <= contentBudget) {
|
|
526
|
+
lines.push(...rows[end].lines);
|
|
527
|
+
used += heightAt(end);
|
|
528
|
+
end++;
|
|
418
529
|
}
|
|
419
|
-
//
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
426
|
-
hiddenFinished++;
|
|
427
|
-
}
|
|
530
|
+
// A selected two-line row must remain addressable even if only one body
|
|
531
|
+
// line is available. Showing its header is preferable to hiding it.
|
|
532
|
+
if (end === start && rows[start] && contentBudget > 0) {
|
|
533
|
+
lines.push(rows[start].lines[0]);
|
|
534
|
+
end = start + 1;
|
|
428
535
|
}
|
|
429
|
-
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
536
|
+
const hiddenBefore = start;
|
|
537
|
+
const hiddenAfter = Math.max(0, rows.length - end);
|
|
538
|
+
const hidden = hiddenBefore + hiddenAfter;
|
|
539
|
+
const hiddenRows = rows.filter((_row, index) => index < start || index >= end);
|
|
540
|
+
const categoryCounts = [
|
|
541
|
+
["running", hiddenRows.filter(row => row.record.status === "running").length],
|
|
542
|
+
["queued", hiddenRows.filter(row => row.record.status === "queued").length],
|
|
543
|
+
["finished", hiddenRows.filter(row => row.record.status !== "running" && row.record.status !== "queued").length],
|
|
544
|
+
].filter(([, count]) => count > 0).map(([label, count]) => `${count} ${label}`);
|
|
545
|
+
const direction = [
|
|
546
|
+
...(hiddenBefore > 0 ? [`↑ ${hiddenBefore} more`] : []),
|
|
547
|
+
...(hiddenAfter > 0 ? [`↓ ${hiddenAfter} more`] : []),
|
|
548
|
+
].join(" · ");
|
|
549
|
+
const summary = `+${hidden} more (${direction}${categoryCounts.length > 0 ? `; ${categoryCounts.join(", ")}` : ""})`;
|
|
550
|
+
lines.push(truncate(theme.fg("dim", "└─") + ` ${theme.fg("dim", summary)}`));
|
|
439
551
|
}
|
|
440
552
|
return lines;
|
|
441
553
|
}
|
|
@@ -468,9 +580,14 @@ export class AgentWidget {
|
|
|
468
580
|
lifetimeUsage: a.lifetimeUsage,
|
|
469
581
|
compactionCount: a.compactionCount,
|
|
470
582
|
isBackground: a.isBackground,
|
|
583
|
+
hasSession: a.session !== undefined,
|
|
584
|
+
transcriptPath: a.transcriptPath,
|
|
585
|
+
openableHistory: this.options.canOpenHistory(a),
|
|
471
586
|
})),
|
|
472
587
|
activities,
|
|
473
|
-
|
|
588
|
+
navigationActive: this.navigationActive,
|
|
589
|
+
selectedAgentId: this.selectedAgentId,
|
|
590
|
+
viewportStart: this.viewportStart,
|
|
474
591
|
});
|
|
475
592
|
}
|
|
476
593
|
/** Force an immediate widget update. `advanceSpinner` is reserved for the timer. */
|
|
@@ -478,21 +595,17 @@ export class AgentWidget {
|
|
|
478
595
|
if (!this.uiCtx)
|
|
479
596
|
return;
|
|
480
597
|
const allAgents = this.widgetAgents();
|
|
598
|
+
const roster = this.roster();
|
|
481
599
|
// Lightweight existence checks — full categorization happens in renderWidget()
|
|
482
600
|
let runningCount = 0;
|
|
483
601
|
let queuedCount = 0;
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
if (a.status === "running") {
|
|
602
|
+
for (const a of roster) {
|
|
603
|
+
if (a.status === "running")
|
|
487
604
|
runningCount++;
|
|
488
|
-
|
|
489
|
-
else if (a.status === "queued") {
|
|
605
|
+
else if (a.status === "queued")
|
|
490
606
|
queuedCount++;
|
|
491
|
-
}
|
|
492
|
-
else if (a.completedAt && this.shouldShowFinished(a.id, a.status)) {
|
|
493
|
-
hasFinished = true;
|
|
494
|
-
}
|
|
495
607
|
}
|
|
608
|
+
const hasFinished = roster.some(a => a.status !== "running" && a.status !== "queued");
|
|
496
609
|
const hasActive = runningCount > 0 || queuedCount > 0;
|
|
497
610
|
// Nothing to show — clear widget
|
|
498
611
|
if (!hasActive && !hasFinished) {
|
|
@@ -506,12 +619,10 @@ export class AgentWidget {
|
|
|
506
619
|
this.lastStatusText = undefined;
|
|
507
620
|
}
|
|
508
621
|
this.lastRenderKey = undefined;
|
|
622
|
+
this.navigationActive = false;
|
|
623
|
+
this.selectedAgentId = undefined;
|
|
624
|
+
this.viewportStart = 0;
|
|
509
625
|
this.syncTimer(false);
|
|
510
|
-
// Clean up stale entries
|
|
511
|
-
for (const [id] of this.finishedTurnAge) {
|
|
512
|
-
if (!allAgents.some(a => a.id === id))
|
|
513
|
-
this.finishedTurnAge.delete(id);
|
|
514
|
-
}
|
|
515
626
|
return;
|
|
516
627
|
}
|
|
517
628
|
// Status bar — only call setStatus when the text actually changes
|
|
@@ -535,6 +646,16 @@ export class AgentWidget {
|
|
|
535
646
|
if (advanceSpinner && runningCount > 0)
|
|
536
647
|
this.widgetFrame++;
|
|
537
648
|
this.syncTimer(runningCount > 0);
|
|
649
|
+
if (this.navigationActive) {
|
|
650
|
+
if (roster.length === 0) {
|
|
651
|
+
this.navigationActive = false;
|
|
652
|
+
this.selectedAgentId = undefined;
|
|
653
|
+
this.viewportStart = 0;
|
|
654
|
+
}
|
|
655
|
+
else if (this.selectedIndexOf(roster) < 0) {
|
|
656
|
+
this.selectedAgentId = roster[Math.min(this.viewportStart, roster.length - 1)].id;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
538
659
|
const renderKey = this.renderKey(allAgents);
|
|
539
660
|
// Register widget callback once; subsequent updates use requestRender()
|
|
540
661
|
// which re-invokes render() without replacing the component (avoids layout thrashing).
|
|
@@ -569,12 +690,16 @@ export class AgentWidget {
|
|
|
569
690
|
this.uiCtx.setWidget("agents", undefined);
|
|
570
691
|
this.uiCtx.setStatus("subagents", undefined);
|
|
571
692
|
}
|
|
693
|
+
this.inputUnsub?.();
|
|
694
|
+
this.inputUnsub = undefined;
|
|
572
695
|
this.widgetRegistered = false;
|
|
573
696
|
this.tui = undefined;
|
|
574
697
|
this.lastStatusText = undefined;
|
|
575
698
|
this.lastRenderKey = undefined;
|
|
699
|
+
this.navigationActive = false;
|
|
700
|
+
this.selectedAgentId = undefined;
|
|
701
|
+
this.viewportStart = 0;
|
|
576
702
|
this.uiCtx = undefined;
|
|
577
|
-
this.finishedTurnAge.clear();
|
|
578
703
|
}
|
|
579
704
|
}
|
|
580
705
|
//# sourceMappingURL=agent-widget.js.map
|