@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
|
@@ -14,14 +14,6 @@ import { highlight, supportsLanguage } from "cli-highlight";
|
|
|
14
14
|
import type { Theme } from "../../../modes/interactive/theme/theme";
|
|
15
15
|
import type { LspParams, LspToolDetails } from "./types";
|
|
16
16
|
|
|
17
|
-
// =============================================================================
|
|
18
|
-
// Tree Drawing Characters
|
|
19
|
-
// =============================================================================
|
|
20
|
-
|
|
21
|
-
const TREE_MID = "├─";
|
|
22
|
-
const TREE_END = "└─";
|
|
23
|
-
const TREE_PIPE = "│";
|
|
24
|
-
|
|
25
17
|
// =============================================================================
|
|
26
18
|
// Call Rendering
|
|
27
19
|
// =============================================================================
|
|
@@ -75,7 +67,7 @@ export function renderResult(
|
|
|
75
67
|
|
|
76
68
|
const errorMatch = text.match(/(\d+)\s+error\(s\)/);
|
|
77
69
|
const warningMatch = text.match(/(\d+)\s+warning\(s\)/);
|
|
78
|
-
if (errorMatch || warningMatch || text.includes(
|
|
70
|
+
if (errorMatch || warningMatch || text.includes(theme.status.error)) {
|
|
79
71
|
return renderDiagnostics(errorMatch, warningMatch, lines, expanded, theme);
|
|
80
72
|
}
|
|
81
73
|
|
|
@@ -112,16 +104,20 @@ function renderHover(
|
|
|
112
104
|
const afterCode = fullText.slice(fullText.indexOf("```", 3) + 3).trim();
|
|
113
105
|
|
|
114
106
|
const codeLines = highlightCode(code, lang, theme);
|
|
115
|
-
const icon = theme.
|
|
107
|
+
const icon = theme.styledSymbol("status.info", "accent");
|
|
116
108
|
const langLabel = lang ? theme.fg("mdCodeBlockBorder", ` ${lang}`) : "";
|
|
117
109
|
|
|
118
110
|
if (expanded) {
|
|
111
|
+
const h = theme.boxSharp.horizontal;
|
|
112
|
+
const v = theme.boxSharp.vertical;
|
|
113
|
+
const top = `${theme.boxSharp.topLeft}${h.repeat(3)}`;
|
|
114
|
+
const bottom = `${theme.boxSharp.bottomLeft}${h.repeat(3)}`;
|
|
119
115
|
let output = `${icon} ${theme.fg("toolTitle", "Hover")}${langLabel}`;
|
|
120
|
-
output += `\n ${theme.fg("mdCodeBlockBorder",
|
|
116
|
+
output += `\n ${theme.fg("mdCodeBlockBorder", top)}`;
|
|
121
117
|
for (const line of codeLines) {
|
|
122
|
-
output += `\n ${theme.fg("mdCodeBlockBorder",
|
|
118
|
+
output += `\n ${theme.fg("mdCodeBlockBorder", v)} ${line}`;
|
|
123
119
|
}
|
|
124
|
-
output += `\n ${theme.fg("mdCodeBlockBorder",
|
|
120
|
+
output += `\n ${theme.fg("mdCodeBlockBorder", bottom)}`;
|
|
125
121
|
if (afterCode) {
|
|
126
122
|
output += `\n ${theme.fg("muted", afterCode)}`;
|
|
127
123
|
}
|
|
@@ -133,17 +129,25 @@ function renderHover(
|
|
|
133
129
|
const expandHint = theme.fg("dim", " (Ctrl+O to expand)");
|
|
134
130
|
|
|
135
131
|
let output = `${icon} ${theme.fg("toolTitle", "Hover")}${langLabel}${expandHint}`;
|
|
136
|
-
|
|
132
|
+
const h = theme.boxSharp.horizontal;
|
|
133
|
+
const v = theme.boxSharp.vertical;
|
|
134
|
+
const bottom = `${theme.boxSharp.bottomLeft}${h.repeat(3)}`;
|
|
135
|
+
output += `\n ${theme.fg("mdCodeBlockBorder", v)} ${firstCodeLine}`;
|
|
137
136
|
|
|
138
137
|
if (codeLines.length > 1) {
|
|
139
|
-
output += `\n ${theme.fg("mdCodeBlockBorder",
|
|
138
|
+
output += `\n ${theme.fg("mdCodeBlockBorder", v)} ${theme.fg(
|
|
139
|
+
"muted",
|
|
140
|
+
`${theme.format.ellipsis} ${codeLines.length - 1} more lines`,
|
|
141
|
+
)}`;
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
if (afterCode) {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
+
const ellipsis = theme.format.ellipsis;
|
|
146
|
+
const sliceLen = Math.max(0, 60 - ellipsis.length);
|
|
147
|
+
const docPreview = afterCode.length > 60 ? `${afterCode.slice(0, sliceLen)}${ellipsis}` : afterCode;
|
|
148
|
+
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", docPreview)}`;
|
|
145
149
|
} else {
|
|
146
|
-
output += `\n ${theme.fg("mdCodeBlockBorder",
|
|
150
|
+
output += `\n ${theme.fg("mdCodeBlockBorder", bottom)}`;
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
return new Text(output, 0, 0);
|
|
@@ -196,23 +200,44 @@ function renderDiagnostics(
|
|
|
196
200
|
const warnCount = warningMatch ? Number.parseInt(warningMatch[1], 10) : 0;
|
|
197
201
|
|
|
198
202
|
const icon =
|
|
199
|
-
errorCount > 0
|
|
203
|
+
errorCount > 0
|
|
204
|
+
? theme.styledSymbol("status.error", "error")
|
|
205
|
+
: warnCount > 0
|
|
206
|
+
? theme.styledSymbol("status.warning", "warning")
|
|
207
|
+
: theme.styledSymbol("status.success", "success");
|
|
200
208
|
|
|
201
209
|
const meta: string[] = [];
|
|
202
210
|
if (errorCount > 0) meta.push(`${errorCount} error${errorCount !== 1 ? "s" : ""}`);
|
|
203
211
|
if (warnCount > 0) meta.push(`${warnCount} warning${warnCount !== 1 ? "s" : ""}`);
|
|
204
212
|
if (meta.length === 0) meta.push("No issues");
|
|
205
213
|
|
|
206
|
-
const diagLines = lines.filter((l) => l.includes(
|
|
214
|
+
const diagLines = lines.filter((l) => l.includes(theme.status.error) || /:\d+:\d+/.test(l));
|
|
215
|
+
const parsedDiagnostics = diagLines
|
|
216
|
+
.map((line) => parseDiagnosticLine(line))
|
|
217
|
+
.filter((diag): diag is ParsedDiagnostic => diag !== null);
|
|
218
|
+
const fallbackDiagnostics: RawDiagnostic[] = diagLines.map((line) => ({ raw: line.trim() }));
|
|
207
219
|
|
|
208
220
|
if (expanded) {
|
|
209
221
|
let output = `${icon} ${theme.fg("toolTitle", "Diagnostics")} ${theme.fg("dim", meta.join(", "))}`;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
|
|
222
|
+
const items: DiagnosticItem[] = parsedDiagnostics.length > 0 ? parsedDiagnostics : fallbackDiagnostics;
|
|
223
|
+
for (let i = 0; i < items.length; i++) {
|
|
224
|
+
const item = items[i];
|
|
225
|
+
const isLast = i === items.length - 1;
|
|
226
|
+
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
227
|
+
const detailPrefix = isLast ? " " : `${theme.tree.vertical} `;
|
|
228
|
+
if ("raw" in item) {
|
|
229
|
+
output += `\n ${theme.fg("dim", branch)} ${theme.fg("muted", item.raw)}`;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const severityColor = severityToColor(item.severity);
|
|
233
|
+
const location = `${item.file}:${item.line}:${item.col}`;
|
|
234
|
+
output += `\n ${theme.fg("dim", branch)} ${theme.fg(severityColor, location)} ${theme.fg(
|
|
235
|
+
"dim",
|
|
236
|
+
`[${item.severity}]`,
|
|
237
|
+
)}`;
|
|
238
|
+
if (item.message) {
|
|
239
|
+
output += `\n ${theme.fg("dim", detailPrefix)}${theme.fg("muted", trimTo(item.message, 120, theme))}`;
|
|
240
|
+
}
|
|
216
241
|
}
|
|
217
242
|
return new Text(output, 0, 0);
|
|
218
243
|
}
|
|
@@ -221,14 +246,28 @@ function renderDiagnostics(
|
|
|
221
246
|
const expandHint = theme.fg("dim", " (Ctrl+O to expand)");
|
|
222
247
|
let output = `${icon} ${theme.fg("toolTitle", "Diagnostics")} ${theme.fg("dim", meta.join(", "))}${expandHint}`;
|
|
223
248
|
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
249
|
+
const previewItems: DiagnosticItem[] =
|
|
250
|
+
parsedDiagnostics.length > 0 ? parsedDiagnostics.slice(0, 3) : fallbackDiagnostics.slice(0, 3);
|
|
251
|
+
const remaining =
|
|
252
|
+
(parsedDiagnostics.length > 0 ? parsedDiagnostics.length : fallbackDiagnostics.length) - previewItems.length;
|
|
253
|
+
for (let i = 0; i < previewItems.length; i++) {
|
|
254
|
+
const item = previewItems[i];
|
|
255
|
+
const isLast = i === previewItems.length - 1 && remaining <= 0;
|
|
256
|
+
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
257
|
+
if ("raw" in item) {
|
|
258
|
+
output += `\n ${theme.fg("dim", branch)} ${theme.fg("muted", item.raw)}`;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const severityColor = severityToColor(item.severity);
|
|
262
|
+
const location = `${item.file}:${item.line}:${item.col}`;
|
|
263
|
+
const message = item.message ? ` ${theme.fg("muted", trimTo(item.message, 80, theme))}` : "";
|
|
264
|
+
output += `\n ${theme.fg("dim", branch)} ${theme.fg(severityColor, location)}${message}`;
|
|
229
265
|
}
|
|
230
|
-
if (
|
|
231
|
-
output += `\n ${theme.fg("dim",
|
|
266
|
+
if (remaining > 0) {
|
|
267
|
+
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
268
|
+
"muted",
|
|
269
|
+
`${theme.format.ellipsis} ${remaining} more`,
|
|
270
|
+
)}`;
|
|
232
271
|
}
|
|
233
272
|
|
|
234
273
|
return new Text(output, 0, 0);
|
|
@@ -243,7 +282,8 @@ function renderDiagnostics(
|
|
|
243
282
|
*/
|
|
244
283
|
function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded: boolean, theme: Theme): Text {
|
|
245
284
|
const refCount = Number.parseInt(refMatch[1], 10);
|
|
246
|
-
const icon =
|
|
285
|
+
const icon =
|
|
286
|
+
refCount > 0 ? theme.styledSymbol("status.success", "success") : theme.styledSymbol("status.warning", "warning");
|
|
247
287
|
|
|
248
288
|
const locLines = lines.filter((l) => /^\s*\S+:\d+:\d+/.test(l));
|
|
249
289
|
|
|
@@ -269,41 +309,55 @@ function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded:
|
|
|
269
309
|
const file = filesToShow[fi];
|
|
270
310
|
const locs = byFile.get(file)!;
|
|
271
311
|
const isLastFile = fi === filesToShow.length - 1 && files.length <= maxFiles;
|
|
272
|
-
const fileBranch = isLastFile ?
|
|
273
|
-
const fileCont = isLastFile ? " " : `${
|
|
274
|
-
|
|
275
|
-
if (locs.length === 1) {
|
|
276
|
-
output += `\n ${theme.fg("dim", fileBranch)} ${theme.fg("accent", file)}:${theme.fg(
|
|
277
|
-
"muted",
|
|
278
|
-
`${locs[0][0]}:${locs[0][1]}`,
|
|
279
|
-
)}`;
|
|
280
|
-
} else {
|
|
281
|
-
output += `\n ${theme.fg("dim", fileBranch)} ${theme.fg("accent", file)}`;
|
|
312
|
+
const fileBranch = isLastFile ? theme.tree.last : theme.tree.branch;
|
|
313
|
+
const fileCont = isLastFile ? " " : `${theme.tree.vertical} `;
|
|
282
314
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const locsText = locStrs.join(", ");
|
|
286
|
-
const hasMore = locs.length > maxLocsPerFile;
|
|
315
|
+
const fileMeta = `${locs.length} reference${locs.length !== 1 ? "s" : ""}`;
|
|
316
|
+
output += `\n ${theme.fg("dim", fileBranch)} ${theme.fg("accent", file)} ${theme.fg("dim", fileMeta)}`;
|
|
287
317
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
318
|
+
if (maxLocsPerFile > 0) {
|
|
319
|
+
const locsToShow = locs.slice(0, maxLocsPerFile);
|
|
320
|
+
for (let li = 0; li < locsToShow.length; li++) {
|
|
321
|
+
const [line, col] = locsToShow[li];
|
|
322
|
+
const isLastLoc = li === locsToShow.length - 1 && locs.length <= maxLocsPerFile;
|
|
323
|
+
const locBranch = isLastLoc ? theme.tree.last : theme.tree.branch;
|
|
324
|
+
const locCont = isLastLoc ? " " : `${theme.tree.vertical} `;
|
|
325
|
+
output += `\n ${theme.fg("dim", fileCont)}${theme.fg("dim", locBranch)} ${theme.fg(
|
|
326
|
+
"muted",
|
|
327
|
+
`line ${line}, col ${col}`,
|
|
328
|
+
)}`;
|
|
329
|
+
if (expanded) {
|
|
330
|
+
const context = `at ${file}:${line}:${col}`;
|
|
331
|
+
output += `\n ${theme.fg("dim", fileCont)}${theme.fg("dim", locCont)}${theme.fg(
|
|
332
|
+
"muted",
|
|
333
|
+
trimTo(context, 120, theme),
|
|
334
|
+
)}`;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (locs.length > maxLocsPerFile) {
|
|
338
|
+
output += `\n ${theme.fg("dim", fileCont)}${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
339
|
+
"muted",
|
|
340
|
+
`${theme.format.ellipsis} ${locs.length - maxLocsPerFile} more`,
|
|
341
|
+
)}`;
|
|
291
342
|
}
|
|
292
343
|
}
|
|
293
344
|
}
|
|
294
345
|
|
|
295
346
|
if (files.length > maxFiles) {
|
|
296
|
-
output += `\n ${theme.fg("dim",
|
|
347
|
+
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
348
|
+
"muted",
|
|
349
|
+
`${theme.format.ellipsis} ${files.length - maxFiles} more files`,
|
|
350
|
+
)}`;
|
|
297
351
|
}
|
|
298
352
|
|
|
299
353
|
return output;
|
|
300
354
|
};
|
|
301
355
|
|
|
302
356
|
if (expanded) {
|
|
303
|
-
return new Text(renderGrouped(files.length,
|
|
357
|
+
return new Text(renderGrouped(files.length, 3, false), 0, 0);
|
|
304
358
|
}
|
|
305
359
|
|
|
306
|
-
return new Text(renderGrouped(
|
|
360
|
+
return new Text(renderGrouped(3, 1, true), 0, 0);
|
|
307
361
|
}
|
|
308
362
|
|
|
309
363
|
// =============================================================================
|
|
@@ -315,12 +369,13 @@ function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded:
|
|
|
315
369
|
*/
|
|
316
370
|
function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded: boolean, theme: Theme): Text {
|
|
317
371
|
const fileName = symbolsMatch[1];
|
|
318
|
-
const icon = theme.
|
|
372
|
+
const icon = theme.styledSymbol("status.info", "accent");
|
|
319
373
|
|
|
320
374
|
interface SymbolInfo {
|
|
321
375
|
name: string;
|
|
322
376
|
line: string;
|
|
323
377
|
indent: number;
|
|
378
|
+
icon: string;
|
|
324
379
|
}
|
|
325
380
|
|
|
326
381
|
const symbolLines = lines.filter((l) => l.includes("@") && l.includes("line"));
|
|
@@ -328,9 +383,9 @@ function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded
|
|
|
328
383
|
|
|
329
384
|
for (const line of symbolLines) {
|
|
330
385
|
const indent = line.match(/^(\s*)/)?.[1].length ?? 0;
|
|
331
|
-
const symMatch = line.trim().match(/^(.+?)\s*@\s*line\s*(\d+)/);
|
|
386
|
+
const symMatch = line.trim().match(/^(\S+)\s+(.+?)\s*@\s*line\s*(\d+)/);
|
|
332
387
|
if (symMatch) {
|
|
333
|
-
symbols.push({
|
|
388
|
+
symbols.push({ icon: symMatch[1], name: symMatch[2], line: symMatch[3], indent });
|
|
334
389
|
}
|
|
335
390
|
}
|
|
336
391
|
|
|
@@ -360,7 +415,7 @@ function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded
|
|
|
360
415
|
if (ancestorIdx >= 0 && isLastSibling(ancestorIdx)) {
|
|
361
416
|
prefix += " ";
|
|
362
417
|
} else {
|
|
363
|
-
prefix += `${
|
|
418
|
+
prefix += `${theme.tree.vertical} `;
|
|
364
419
|
}
|
|
365
420
|
}
|
|
366
421
|
return prefix;
|
|
@@ -374,28 +429,37 @@ function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded
|
|
|
374
429
|
for (let i = 0; i < symbols.length; i++) {
|
|
375
430
|
const sym = symbols[i];
|
|
376
431
|
const prefix = getPrefix(i);
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
432
|
+
const isLast = isLastSibling(i);
|
|
433
|
+
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
434
|
+
const detailPrefix = isLast ? " " : `${theme.tree.vertical} `;
|
|
435
|
+
output += `\n${prefix}${theme.fg("dim", branch)} ${theme.fg("accent", sym.icon)} ${theme.fg(
|
|
436
|
+
"accent",
|
|
437
|
+
sym.name,
|
|
381
438
|
)}`;
|
|
439
|
+
output += `\n${prefix}${theme.fg("dim", detailPrefix)}${theme.fg("muted", `line ${sym.line}`)}`;
|
|
382
440
|
}
|
|
383
441
|
return new Text(output, 0, 0);
|
|
384
442
|
}
|
|
385
443
|
|
|
386
|
-
// Collapsed: show first
|
|
444
|
+
// Collapsed: show first 3 top-level symbols
|
|
387
445
|
const expandHint = theme.fg("dim", " (Ctrl+O to expand)");
|
|
388
446
|
let output = `${icon} ${theme.fg("toolTitle", "Symbols")} ${theme.fg("dim", `in ${fileName}`)}${expandHint}`;
|
|
389
447
|
|
|
390
|
-
const topLevel = symbols.filter((s) => s.indent === 0).slice(0,
|
|
448
|
+
const topLevel = symbols.filter((s) => s.indent === 0).slice(0, 3);
|
|
391
449
|
for (let i = 0; i < topLevel.length; i++) {
|
|
392
450
|
const sym = topLevel[i];
|
|
393
|
-
const isLast = i === topLevel.length - 1 && topLevelCount <=
|
|
394
|
-
const branch = isLast ?
|
|
395
|
-
output += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", sym.
|
|
451
|
+
const isLast = i === topLevel.length - 1 && topLevelCount <= 3;
|
|
452
|
+
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
453
|
+
output += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", sym.icon)} ${theme.fg(
|
|
454
|
+
"accent",
|
|
455
|
+
sym.name,
|
|
456
|
+
)} ${theme.fg("muted", `line ${sym.line}`)}`;
|
|
396
457
|
}
|
|
397
|
-
if (topLevelCount >
|
|
398
|
-
output += `\n ${theme.fg("dim",
|
|
458
|
+
if (topLevelCount > 3) {
|
|
459
|
+
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
460
|
+
"muted",
|
|
461
|
+
`${theme.format.ellipsis} ${topLevelCount - 3} more`,
|
|
462
|
+
)}`;
|
|
399
463
|
}
|
|
400
464
|
|
|
401
465
|
return new Text(output, 0, 0);
|
|
@@ -409,20 +473,22 @@ function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded
|
|
|
409
473
|
* Generic fallback rendering for unknown result types.
|
|
410
474
|
*/
|
|
411
475
|
function renderGeneric(text: string, lines: string[], expanded: boolean, theme: Theme): Text {
|
|
412
|
-
const hasError = text.includes("Error:") || text.includes(
|
|
413
|
-
const hasSuccess = text.includes(
|
|
476
|
+
const hasError = text.includes("Error:") || text.includes(theme.status.error);
|
|
477
|
+
const hasSuccess = text.includes(theme.status.success) || text.includes("Applied");
|
|
414
478
|
|
|
415
479
|
const icon =
|
|
416
480
|
hasError && !hasSuccess
|
|
417
|
-
? theme.
|
|
481
|
+
? theme.styledSymbol("status.error", "error")
|
|
418
482
|
: hasSuccess && !hasError
|
|
419
|
-
? theme.
|
|
420
|
-
: theme.
|
|
483
|
+
? theme.styledSymbol("status.success", "success")
|
|
484
|
+
: theme.styledSymbol("status.info", "accent");
|
|
421
485
|
|
|
422
486
|
if (expanded) {
|
|
423
|
-
let output = `${icon} ${theme.fg("toolTitle", "LSP")}`;
|
|
424
|
-
for (
|
|
425
|
-
|
|
487
|
+
let output = `${icon} ${theme.fg("toolTitle", "LSP")} ${theme.fg("dim", "Output")}`;
|
|
488
|
+
for (let i = 0; i < lines.length; i++) {
|
|
489
|
+
const isLast = i === lines.length - 1;
|
|
490
|
+
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
491
|
+
output += `\n ${theme.fg("dim", branch)} ${lines[i]}`;
|
|
426
492
|
}
|
|
427
493
|
return new Text(output, 0, 0);
|
|
428
494
|
}
|
|
@@ -435,13 +501,60 @@ function renderGeneric(text: string, lines: string[], expanded: boolean, theme:
|
|
|
435
501
|
const previewLines = lines.slice(1, 4);
|
|
436
502
|
for (let i = 0; i < previewLines.length; i++) {
|
|
437
503
|
const isLast = i === previewLines.length - 1 && lines.length <= 4;
|
|
438
|
-
const branch = isLast ?
|
|
504
|
+
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
439
505
|
output += `\n ${theme.fg("dim", branch)} ${theme.fg("dim", previewLines[i].trim().slice(0, 80))}`;
|
|
440
506
|
}
|
|
441
507
|
if (lines.length > 4) {
|
|
442
|
-
output += `\n ${theme.fg("dim",
|
|
508
|
+
output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
509
|
+
"muted",
|
|
510
|
+
`${theme.format.ellipsis} ${lines.length - 4} more lines`,
|
|
511
|
+
)}`;
|
|
443
512
|
}
|
|
444
513
|
}
|
|
445
514
|
|
|
446
515
|
return new Text(output, 0, 0);
|
|
447
516
|
}
|
|
517
|
+
|
|
518
|
+
// =============================================================================
|
|
519
|
+
// Parsing Helpers
|
|
520
|
+
// =============================================================================
|
|
521
|
+
|
|
522
|
+
interface ParsedDiagnostic {
|
|
523
|
+
file: string;
|
|
524
|
+
line: string;
|
|
525
|
+
col: string;
|
|
526
|
+
severity: string;
|
|
527
|
+
message: string;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
interface RawDiagnostic {
|
|
531
|
+
raw: string;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
type DiagnosticItem = ParsedDiagnostic | RawDiagnostic;
|
|
535
|
+
|
|
536
|
+
function parseDiagnosticLine(line: string): ParsedDiagnostic | null {
|
|
537
|
+
const match = line.trim().match(/^(.*):(\d+):(\d+)\s+\[(\w+)\]\s*(.*)$/);
|
|
538
|
+
if (!match) return null;
|
|
539
|
+
const [, file, lineNum, colNum, severity, message] = match;
|
|
540
|
+
return { file, line: lineNum, col: colNum, severity: severity.toLowerCase(), message };
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function severityToColor(severity: string): "error" | "warning" | "accent" | "dim" {
|
|
544
|
+
switch (severity) {
|
|
545
|
+
case "error":
|
|
546
|
+
return "error";
|
|
547
|
+
case "warning":
|
|
548
|
+
return "warning";
|
|
549
|
+
case "info":
|
|
550
|
+
return "accent";
|
|
551
|
+
default:
|
|
552
|
+
return "dim";
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function trimTo(value: string, maxLength: number, theme: Theme): string {
|
|
557
|
+
if (value.length <= maxLength) return value;
|
|
558
|
+
const sliceLen = Math.max(0, maxLength - theme.format.ellipsis.length);
|
|
559
|
+
return `${value.slice(0, sliceLen)}${theme.format.ellipsis}`;
|
|
560
|
+
}
|
|
@@ -316,6 +316,28 @@ export interface Hover {
|
|
|
316
316
|
range?: Range;
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
+
// =============================================================================
|
|
320
|
+
// Linter Client Interface
|
|
321
|
+
// =============================================================================
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Interface for linter/formatter clients.
|
|
325
|
+
* Can be implemented using LSP protocol or CLI tools.
|
|
326
|
+
*/
|
|
327
|
+
export interface LinterClient {
|
|
328
|
+
/** Format file content. Returns formatted content. */
|
|
329
|
+
format(filePath: string, content: string): Promise<string>;
|
|
330
|
+
|
|
331
|
+
/** Get diagnostics for a file. Content should already be written to disk. */
|
|
332
|
+
lint(filePath: string): Promise<Diagnostic[]>;
|
|
333
|
+
|
|
334
|
+
/** Dispose of any resources (e.g., LSP connection) */
|
|
335
|
+
dispose?(): void;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Factory function to create a LinterClient */
|
|
339
|
+
export type LinterClientFactory = (config: ServerConfig, cwd: string) => LinterClient;
|
|
340
|
+
|
|
319
341
|
// =============================================================================
|
|
320
342
|
// Server Configuration
|
|
321
343
|
// =============================================================================
|
|
@@ -341,6 +363,11 @@ export interface ServerConfig {
|
|
|
341
363
|
isLinter?: boolean;
|
|
342
364
|
/** Resolved absolute path to the command binary (set during config loading) */
|
|
343
365
|
resolvedCommand?: string;
|
|
366
|
+
/**
|
|
367
|
+
* Custom linter client factory. If provided, creates a custom client instead of using LSP.
|
|
368
|
+
* The client handles format/lint operations. Useful for tools with buggy LSP implementations.
|
|
369
|
+
*/
|
|
370
|
+
createClient?: LinterClientFactory;
|
|
344
371
|
}
|
|
345
372
|
|
|
346
373
|
// =============================================================================
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { type Theme, theme } from "../../../modes/interactive/theme/theme";
|
|
2
3
|
import type {
|
|
3
4
|
Diagnostic,
|
|
4
5
|
DiagnosticSeverity,
|
|
@@ -197,13 +198,6 @@ const SEVERITY_NAMES: Record<DiagnosticSeverity, string> = {
|
|
|
197
198
|
4: "hint",
|
|
198
199
|
};
|
|
199
200
|
|
|
200
|
-
const SEVERITY_ICONS: Record<DiagnosticSeverity, string> = {
|
|
201
|
-
1: "✖",
|
|
202
|
-
2: "⚠",
|
|
203
|
-
3: "ℹ",
|
|
204
|
-
4: "💡",
|
|
205
|
-
};
|
|
206
|
-
|
|
207
201
|
/**
|
|
208
202
|
* Convert diagnostic severity number to string name.
|
|
209
203
|
*/
|
|
@@ -215,7 +209,21 @@ export function severityToString(severity?: DiagnosticSeverity): string {
|
|
|
215
209
|
* Get icon for diagnostic severity.
|
|
216
210
|
*/
|
|
217
211
|
export function severityToIcon(severity?: DiagnosticSeverity): string {
|
|
218
|
-
|
|
212
|
+
const currentTheme = theme as Theme | undefined;
|
|
213
|
+
const fallback = currentTheme?.format?.bullet ?? "*";
|
|
214
|
+
const status = currentTheme?.status;
|
|
215
|
+
switch (severity ?? 1) {
|
|
216
|
+
case 1:
|
|
217
|
+
return status?.error ?? fallback;
|
|
218
|
+
case 2:
|
|
219
|
+
return status?.warning ?? fallback;
|
|
220
|
+
case 3:
|
|
221
|
+
return status?.info ?? fallback;
|
|
222
|
+
case 4:
|
|
223
|
+
return currentTheme?.format?.bullet ?? fallback;
|
|
224
|
+
default:
|
|
225
|
+
return status?.error ?? fallback;
|
|
226
|
+
}
|
|
219
227
|
}
|
|
220
228
|
|
|
221
229
|
/**
|
|
@@ -305,7 +313,7 @@ export function formatWorkspaceEdit(edit: WorkspaceEdit, cwd: string): string[]
|
|
|
305
313
|
break;
|
|
306
314
|
case "rename":
|
|
307
315
|
results.push(
|
|
308
|
-
`RENAME: ${path.relative(cwd, uriToFile(change.oldUri))}
|
|
316
|
+
`RENAME: ${path.relative(cwd, uriToFile(change.oldUri))} ${theme.nav.cursor} ${path.relative(cwd, uriToFile(change.newUri))}`,
|
|
309
317
|
);
|
|
310
318
|
break;
|
|
311
319
|
case "delete":
|
|
@@ -328,30 +336,62 @@ export function formatTextEdit(edit: TextEdit, maxLength = 50): string {
|
|
|
328
336
|
edit.newText.length > maxLength
|
|
329
337
|
? `${edit.newText.slice(0, maxLength).replace(/\n/g, "\\n")}...`
|
|
330
338
|
: edit.newText.replace(/\n/g, "\\n");
|
|
331
|
-
return `line ${range}
|
|
339
|
+
return `line ${range} ${theme.nav.cursor} "${preview}"`;
|
|
332
340
|
}
|
|
333
341
|
|
|
334
342
|
// =============================================================================
|
|
335
343
|
// Symbol Formatting
|
|
336
344
|
// =============================================================================
|
|
337
345
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
346
|
+
function getSymbolKindIcons(): Record<SymbolKind, string> {
|
|
347
|
+
const currentTheme = theme as Theme | undefined;
|
|
348
|
+
const fallback = currentTheme?.format?.bullet ?? "*";
|
|
349
|
+
const dash = currentTheme?.format?.dash ?? fallback;
|
|
350
|
+
const icon = currentTheme?.icon;
|
|
351
|
+
|
|
352
|
+
const file = icon?.file ?? fallback;
|
|
353
|
+
const folder = icon?.folder ?? fallback;
|
|
354
|
+
const pkg = icon?.package ?? folder;
|
|
355
|
+
const model = icon?.model ?? fallback;
|
|
356
|
+
const func = icon?.auto ?? dash;
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
1: file, // File
|
|
360
|
+
2: folder, // Module
|
|
361
|
+
3: folder, // Namespace
|
|
362
|
+
4: pkg, // Package
|
|
363
|
+
5: model, // Class
|
|
364
|
+
6: func, // Method
|
|
365
|
+
7: fallback, // Property
|
|
366
|
+
8: fallback, // Field
|
|
367
|
+
9: func, // Constructor
|
|
368
|
+
10: fallback, // Enum
|
|
369
|
+
11: model, // Interface
|
|
370
|
+
12: func, // Function
|
|
371
|
+
13: fallback, // Variable
|
|
372
|
+
14: fallback, // Constant
|
|
373
|
+
15: fallback, // String
|
|
374
|
+
16: fallback, // Number
|
|
375
|
+
17: fallback, // Boolean
|
|
376
|
+
18: fallback, // Array
|
|
377
|
+
19: fallback, // Object
|
|
378
|
+
20: fallback, // Key
|
|
379
|
+
21: fallback, // Null
|
|
380
|
+
22: fallback, // EnumMember
|
|
381
|
+
23: folder, // Struct
|
|
382
|
+
24: fallback, // Event
|
|
383
|
+
25: fallback, // Operator
|
|
384
|
+
26: fallback, // TypeParameter
|
|
385
|
+
};
|
|
386
|
+
}
|
|
349
387
|
|
|
350
388
|
/**
|
|
351
389
|
* Get icon for symbol kind.
|
|
352
390
|
*/
|
|
353
391
|
export function symbolKindToIcon(kind: SymbolKind): string {
|
|
354
|
-
|
|
392
|
+
const currentTheme = theme as Theme | undefined;
|
|
393
|
+
const bullet = currentTheme?.format?.bullet ?? "*";
|
|
394
|
+
return getSymbolKindIcons()[kind] ?? bullet;
|
|
355
395
|
}
|
|
356
396
|
|
|
357
397
|
/**
|
|
@@ -26,6 +26,8 @@ export interface NotebookToolDetails {
|
|
|
26
26
|
cellType?: string;
|
|
27
27
|
/** Total cell count after operation */
|
|
28
28
|
totalCells: number;
|
|
29
|
+
/** Cell content lines after operation (or removed content for delete) */
|
|
30
|
+
cellSource?: string[];
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
interface NotebookCell {
|
|
@@ -110,12 +112,14 @@ export function createNotebookTool(cwd: string): AgentTool<typeof notebookSchema
|
|
|
110
112
|
// Perform the action
|
|
111
113
|
let resultMessage: string;
|
|
112
114
|
let finalCellType: string | undefined;
|
|
115
|
+
let cellSource: string[] | undefined;
|
|
113
116
|
|
|
114
117
|
switch (action) {
|
|
115
118
|
case "edit": {
|
|
116
119
|
const sourceLines = splitIntoLines(content!);
|
|
117
120
|
notebook.cells[cell_index].source = sourceLines;
|
|
118
121
|
finalCellType = notebook.cells[cell_index].cell_type;
|
|
122
|
+
cellSource = sourceLines;
|
|
119
123
|
resultMessage = `Replaced cell ${cell_index} (${finalCellType})`;
|
|
120
124
|
break;
|
|
121
125
|
}
|
|
@@ -133,11 +137,14 @@ export function createNotebookTool(cwd: string): AgentTool<typeof notebookSchema
|
|
|
133
137
|
}
|
|
134
138
|
notebook.cells.splice(cell_index, 0, newCell);
|
|
135
139
|
finalCellType = newCellType;
|
|
140
|
+
cellSource = sourceLines;
|
|
136
141
|
resultMessage = `Inserted ${newCellType} cell at position ${cell_index}`;
|
|
137
142
|
break;
|
|
138
143
|
}
|
|
139
144
|
case "delete": {
|
|
140
|
-
|
|
145
|
+
const removedCell = notebook.cells[cell_index];
|
|
146
|
+
finalCellType = removedCell.cell_type;
|
|
147
|
+
cellSource = removedCell.source;
|
|
141
148
|
notebook.cells.splice(cell_index, 1);
|
|
142
149
|
resultMessage = `Deleted cell ${cell_index} (${finalCellType})`;
|
|
143
150
|
break;
|
|
@@ -163,6 +170,7 @@ export function createNotebookTool(cwd: string): AgentTool<typeof notebookSchema
|
|
|
163
170
|
cellIndex: cell_index,
|
|
164
171
|
cellType: finalCellType,
|
|
165
172
|
totalCells: newCellCount,
|
|
173
|
+
cellSource,
|
|
166
174
|
},
|
|
167
175
|
};
|
|
168
176
|
});
|