@oh-my-pi/pi-coding-agent 3.14.0 → 3.15.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 +79 -0
- package/docs/theme.md +38 -5
- package/examples/sdk/11-sessions.ts +2 -2
- package/package.json +7 -4
- package/src/cli/file-processor.ts +51 -2
- package/src/cli/plugin-cli.ts +25 -19
- package/src/cli/update-cli.ts +4 -3
- package/src/core/agent-session.ts +31 -4
- package/src/core/compaction/branch-summarization.ts +4 -32
- package/src/core/compaction/compaction.ts +6 -84
- package/src/core/compaction/utils.ts +2 -3
- package/src/core/custom-tools/types.ts +2 -0
- package/src/core/export-html/index.ts +1 -1
- package/src/core/hooks/tool-wrapper.ts +0 -1
- package/src/core/hooks/types.ts +2 -2
- package/src/core/plugins/doctor.ts +9 -1
- package/src/core/sdk.ts +2 -1
- package/src/core/session-manager.ts +518 -40
- package/src/core/settings-manager.ts +174 -0
- package/src/core/system-prompt.ts +9 -14
- package/src/core/title-generator.ts +2 -8
- package/src/core/tools/ask.ts +19 -37
- package/src/core/tools/bash.ts +2 -37
- package/src/core/tools/edit.ts +2 -9
- package/src/core/tools/exa/render.ts +52 -48
- package/src/core/tools/find.ts +10 -8
- package/src/core/tools/grep.ts +45 -17
- package/src/core/tools/ls.ts +22 -2
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +3 -0
- package/src/core/tools/lsp/index.ts +107 -55
- package/src/core/tools/lsp/render.ts +192 -79
- package/src/core/tools/lsp/types.ts +27 -0
- package/src/core/tools/lsp/utils.ts +62 -22
- package/src/core/tools/notebook.ts +9 -1
- package/src/core/tools/output.ts +37 -14
- package/src/core/tools/read.ts +349 -34
- package/src/core/tools/renderers.ts +290 -89
- package/src/core/tools/review.ts +12 -5
- package/src/core/tools/task/agents.ts +5 -5
- package/src/core/tools/task/commands.ts +3 -3
- package/src/core/tools/task/executor.ts +33 -1
- package/src/core/tools/task/index.ts +93 -6
- package/src/core/tools/task/render.ts +147 -66
- package/src/core/tools/task/types.ts +14 -9
- package/src/core/tools/web-fetch.ts +242 -103
- package/src/core/tools/web-search/index.ts +64 -20
- package/src/core/tools/web-search/providers/exa.ts +68 -172
- package/src/core/tools/web-search/render.ts +264 -74
- package/src/core/tools/write.ts +2 -8
- package/src/main.ts +10 -6
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +9 -4
- package/src/modes/interactive/components/bash-execution.ts +6 -3
- package/src/modes/interactive/components/branch-summary-message.ts +1 -1
- package/src/modes/interactive/components/compaction-summary-message.ts +1 -1
- package/src/modes/interactive/components/dynamic-border.ts +1 -1
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +4 -5
- package/src/modes/interactive/components/extensions/extension-list.ts +18 -16
- package/src/modes/interactive/components/extensions/inspector-panel.ts +8 -8
- package/src/modes/interactive/components/hook-message.ts +2 -2
- package/src/modes/interactive/components/hook-selector.ts +1 -1
- package/src/modes/interactive/components/model-selector.ts +22 -9
- package/src/modes/interactive/components/oauth-selector.ts +20 -4
- package/src/modes/interactive/components/plugin-settings.ts +4 -2
- package/src/modes/interactive/components/session-selector.ts +9 -6
- package/src/modes/interactive/components/settings-defs.ts +285 -1
- package/src/modes/interactive/components/settings-selector.ts +176 -3
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +170 -223
- package/src/modes/interactive/components/tool-execution.ts +446 -211
- package/src/modes/interactive/components/tree-selector.ts +17 -6
- package/src/modes/interactive/components/ttsr-notification.ts +4 -4
- package/src/modes/interactive/components/welcome.ts +27 -19
- package/src/modes/interactive/interactive-mode.ts +98 -13
- package/src/modes/interactive/theme/dark.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +3 -2
- package/src/modes/interactive/theme/theme-schema.json +120 -4
- package/src/modes/interactive/theme/theme.ts +1228 -14
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/init.md +30 -0
- package/src/{core/tools/task/bundled-agents → prompts}/reviewer.md +6 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/{core/tools/task/bundled-agents → prompts}/task.md +2 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -0
- package/src/commands/init.md +0 -20
- /package/src/{core/tools/task/bundled-commands → prompts}/architect-plan.md +0 -0
- /package/src/{core/tools/task/bundled-agents → prompts}/browser.md +0 -0
- /package/src/{core/tools/task/bundled-agents → prompts}/explore.md +0 -0
- /package/src/{core/tools/task/bundled-commands → prompts}/implement-with-critic.md +0 -0
- /package/src/{core/tools/task/bundled-commands → prompts}/implement.md +0 -0
- /package/src/{core/tools/task/bundled-agents → prompts}/plan.md +0 -0
|
@@ -22,6 +22,289 @@ import { truncateToVisualLines } from "./visual-truncate";
|
|
|
22
22
|
|
|
23
23
|
// Preview line limit for bash when not expanded
|
|
24
24
|
const BASH_PREVIEW_LINES = 5;
|
|
25
|
+
const GENERIC_PREVIEW_LINES = 6;
|
|
26
|
+
const GENERIC_ARG_PREVIEW = 6;
|
|
27
|
+
const GENERIC_VALUE_MAX = 80;
|
|
28
|
+
const EDIT_DIFF_PREVIEW_HUNKS = 2;
|
|
29
|
+
const EDIT_DIFF_PREVIEW_LINES = 24;
|
|
30
|
+
|
|
31
|
+
function wrapBrackets(text: string): string {
|
|
32
|
+
return `${theme.format.bracketLeft}${text}${theme.format.bracketRight}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function countLines(text: string): number {
|
|
36
|
+
if (!text) return 0;
|
|
37
|
+
return text.split("\n").length;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function formatMetadataLine(lineCount: number | null, language: string | undefined): string {
|
|
41
|
+
const icon = theme.getLangIcon(language);
|
|
42
|
+
if (lineCount !== null) {
|
|
43
|
+
return theme.fg("dim", `${icon} ${lineCount} lines`);
|
|
44
|
+
}
|
|
45
|
+
return theme.fg("dim", `${icon}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const IMAGE_EXTENSIONS = new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "ico", "bmp", "tiff"]);
|
|
49
|
+
const BINARY_EXTENSIONS = new Set(["pdf", "zip", "tar", "gz", "exe", "dll", "so", "dylib", "wasm"]);
|
|
50
|
+
|
|
51
|
+
function getFileType(filePath: string): "image" | "binary" | "text" {
|
|
52
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
53
|
+
if (!ext) return "text";
|
|
54
|
+
if (IMAGE_EXTENSIONS.has(ext)) return "image";
|
|
55
|
+
if (BINARY_EXTENSIONS.has(ext)) return "binary";
|
|
56
|
+
return "text";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function formatDiffStats(added: number, removed: number, hunks: number): string {
|
|
60
|
+
const parts: string[] = [];
|
|
61
|
+
if (added > 0) parts.push(theme.fg("success", `+${added}`));
|
|
62
|
+
if (removed > 0) parts.push(theme.fg("error", `-${removed}`));
|
|
63
|
+
if (hunks > 0) parts.push(theme.fg("dim", `${hunks} hunk${hunks !== 1 ? "s" : ""}`));
|
|
64
|
+
return parts.join(theme.fg("dim", " / "));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type DiffStats = {
|
|
68
|
+
added: number;
|
|
69
|
+
removed: number;
|
|
70
|
+
hunks: number;
|
|
71
|
+
lines: number;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function getDiffStats(diffText: string): DiffStats {
|
|
75
|
+
const lines = diffText ? diffText.split("\n") : [];
|
|
76
|
+
let added = 0;
|
|
77
|
+
let removed = 0;
|
|
78
|
+
let hunks = 0;
|
|
79
|
+
let inHunk = false;
|
|
80
|
+
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
const isAdded = line.startsWith("+");
|
|
83
|
+
const isRemoved = line.startsWith("-");
|
|
84
|
+
const isChange = isAdded || isRemoved;
|
|
85
|
+
|
|
86
|
+
if (isAdded) added++;
|
|
87
|
+
if (isRemoved) removed++;
|
|
88
|
+
|
|
89
|
+
if (isChange && !inHunk) {
|
|
90
|
+
hunks++;
|
|
91
|
+
inHunk = true;
|
|
92
|
+
} else if (!isChange) {
|
|
93
|
+
inHunk = false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { added, removed, hunks, lines: lines.length };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function truncateDiffByHunk(
|
|
101
|
+
diffText: string,
|
|
102
|
+
maxHunks: number,
|
|
103
|
+
maxLines: number,
|
|
104
|
+
): { text: string; hiddenHunks: number; hiddenLines: number } {
|
|
105
|
+
const lines = diffText ? diffText.split("\n") : [];
|
|
106
|
+
const totalStats = getDiffStats(diffText);
|
|
107
|
+
const kept: string[] = [];
|
|
108
|
+
let inHunk = false;
|
|
109
|
+
let currentHunks = 0;
|
|
110
|
+
let reachedLimit = false;
|
|
111
|
+
|
|
112
|
+
for (const line of lines) {
|
|
113
|
+
const isChange = line.startsWith("+") || line.startsWith("-");
|
|
114
|
+
if (isChange && !inHunk) {
|
|
115
|
+
currentHunks++;
|
|
116
|
+
inHunk = true;
|
|
117
|
+
}
|
|
118
|
+
if (!isChange) {
|
|
119
|
+
inHunk = false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (currentHunks > maxHunks) {
|
|
123
|
+
reachedLimit = true;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
kept.push(line);
|
|
128
|
+
if (kept.length >= maxLines) {
|
|
129
|
+
reachedLimit = true;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!reachedLimit) {
|
|
135
|
+
return { text: diffText, hiddenHunks: 0, hiddenLines: 0 };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const keptStats = getDiffStats(kept.join("\n"));
|
|
139
|
+
return {
|
|
140
|
+
text: kept.join("\n"),
|
|
141
|
+
hiddenHunks: Math.max(0, totalStats.hunks - keptStats.hunks),
|
|
142
|
+
hiddenLines: Math.max(0, totalStats.lines - kept.length),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
interface ParsedDiagnostic {
|
|
147
|
+
filePath: string;
|
|
148
|
+
line: number;
|
|
149
|
+
col: number;
|
|
150
|
+
severity: "error" | "warning" | "info" | "hint";
|
|
151
|
+
source?: string;
|
|
152
|
+
message: string;
|
|
153
|
+
code?: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function parseDiagnosticMessage(msg: string): ParsedDiagnostic | null {
|
|
157
|
+
// Format: filePath:line:col [severity] [source] message (code)
|
|
158
|
+
const match = msg.match(/^(.+?):(\d+):(\d+)\s+\[(\w+)\]\s+(?:\[([^\]]+)\]\s+)?(.+?)(?:\s+\(([^)]+)\))?$/);
|
|
159
|
+
if (!match) return null;
|
|
160
|
+
return {
|
|
161
|
+
filePath: match[1],
|
|
162
|
+
line: parseInt(match[2], 10),
|
|
163
|
+
col: parseInt(match[3], 10),
|
|
164
|
+
severity: match[4] as ParsedDiagnostic["severity"],
|
|
165
|
+
source: match[5],
|
|
166
|
+
message: match[6],
|
|
167
|
+
code: match[7],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function formatDiagnostics(diag: { errored: boolean; summary: string; messages: string[] }, expanded: boolean): string {
|
|
172
|
+
if (diag.messages.length === 0) return "";
|
|
173
|
+
|
|
174
|
+
// Parse and group diagnostics by file
|
|
175
|
+
const byFile = new Map<string, ParsedDiagnostic[]>();
|
|
176
|
+
const unparsed: string[] = [];
|
|
177
|
+
|
|
178
|
+
for (const msg of diag.messages) {
|
|
179
|
+
const parsed = parseDiagnosticMessage(msg);
|
|
180
|
+
if (parsed) {
|
|
181
|
+
const existing = byFile.get(parsed.filePath) ?? [];
|
|
182
|
+
existing.push(parsed);
|
|
183
|
+
byFile.set(parsed.filePath, existing);
|
|
184
|
+
} else {
|
|
185
|
+
unparsed.push(msg);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const headerIcon = diag.errored
|
|
190
|
+
? theme.styledSymbol("status.error", "error")
|
|
191
|
+
: theme.styledSymbol("status.warning", "warning");
|
|
192
|
+
let output = `\n\n${headerIcon} ${theme.fg("toolTitle", "Diagnostics")} ${theme.fg("dim", `(${diag.summary})`)}`;
|
|
193
|
+
|
|
194
|
+
const maxDiags = expanded ? diag.messages.length : 5;
|
|
195
|
+
let shown = 0;
|
|
196
|
+
|
|
197
|
+
// Render grouped diagnostics with file icons
|
|
198
|
+
const files = Array.from(byFile.entries());
|
|
199
|
+
for (let fi = 0; fi < files.length && shown < maxDiags; fi++) {
|
|
200
|
+
const [filePath, diagnostics] = files[fi];
|
|
201
|
+
const isLastFile = fi === files.length - 1 && unparsed.length === 0;
|
|
202
|
+
const fileBranch = isLastFile ? theme.tree.last : theme.tree.branch;
|
|
203
|
+
|
|
204
|
+
// File header with icon
|
|
205
|
+
output += `\n ${theme.fg("dim", fileBranch)} ${theme.fg("muted", theme.icon.file)} ${theme.fg("accent", filePath)}`;
|
|
206
|
+
shown++;
|
|
207
|
+
|
|
208
|
+
// Render diagnostics for this file
|
|
209
|
+
for (let di = 0; di < diagnostics.length && shown < maxDiags; di++) {
|
|
210
|
+
const d = diagnostics[di];
|
|
211
|
+
const isLastDiag = di === diagnostics.length - 1;
|
|
212
|
+
const diagBranch = isLastFile
|
|
213
|
+
? isLastDiag
|
|
214
|
+
? ` ${theme.tree.last}`
|
|
215
|
+
: ` ${theme.tree.branch}`
|
|
216
|
+
: isLastDiag
|
|
217
|
+
? ` ${theme.tree.vertical} ${theme.tree.last}`
|
|
218
|
+
: ` ${theme.tree.vertical} ${theme.tree.branch}`;
|
|
219
|
+
|
|
220
|
+
const sevIcon =
|
|
221
|
+
d.severity === "error"
|
|
222
|
+
? theme.styledSymbol("status.error", "error")
|
|
223
|
+
: d.severity === "warning"
|
|
224
|
+
? theme.styledSymbol("status.warning", "warning")
|
|
225
|
+
: theme.styledSymbol("status.info", "muted");
|
|
226
|
+
const location = theme.fg("dim", `:${d.line}:${d.col}`);
|
|
227
|
+
const codeTag = d.code ? theme.fg("dim", ` (${d.code})`) : "";
|
|
228
|
+
const msgColor = d.severity === "error" ? "error" : d.severity === "warning" ? "warning" : "toolOutput";
|
|
229
|
+
|
|
230
|
+
output += `\n ${theme.fg("dim", diagBranch)} ${sevIcon}${location} ${theme.fg(msgColor, d.message)}${codeTag}`;
|
|
231
|
+
shown++;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Render unparsed messages (fallback)
|
|
236
|
+
for (const msg of unparsed) {
|
|
237
|
+
if (shown >= maxDiags) break;
|
|
238
|
+
const color = msg.includes("[error]") ? "error" : msg.includes("[warning]") ? "warning" : "dim";
|
|
239
|
+
output += `\n ${theme.fg("dim", theme.tree.branch)} ${theme.fg(color, msg)}`;
|
|
240
|
+
shown++;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (diag.messages.length > shown) {
|
|
244
|
+
const remaining = diag.messages.length - shown;
|
|
245
|
+
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", `${theme.format.ellipsis} ${remaining} more`)} ${theme.fg("dim", "(Ctrl+O to expand)")}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return output;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function formatCompactValue(value: unknown, maxLength: number): string {
|
|
252
|
+
let rendered = "";
|
|
253
|
+
|
|
254
|
+
if (value === null) {
|
|
255
|
+
rendered = "null";
|
|
256
|
+
} else if (value === undefined) {
|
|
257
|
+
rendered = "undefined";
|
|
258
|
+
} else if (typeof value === "string") {
|
|
259
|
+
rendered = value;
|
|
260
|
+
} else if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
261
|
+
rendered = String(value);
|
|
262
|
+
} else if (Array.isArray(value)) {
|
|
263
|
+
const previewItems = value.slice(0, 3).map((item) => formatCompactValue(item, maxLength));
|
|
264
|
+
rendered = `[${previewItems.join(", ")}${value.length > 3 ? ", ..." : ""}]`;
|
|
265
|
+
} else if (typeof value === "object") {
|
|
266
|
+
try {
|
|
267
|
+
rendered = JSON.stringify(value);
|
|
268
|
+
} catch {
|
|
269
|
+
rendered = "[object]";
|
|
270
|
+
}
|
|
271
|
+
} else if (typeof value === "function") {
|
|
272
|
+
rendered = "[function]";
|
|
273
|
+
} else {
|
|
274
|
+
rendered = String(value);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (rendered.length > maxLength) {
|
|
278
|
+
rendered = `${rendered.slice(0, maxLength - 1)}${theme.format.ellipsis}`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return rendered;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function formatArgsPreview(
|
|
285
|
+
args: unknown,
|
|
286
|
+
maxEntries: number,
|
|
287
|
+
maxValueLength: number,
|
|
288
|
+
): { lines: string[]; remaining: number; total: number } {
|
|
289
|
+
if (args === undefined) {
|
|
290
|
+
return { lines: [theme.fg("dim", "(none)")], remaining: 0, total: 0 };
|
|
291
|
+
}
|
|
292
|
+
if (args === null || typeof args !== "object") {
|
|
293
|
+
const single = theme.fg("toolOutput", formatCompactValue(args, maxValueLength));
|
|
294
|
+
return { lines: [single], remaining: 0, total: 1 };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const entries = Object.entries(args as Record<string, unknown>);
|
|
298
|
+
const total = entries.length;
|
|
299
|
+
const visible = entries.slice(0, maxEntries);
|
|
300
|
+
const lines = visible.map(([key, value]) => {
|
|
301
|
+
const keyText = theme.fg("accent", key);
|
|
302
|
+
const valueText = theme.fg("toolOutput", formatCompactValue(value, maxValueLength));
|
|
303
|
+
return `${keyText}: ${valueText}`;
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return { lines, remaining: Math.max(total - visible.length, 0), total };
|
|
307
|
+
}
|
|
25
308
|
|
|
26
309
|
/**
|
|
27
310
|
* Convert absolute path to tilde notation if it's in home directory
|
|
@@ -63,12 +346,15 @@ export class ToolExecutionComponent extends Container {
|
|
|
63
346
|
private cwd: string;
|
|
64
347
|
private result?: {
|
|
65
348
|
content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;
|
|
66
|
-
isError
|
|
349
|
+
isError?: boolean;
|
|
67
350
|
details?: any;
|
|
68
351
|
};
|
|
69
352
|
// Cached edit diff preview (computed when args arrive, before tool executes)
|
|
70
353
|
private editDiffPreview?: EditDiffResult | EditDiffError;
|
|
71
354
|
private editDiffArgsKey?: string; // Track which args the preview is for
|
|
355
|
+
// Spinner animation for partial task results
|
|
356
|
+
private spinnerFrame = 0;
|
|
357
|
+
private spinnerInterval: ReturnType<typeof setInterval> | null = null;
|
|
72
358
|
|
|
73
359
|
constructor(
|
|
74
360
|
toolName: string,
|
|
@@ -153,15 +439,45 @@ export class ToolExecutionComponent extends Container {
|
|
|
153
439
|
result: {
|
|
154
440
|
content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;
|
|
155
441
|
details?: any;
|
|
156
|
-
isError
|
|
442
|
+
isError?: boolean;
|
|
157
443
|
},
|
|
158
444
|
isPartial = false,
|
|
159
445
|
): void {
|
|
160
446
|
this.result = result;
|
|
161
447
|
this.isPartial = isPartial;
|
|
448
|
+
this.updateSpinnerAnimation();
|
|
162
449
|
this.updateDisplay();
|
|
163
450
|
}
|
|
164
451
|
|
|
452
|
+
/**
|
|
453
|
+
* Start or stop spinner animation based on whether this is a partial task result.
|
|
454
|
+
*/
|
|
455
|
+
private updateSpinnerAnimation(): void {
|
|
456
|
+
const needsSpinner = this.isPartial && this.toolName === "task";
|
|
457
|
+
if (needsSpinner && !this.spinnerInterval) {
|
|
458
|
+
this.spinnerInterval = setInterval(() => {
|
|
459
|
+
const frameCount = theme.spinnerFrames.length;
|
|
460
|
+
if (frameCount === 0) return;
|
|
461
|
+
this.spinnerFrame = (this.spinnerFrame + 1) % frameCount;
|
|
462
|
+
this.updateDisplay();
|
|
463
|
+
this.ui.requestRender();
|
|
464
|
+
}, 80);
|
|
465
|
+
} else if (!needsSpinner && this.spinnerInterval) {
|
|
466
|
+
clearInterval(this.spinnerInterval);
|
|
467
|
+
this.spinnerInterval = null;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Stop spinner animation and cleanup resources.
|
|
473
|
+
*/
|
|
474
|
+
stopAnimation(): void {
|
|
475
|
+
if (this.spinnerInterval) {
|
|
476
|
+
clearInterval(this.spinnerInterval);
|
|
477
|
+
this.spinnerInterval = null;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
165
481
|
setExpanded(expanded: boolean): void {
|
|
166
482
|
this.expanded = expanded;
|
|
167
483
|
this.updateDisplay();
|
|
@@ -207,7 +523,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
207
523
|
try {
|
|
208
524
|
const resultComponent = this.customTool.renderResult(
|
|
209
525
|
{ content: this.result.content as any, details: this.result.details },
|
|
210
|
-
{ expanded: this.expanded, isPartial: this.isPartial },
|
|
526
|
+
{ expanded: this.expanded, isPartial: this.isPartial, spinnerFrame: this.spinnerFrame },
|
|
211
527
|
theme,
|
|
212
528
|
);
|
|
213
529
|
if (resultComponent) {
|
|
@@ -254,7 +570,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
254
570
|
try {
|
|
255
571
|
const resultComponent = renderer.renderResult(
|
|
256
572
|
{ content: this.result.content as any, details: this.result.details },
|
|
257
|
-
{ expanded: this.expanded, isPartial: this.isPartial },
|
|
573
|
+
{ expanded: this.expanded, isPartial: this.isPartial, spinnerFrame: this.spinnerFrame },
|
|
258
574
|
theme,
|
|
259
575
|
);
|
|
260
576
|
if (resultComponent) {
|
|
@@ -314,7 +630,11 @@ export class ToolExecutionComponent extends Container {
|
|
|
314
630
|
|
|
315
631
|
// Header
|
|
316
632
|
this.contentBox.addChild(
|
|
317
|
-
new Text(
|
|
633
|
+
new Text(
|
|
634
|
+
theme.fg("toolTitle", theme.bold(`$ ${command || theme.fg("toolOutput", theme.format.ellipsis)}`)),
|
|
635
|
+
0,
|
|
636
|
+
0,
|
|
637
|
+
),
|
|
318
638
|
);
|
|
319
639
|
|
|
320
640
|
if (this.result) {
|
|
@@ -339,9 +659,17 @@ export class ToolExecutionComponent extends Container {
|
|
|
339
659
|
this.ui.terminal.columns - 2,
|
|
340
660
|
);
|
|
341
661
|
|
|
662
|
+
const totalVisualLines = skippedCount + visualLines.length;
|
|
342
663
|
if (skippedCount > 0) {
|
|
343
664
|
this.contentBox.addChild(
|
|
344
|
-
new Text(
|
|
665
|
+
new Text(
|
|
666
|
+
theme.fg(
|
|
667
|
+
"dim",
|
|
668
|
+
`\n${theme.format.ellipsis} (${skippedCount} earlier lines, showing ${visualLines.length} of ${totalVisualLines}) (ctrl+o to expand)`,
|
|
669
|
+
),
|
|
670
|
+
0,
|
|
671
|
+
0,
|
|
672
|
+
),
|
|
345
673
|
);
|
|
346
674
|
}
|
|
347
675
|
|
|
@@ -372,7 +700,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
372
700
|
);
|
|
373
701
|
}
|
|
374
702
|
}
|
|
375
|
-
this.contentBox.addChild(new Text(`\n${theme.fg("warning",
|
|
703
|
+
this.contentBox.addChild(new Text(`\n${theme.fg("warning", wrapBrackets(warnings.join(". ")))}`, 0, 0));
|
|
376
704
|
}
|
|
377
705
|
}
|
|
378
706
|
}
|
|
@@ -408,11 +736,13 @@ export class ToolExecutionComponent extends Container {
|
|
|
408
736
|
let text = "";
|
|
409
737
|
|
|
410
738
|
if (this.toolName === "read") {
|
|
411
|
-
const
|
|
739
|
+
const rawPath = this.args?.file_path || this.args?.path || "";
|
|
740
|
+
const path = shortenPath(rawPath);
|
|
412
741
|
const offset = this.args?.offset;
|
|
413
742
|
const limit = this.args?.limit;
|
|
743
|
+
const fileType = getFileType(rawPath);
|
|
414
744
|
|
|
415
|
-
let pathDisplay = path ? theme.fg("accent", path) : theme.fg("toolOutput",
|
|
745
|
+
let pathDisplay = path ? theme.fg("accent", path) : theme.fg("toolOutput", theme.format.ellipsis);
|
|
416
746
|
if (offset !== undefined || limit !== undefined) {
|
|
417
747
|
const startLine = offset ?? 1;
|
|
418
748
|
const endLine = limit !== undefined ? startLine + limit - 1 : "";
|
|
@@ -423,50 +753,46 @@ export class ToolExecutionComponent extends Container {
|
|
|
423
753
|
|
|
424
754
|
if (this.result) {
|
|
425
755
|
const output = this.getTextOutput();
|
|
426
|
-
const rawPath = this.args?.file_path || this.args?.path || "";
|
|
427
|
-
const lang = getLanguageFromPath(rawPath);
|
|
428
|
-
const lines = lang ? highlightCode(replaceTabs(output), lang) : output.split("\n");
|
|
429
756
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
757
|
+
if (fileType === "image") {
|
|
758
|
+
// Image file - use image icon
|
|
759
|
+
const ext = rawPath.split(".").pop()?.toLowerCase() ?? "image";
|
|
760
|
+
text += `${theme.sep.dot}${theme.fg("dim", theme.getLangIcon(ext))}`;
|
|
761
|
+
// Images are rendered by the image component, just show hint
|
|
762
|
+
text += `\n${theme.fg("muted", "Image rendered below")}`;
|
|
763
|
+
} else if (fileType === "binary") {
|
|
764
|
+
// Binary file - use binary/pdf/archive icon based on extension
|
|
765
|
+
const ext = rawPath.split(".").pop()?.toLowerCase() ?? "binary";
|
|
766
|
+
text += `${theme.sep.dot}${theme.fg("dim", theme.getLangIcon(ext))}`;
|
|
767
|
+
} else {
|
|
768
|
+
// Text file - show line count and language on same line
|
|
769
|
+
const lang = getLanguageFromPath(rawPath);
|
|
770
|
+
const lines = lang ? highlightCode(replaceTabs(output), lang) : output.split("\n");
|
|
771
|
+
text += `${theme.sep.dot}${formatMetadataLine(null, lang)}`;
|
|
442
772
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if (truncation.firstLineExceedsLimit) {
|
|
773
|
+
// Content is hidden by default, only shown when expanded
|
|
774
|
+
if (this.expanded) {
|
|
446
775
|
text +=
|
|
447
|
-
"\n" +
|
|
448
|
-
|
|
449
|
-
"
|
|
450
|
-
|
|
451
|
-
);
|
|
452
|
-
} else if (truncation.truncatedBy === "lines") {
|
|
453
|
-
text +=
|
|
454
|
-
"\n" +
|
|
455
|
-
theme.fg(
|
|
456
|
-
"warning",
|
|
457
|
-
`[Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${
|
|
458
|
-
truncation.maxLines ?? DEFAULT_MAX_LINES
|
|
459
|
-
} line limit)]`,
|
|
460
|
-
);
|
|
776
|
+
"\n\n" +
|
|
777
|
+
lines
|
|
778
|
+
.map((line: string) => (lang ? replaceTabs(line) : theme.fg("toolOutput", replaceTabs(line))))
|
|
779
|
+
.join("\n");
|
|
461
780
|
} else {
|
|
462
|
-
text +=
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
781
|
+
text += `\n${theme.fg("dim", `${theme.nav.expand} Ctrl+O to show content`)}`;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Truncation warning
|
|
785
|
+
const truncation = this.result.details?.truncation;
|
|
786
|
+
if (truncation?.truncated) {
|
|
787
|
+
let warning: string;
|
|
788
|
+
if (truncation.firstLineExceedsLimit) {
|
|
789
|
+
warning = `First line exceeds ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`;
|
|
790
|
+
} else if (truncation.truncatedBy === "lines") {
|
|
791
|
+
warning = `Truncated: ${truncation.outputLines} of ${truncation.totalLines} lines (${truncation.maxLines ?? DEFAULT_MAX_LINES} line limit)`;
|
|
792
|
+
} else {
|
|
793
|
+
warning = `Truncated: ${truncation.outputLines} lines (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`;
|
|
794
|
+
}
|
|
795
|
+
text += `\n${theme.fg("warning", wrapBrackets(warning))}`;
|
|
470
796
|
}
|
|
471
797
|
}
|
|
472
798
|
}
|
|
@@ -485,7 +811,9 @@ export class ToolExecutionComponent extends Container {
|
|
|
485
811
|
text =
|
|
486
812
|
theme.fg("toolTitle", theme.bold("write")) +
|
|
487
813
|
" " +
|
|
488
|
-
(path ? theme.fg("accent", path) : theme.fg("toolOutput",
|
|
814
|
+
(path ? theme.fg("accent", path) : theme.fg("toolOutput", theme.format.ellipsis));
|
|
815
|
+
|
|
816
|
+
text += `\n${formatMetadataLine(countLines(fileContent), lang ?? "text")}`;
|
|
489
817
|
|
|
490
818
|
if (fileContent) {
|
|
491
819
|
const maxLines = this.expanded ? lines.length : 10;
|
|
@@ -498,33 +826,23 @@ export class ToolExecutionComponent extends Container {
|
|
|
498
826
|
.map((line: string) => (lang ? replaceTabs(line) : theme.fg("toolOutput", replaceTabs(line))))
|
|
499
827
|
.join("\n");
|
|
500
828
|
if (remaining > 0) {
|
|
501
|
-
text += theme.fg(
|
|
829
|
+
text += theme.fg(
|
|
830
|
+
"toolOutput",
|
|
831
|
+
`\n${theme.format.ellipsis} (${remaining} more lines, ${totalLines} total) ${wrapBrackets("Ctrl+O to expand")}`,
|
|
832
|
+
);
|
|
502
833
|
}
|
|
503
834
|
}
|
|
504
835
|
|
|
505
836
|
// Show LSP diagnostics if available
|
|
506
837
|
if (this.result?.details?.diagnostics) {
|
|
507
|
-
|
|
508
|
-
if (diag.messages.length > 0) {
|
|
509
|
-
const icon = diag.errored ? theme.fg("error", "●") : theme.fg("warning", "●");
|
|
510
|
-
text += `\n\n${icon} ${theme.fg("toolTitle", "LSP Diagnostics")} ${theme.fg("dim", `(${diag.summary})`)}`;
|
|
511
|
-
const maxDiags = this.expanded ? diag.messages.length : 5;
|
|
512
|
-
const displayDiags = diag.messages.slice(0, maxDiags);
|
|
513
|
-
for (const d of displayDiags) {
|
|
514
|
-
const color = d.includes("[error]") ? "error" : d.includes("[warning]") ? "warning" : "dim";
|
|
515
|
-
text += `\n ${theme.fg(color, d)}`;
|
|
516
|
-
}
|
|
517
|
-
if (diag.messages.length > maxDiags) {
|
|
518
|
-
text += theme.fg("dim", `\n ... (${diag.messages.length - maxDiags} more)`);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
838
|
+
text += formatDiagnostics(this.result.details.diagnostics, this.expanded);
|
|
521
839
|
}
|
|
522
840
|
} else if (this.toolName === "edit") {
|
|
523
841
|
const rawPath = this.args?.file_path || this.args?.path || "";
|
|
524
842
|
const path = shortenPath(rawPath);
|
|
525
843
|
|
|
526
844
|
// Build path display, appending :line if we have diff info
|
|
527
|
-
let pathDisplay = path ? theme.fg("accent", path) : theme.fg("toolOutput",
|
|
845
|
+
let pathDisplay = path ? theme.fg("accent", path) : theme.fg("toolOutput", theme.format.ellipsis);
|
|
528
846
|
const firstChangedLine =
|
|
529
847
|
(this.editDiffPreview && "firstChangedLine" in this.editDiffPreview
|
|
530
848
|
? this.editDiffPreview.firstChangedLine
|
|
@@ -536,6 +854,10 @@ export class ToolExecutionComponent extends Container {
|
|
|
536
854
|
|
|
537
855
|
text = `${theme.fg("toolTitle", theme.bold("edit"))} ${pathDisplay}`;
|
|
538
856
|
|
|
857
|
+
const editLanguage = getLanguageFromPath(rawPath) ?? "text";
|
|
858
|
+
const editLineCount = countLines(this.args?.newText ?? this.args?.oldText ?? "");
|
|
859
|
+
text += `\n${formatMetadataLine(editLineCount, editLanguage)}`;
|
|
860
|
+
|
|
539
861
|
if (this.result?.isError) {
|
|
540
862
|
// Show error from result
|
|
541
863
|
const errorText = this.getTextOutput();
|
|
@@ -547,162 +869,75 @@ export class ToolExecutionComponent extends Container {
|
|
|
547
869
|
if ("error" in this.editDiffPreview) {
|
|
548
870
|
text += `\n\n${theme.fg("error", this.editDiffPreview.error)}`;
|
|
549
871
|
} else if (this.editDiffPreview.diff) {
|
|
550
|
-
|
|
872
|
+
const diffStats = getDiffStats(this.editDiffPreview.diff);
|
|
873
|
+
text += `\n${theme.fg("dim", theme.format.bracketLeft)}${formatDiffStats(diffStats.added, diffStats.removed, diffStats.hunks)}${theme.fg("dim", theme.format.bracketRight)}`;
|
|
874
|
+
|
|
875
|
+
const {
|
|
876
|
+
text: diffText,
|
|
877
|
+
hiddenHunks,
|
|
878
|
+
hiddenLines,
|
|
879
|
+
} = this.expanded
|
|
880
|
+
? { text: this.editDiffPreview.diff, hiddenHunks: 0, hiddenLines: 0 }
|
|
881
|
+
: truncateDiffByHunk(this.editDiffPreview.diff, EDIT_DIFF_PREVIEW_HUNKS, EDIT_DIFF_PREVIEW_LINES);
|
|
882
|
+
|
|
883
|
+
text += `\n\n${renderDiff(diffText, { filePath: rawPath })}`;
|
|
884
|
+
if (!this.expanded && (hiddenHunks > 0 || hiddenLines > 0)) {
|
|
885
|
+
const remainder: string[] = [];
|
|
886
|
+
if (hiddenHunks > 0) remainder.push(`${hiddenHunks} more hunks`);
|
|
887
|
+
if (hiddenLines > 0) remainder.push(`${hiddenLines} more lines`);
|
|
888
|
+
text += theme.fg(
|
|
889
|
+
"toolOutput",
|
|
890
|
+
`\n${theme.format.ellipsis} (${remainder.join(", ")}) ${wrapBrackets("Ctrl+O to expand")}`,
|
|
891
|
+
);
|
|
892
|
+
}
|
|
551
893
|
}
|
|
552
894
|
}
|
|
553
895
|
|
|
554
896
|
// Show LSP diagnostics if available
|
|
555
897
|
if (this.result?.details?.diagnostics) {
|
|
556
|
-
|
|
557
|
-
if (diag.messages.length > 0) {
|
|
558
|
-
const icon = diag.errored ? theme.fg("error", "●") : theme.fg("warning", "●");
|
|
559
|
-
text += `\n\n${icon} ${theme.fg("toolTitle", "LSP Diagnostics")} ${theme.fg("dim", `(${diag.summary})`)}`;
|
|
560
|
-
const maxDiags = this.expanded ? diag.messages.length : 5;
|
|
561
|
-
const displayDiags = diag.messages.slice(0, maxDiags);
|
|
562
|
-
for (const d of displayDiags) {
|
|
563
|
-
const color = d.includes("[error]") ? "error" : d.includes("[warning]") ? "warning" : "dim";
|
|
564
|
-
text += `\n ${theme.fg(color, d)}`;
|
|
565
|
-
}
|
|
566
|
-
if (diag.messages.length > maxDiags) {
|
|
567
|
-
text += theme.fg("dim", `\n ... (${diag.messages.length - maxDiags} more)`);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
} else if (this.toolName === "ls") {
|
|
572
|
-
const path = shortenPath(this.args?.path || ".");
|
|
573
|
-
const limit = this.args?.limit;
|
|
574
|
-
|
|
575
|
-
text = `${theme.fg("toolTitle", theme.bold("ls"))} ${theme.fg("accent", path)}`;
|
|
576
|
-
if (limit !== undefined) {
|
|
577
|
-
text += theme.fg("toolOutput", ` (limit ${limit})`);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
if (this.result) {
|
|
581
|
-
const output = this.getTextOutput().trim();
|
|
582
|
-
if (output) {
|
|
583
|
-
const lines = output.split("\n");
|
|
584
|
-
const maxLines = this.expanded ? lines.length : 20;
|
|
585
|
-
const displayLines = lines.slice(0, maxLines);
|
|
586
|
-
const remaining = lines.length - maxLines;
|
|
587
|
-
|
|
588
|
-
text += `\n\n${displayLines.map((line: string) => theme.fg("toolOutput", line)).join("\n")}`;
|
|
589
|
-
if (remaining > 0) {
|
|
590
|
-
text += theme.fg("toolOutput", `\n... (${remaining} more lines)`);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
const entryLimit = this.result.details?.entryLimitReached;
|
|
595
|
-
const truncation = this.result.details?.truncation;
|
|
596
|
-
if (entryLimit || truncation?.truncated) {
|
|
597
|
-
const warnings: string[] = [];
|
|
598
|
-
if (entryLimit) {
|
|
599
|
-
warnings.push(`${entryLimit} entries limit`);
|
|
600
|
-
}
|
|
601
|
-
if (truncation?.truncated) {
|
|
602
|
-
warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);
|
|
603
|
-
}
|
|
604
|
-
text += `\n${theme.fg("warning", `[Truncated: ${warnings.join(", ")}]`)}`;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
} else if (this.toolName === "find") {
|
|
608
|
-
const pattern = this.args?.pattern || "";
|
|
609
|
-
const path = shortenPath(this.args?.path || ".");
|
|
610
|
-
const limit = this.args?.limit;
|
|
611
|
-
|
|
612
|
-
text =
|
|
613
|
-
theme.fg("toolTitle", theme.bold("find")) +
|
|
614
|
-
" " +
|
|
615
|
-
theme.fg("accent", pattern) +
|
|
616
|
-
theme.fg("toolOutput", ` in ${path}`);
|
|
617
|
-
if (limit !== undefined) {
|
|
618
|
-
text += theme.fg("toolOutput", ` (limit ${limit})`);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
if (this.result) {
|
|
622
|
-
const output = this.getTextOutput().trim();
|
|
623
|
-
if (output) {
|
|
624
|
-
const lines = output.split("\n");
|
|
625
|
-
const maxLines = this.expanded ? lines.length : 20;
|
|
626
|
-
const displayLines = lines.slice(0, maxLines);
|
|
627
|
-
const remaining = lines.length - maxLines;
|
|
628
|
-
|
|
629
|
-
text += `\n\n${displayLines.map((line: string) => theme.fg("toolOutput", line)).join("\n")}`;
|
|
630
|
-
if (remaining > 0) {
|
|
631
|
-
text += theme.fg("toolOutput", `\n... (${remaining} more lines)`);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
const resultLimit = this.result.details?.resultLimitReached;
|
|
636
|
-
const truncation = this.result.details?.truncation;
|
|
637
|
-
if (resultLimit || truncation?.truncated) {
|
|
638
|
-
const warnings: string[] = [];
|
|
639
|
-
if (resultLimit) {
|
|
640
|
-
warnings.push(`${resultLimit} results limit`);
|
|
641
|
-
}
|
|
642
|
-
if (truncation?.truncated) {
|
|
643
|
-
warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);
|
|
644
|
-
}
|
|
645
|
-
text += `\n${theme.fg("warning", `[Truncated: ${warnings.join(", ")}]`)}`;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
} else if (this.toolName === "grep") {
|
|
649
|
-
const pattern = this.args?.pattern || "";
|
|
650
|
-
const path = shortenPath(this.args?.path || ".");
|
|
651
|
-
const glob = this.args?.glob;
|
|
652
|
-
const limit = this.args?.limit;
|
|
653
|
-
|
|
654
|
-
text =
|
|
655
|
-
theme.fg("toolTitle", theme.bold("grep")) +
|
|
656
|
-
" " +
|
|
657
|
-
theme.fg("accent", `/${pattern}/`) +
|
|
658
|
-
theme.fg("toolOutput", ` in ${path}`);
|
|
659
|
-
if (glob) {
|
|
660
|
-
text += theme.fg("toolOutput", ` (${glob})`);
|
|
661
|
-
}
|
|
662
|
-
if (limit !== undefined) {
|
|
663
|
-
text += theme.fg("toolOutput", ` limit ${limit}`);
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
if (this.result) {
|
|
667
|
-
const output = this.getTextOutput().trim();
|
|
668
|
-
if (output) {
|
|
669
|
-
const lines = output.split("\n");
|
|
670
|
-
const maxLines = this.expanded ? lines.length : 15;
|
|
671
|
-
const displayLines = lines.slice(0, maxLines);
|
|
672
|
-
const remaining = lines.length - maxLines;
|
|
673
|
-
|
|
674
|
-
text += `\n\n${displayLines.map((line: string) => theme.fg("toolOutput", line)).join("\n")}`;
|
|
675
|
-
if (remaining > 0) {
|
|
676
|
-
text += theme.fg("toolOutput", `\n... (${remaining} more lines)`);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
const matchLimit = this.result.details?.matchLimitReached;
|
|
681
|
-
const truncation = this.result.details?.truncation;
|
|
682
|
-
const linesTruncated = this.result.details?.linesTruncated;
|
|
683
|
-
if (matchLimit || truncation?.truncated || linesTruncated) {
|
|
684
|
-
const warnings: string[] = [];
|
|
685
|
-
if (matchLimit) {
|
|
686
|
-
warnings.push(`${matchLimit} matches limit`);
|
|
687
|
-
}
|
|
688
|
-
if (truncation?.truncated) {
|
|
689
|
-
warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);
|
|
690
|
-
}
|
|
691
|
-
if (linesTruncated) {
|
|
692
|
-
warnings.push("some lines truncated");
|
|
693
|
-
}
|
|
694
|
-
text += `\n${theme.fg("warning", `[Truncated: ${warnings.join(", ")}]`)}`;
|
|
695
|
-
}
|
|
898
|
+
text += formatDiagnostics(this.result.details.diagnostics, this.expanded);
|
|
696
899
|
}
|
|
697
900
|
} else {
|
|
698
901
|
// Generic tool (shouldn't reach here for custom tools)
|
|
699
902
|
text = theme.fg("toolTitle", theme.bold(this.toolName));
|
|
700
903
|
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
904
|
+
const argTotal =
|
|
905
|
+
this.args && typeof this.args === "object"
|
|
906
|
+
? Object.keys(this.args as Record<string, unknown>).length
|
|
907
|
+
: this.args === undefined
|
|
908
|
+
? 0
|
|
909
|
+
: 1;
|
|
910
|
+
const argPreviewLimit = this.expanded ? argTotal : GENERIC_ARG_PREVIEW;
|
|
911
|
+
const valueLimit = this.expanded ? 2000 : GENERIC_VALUE_MAX;
|
|
912
|
+
const argsPreview = formatArgsPreview(this.args, argPreviewLimit, valueLimit);
|
|
913
|
+
|
|
914
|
+
text += `\n\n${theme.fg("toolTitle", "Args")} ${theme.fg("dim", `(${argsPreview.total})`)}`;
|
|
915
|
+
if (argsPreview.lines.length > 0) {
|
|
916
|
+
text += `\n${argsPreview.lines.join("\n")}`;
|
|
917
|
+
} else {
|
|
918
|
+
text += `\n${theme.fg("dim", "(none)")}`;
|
|
919
|
+
}
|
|
920
|
+
if (argsPreview.remaining > 0) {
|
|
921
|
+
text += theme.fg(
|
|
922
|
+
"dim",
|
|
923
|
+
`\n${theme.format.ellipsis} (${argsPreview.remaining} more args) (ctrl+o to expand)`,
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const output = this.getTextOutput().trim();
|
|
928
|
+
text += `\n\n${theme.fg("toolTitle", "Output")}`;
|
|
704
929
|
if (output) {
|
|
705
|
-
|
|
930
|
+
const lines = output.split("\n");
|
|
931
|
+
const maxLines = this.expanded ? lines.length : GENERIC_PREVIEW_LINES;
|
|
932
|
+
const displayLines = lines.slice(-maxLines);
|
|
933
|
+
const remaining = lines.length - displayLines.length;
|
|
934
|
+
text += ` ${theme.fg("dim", `(${lines.length} lines)`)}`;
|
|
935
|
+
text += `\n${displayLines.map((line) => theme.fg("toolOutput", line)).join("\n")}`;
|
|
936
|
+
if (remaining > 0) {
|
|
937
|
+
text += theme.fg("dim", `\n${theme.format.ellipsis} (${remaining} earlier lines) (ctrl+o to expand)`);
|
|
938
|
+
}
|
|
939
|
+
} else {
|
|
940
|
+
text += ` ${theme.fg("dim", "(empty)")}`;
|
|
706
941
|
}
|
|
707
942
|
}
|
|
708
943
|
|