@esso0428/pi-subagents 0.15.14 → 0.15.16
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 +9 -32
- package/dist/ui/agent-widget.d.ts +8 -0
- package/dist/ui/agent-widget.d.ts.map +1 -1
- package/dist/ui/agent-widget.js +40 -12
- package/dist/ui/agent-widget.js.map +1 -1
- package/dist/ui/ccstyle/diff/diff-renderer.d.ts.map +1 -1
- package/dist/ui/ccstyle/diff/diff-renderer.js +2 -4
- package/dist/ui/ccstyle/diff/diff-renderer.js.map +1 -1
- package/dist/ui/ccstyle/tool-renderer.d.ts.map +1 -1
- package/dist/ui/ccstyle/tool-renderer.js +4 -10
- package/dist/ui/ccstyle/tool-renderer.js.map +1 -1
- package/dist/ui/ccstyle/tool-result.d.ts +9 -16
- package/dist/ui/ccstyle/tool-result.d.ts.map +1 -1
- package/dist/ui/ccstyle/tool-result.js +71 -121
- package/dist/ui/ccstyle/tool-result.js.map +1 -1
- package/dist/ui/conversation-timeline.d.ts +14 -9
- package/dist/ui/conversation-timeline.d.ts.map +1 -1
- package/dist/ui/conversation-timeline.js +60 -72
- package/dist/ui/conversation-timeline.js.map +1 -1
- package/dist/ui/conversation-viewer.d.ts +4 -1
- package/dist/ui/conversation-viewer.d.ts.map +1 -1
- package/dist/ui/conversation-viewer.js +95 -94
- package/dist/ui/conversation-viewer.js.map +1 -1
- package/dist/write-execution.d.ts +0 -2
- package/dist/write-execution.d.ts.map +1 -1
- package/dist/write-execution.js +1 -5
- package/dist/write-execution.js.map +1 -1
- package/package.json +1 -1
- package/src/ui/agent-widget.ts +106 -20
- package/src/ui/ccstyle/diff/diff-renderer.ts +2 -7
- package/src/ui/ccstyle/tool-renderer.ts +9 -22
- package/src/ui/ccstyle/tool-result.ts +82 -186
- package/src/ui/conversation-blocks.ts +13 -11
- package/src/ui/conversation-timeline.ts +211 -122
- package/src/ui/conversation-viewer.ts +149 -125
- package/src/ui/fleet-list.ts +34 -1
- package/src/write-execution.ts +1 -7
- package/test/ui/agent-widget.test.ts +161 -0
- package/test/ui/conversation-diff.test.ts +1 -47
- package/test/ui/conversation-viewer.test.ts +181 -112
- package/test/ui/fleet-list.test.ts +5 -1
- package/test/write-execution.test.ts +0 -2
- package/.pi/tasks/tasks-01a09263-0ac6-77be-97f5-664b3ffea76b.json +0 -53
- package/.pi/workflows/index.json +0 -5
package/src/ui/agent-widget.ts
CHANGED
|
@@ -14,7 +14,19 @@ import { getLifetimeTotal, getSessionContextPercent, type LifetimeUsage, type Se
|
|
|
14
14
|
// ---- Constants ----
|
|
15
15
|
|
|
16
16
|
/** Maximum number of rendered lines before overflow collapse kicks in. */
|
|
17
|
-
const MAX_WIDGET_LINES = 12;
|
|
17
|
+
export const MAX_WIDGET_LINES = 12;
|
|
18
|
+
/** Keep a small editor/input area visible below the above-editor widget. */
|
|
19
|
+
const MIN_EDITOR_LINES = 4;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Derive the widget ceiling from the terminal height while retaining the
|
|
23
|
+
* historical MAX_WIDGET_LINES ceiling. Very short terminals may have no room
|
|
24
|
+
* for the widget; that is preferable to consuming the editor/input area.
|
|
25
|
+
*/
|
|
26
|
+
export function getWidgetLineBudget(rows: number): number {
|
|
27
|
+
if (!Number.isFinite(rows)) return MAX_WIDGET_LINES;
|
|
28
|
+
return Math.min(MAX_WIDGET_LINES, Math.max(0, Math.floor(rows) - MIN_EDITOR_LINES));
|
|
29
|
+
}
|
|
18
30
|
|
|
19
31
|
/** Braille spinner frames for animated running indicator. */
|
|
20
32
|
export const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
@@ -176,9 +188,9 @@ export function buildInvocationTags(
|
|
|
176
188
|
return { modelName: invocation.modelName, tags };
|
|
177
189
|
}
|
|
178
190
|
|
|
179
|
-
/**
|
|
191
|
+
/** Normalize and truncate text so it can never add physical widget rows. */
|
|
180
192
|
function truncateLine(text: string, len = 60): string {
|
|
181
|
-
const line = text.
|
|
193
|
+
const line = text.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
|
|
182
194
|
if (line.length <= len) return line;
|
|
183
195
|
return line.slice(0, len) + "…";
|
|
184
196
|
}
|
|
@@ -228,6 +240,8 @@ export class AgentWidget {
|
|
|
228
240
|
private tui: any | undefined;
|
|
229
241
|
/** Last status bar text, used to avoid redundant setStatus calls. */
|
|
230
242
|
private lastStatusText: string | undefined;
|
|
243
|
+
/** Snapshot of the state used for the last widget registration/render request. */
|
|
244
|
+
private lastRenderKey: string | undefined;
|
|
231
245
|
|
|
232
246
|
constructor(
|
|
233
247
|
private manager: AgentManager,
|
|
@@ -269,6 +283,7 @@ export class AgentWidget {
|
|
|
269
283
|
this.widgetRegistered = false;
|
|
270
284
|
this.tui = undefined;
|
|
271
285
|
this.lastStatusText = undefined;
|
|
286
|
+
this.lastRenderKey = undefined;
|
|
272
287
|
}
|
|
273
288
|
}
|
|
274
289
|
|
|
@@ -285,10 +300,20 @@ export class AgentWidget {
|
|
|
285
300
|
this.update();
|
|
286
301
|
}
|
|
287
302
|
|
|
288
|
-
/**
|
|
303
|
+
/** Keep the spinner/elapsed-time timer alive only while a visible agent runs. */
|
|
289
304
|
ensureTimer() {
|
|
305
|
+
if (!this.uiCtx || !this.widgetAgents().some(a => a.status === "running")) return;
|
|
290
306
|
if (!this.widgetInterval) {
|
|
291
|
-
this.widgetInterval = setInterval(() => this.update(), 80);
|
|
307
|
+
this.widgetInterval = setInterval(() => this.update(true), 80);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private syncTimer(shouldRun: boolean): void {
|
|
312
|
+
if (shouldRun) {
|
|
313
|
+
this.ensureTimer();
|
|
314
|
+
} else if (this.widgetInterval) {
|
|
315
|
+
clearInterval(this.widgetInterval);
|
|
316
|
+
this.widgetInterval = undefined;
|
|
292
317
|
}
|
|
293
318
|
}
|
|
294
319
|
|
|
@@ -325,7 +350,7 @@ export class AgentWidget {
|
|
|
325
350
|
statusText = theme.fg("dim", " stopped");
|
|
326
351
|
} else if (a.status === "error") {
|
|
327
352
|
icon = theme.fg("error", "✗");
|
|
328
|
-
const errMsg = a.error ? `: ${a.error
|
|
353
|
+
const errMsg = a.error ? `: ${truncateLine(a.error)}` : "";
|
|
329
354
|
statusText = theme.fg("error", ` error${errMsg}`);
|
|
330
355
|
} else {
|
|
331
356
|
// aborted
|
|
@@ -340,7 +365,7 @@ export class AgentWidget {
|
|
|
340
365
|
parts.push(duration);
|
|
341
366
|
|
|
342
367
|
const modeTag = modeLabel ? ` ${theme.fg("dim", `(${modeLabel})`)}` : "";
|
|
343
|
-
return `${icon} ${theme.fg("dim", name)}${modeTag} ${theme.fg("dim", a.description)} ${theme.fg("dim", "·")} ${theme.fg("dim", parts.join(" · "))}${statusText}`;
|
|
368
|
+
return `${icon} ${theme.fg("dim", name)}${modeTag} ${theme.fg("dim", truncateLine(a.description))} ${theme.fg("dim", "·")} ${theme.fg("dim", parts.join(" · "))}${statusText}`;
|
|
344
369
|
}
|
|
345
370
|
|
|
346
371
|
/**
|
|
@@ -363,6 +388,8 @@ export class AgentWidget {
|
|
|
363
388
|
if (!hasActive && !hasFinished) return [];
|
|
364
389
|
|
|
365
390
|
const w = tui.terminal.columns;
|
|
391
|
+
const maxLines = getWidgetLineBudget(tui.terminal.rows);
|
|
392
|
+
if (maxLines === 0) return [];
|
|
366
393
|
const truncate = (line: string) => truncateToWidth(line, w);
|
|
367
394
|
const headingColor = hasActive ? "accent" : "dim";
|
|
368
395
|
const headingIcon = hasActive ? "●" : "○";
|
|
@@ -399,7 +426,7 @@ export class AgentWidget {
|
|
|
399
426
|
const activity = bg ? describeActivity(bg.activeTools, bg.responseText) : "thinking…";
|
|
400
427
|
|
|
401
428
|
runningLines.push([
|
|
402
|
-
truncate(theme.fg("dim", "├─") + ` ${theme.fg("accent", frame)} ${theme.bold(name)}${modeTag} ${theme.fg("muted", a.description)} ${theme.fg("dim", "·")} ${fgPreservingNestedStyles(theme, "dim", statsText)}`),
|
|
429
|
+
truncate(theme.fg("dim", "├─") + ` ${theme.fg("accent", frame)} ${theme.bold(name)}${modeTag} ${theme.fg("muted", truncateLine(a.description))} ${theme.fg("dim", "·")} ${fgPreservingNestedStyles(theme, "dim", statsText)}`),
|
|
403
430
|
truncate(theme.fg("dim", "│ ") + theme.fg("dim", ` ⎿ ${activity}`)),
|
|
404
431
|
]);
|
|
405
432
|
}
|
|
@@ -408,12 +435,18 @@ export class AgentWidget {
|
|
|
408
435
|
? truncate(theme.fg("dim", "├─") + ` ${theme.fg("muted", "◦")} ${theme.fg("dim", `${queued.length} queued`)}`)
|
|
409
436
|
: undefined;
|
|
410
437
|
|
|
411
|
-
// Assemble with
|
|
412
|
-
|
|
438
|
+
// Assemble with a responsive cap (heading + overflow indicator = 2
|
|
439
|
+
// reserved lines when content exceeds the available body budget).
|
|
440
|
+
const maxBody = maxLines - 1; // heading takes 1 line
|
|
413
441
|
const totalBody = finishedLines.length + runningLines.length * 2 + (queuedLine ? 1 : 0);
|
|
414
442
|
|
|
415
443
|
const lines: string[] = [truncate(theme.fg(headingColor, headingIcon) + " " + theme.fg(headingColor, "Agents"))];
|
|
416
444
|
|
|
445
|
+
if (maxLines === 1) {
|
|
446
|
+
// There is room only for the heading; do not consume the editor/input row.
|
|
447
|
+
return lines;
|
|
448
|
+
}
|
|
449
|
+
|
|
417
450
|
if (totalBody <= maxBody) {
|
|
418
451
|
// Everything fits — add all lines and fix up connectors for the last item.
|
|
419
452
|
lines.push(...finishedLines);
|
|
@@ -439,6 +472,7 @@ export class AgentWidget {
|
|
|
439
472
|
// Reserve 1 line for overflow indicator.
|
|
440
473
|
let budget = maxBody - 1;
|
|
441
474
|
let hiddenRunning = 0;
|
|
475
|
+
let hiddenQueued = 0;
|
|
442
476
|
let hiddenFinished = 0;
|
|
443
477
|
|
|
444
478
|
// 1. Running agents (2 lines each)
|
|
@@ -452,9 +486,13 @@ export class AgentWidget {
|
|
|
452
486
|
}
|
|
453
487
|
|
|
454
488
|
// 2. Queued line
|
|
455
|
-
if (queuedLine
|
|
456
|
-
|
|
457
|
-
|
|
489
|
+
if (queuedLine) {
|
|
490
|
+
if (budget >= 1) {
|
|
491
|
+
lines.push(queuedLine);
|
|
492
|
+
budget--;
|
|
493
|
+
} else {
|
|
494
|
+
hiddenQueued = 1;
|
|
495
|
+
}
|
|
458
496
|
}
|
|
459
497
|
|
|
460
498
|
// 3. Finished agents
|
|
@@ -470,17 +508,53 @@ export class AgentWidget {
|
|
|
470
508
|
// Overflow summary
|
|
471
509
|
const overflowParts: string[] = [];
|
|
472
510
|
if (hiddenRunning > 0) overflowParts.push(`${hiddenRunning} running`);
|
|
511
|
+
if (hiddenQueued > 0) overflowParts.push(`${hiddenQueued} queued`);
|
|
473
512
|
if (hiddenFinished > 0) overflowParts.push(`${hiddenFinished} finished`);
|
|
474
513
|
const overflowText = overflowParts.join(", ");
|
|
475
|
-
lines.push(truncate(theme.fg("dim", "└─") + ` ${theme.fg("dim", `+${hiddenRunning + hiddenFinished} more (${overflowText})`)}`)
|
|
514
|
+
lines.push(truncate(theme.fg("dim", "└─") + ` ${theme.fg("dim", `+${hiddenRunning + hiddenQueued + hiddenFinished} more (${overflowText})`)}`)
|
|
476
515
|
);
|
|
477
516
|
}
|
|
478
517
|
|
|
479
518
|
return lines;
|
|
480
519
|
}
|
|
481
520
|
|
|
482
|
-
/**
|
|
483
|
-
|
|
521
|
+
/** Build a render-relevant snapshot without capturing mutable records. */
|
|
522
|
+
private renderKey(allAgents: ReturnType<AgentManager["listAgents"]>): string {
|
|
523
|
+
const activities = allAgents.map(a => {
|
|
524
|
+
const activity = this.agentActivity.get(a.id);
|
|
525
|
+
return activity ? {
|
|
526
|
+
id: a.id,
|
|
527
|
+
activeTools: [...activity.activeTools.entries()],
|
|
528
|
+
toolUses: activity.toolUses,
|
|
529
|
+
responseText: activity.responseText,
|
|
530
|
+
turnCount: activity.turnCount,
|
|
531
|
+
maxTurns: activity.maxTurns,
|
|
532
|
+
lifetimeUsage: activity.lifetimeUsage,
|
|
533
|
+
contextPercent: getSessionContextPercent(activity.session),
|
|
534
|
+
} : undefined;
|
|
535
|
+
});
|
|
536
|
+
return JSON.stringify({
|
|
537
|
+
frame: this.widgetFrame,
|
|
538
|
+
agents: allAgents.map(a => ({
|
|
539
|
+
id: a.id,
|
|
540
|
+
type: a.type,
|
|
541
|
+
description: a.description,
|
|
542
|
+
status: a.status,
|
|
543
|
+
toolUses: a.toolUses,
|
|
544
|
+
startedAt: a.startedAt,
|
|
545
|
+
completedAt: a.completedAt,
|
|
546
|
+
error: a.error,
|
|
547
|
+
lifetimeUsage: a.lifetimeUsage,
|
|
548
|
+
compactionCount: a.compactionCount,
|
|
549
|
+
isBackground: a.isBackground,
|
|
550
|
+
})),
|
|
551
|
+
activities,
|
|
552
|
+
finishedTurnAge: [...this.finishedTurnAge.entries()],
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** Force an immediate widget update. `advanceSpinner` is reserved for the timer. */
|
|
557
|
+
update(advanceSpinner = false) {
|
|
484
558
|
if (!this.uiCtx) return;
|
|
485
559
|
const allAgents = this.widgetAgents();
|
|
486
560
|
|
|
@@ -506,7 +580,8 @@ export class AgentWidget {
|
|
|
506
580
|
this.uiCtx.setStatus("subagents", undefined);
|
|
507
581
|
this.lastStatusText = undefined;
|
|
508
582
|
}
|
|
509
|
-
|
|
583
|
+
this.lastRenderKey = undefined;
|
|
584
|
+
this.syncTimer(false);
|
|
510
585
|
// Clean up stale entries
|
|
511
586
|
for (const [id] of this.finishedTurnAge) {
|
|
512
587
|
if (!allAgents.some(a => a.id === id)) this.finishedTurnAge.delete(id);
|
|
@@ -528,7 +603,12 @@ export class AgentWidget {
|
|
|
528
603
|
this.lastStatusText = newStatusText;
|
|
529
604
|
}
|
|
530
605
|
|
|
531
|
-
|
|
606
|
+
// Spinner animation is driven only by the timer while a visible running
|
|
607
|
+
// agent exists. Event-driven updates refresh changed stats without making
|
|
608
|
+
// an otherwise idle widget advance.
|
|
609
|
+
if (advanceSpinner && runningCount > 0) this.widgetFrame++;
|
|
610
|
+
this.syncTimer(runningCount > 0);
|
|
611
|
+
const renderKey = this.renderKey(allAgents);
|
|
532
612
|
|
|
533
613
|
// Register widget callback once; subsequent updates use requestRender()
|
|
534
614
|
// which re-invokes render() without replacing the component (avoids layout thrashing).
|
|
@@ -541,14 +621,17 @@ export class AgentWidget {
|
|
|
541
621
|
// Theme changed — force re-registration so factory captures fresh theme.
|
|
542
622
|
this.widgetRegistered = false;
|
|
543
623
|
this.tui = undefined;
|
|
624
|
+
this.lastRenderKey = undefined;
|
|
544
625
|
},
|
|
545
626
|
};
|
|
546
627
|
}, { placement: "aboveEditor" });
|
|
547
628
|
this.widgetRegistered = true;
|
|
548
|
-
} else {
|
|
549
|
-
// Widget already registered —
|
|
629
|
+
} else if (renderKey !== this.lastRenderKey) {
|
|
630
|
+
// Widget already registered — request a re-render only when visible state
|
|
631
|
+
// changed (or the spinner timer advanced).
|
|
550
632
|
this.tui?.requestRender();
|
|
551
633
|
}
|
|
634
|
+
this.lastRenderKey = renderKey;
|
|
552
635
|
}
|
|
553
636
|
|
|
554
637
|
dispose() {
|
|
@@ -563,5 +646,8 @@ export class AgentWidget {
|
|
|
563
646
|
this.widgetRegistered = false;
|
|
564
647
|
this.tui = undefined;
|
|
565
648
|
this.lastStatusText = undefined;
|
|
649
|
+
this.lastRenderKey = undefined;
|
|
650
|
+
this.uiCtx = undefined;
|
|
651
|
+
this.finishedTurnAge.clear();
|
|
566
652
|
}
|
|
567
653
|
}
|
|
@@ -2951,11 +2951,6 @@ export function renderWriteDiffResult(
|
|
|
2951
2951
|
return new Text(theme.fg("warning", "↳ diff unavailable: durable diff has no displayable lines"), 0, 0);
|
|
2952
2952
|
}
|
|
2953
2953
|
}
|
|
2954
|
-
const persistedWasOverwrite = persistedParsed?.entries.some(
|
|
2955
|
-
(entry) => entry.kind === "line" && entry.lineKind === "remove",
|
|
2956
|
-
) ?? false;
|
|
2957
|
-
const wasOverwrite = options.fileExistedBeforeWrite === true || persistedWasOverwrite;
|
|
2958
|
-
|
|
2959
2954
|
if (typeof content !== "string" && !persistedParsed) {
|
|
2960
2955
|
if (!safeFallbackText.trim()) {
|
|
2961
2956
|
return new Text(theme.fg("muted", "↳ write completed"), 0, 0);
|
|
@@ -3036,7 +3031,7 @@ export function renderWriteDiffResult(
|
|
|
3036
3031
|
hovered,
|
|
3037
3032
|
clampDiffLinesToWidth(
|
|
3038
3033
|
renderWriteCollapsedHintLine(
|
|
3039
|
-
|
|
3034
|
+
options.fileExistedBeforeWrite === true,
|
|
3040
3035
|
safeWidth,
|
|
3041
3036
|
theme,
|
|
3042
3037
|
hovered,
|
|
@@ -3048,7 +3043,7 @@ export function renderWriteDiffResult(
|
|
|
3048
3043
|
}
|
|
3049
3044
|
|
|
3050
3045
|
const header = renderWriteHeader(
|
|
3051
|
-
|
|
3046
|
+
options.fileExistedBeforeWrite === true,
|
|
3052
3047
|
safeWidth,
|
|
3053
3048
|
theme,
|
|
3054
3049
|
options.headerLabel,
|
|
@@ -31,11 +31,8 @@ function summaryForCall(toolName: string, args: any): { main: string; detail: st
|
|
|
31
31
|
detail: details.length ? ` (${details.join(", ")})` : "",
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
const value =
|
|
35
|
-
|
|
36
|
-
return value === undefined
|
|
37
|
-
? { main: title, detail: "" }
|
|
38
|
-
: { main: `${title} ${oneLine(value)}`, detail: "" };
|
|
34
|
+
const value = args.path ?? args.file_path ?? args.command ?? args.query ?? args.pattern ?? args.url ?? args.name;
|
|
35
|
+
return value === undefined ? { main: title, detail: "" } : { main: `${title} ${oneLine(value)}`, detail: "" };
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
function renderCallComponent(toolName: string, args: any, theme: any, context: any): Component {
|
|
@@ -45,11 +42,9 @@ function renderCallComponent(toolName: string, args: any, theme: any, context: a
|
|
|
45
42
|
return {
|
|
46
43
|
render(width: number): string[] {
|
|
47
44
|
const safeWidth = Math.max(1, Math.floor(Number.isFinite(width) ? width : 1));
|
|
48
|
-
const summary = summaryForCall(toolName, args);
|
|
49
45
|
const prefix = ` ${theme.fg(color, icon)} `;
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
return [truncateToWidth(prefix + title + detail, safeWidth, "")];
|
|
46
|
+
const summary = summaryForCall(toolName, args);
|
|
47
|
+
return [truncateToWidth(prefix + theme.fg("toolTitle", summary.main) + theme.fg("dim", summary.detail), safeWidth, "")];
|
|
53
48
|
},
|
|
54
49
|
invalidate() {},
|
|
55
50
|
};
|
|
@@ -78,19 +73,11 @@ export function createViewerCcstyleTool(toolName: string, isHovered: () => boole
|
|
|
78
73
|
},
|
|
79
74
|
renderResult(result, options, theme, context) {
|
|
80
75
|
settled = true;
|
|
81
|
-
return createViewerCcstyleResult(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
{ ...
|
|
85
|
-
|
|
86
|
-
{
|
|
87
|
-
...context,
|
|
88
|
-
isHovered,
|
|
89
|
-
diffConfig: {
|
|
90
|
-
...DEFAULT_TOOL_DISPLAY_CONFIG,
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
);
|
|
76
|
+
return createViewerCcstyleResult(name, result, { ...options, isHovered }, theme, {
|
|
77
|
+
...context,
|
|
78
|
+
isHovered,
|
|
79
|
+
diffConfig: { ...DEFAULT_TOOL_DISPLAY_CONFIG },
|
|
80
|
+
});
|
|
94
81
|
},
|
|
95
82
|
};
|
|
96
83
|
}
|