@esso0428/pi-subagents 0.15.14 → 0.15.15
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 +6 -37
- 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 +38 -12
- 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-timeline.ts +77 -73
- package/src/ui/conversation-viewer.ts +102 -108
- package/src/write-execution.ts +1 -7
- package/test/ui/agent-widget.test.ts +123 -0
- package/test/ui/conversation-diff.test.ts +1 -47
- package/test/ui/conversation-viewer.test.ts +181 -112
- 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
|
}
|
|
@@ -325,7 +337,7 @@ export class AgentWidget {
|
|
|
325
337
|
statusText = theme.fg("dim", " stopped");
|
|
326
338
|
} else if (a.status === "error") {
|
|
327
339
|
icon = theme.fg("error", "✗");
|
|
328
|
-
const errMsg = a.error ? `: ${a.error
|
|
340
|
+
const errMsg = a.error ? `: ${truncateLine(a.error)}` : "";
|
|
329
341
|
statusText = theme.fg("error", ` error${errMsg}`);
|
|
330
342
|
} else {
|
|
331
343
|
// aborted
|
|
@@ -340,7 +352,7 @@ export class AgentWidget {
|
|
|
340
352
|
parts.push(duration);
|
|
341
353
|
|
|
342
354
|
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}`;
|
|
355
|
+
return `${icon} ${theme.fg("dim", name)}${modeTag} ${theme.fg("dim", truncateLine(a.description))} ${theme.fg("dim", "·")} ${theme.fg("dim", parts.join(" · "))}${statusText}`;
|
|
344
356
|
}
|
|
345
357
|
|
|
346
358
|
/**
|
|
@@ -363,6 +375,8 @@ export class AgentWidget {
|
|
|
363
375
|
if (!hasActive && !hasFinished) return [];
|
|
364
376
|
|
|
365
377
|
const w = tui.terminal.columns;
|
|
378
|
+
const maxLines = getWidgetLineBudget(tui.terminal.rows);
|
|
379
|
+
if (maxLines === 0) return [];
|
|
366
380
|
const truncate = (line: string) => truncateToWidth(line, w);
|
|
367
381
|
const headingColor = hasActive ? "accent" : "dim";
|
|
368
382
|
const headingIcon = hasActive ? "●" : "○";
|
|
@@ -399,7 +413,7 @@ export class AgentWidget {
|
|
|
399
413
|
const activity = bg ? describeActivity(bg.activeTools, bg.responseText) : "thinking…";
|
|
400
414
|
|
|
401
415
|
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)}`),
|
|
416
|
+
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
417
|
truncate(theme.fg("dim", "│ ") + theme.fg("dim", ` ⎿ ${activity}`)),
|
|
404
418
|
]);
|
|
405
419
|
}
|
|
@@ -408,12 +422,18 @@ export class AgentWidget {
|
|
|
408
422
|
? truncate(theme.fg("dim", "├─") + ` ${theme.fg("muted", "◦")} ${theme.fg("dim", `${queued.length} queued`)}`)
|
|
409
423
|
: undefined;
|
|
410
424
|
|
|
411
|
-
// Assemble with
|
|
412
|
-
|
|
425
|
+
// Assemble with a responsive cap (heading + overflow indicator = 2
|
|
426
|
+
// reserved lines when content exceeds the available body budget).
|
|
427
|
+
const maxBody = maxLines - 1; // heading takes 1 line
|
|
413
428
|
const totalBody = finishedLines.length + runningLines.length * 2 + (queuedLine ? 1 : 0);
|
|
414
429
|
|
|
415
430
|
const lines: string[] = [truncate(theme.fg(headingColor, headingIcon) + " " + theme.fg(headingColor, "Agents"))];
|
|
416
431
|
|
|
432
|
+
if (maxLines === 1) {
|
|
433
|
+
// There is room only for the heading; do not consume the editor/input row.
|
|
434
|
+
return lines;
|
|
435
|
+
}
|
|
436
|
+
|
|
417
437
|
if (totalBody <= maxBody) {
|
|
418
438
|
// Everything fits — add all lines and fix up connectors for the last item.
|
|
419
439
|
lines.push(...finishedLines);
|
|
@@ -439,6 +459,7 @@ export class AgentWidget {
|
|
|
439
459
|
// Reserve 1 line for overflow indicator.
|
|
440
460
|
let budget = maxBody - 1;
|
|
441
461
|
let hiddenRunning = 0;
|
|
462
|
+
let hiddenQueued = 0;
|
|
442
463
|
let hiddenFinished = 0;
|
|
443
464
|
|
|
444
465
|
// 1. Running agents (2 lines each)
|
|
@@ -452,9 +473,13 @@ export class AgentWidget {
|
|
|
452
473
|
}
|
|
453
474
|
|
|
454
475
|
// 2. Queued line
|
|
455
|
-
if (queuedLine
|
|
456
|
-
|
|
457
|
-
|
|
476
|
+
if (queuedLine) {
|
|
477
|
+
if (budget >= 1) {
|
|
478
|
+
lines.push(queuedLine);
|
|
479
|
+
budget--;
|
|
480
|
+
} else {
|
|
481
|
+
hiddenQueued = 1;
|
|
482
|
+
}
|
|
458
483
|
}
|
|
459
484
|
|
|
460
485
|
// 3. Finished agents
|
|
@@ -470,9 +495,10 @@ export class AgentWidget {
|
|
|
470
495
|
// Overflow summary
|
|
471
496
|
const overflowParts: string[] = [];
|
|
472
497
|
if (hiddenRunning > 0) overflowParts.push(`${hiddenRunning} running`);
|
|
498
|
+
if (hiddenQueued > 0) overflowParts.push(`${hiddenQueued} queued`);
|
|
473
499
|
if (hiddenFinished > 0) overflowParts.push(`${hiddenFinished} finished`);
|
|
474
500
|
const overflowText = overflowParts.join(", ");
|
|
475
|
-
lines.push(truncate(theme.fg("dim", "└─") + ` ${theme.fg("dim", `+${hiddenRunning + hiddenFinished} more (${overflowText})`)}`)
|
|
501
|
+
lines.push(truncate(theme.fg("dim", "└─") + ` ${theme.fg("dim", `+${hiddenRunning + hiddenQueued + hiddenFinished} more (${overflowText})`)}`)
|
|
476
502
|
);
|
|
477
503
|
}
|
|
478
504
|
|
|
@@ -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
|
}
|
|
@@ -51,21 +51,16 @@ export function outputLineCount(result: any): number {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export function countLines(text: string): number {
|
|
54
|
-
return text.trim() ? text.trim().split("\n").filter((line) => line.trim()
|
|
54
|
+
return text.trim() ? text.trim().split("\n").filter((line) => line.trim()).length : 0;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/** Format arguments as stable, readable `key: value` rows for the Input rail. */
|
|
58
58
|
export function formatToolInputArgs(args: unknown, maxChars = 8_000): string {
|
|
59
59
|
if (args === undefined || args === null) return "";
|
|
60
|
-
if (typeof args !== "object") {
|
|
61
|
-
const
|
|
62
|
-
return
|
|
60
|
+
if (typeof args !== "object" || Array.isArray(args)) {
|
|
61
|
+
const rendered = typeof args === "string" ? sanitizeToolResultText(args) : safeJson(args);
|
|
62
|
+
return rendered.length > maxChars ? `${rendered.slice(0, maxChars)}…` : rendered;
|
|
63
63
|
}
|
|
64
|
-
if (Array.isArray(args)) {
|
|
65
|
-
const text = safeJson(args);
|
|
66
|
-
return text.length > maxChars ? `${text.slice(0, maxChars)}…` : text;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
64
|
const preferred = [
|
|
70
65
|
"path", "file_path", "command", "query", "pattern", "url", "name",
|
|
71
66
|
"message", "content", "old_string", "new_string",
|
|
@@ -81,32 +76,24 @@ export function formatToolInputArgs(args: unknown, maxChars = 8_000): string {
|
|
|
81
76
|
return li - ri;
|
|
82
77
|
});
|
|
83
78
|
const lines: string[] = [];
|
|
84
|
-
for (const [
|
|
85
|
-
const
|
|
79
|
+
for (const [key, value] of entries) {
|
|
80
|
+
const safeKey = sanitizeToolResultText(key);
|
|
86
81
|
if (typeof value === "string") {
|
|
87
82
|
const safeValue = sanitizeToolResultText(value);
|
|
88
83
|
if (safeValue.includes("\n")) {
|
|
89
|
-
lines.push(`${
|
|
90
|
-
|
|
91
|
-
} else {
|
|
92
|
-
lines.push(`${key}: ${safeValue}`);
|
|
93
|
-
}
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
if (typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
97
|
-
lines.push(`${key}: ${String(value)}`);
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
const json = safeJson(value);
|
|
101
|
-
if (json.includes("\n")) {
|
|
102
|
-
lines.push(`${key}:`);
|
|
103
|
-
for (const line of json.split("\n")) lines.push(` ${line}`);
|
|
84
|
+
lines.push(`${safeKey}:`);
|
|
85
|
+
lines.push(...safeValue.split("\n").map((line) => ` ${line}`));
|
|
86
|
+
} else lines.push(`${safeKey}: ${safeValue}`);
|
|
104
87
|
} else {
|
|
105
|
-
|
|
88
|
+
const rendered = safeJson(value);
|
|
89
|
+
if (rendered.includes("\n")) {
|
|
90
|
+
lines.push(`${safeKey}:`);
|
|
91
|
+
lines.push(...rendered.split("\n").map((line) => ` ${line}`));
|
|
92
|
+
} else lines.push(`${safeKey}: ${rendered}`);
|
|
106
93
|
}
|
|
107
94
|
}
|
|
108
|
-
const
|
|
109
|
-
return
|
|
95
|
+
const rendered = lines.join("\n");
|
|
96
|
+
return rendered.length > maxChars ? `${rendered.slice(0, maxChars)}…` : rendered;
|
|
110
97
|
}
|
|
111
98
|
|
|
112
99
|
function hasExpandableDetail(text: string, args: unknown): boolean {
|
|
@@ -115,18 +102,8 @@ function hasExpandableDetail(text: string, args: unknown): boolean {
|
|
|
115
102
|
|
|
116
103
|
export type ToolIoSection = "input" | "output";
|
|
117
104
|
|
|
118
|
-
/**
|
|
119
|
-
|
|
120
|
-
* mirror ccstyle, but state is kept on this component so history remains
|
|
121
|
-
* deterministic and no global terminal mouse handler is needed.
|
|
122
|
-
*/
|
|
123
|
-
class ExpandedToolIoView implements Component {
|
|
124
|
-
private readonly isError: boolean;
|
|
125
|
-
private input: string;
|
|
126
|
-
private output: string;
|
|
127
|
-
private maxOutputLines: number;
|
|
128
|
-
private maxInputLines: number;
|
|
129
|
-
private theme: any;
|
|
105
|
+
/** Viewer-local adaptation of pi-cc's structured Input/Output tool card. */
|
|
106
|
+
export class ExpandedToolIoView implements Component {
|
|
130
107
|
private cachedWidth: number | undefined;
|
|
131
108
|
private cachedLines: string[] | undefined;
|
|
132
109
|
private lineSections: Array<ToolIoSection | null> = [];
|
|
@@ -134,28 +111,16 @@ class ExpandedToolIoView implements Component {
|
|
|
134
111
|
private showMoreRows: Partial<Record<ToolIoSection, number>> = {};
|
|
135
112
|
|
|
136
113
|
constructor(
|
|
137
|
-
input: string,
|
|
138
|
-
output: string,
|
|
139
|
-
isError: boolean,
|
|
140
|
-
maxOutputLines = DEFAULT_TOOL_DISPLAY_CONFIG.expandedPreviewMaxLines,
|
|
141
|
-
maxInputLines = DEFAULT_TOOL_DISPLAY_CONFIG.expandedPreviewMaxLines,
|
|
142
|
-
theme?: any,
|
|
143
|
-
) {
|
|
144
|
-
this.input = input;
|
|
145
|
-
this.output = output;
|
|
146
|
-
this.isError = isError;
|
|
147
|
-
this.maxOutputLines = Math.max(1, maxOutputLines);
|
|
148
|
-
this.maxInputLines = Math.max(1, maxInputLines);
|
|
149
|
-
this.theme = theme;
|
|
150
|
-
}
|
|
114
|
+
private input: string,
|
|
115
|
+
private output: string,
|
|
116
|
+
private readonly isError: boolean,
|
|
117
|
+
private readonly maxOutputLines = DEFAULT_TOOL_DISPLAY_CONFIG.expandedPreviewMaxLines,
|
|
118
|
+
private readonly maxInputLines = DEFAULT_TOOL_DISPLAY_CONFIG.expandedPreviewMaxLines,
|
|
119
|
+
private readonly theme?: any,
|
|
120
|
+
) {}
|
|
151
121
|
|
|
152
|
-
getInputBody(): string {
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
getOutputBody(): string {
|
|
157
|
-
return this.output.trim() ? this.output : "Done";
|
|
158
|
-
}
|
|
122
|
+
getInputBody(): string { return this.input; }
|
|
123
|
+
getOutputBody(): string { return this.output.trim() ? this.output : "Done"; }
|
|
159
124
|
|
|
160
125
|
setHoveredSection(section: ToolIoSection | null): void {
|
|
161
126
|
if (this.hoveredSection === section) return;
|
|
@@ -163,47 +128,30 @@ class ExpandedToolIoView implements Component {
|
|
|
163
128
|
this.invalidate();
|
|
164
129
|
}
|
|
165
130
|
|
|
166
|
-
sectionAtLine(line: number): ToolIoSection | null {
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
showMoreLine(section: ToolIoSection): number | undefined {
|
|
171
|
-
return this.showMoreRows[section];
|
|
172
|
-
}
|
|
131
|
+
sectionAtLine(line: number): ToolIoSection | null { return this.lineSections[line] ?? null; }
|
|
132
|
+
showMoreLine(section: ToolIoSection): number | undefined { return this.showMoreRows[section]; }
|
|
173
133
|
|
|
174
134
|
render(width: number): string[] {
|
|
175
135
|
const safeWidth = toolViewportWidth(width);
|
|
176
136
|
if (this.cachedLines && this.cachedWidth === safeWidth) return this.cachedLines;
|
|
177
|
-
|
|
178
|
-
const
|
|
137
|
+
const theme = this.theme;
|
|
138
|
+
const color = (name: string, value: string): string => typeof theme?.fg === "function" ? theme.fg(name, value) : value;
|
|
139
|
+
const bold = (value: string): string => typeof theme?.bold === "function" ? theme.bold(value) : value;
|
|
179
140
|
const rail = " │ ";
|
|
180
141
|
const contentWidth = Math.max(1, safeWidth - visibleWidth(rail));
|
|
142
|
+
const bodyColor = this.isError ? "error" : "toolOutput";
|
|
181
143
|
const rows: string[] = [];
|
|
182
144
|
this.lineSections = [];
|
|
183
145
|
this.showMoreRows = {};
|
|
184
|
-
|
|
185
146
|
const push = (line: string, section: ToolIoSection | null) => {
|
|
186
147
|
rows.push(truncateToWidth(line, safeWidth, ""));
|
|
187
148
|
this.lineSections.push(section);
|
|
188
149
|
};
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
color("dim", ` ${corner} `) + color("accent", bold(title)),
|
|
195
|
-
null,
|
|
196
|
-
);
|
|
197
|
-
const pushRail = (value: string, section: ToolIoSection, continued: boolean) => push(
|
|
198
|
-
color("dim", continued ? rail : " ") + value,
|
|
199
|
-
section,
|
|
200
|
-
);
|
|
201
|
-
const pushBlank = () => push(color("dim", " │"), null);
|
|
202
|
-
const styleInput = (line: string): string => {
|
|
203
|
-
const match = line.match(/^([A-Za-z_][\w.-]*)(:\s*)(.*)$/);
|
|
204
|
-
return match
|
|
205
|
-
? color("dim", `${match[1]}${match[2]}`) + color("muted", match[3] ?? "")
|
|
206
|
-
: color("muted", line);
|
|
150
|
+
const pushHeader = (corner: "├" | "└", title: string) => {
|
|
151
|
+
push(color("dim", ` ${corner} `) + color("accent", bold(title)), null);
|
|
152
|
+
};
|
|
153
|
+
const pushRail = (line: string, section: ToolIoSection, continued: boolean) => {
|
|
154
|
+
push(color("dim", continued ? rail : " ") + line, section);
|
|
207
155
|
};
|
|
208
156
|
const pushBody = (body: string, section: ToolIoSection, limit: number, continued: boolean, input: boolean) => {
|
|
209
157
|
const raw = body.replace(/\t/g, " ").replace(/\n+$/, "");
|
|
@@ -213,20 +161,25 @@ class ExpandedToolIoView implements Component {
|
|
|
213
161
|
}
|
|
214
162
|
const sources = raw.split("\n");
|
|
215
163
|
const wrapped: string[] = [];
|
|
164
|
+
const styleInput = (line: string): string => {
|
|
165
|
+
const match = line.match(/^([A-Za-z_][\w.-]*)(:\s*)(.*)$/);
|
|
166
|
+
return match
|
|
167
|
+
? color("dim", `${match[1]}${match[2]}`) + color("muted", match[3] ?? "")
|
|
168
|
+
: color("muted", line);
|
|
169
|
+
};
|
|
216
170
|
for (const source of sources) {
|
|
217
171
|
const styled = input ? styleInput(source) : color(bodyColor, source);
|
|
218
|
-
const
|
|
219
|
-
wrapped.push(...(
|
|
172
|
+
const lines = wrapTextWithAnsi(styled, contentWidth);
|
|
173
|
+
wrapped.push(...(lines.length ? lines : [styled]));
|
|
220
174
|
}
|
|
221
175
|
const truncated = sources.length > limit || wrapped.length > limit;
|
|
222
176
|
const visible = truncated ? wrapped.slice(0, limit) : wrapped;
|
|
223
177
|
for (const line of visible) pushRail(line, section, continued);
|
|
224
|
-
if (truncated) {
|
|
225
|
-
const hidden =
|
|
178
|
+
if (truncated && wrapped.length > visible.length) {
|
|
179
|
+
const hidden = wrapped.length - visible.length;
|
|
226
180
|
const hintColor = this.hoveredSection === section ? "text" : "dim";
|
|
227
181
|
pushRail(
|
|
228
|
-
color("dim", `… +${hidden} more lines`) +
|
|
229
|
-
color("dim", " •") + color(hintColor, ` ${showMoreHintText()}`),
|
|
182
|
+
color("dim", `… +${hidden} more lines`) + color("dim", " •") + color(hintColor, ` ${showMoreHintText()}`),
|
|
230
183
|
section,
|
|
231
184
|
continued,
|
|
232
185
|
);
|
|
@@ -235,15 +188,13 @@ class ExpandedToolIoView implements Component {
|
|
|
235
188
|
};
|
|
236
189
|
|
|
237
190
|
const hasInput = this.input.trim().length > 0;
|
|
238
|
-
const output = this.getOutputBody();
|
|
239
191
|
if (hasInput) {
|
|
240
192
|
pushHeader("├", "Input");
|
|
241
|
-
pushBody(this.input, "input", this.maxInputLines, true, true);
|
|
242
|
-
|
|
193
|
+
pushBody(this.input, "input", Math.max(1, this.maxInputLines), true, true);
|
|
194
|
+
push(color("dim", " │"), null);
|
|
243
195
|
}
|
|
244
196
|
pushHeader("└", "Output");
|
|
245
|
-
pushBody(
|
|
246
|
-
|
|
197
|
+
pushBody(this.getOutputBody(), "output", Math.max(1, this.maxOutputLines), false, false);
|
|
247
198
|
this.cachedWidth = safeWidth;
|
|
248
199
|
this.cachedLines = rows;
|
|
249
200
|
return rows;
|
|
@@ -253,7 +204,6 @@ class ExpandedToolIoView implements Component {
|
|
|
253
204
|
this.cachedWidth = undefined;
|
|
254
205
|
this.cachedLines = undefined;
|
|
255
206
|
}
|
|
256
|
-
|
|
257
207
|
}
|
|
258
208
|
|
|
259
209
|
function unavailableComponent(reason: string, theme: any): Component {
|
|
@@ -265,34 +215,24 @@ function unavailableComponent(reason: string, theme: any): Component {
|
|
|
265
215
|
};
|
|
266
216
|
}
|
|
267
217
|
|
|
268
|
-
function resultText(result: any): string {
|
|
269
|
-
return rawTextFromResult(result);
|
|
270
|
-
}
|
|
218
|
+
function resultText(result: any): string { return rawTextFromResult(result); }
|
|
271
219
|
|
|
272
220
|
function persistedFromDetails(details: any): PersistedDiff | undefined {
|
|
273
221
|
if (!details || typeof details !== "object") return undefined;
|
|
274
222
|
const persisted: PersistedDiff = {};
|
|
275
223
|
if (typeof details.diff === "string") persisted.diff = details.diff;
|
|
276
224
|
if (typeof details.patch === "string") persisted.patch = details.patch;
|
|
277
|
-
if (typeof details.diffUnavailableReason === "string")
|
|
278
|
-
persisted.diffUnavailableReason = details.diffUnavailableReason;
|
|
279
|
-
}
|
|
225
|
+
if (typeof details.diffUnavailableReason === "string") persisted.diffUnavailableReason = details.diffUnavailableReason;
|
|
280
226
|
return Object.keys(persisted).length ? persisted : undefined;
|
|
281
227
|
}
|
|
282
228
|
|
|
283
229
|
function viewerConfig(context: any): ViewerDiffConfig {
|
|
284
230
|
const candidate = context?.diffConfig;
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
...candidate,
|
|
289
|
-
};
|
|
231
|
+
return candidate && typeof candidate === "object"
|
|
232
|
+
? { ...DEFAULT_TOOL_DISPLAY_CONFIG, ...candidate }
|
|
233
|
+
: DEFAULT_TOOL_DISPLAY_CONFIG;
|
|
290
234
|
}
|
|
291
235
|
|
|
292
|
-
/**
|
|
293
|
-
* Rich edit/write rendering for the conversation viewer. Durable details are
|
|
294
|
-
* passed through before any live/ephemeral context so history is deterministic.
|
|
295
|
-
*/
|
|
296
236
|
export function renderRichToolResult(
|
|
297
237
|
toolName: string,
|
|
298
238
|
result: any,
|
|
@@ -303,61 +243,35 @@ export function renderRichToolResult(
|
|
|
303
243
|
if (options?.isPartial || options?.isError || context?.isError) return undefined;
|
|
304
244
|
const expanded = options?.expanded === true || context?.expanded === true;
|
|
305
245
|
const args = context?.args ?? options?.args;
|
|
306
|
-
const filePath = args?.file_path ?? args?.path;
|
|
307
246
|
const details = result?.details;
|
|
308
247
|
const persistedDiff = context?.persistedDiff ?? persistedFromDetails(details);
|
|
309
248
|
const fileExistedBeforeWrite = typeof context?.fileExistedBeforeWrite === "boolean"
|
|
310
249
|
? context.fileExistedBeforeWrite
|
|
311
|
-
: typeof details?.fileExistedBeforeWrite === "boolean"
|
|
312
|
-
? details.fileExistedBeforeWrite
|
|
313
|
-
: undefined;
|
|
250
|
+
: typeof details?.fileExistedBeforeWrite === "boolean" ? details.fileExistedBeforeWrite : undefined;
|
|
314
251
|
|
|
315
252
|
if (toolName === "edit") {
|
|
316
|
-
return renderEditDiffResult(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
theme,
|
|
321
|
-
resultText(result),
|
|
322
|
-
);
|
|
253
|
+
return renderEditDiffResult(details, {
|
|
254
|
+
expanded, filePath: args?.file_path ?? args?.path, persistedDiff,
|
|
255
|
+
isHovered: options?.isHovered, invalidate: context?.invalidate,
|
|
256
|
+
}, viewerConfig(context), theme, resultText(result));
|
|
323
257
|
}
|
|
324
258
|
if (toolName !== "write") return undefined;
|
|
325
259
|
if (persistedDiff?.diffUnavailableReason && !persistedDiff.diff && !persistedDiff.patch) {
|
|
326
260
|
return unavailableComponent(persistedDiff.diffUnavailableReason, theme);
|
|
327
261
|
}
|
|
328
|
-
|
|
329
262
|
const content = typeof args?.content === "string" ? args.content : undefined;
|
|
330
|
-
if (
|
|
263
|
+
if (typeof content !== "string" && !persistedDiff?.diff && !persistedDiff?.patch) {
|
|
331
264
|
return unavailableComponent("execution metadata is unavailable", theme);
|
|
332
265
|
}
|
|
333
|
-
return renderWriteDiffResult(
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
invalidate: context?.invalidate,
|
|
343
|
-
},
|
|
344
|
-
viewerConfig(context),
|
|
345
|
-
theme,
|
|
346
|
-
resultText(result),
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function renderCallSummary(toolName: string, args: any, theme: any): string {
|
|
351
|
-
const title = toolName.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ");
|
|
352
|
-
const value = args?.path ?? args?.file_path ?? args?.command ?? args?.query ?? args?.name;
|
|
353
|
-
const summary = value === undefined ? title : `${title} ${oneLine(value)}`;
|
|
354
|
-
return theme.fg("toolTitle", summary);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function settledIcon(context: any): string {
|
|
358
|
-
if (context?.isError) return "✗";
|
|
359
|
-
if (context?.isPartial || context?.executionStarted) return "●";
|
|
360
|
-
return "✓";
|
|
266
|
+
return renderWriteDiffResult(content, {
|
|
267
|
+
expanded,
|
|
268
|
+
filePath: args?.file_path ?? args?.path,
|
|
269
|
+
persistedDiff,
|
|
270
|
+
previousContent: context?.previousContent,
|
|
271
|
+
fileExistedBeforeWrite,
|
|
272
|
+
isHovered: options?.isHovered,
|
|
273
|
+
invalidate: context?.invalidate,
|
|
274
|
+
}, viewerConfig(context), theme, resultText(result));
|
|
361
275
|
}
|
|
362
276
|
|
|
363
277
|
function renderFallbackResult(result: any, options: any, theme: any, context: any): Component {
|
|
@@ -365,49 +279,31 @@ function renderFallbackResult(result: any, options: any, theme: any, context: an
|
|
|
365
279
|
const expanded = options?.expanded === true || context?.expanded === true;
|
|
366
280
|
const maxChars = Number.isFinite(context?.maxChars) ? Math.max(0, Math.floor(context.maxChars)) : 16_384;
|
|
367
281
|
const text = textFromResult(result, expanded, maxChars);
|
|
368
|
-
|
|
282
|
+
const input = formatToolInputArgs(context?.args, maxChars);
|
|
283
|
+
if (expanded && (text.trim() || input.trim())) {
|
|
369
284
|
const maxLines = Number.isFinite(context?.diffConfig?.expandedPreviewMaxLines)
|
|
370
285
|
? Math.max(1, Math.floor(context.diffConfig.expandedPreviewMaxLines))
|
|
371
286
|
: DEFAULT_TOOL_DISPLAY_CONFIG.expandedPreviewMaxLines;
|
|
372
|
-
return new ExpandedToolIoView(
|
|
373
|
-
formatToolInputArgs(context?.args, maxChars),
|
|
374
|
-
text,
|
|
375
|
-
Boolean(error),
|
|
376
|
-
maxLines,
|
|
377
|
-
maxLines,
|
|
378
|
-
theme,
|
|
379
|
-
);
|
|
287
|
+
return new ExpandedToolIoView(input, text, Boolean(error), maxLines, maxLines, theme);
|
|
380
288
|
}
|
|
381
|
-
const
|
|
382
|
-
const count = lines.length;
|
|
289
|
+
const count = text ? text.split("\n").length : 0;
|
|
383
290
|
const summary = error ? oneLine(text) || "Failed" : count ? `${count} ${count === 1 ? "line" : "lines"} returned` : "Done";
|
|
384
291
|
const expandable = !expanded && hasExpandableDetail(text, context?.args);
|
|
385
292
|
return {
|
|
386
293
|
render(width: number): string[] {
|
|
387
294
|
const hovered = options?.isHovered?.() ?? context?.isHovered?.() ?? false;
|
|
388
|
-
const
|
|
389
|
-
const hint = expandable ? ` • ${clickLabel}` : "";
|
|
390
|
-
const color = error ? "error" : "muted";
|
|
295
|
+
const hint = expandable ? ` • ${showMoreHintText()}` : "";
|
|
391
296
|
const line = expandable && hovered
|
|
392
|
-
? theme.fg(
|
|
393
|
-
: theme.fg(
|
|
297
|
+
? theme.fg(error ? "error" : "muted", ` ↳ ${summary} • `) + theme.fg("text", showMoreHintText())
|
|
298
|
+
: theme.fg(error ? "error" : "muted", ` ↳ ${summary}${hint}`);
|
|
394
299
|
return [truncateToWidth(line, toolViewportWidth(width), "")];
|
|
395
300
|
},
|
|
396
301
|
invalidate() {},
|
|
397
302
|
};
|
|
398
303
|
}
|
|
399
304
|
|
|
400
|
-
export function createViewerCcstyleResult(
|
|
401
|
-
toolName
|
|
402
|
-
result: any,
|
|
403
|
-
options: any,
|
|
404
|
-
theme: any,
|
|
405
|
-
context: any,
|
|
406
|
-
): Component {
|
|
407
|
-
return (
|
|
408
|
-
renderRichToolResult(toolName, result, options, theme, context) ??
|
|
409
|
-
renderFallbackResult(result, options, theme, context)
|
|
410
|
-
);
|
|
305
|
+
export function createViewerCcstyleResult(toolName: string, result: any, options: any, theme: any, context: any): Component {
|
|
306
|
+
return renderRichToolResult(toolName, result, options, theme, context) ?? renderFallbackResult(result, options, theme, context);
|
|
411
307
|
}
|
|
412
308
|
|
|
413
|
-
export {
|
|
309
|
+
export type { DisplayConfigInput };
|