@esso0428/pi-subagents 0.17.8 → 0.17.10
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 +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -9
- package/dist/index.js.map +1 -1
- package/dist/ui/agent-widget.d.ts +5 -2
- package/dist/ui/agent-widget.d.ts.map +1 -1
- package/dist/ui/agent-widget.js +64 -39
- package/dist/ui/agent-widget.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +11 -9
- package/src/ui/agent-widget.ts +64 -38
package/src/ui/agent-widget.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Uses the callback form of setWidget for themed rendering.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
8
|
+
import { Editor, isKeyRelease, Key, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
9
9
|
import { renderAgentName } from "../agent-color.js";
|
|
10
10
|
import { type AgentManager, isTopLevelAgent } from "../agent-manager.js";
|
|
11
11
|
import { getConfig } from "../agent-types.js";
|
|
@@ -19,7 +19,8 @@ export const MAX_WIDGET_LINES = 12;
|
|
|
19
19
|
|
|
20
20
|
/** Number of lines available above an editor, retaining room for the prompt. */
|
|
21
21
|
export function getWidgetLineBudget(terminalRows: number): number {
|
|
22
|
-
|
|
22
|
+
if (!Number.isFinite(terminalRows)) return MAX_WIDGET_LINES;
|
|
23
|
+
return Math.min(MAX_WIDGET_LINES, Math.max(0, Math.floor(terminalRows) - 4));
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export type AgentWidgetOpenMode = "live" | "history";
|
|
@@ -348,19 +349,19 @@ export class AgentWidget {
|
|
|
348
349
|
}
|
|
349
350
|
|
|
350
351
|
/** Set the UI context (grabbed from first tool execution). */
|
|
351
|
-
setUICtx(ctx: UICtx) {
|
|
352
|
-
if (ctx
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
352
|
+
setUICtx(ctx: UICtx): boolean {
|
|
353
|
+
if (ctx === this.uiCtx) return false;
|
|
354
|
+
this.inputCleanup?.();
|
|
355
|
+
this.inputCleanup = undefined;
|
|
356
|
+
this.uiCtx = ctx;
|
|
357
|
+
this.widgetRegistered = false;
|
|
358
|
+
this.tui = undefined;
|
|
359
|
+
this.lastStatusText = undefined;
|
|
360
|
+
this.navigationActive = false;
|
|
361
|
+
this.selectedAgentId = undefined;
|
|
362
|
+
this.viewportStart = 0;
|
|
363
|
+
if (ctx.onTerminalInput) this.inputCleanup = ctx.onTerminalInput(data => this.handleInput(data));
|
|
364
|
+
return true;
|
|
364
365
|
}
|
|
365
366
|
|
|
366
367
|
/**
|
|
@@ -379,7 +380,7 @@ export class AgentWidget {
|
|
|
379
380
|
/** Ensure the widget update timer is running. */
|
|
380
381
|
ensureTimer() {
|
|
381
382
|
if (!this.widgetInterval) {
|
|
382
|
-
this.widgetInterval = setInterval(() => this.update(),
|
|
383
|
+
this.widgetInterval = setInterval(() => this.update(true), 250);
|
|
383
384
|
}
|
|
384
385
|
}
|
|
385
386
|
|
|
@@ -429,13 +430,26 @@ export class AgentWidget {
|
|
|
429
430
|
this.viewportStart = Math.max(0, Math.min(this.viewportStart, Math.max(0, roster.length - visibleRows)));
|
|
430
431
|
}
|
|
431
432
|
|
|
433
|
+
private editorHasFocus(): boolean {
|
|
434
|
+
const focused = (this.tui as { focusedComponent?: unknown } | undefined)?.focusedComponent;
|
|
435
|
+
return focused == null || focused instanceof Editor;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
private deactivate(): void {
|
|
439
|
+
this.navigationActive = false;
|
|
440
|
+
this.requestUiRefresh();
|
|
441
|
+
}
|
|
442
|
+
|
|
432
443
|
private handleInput(data: string): { consume?: boolean; data?: string } | undefined {
|
|
433
|
-
|
|
434
|
-
|
|
444
|
+
if (!this.uiCtx || isKeyRelease(data)) return undefined;
|
|
445
|
+
if (!this.editorHasFocus()) {
|
|
446
|
+
if (this.navigationActive) this.deactivate();
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
449
|
+
|
|
435
450
|
if (!this.navigationActive) {
|
|
436
|
-
if (!down || !this.tui || this.uiCtx?.getEditorText?.() !== "") return undefined;
|
|
437
451
|
const roster = this.navigationAgents();
|
|
438
|
-
if (roster.length === 0) return undefined;
|
|
452
|
+
if (!matchesKey(data, "down") || (this.uiCtx.getEditorText?.() ?? "") !== "" || roster.length === 0) return undefined;
|
|
439
453
|
this.navigationActive = true;
|
|
440
454
|
const index = this.selectedAgentId ? roster.findIndex(agent => agent.id === this.selectedAgentId) : -1;
|
|
441
455
|
this.selectedRosterIndex = index >= 0 ? index : 0;
|
|
@@ -444,25 +458,25 @@ export class AgentWidget {
|
|
|
444
458
|
this.requestUiRefresh();
|
|
445
459
|
return { consume: true };
|
|
446
460
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
this.
|
|
461
|
+
|
|
462
|
+
if (matchesKey(data, "escape")) {
|
|
463
|
+
this.deactivate();
|
|
450
464
|
return { consume: true };
|
|
451
465
|
}
|
|
452
|
-
if (
|
|
466
|
+
if (matchesKey(data, "up") || matchesKey(data, "down")) {
|
|
453
467
|
const roster = this.navigationAgents();
|
|
454
468
|
if (roster.length === 0) return { consume: true };
|
|
455
|
-
if (up && this.selectedRosterIndex === 0) {
|
|
456
|
-
this.
|
|
469
|
+
if (matchesKey(data, "up") && this.selectedRosterIndex === 0) {
|
|
470
|
+
this.deactivate();
|
|
457
471
|
} else {
|
|
458
|
-
this.selectedRosterIndex = Math.max(0, Math.min(roster.length - 1, this.selectedRosterIndex + (down ? 1 : -1)));
|
|
472
|
+
this.selectedRosterIndex = Math.max(0, Math.min(roster.length - 1, this.selectedRosterIndex + (matchesKey(data, "down") ? 1 : -1)));
|
|
459
473
|
this.selectedAgentId = roster[this.selectedRosterIndex]?.id;
|
|
460
474
|
this.updateViewport(roster);
|
|
475
|
+
this.requestUiRefresh();
|
|
461
476
|
}
|
|
462
|
-
this.requestUiRefresh();
|
|
463
477
|
return { consume: true };
|
|
464
478
|
}
|
|
465
|
-
if (data
|
|
479
|
+
if (matchesKey(data, Key.enter)) {
|
|
466
480
|
const record = this.navigationAgents()[this.selectedRosterIndex];
|
|
467
481
|
if (record) {
|
|
468
482
|
const terminal = record.status !== "running" && record.status !== "queued";
|
|
@@ -472,16 +486,24 @@ export class AgentWidget {
|
|
|
472
486
|
}
|
|
473
487
|
return { consume: true };
|
|
474
488
|
}
|
|
475
|
-
|
|
489
|
+
|
|
490
|
+
// Only arrow keys navigate. Other keys flow to the editor and leave navigation mode.
|
|
491
|
+
this.deactivate();
|
|
476
492
|
return undefined;
|
|
477
493
|
}
|
|
478
494
|
|
|
479
|
-
private requestUiRefresh(force =
|
|
495
|
+
private requestUiRefresh(force = true): boolean {
|
|
480
496
|
if (!this.tui || typeof this.tui.requestRender !== "function") return false;
|
|
481
497
|
this.tui.requestRender(force);
|
|
482
498
|
return true;
|
|
483
499
|
}
|
|
484
500
|
|
|
501
|
+
private selectionBullet(agentId: string, theme: Theme): string {
|
|
502
|
+
return this.navigationActive && this.selectedAgentId === agentId
|
|
503
|
+
? theme.fg("accent", "●")
|
|
504
|
+
: theme.fg("dim", "○");
|
|
505
|
+
}
|
|
506
|
+
|
|
485
507
|
/** Render a finished agent line. */
|
|
486
508
|
private renderFinishedLine(a: { id: string; type: SubagentType; status: string; description: string; toolUses: number; startedAt: number; completedAt?: number; error?: string; lifetimeUsage?: LifetimeUsage }, theme: Theme): string {
|
|
487
509
|
const modeLabel = getPromptModeLabel(a.type);
|
|
@@ -545,6 +567,7 @@ export class AgentWidget {
|
|
|
545
567
|
|
|
546
568
|
const w = tui.terminal.columns;
|
|
547
569
|
const budget = getWidgetLineBudget(tui.terminal.rows ?? 24);
|
|
570
|
+
if (budget === 0) return [];
|
|
548
571
|
const truncate = (line: string) => truncateToWidth(line.replace(/[\r\n]+/g, " "), w);
|
|
549
572
|
const headingColor = hasActive ? "accent" : "dim";
|
|
550
573
|
const headingIcon = hasActive ? "●" : "○";
|
|
@@ -555,7 +578,7 @@ export class AgentWidget {
|
|
|
555
578
|
|
|
556
579
|
const finishedLines: string[] = [];
|
|
557
580
|
for (const a of finished) {
|
|
558
|
-
finishedLines.push(truncate(theme.fg("dim", "├─") +
|
|
581
|
+
finishedLines.push(truncate(theme.fg("dim", "├─") + ` ${this.selectionBullet(a.id, theme)} ` + this.renderFinishedLine(a, theme)));
|
|
559
582
|
}
|
|
560
583
|
|
|
561
584
|
const runningLines: string[][] = []; // each entry is [header, activity]
|
|
@@ -595,13 +618,14 @@ export class AgentWidget {
|
|
|
595
618
|
const activity = (bg ? describeActivity(bg.activeTools, bg.responseText) : "thinking…").replace(/[\r\n]+/g, " ");
|
|
596
619
|
|
|
597
620
|
runningLines.push([
|
|
598
|
-
truncate(theme.fg("dim", "├─") + ` ${theme.fg("accent", frame)} ${renderAgentName(a.type, theme, { bold: true })}${modeTag} ${theme.fg("muted", a.description)} ${theme.fg("dim", "·")} ${fgPreservingNestedStyles(theme, "dim", statsText)}`),
|
|
621
|
+
truncate(theme.fg("dim", "├─") + ` ${this.selectionBullet(a.id, theme)} ${theme.fg("accent", frame)} ${renderAgentName(a.type, theme, { bold: true })}${modeTag} ${theme.fg("muted", a.description)} ${theme.fg("dim", "·")} ${fgPreservingNestedStyles(theme, "dim", statsText)}`),
|
|
599
622
|
truncate(theme.fg("dim", "│ ") + theme.fg("dim", ` ⎿ ${activity}`)),
|
|
600
623
|
]);
|
|
601
624
|
}
|
|
602
625
|
|
|
626
|
+
const selectedQueued = this.navigationActive && this.selectedAgentId !== undefined && queued.some(agent => agent.id === this.selectedAgentId);
|
|
603
627
|
const queuedLine = queued.length > 0
|
|
604
|
-
? truncate(theme.fg("dim", "├─") + ` ${theme.fg("muted", "◦")} ${theme.fg("dim", `${queued.length} queued`)}`)
|
|
628
|
+
? truncate(theme.fg("dim", "├─") + ` ${selectedQueued ? theme.fg("accent", "●") : theme.fg("dim", "○")} ${theme.fg("muted", "◦")} ${theme.fg("dim", `${queued.length} queued`)}`)
|
|
605
629
|
: undefined;
|
|
606
630
|
|
|
607
631
|
// Assemble with overflow cap (heading + overflow indicator = 2 reserved lines).
|
|
@@ -696,7 +720,7 @@ export class AgentWidget {
|
|
|
696
720
|
const selected = roster[this.selectedRosterIndex];
|
|
697
721
|
const hasSelected = selected ? lines.some(line => line.includes(selected.description.replace(/[\r\n]+/g, " "))) : true;
|
|
698
722
|
if (selected && !hasSelected) {
|
|
699
|
-
const selectedLine = truncate(theme.fg("dim", "└─") + ` ${selected.status === "queued" ? "◦" : "⠋"} ${theme.fg("muted", selected.description.replace(/[\r\n]+/g, " "))}`);
|
|
723
|
+
const selectedLine = truncate(theme.fg("dim", "└─") + ` ${this.selectionBullet(selected.id, theme)} ${selected.status === "queued" ? "◦" : "⠋"} ${theme.fg("muted", selected.description.replace(/[\r\n]+/g, " "))}`);
|
|
700
724
|
if (lines.length >= budget) lines[lines.length - 1] = selectedLine;
|
|
701
725
|
else lines.push(selectedLine);
|
|
702
726
|
}
|
|
@@ -715,7 +739,7 @@ export class AgentWidget {
|
|
|
715
739
|
}
|
|
716
740
|
|
|
717
741
|
/** Force an immediate widget update. */
|
|
718
|
-
update() {
|
|
742
|
+
update(advanceSpinner = false) {
|
|
719
743
|
if (!this.uiCtx) return;
|
|
720
744
|
const allAgents = this.widgetAgents();
|
|
721
745
|
if (this.navigationActive || this.selectedAgentId) {
|
|
@@ -779,6 +803,8 @@ export class AgentWidget {
|
|
|
779
803
|
this.lastStatusText = newStatusText;
|
|
780
804
|
}
|
|
781
805
|
|
|
806
|
+
if (advanceSpinner && runningCount > 0) this.widgetFrame++;
|
|
807
|
+
|
|
782
808
|
const snapshot = allAgents.map(agent => {
|
|
783
809
|
const activity = this.agentActivity.get(agent.id);
|
|
784
810
|
return [
|
|
@@ -794,9 +820,9 @@ export class AgentWidget {
|
|
|
794
820
|
Math.floor((Date.now() - agent.startedAt) / 1000),
|
|
795
821
|
].join("\u0000");
|
|
796
822
|
}).join("\u0001");
|
|
797
|
-
if (this.widgetRegistered && snapshot === this.lastSnapshot) return;
|
|
823
|
+
if (this.widgetRegistered && snapshot === this.lastSnapshot && !advanceSpinner) return;
|
|
798
824
|
this.lastSnapshot = snapshot;
|
|
799
|
-
this.widgetFrame++;
|
|
825
|
+
if (!advanceSpinner) this.widgetFrame++;
|
|
800
826
|
|
|
801
827
|
// Register widget callback once; subsequent updates use requestRender()
|
|
802
828
|
// which re-invokes render() without replacing the component (avoids layout thrashing).
|