@oh-my-pi/pi-coding-agent 6.2.0 → 6.7.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 +46 -0
- package/docs/sdk.md +1 -1
- package/package.json +5 -5
- package/scripts/generate-template.ts +6 -6
- package/src/cli/args.ts +3 -0
- package/src/core/agent-session.ts +39 -0
- package/src/core/bash-executor.ts +3 -3
- package/src/core/cursor/exec-bridge.ts +95 -88
- package/src/core/custom-commands/bundled/review/index.ts +142 -145
- package/src/core/custom-commands/bundled/wt/index.ts +68 -66
- package/src/core/custom-commands/loader.ts +4 -6
- package/src/core/custom-tools/index.ts +2 -2
- package/src/core/custom-tools/loader.ts +66 -61
- package/src/core/custom-tools/types.ts +4 -4
- package/src/core/custom-tools/wrapper.ts +61 -25
- package/src/core/event-bus.ts +19 -47
- package/src/core/extensions/index.ts +8 -4
- package/src/core/extensions/loader.ts +160 -120
- package/src/core/extensions/types.ts +4 -4
- package/src/core/extensions/wrapper.ts +149 -100
- package/src/core/hooks/index.ts +1 -1
- package/src/core/hooks/tool-wrapper.ts +96 -70
- package/src/core/hooks/types.ts +1 -2
- package/src/core/index.ts +1 -0
- package/src/core/mcp/index.ts +6 -2
- package/src/core/mcp/json-rpc.ts +88 -0
- package/src/core/mcp/loader.ts +22 -4
- package/src/core/mcp/manager.ts +202 -48
- package/src/core/mcp/tool-bridge.ts +143 -55
- package/src/core/mcp/tool-cache.ts +122 -0
- package/src/core/python-executor.ts +3 -9
- package/src/core/sdk.ts +33 -32
- package/src/core/session-manager.ts +30 -0
- package/src/core/settings-manager.ts +34 -1
- package/src/core/ssh/ssh-executor.ts +6 -84
- package/src/core/streaming-output.ts +107 -53
- package/src/core/tools/ask.ts +92 -93
- package/src/core/tools/bash.ts +103 -94
- package/src/core/tools/calculator.ts +41 -26
- package/src/core/tools/complete.ts +76 -66
- package/src/core/tools/context.ts +22 -24
- package/src/core/tools/exa/index.ts +1 -1
- package/src/core/tools/exa/mcp-client.ts +56 -101
- package/src/core/tools/find.ts +250 -253
- package/src/core/tools/git.ts +39 -33
- package/src/core/tools/grep.ts +440 -427
- package/src/core/tools/index.ts +62 -61
- package/src/core/tools/ls.ts +119 -114
- package/src/core/tools/lsp/clients/biome-client.ts +5 -7
- package/src/core/tools/lsp/clients/index.ts +4 -4
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +5 -7
- package/src/core/tools/lsp/config.ts +2 -2
- package/src/core/tools/lsp/index.ts +604 -578
- package/src/core/tools/notebook.ts +121 -119
- package/src/core/tools/output.ts +163 -147
- package/src/core/tools/patch/applicator.ts +1100 -0
- package/src/core/tools/patch/diff.ts +362 -0
- package/src/core/tools/patch/fuzzy.ts +647 -0
- package/src/core/tools/patch/index.ts +430 -0
- package/src/core/tools/patch/normalize.ts +220 -0
- package/src/core/tools/patch/normative.ts +49 -0
- package/src/core/tools/patch/parser.ts +528 -0
- package/src/core/tools/patch/shared.ts +228 -0
- package/src/core/tools/patch/types.ts +244 -0
- package/src/core/tools/python.ts +139 -136
- package/src/core/tools/read.ts +237 -216
- package/src/core/tools/render-utils.ts +196 -77
- package/src/core/tools/renderers.ts +1 -1
- package/src/core/tools/ssh.ts +99 -80
- package/src/core/tools/task/executor.ts +11 -7
- package/src/core/tools/task/index.ts +352 -343
- package/src/core/tools/task/worker.ts +13 -23
- package/src/core/tools/todo-write.ts +74 -59
- package/src/core/tools/web-fetch.ts +54 -47
- package/src/core/tools/web-search/index.ts +27 -16
- package/src/core/tools/write.ts +73 -44
- package/src/core/ttsr.ts +106 -152
- package/src/core/voice.ts +49 -39
- package/src/index.ts +16 -12
- package/src/lib/worktree/index.ts +1 -9
- package/src/modes/interactive/components/diff.ts +15 -8
- package/src/modes/interactive/components/settings-defs.ts +24 -0
- package/src/modes/interactive/components/tool-execution.ts +34 -6
- package/src/modes/interactive/controllers/event-controller.ts +6 -19
- package/src/modes/interactive/controllers/input-controller.ts +1 -1
- package/src/modes/interactive/utils/ui-helpers.ts +5 -1
- package/src/modes/rpc/rpc-mode.ts +99 -81
- package/src/prompts/tools/patch.md +76 -0
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/{edit.md → replace.md} +1 -0
- package/src/utils/shell.ts +0 -40
- package/src/core/tools/edit-diff.ts +0 -574
- package/src/core/tools/edit.ts +0 -345
package/src/core/tools/index.ts
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
export { type AskToolDetails
|
|
2
|
-
export { type BashOperations, type BashToolDetails, type BashToolOptions
|
|
3
|
-
export { type CalculatorToolDetails
|
|
4
|
-
export {
|
|
5
|
-
export { createEditTool, type EditToolDetails } from "./edit";
|
|
1
|
+
export { AskTool, type AskToolDetails } from "./ask";
|
|
2
|
+
export { type BashOperations, BashTool, type BashToolDetails, type BashToolOptions } from "./bash";
|
|
3
|
+
export { CalculatorTool, type CalculatorToolDetails } from "./calculator";
|
|
4
|
+
export { CompleteTool } from "./complete";
|
|
6
5
|
// Exa MCP tools (22 tools)
|
|
7
6
|
export { exaTools } from "./exa/index";
|
|
8
7
|
export type { ExaRenderDetails, ExaSearchResponse, ExaSearchResult } from "./exa/types";
|
|
9
|
-
export {
|
|
8
|
+
export { type FindOperations, FindTool, type FindToolDetails, type FindToolOptions } from "./find";
|
|
10
9
|
export { setPreferredImageProvider } from "./gemini-image";
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
10
|
+
export { GitTool, type GitToolDetails } from "./git";
|
|
11
|
+
export { type GrepOperations, GrepTool, type GrepToolDetails, type GrepToolOptions } from "./grep";
|
|
12
|
+
export { type LsOperations, LsTool, type LsToolDetails, type LsToolOptions } from "./ls";
|
|
14
13
|
export {
|
|
15
|
-
createLspTool,
|
|
16
14
|
type FileDiagnosticsResult,
|
|
17
15
|
type FileFormatResult,
|
|
18
16
|
getLspStatus,
|
|
19
17
|
type LspServerStatus,
|
|
18
|
+
LspTool,
|
|
20
19
|
type LspToolDetails,
|
|
21
20
|
type LspWarmupOptions,
|
|
22
21
|
type LspWarmupResult,
|
|
23
22
|
warmupLspServers,
|
|
24
23
|
} from "./lsp/index";
|
|
25
|
-
export {
|
|
26
|
-
export {
|
|
27
|
-
export {
|
|
28
|
-
export {
|
|
24
|
+
export { NotebookTool, type NotebookToolDetails } from "./notebook";
|
|
25
|
+
export { OutputTool, type OutputToolDetails } from "./output";
|
|
26
|
+
export { EditTool, type EditToolDetails } from "./patch";
|
|
27
|
+
export { PythonTool, type PythonToolDetails, type PythonToolOptions } from "./python";
|
|
28
|
+
export { ReadTool, type ReadToolDetails } from "./read";
|
|
29
29
|
export { reportFindingTool, type SubmitReviewDetails } from "./review";
|
|
30
|
-
export {
|
|
31
|
-
export { BUNDLED_AGENTS,
|
|
32
|
-
export {
|
|
30
|
+
export { loadSshTool, type SSHToolDetails, SshTool } from "./ssh";
|
|
31
|
+
export { BUNDLED_AGENTS, TaskTool } from "./task/index";
|
|
32
|
+
export { type TodoItem, TodoWriteTool, type TodoWriteToolDetails } from "./todo-write";
|
|
33
33
|
export {
|
|
34
34
|
DEFAULT_MAX_BYTES,
|
|
35
35
|
DEFAULT_MAX_LINES,
|
|
@@ -40,10 +40,9 @@ export {
|
|
|
40
40
|
truncateLine,
|
|
41
41
|
truncateTail,
|
|
42
42
|
} from "./truncate";
|
|
43
|
-
export {
|
|
43
|
+
export { WebFetchTool, type WebFetchToolDetails } from "./web-fetch";
|
|
44
44
|
export {
|
|
45
45
|
companyWebSearchTools,
|
|
46
|
-
createWebSearchTool,
|
|
47
46
|
exaWebSearchTools,
|
|
48
47
|
getWebSearchTools,
|
|
49
48
|
hasExaWebSearch,
|
|
@@ -51,6 +50,7 @@ export {
|
|
|
51
50
|
setPreferredWebSearchProvider,
|
|
52
51
|
type WebSearchProvider,
|
|
53
52
|
type WebSearchResponse,
|
|
53
|
+
WebSearchTool,
|
|
54
54
|
type WebSearchToolsOptions,
|
|
55
55
|
webSearchCodeContextTool,
|
|
56
56
|
webSearchCompanyTool,
|
|
@@ -58,9 +58,8 @@ export {
|
|
|
58
58
|
webSearchCustomTool,
|
|
59
59
|
webSearchDeepTool,
|
|
60
60
|
webSearchLinkedinTool,
|
|
61
|
-
webSearchTool,
|
|
62
61
|
} from "./web-search/index";
|
|
63
|
-
export {
|
|
62
|
+
export { WriteTool, type WriteToolDetails } from "./write";
|
|
64
63
|
|
|
65
64
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
66
65
|
import type { EventBus } from "../event-bus";
|
|
@@ -68,27 +67,27 @@ import { logger } from "../logger";
|
|
|
68
67
|
import { getPreludeDocs, warmPythonEnvironment } from "../python-executor";
|
|
69
68
|
import { checkPythonKernelAvailability } from "../python-kernel";
|
|
70
69
|
import type { BashInterceptorRule } from "../settings-manager";
|
|
71
|
-
import {
|
|
72
|
-
import {
|
|
73
|
-
import {
|
|
74
|
-
import {
|
|
75
|
-
import {
|
|
76
|
-
import {
|
|
77
|
-
import {
|
|
78
|
-
import {
|
|
79
|
-
import {
|
|
80
|
-
import {
|
|
81
|
-
import {
|
|
82
|
-
import {
|
|
83
|
-
import {
|
|
84
|
-
import {
|
|
70
|
+
import { AskTool } from "./ask";
|
|
71
|
+
import { BashTool } from "./bash";
|
|
72
|
+
import { CalculatorTool } from "./calculator";
|
|
73
|
+
import { CompleteTool } from "./complete";
|
|
74
|
+
import { FindTool } from "./find";
|
|
75
|
+
import { GitTool } from "./git";
|
|
76
|
+
import { GrepTool } from "./grep";
|
|
77
|
+
import { LsTool } from "./ls";
|
|
78
|
+
import { LspTool } from "./lsp/index";
|
|
79
|
+
import { NotebookTool } from "./notebook";
|
|
80
|
+
import { OutputTool } from "./output";
|
|
81
|
+
import { EditTool } from "./patch";
|
|
82
|
+
import { PythonTool } from "./python";
|
|
83
|
+
import { ReadTool } from "./read";
|
|
85
84
|
import { reportFindingTool } from "./review";
|
|
86
|
-
import {
|
|
87
|
-
import {
|
|
88
|
-
import {
|
|
89
|
-
import {
|
|
90
|
-
import {
|
|
91
|
-
import {
|
|
85
|
+
import { loadSshTool } from "./ssh";
|
|
86
|
+
import { TaskTool } from "./task/index";
|
|
87
|
+
import { TodoWriteTool } from "./todo-write";
|
|
88
|
+
import { WebFetchTool } from "./web-fetch";
|
|
89
|
+
import { WebSearchTool } from "./web-search/index";
|
|
90
|
+
import { WriteTool } from "./write";
|
|
92
91
|
|
|
93
92
|
/** Tool type (AgentTool from pi-ai) */
|
|
94
93
|
export type Tool = AgentTool<any, any, any>;
|
|
@@ -130,6 +129,8 @@ export interface ToolSession {
|
|
|
130
129
|
getLspDiagnosticsOnWrite(): boolean;
|
|
131
130
|
getLspDiagnosticsOnEdit(): boolean;
|
|
132
131
|
getEditFuzzyMatch(): boolean;
|
|
132
|
+
getEditFuzzyThreshold?(): number;
|
|
133
|
+
getEditPatchMode?(): boolean;
|
|
133
134
|
getGitToolEnabled(): boolean;
|
|
134
135
|
getBashInterceptorEnabled(): boolean;
|
|
135
136
|
getBashInterceptorSimpleLsEnabled(): boolean;
|
|
@@ -143,29 +144,29 @@ export interface ToolSession {
|
|
|
143
144
|
type ToolFactory = (session: ToolSession) => Tool | null | Promise<Tool | null>;
|
|
144
145
|
|
|
145
146
|
export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
146
|
-
ask:
|
|
147
|
-
bash:
|
|
148
|
-
python:
|
|
149
|
-
calc:
|
|
150
|
-
ssh:
|
|
151
|
-
edit:
|
|
152
|
-
find:
|
|
153
|
-
git:
|
|
154
|
-
grep:
|
|
155
|
-
ls:
|
|
156
|
-
lsp:
|
|
157
|
-
notebook:
|
|
158
|
-
output:
|
|
159
|
-
read:
|
|
160
|
-
task:
|
|
161
|
-
todo_write:
|
|
162
|
-
web_fetch:
|
|
163
|
-
web_search:
|
|
164
|
-
write:
|
|
147
|
+
ask: AskTool.createIf,
|
|
148
|
+
bash: (s) => new BashTool(s),
|
|
149
|
+
python: (s) => new PythonTool(s),
|
|
150
|
+
calc: (s) => new CalculatorTool(s),
|
|
151
|
+
ssh: loadSshTool,
|
|
152
|
+
edit: (s) => new EditTool(s),
|
|
153
|
+
find: (s) => new FindTool(s),
|
|
154
|
+
git: GitTool.createIf,
|
|
155
|
+
grep: (s) => new GrepTool(s),
|
|
156
|
+
ls: (s) => new LsTool(s),
|
|
157
|
+
lsp: LspTool.createIf,
|
|
158
|
+
notebook: (s) => new NotebookTool(s),
|
|
159
|
+
output: (s) => new OutputTool(s),
|
|
160
|
+
read: (s) => new ReadTool(s),
|
|
161
|
+
task: TaskTool.create,
|
|
162
|
+
todo_write: (s) => new TodoWriteTool(s),
|
|
163
|
+
web_fetch: (s) => new WebFetchTool(s),
|
|
164
|
+
web_search: (s) => new WebSearchTool(s),
|
|
165
|
+
write: (s) => new WriteTool(s),
|
|
165
166
|
};
|
|
166
167
|
|
|
167
168
|
export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
|
|
168
|
-
complete:
|
|
169
|
+
complete: (s) => new CompleteTool(s),
|
|
169
170
|
report_finding: () => reportFindingTool,
|
|
170
171
|
};
|
|
171
172
|
|
package/src/core/tools/ls.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import nodePath from "node:path";
|
|
2
|
-
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
3
|
-
import type
|
|
4
|
-
import { Text } from "@oh-my-pi/pi-tui";
|
|
2
|
+
import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
3
|
+
import { type Component, Text } from "@oh-my-pi/pi-tui";
|
|
5
4
|
import { Type } from "@sinclair/typebox";
|
|
6
5
|
import { getLanguageFromPath, type Theme } from "../../modes/interactive/theme/theme";
|
|
7
6
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
@@ -69,129 +68,135 @@ const defaultLsOperations: LsOperations = {
|
|
|
69
68
|
},
|
|
70
69
|
};
|
|
71
70
|
|
|
72
|
-
export
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
71
|
+
export class LsTool implements AgentTool<typeof lsSchema, LsToolDetails> {
|
|
72
|
+
public readonly name = "ls";
|
|
73
|
+
public readonly label = "Ls";
|
|
74
|
+
public readonly description =
|
|
75
|
+
'List directory contents with modification times. Returns entries sorted alphabetically, with \'/\' suffix for directories and relative age (e.g., "2d ago", "just now"). Includes dotfiles. Output is truncated to 500 entries or 50KB (whichever is hit first).';
|
|
76
|
+
public readonly parameters = lsSchema;
|
|
77
|
+
|
|
78
|
+
private readonly session: ToolSession;
|
|
79
|
+
private readonly ops: LsOperations;
|
|
80
|
+
|
|
81
|
+
constructor(session: ToolSession, options?: LsToolOptions) {
|
|
82
|
+
this.session = session;
|
|
83
|
+
this.ops = options?.operations ?? defaultLsOperations;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public async execute(
|
|
87
|
+
_toolCallId: string,
|
|
88
|
+
{ path, limit }: { path?: string; limit?: number },
|
|
89
|
+
signal?: AbortSignal,
|
|
90
|
+
): Promise<AgentToolResult<LsToolDetails>> {
|
|
91
|
+
return untilAborted(signal, async () => {
|
|
92
|
+
const dirPath = resolveToCwd(path || ".", this.session.cwd);
|
|
93
|
+
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
|
94
|
+
|
|
95
|
+
// Check if path exists and is a directory
|
|
96
|
+
const dirStat = await this.ops.stat(dirPath);
|
|
97
|
+
if (!dirStat) {
|
|
98
|
+
throw new Error(`Path not found: ${dirPath}`);
|
|
99
|
+
}
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
if (!dirStat.isDirectory()) {
|
|
102
|
+
throw new Error(`Not a directory: ${dirPath}`);
|
|
103
|
+
}
|
|
98
104
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
// Read directory entries
|
|
106
|
+
let entries: string[];
|
|
107
|
+
try {
|
|
108
|
+
entries = await this.ops.readdir(dirPath);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
111
|
+
throw new Error(`Cannot read directory: ${message}`);
|
|
112
|
+
}
|
|
107
113
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// Format entries with directory indicators
|
|
112
|
-
const results: string[] = [];
|
|
113
|
-
let entryLimitReached = false;
|
|
114
|
-
let dirCount = 0;
|
|
115
|
-
let fileCount = 0;
|
|
116
|
-
|
|
117
|
-
for (const entry of entries) {
|
|
118
|
-
signal?.throwIfAborted();
|
|
119
|
-
if (results.length >= effectiveLimit) {
|
|
120
|
-
entryLimitReached = true;
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const fullPath = nodePath.join(dirPath, entry);
|
|
125
|
-
let suffix = "";
|
|
126
|
-
let age = "";
|
|
127
|
-
|
|
128
|
-
const entryStat = await ops.stat(fullPath);
|
|
129
|
-
if (!entryStat) {
|
|
130
|
-
// Skip entries we can't stat
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (entryStat.isDirectory()) {
|
|
135
|
-
suffix = "/";
|
|
136
|
-
dirCount += 1;
|
|
137
|
-
} else {
|
|
138
|
-
fileCount += 1;
|
|
139
|
-
}
|
|
140
|
-
// Calculate age from mtime
|
|
141
|
-
const ageSeconds = Math.floor((Date.now() - entryStat.mtimeMs) / 1000);
|
|
142
|
-
age = formatAge(ageSeconds);
|
|
143
|
-
|
|
144
|
-
// Format: "name/ (2d ago)" or "name (just now)"
|
|
145
|
-
const line = age ? `${entry}${suffix} (${age})` : entry + suffix;
|
|
146
|
-
results.push(line);
|
|
147
|
-
}
|
|
114
|
+
// Sort alphabetically (case-insensitive)
|
|
115
|
+
entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
148
116
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
117
|
+
// Format entries with directory indicators
|
|
118
|
+
const results: string[] = [];
|
|
119
|
+
let entryLimitReached = false;
|
|
120
|
+
let dirCount = 0;
|
|
121
|
+
let fileCount = 0;
|
|
152
122
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const details: LsToolDetails = {
|
|
159
|
-
entries: results,
|
|
160
|
-
dirCount,
|
|
161
|
-
fileCount,
|
|
162
|
-
};
|
|
163
|
-
const truncationReasons: Array<"entryLimit" | "byteLimit"> = [];
|
|
164
|
-
|
|
165
|
-
// Build notices
|
|
166
|
-
const notices: string[] = [];
|
|
167
|
-
|
|
168
|
-
if (entryLimitReached) {
|
|
169
|
-
notices.push(`${effectiveLimit} entries limit reached. Use limit=${effectiveLimit * 2} for more`);
|
|
170
|
-
details.entryLimitReached = effectiveLimit;
|
|
171
|
-
truncationReasons.push("entryLimit");
|
|
123
|
+
for (const entry of entries) {
|
|
124
|
+
signal?.throwIfAborted();
|
|
125
|
+
if (results.length >= effectiveLimit) {
|
|
126
|
+
entryLimitReached = true;
|
|
127
|
+
break;
|
|
172
128
|
}
|
|
173
129
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
truncationReasons.push("byteLimit");
|
|
178
|
-
}
|
|
130
|
+
const fullPath = nodePath.join(dirPath, entry);
|
|
131
|
+
let suffix = "";
|
|
132
|
+
let age = "";
|
|
179
133
|
|
|
180
|
-
|
|
181
|
-
|
|
134
|
+
const entryStat = await this.ops.stat(fullPath);
|
|
135
|
+
if (!entryStat) {
|
|
136
|
+
// Skip entries we can't stat
|
|
137
|
+
continue;
|
|
182
138
|
}
|
|
183
139
|
|
|
184
|
-
if (
|
|
185
|
-
|
|
140
|
+
if (entryStat.isDirectory()) {
|
|
141
|
+
suffix = "/";
|
|
142
|
+
dirCount += 1;
|
|
143
|
+
} else {
|
|
144
|
+
fileCount += 1;
|
|
186
145
|
}
|
|
146
|
+
// Calculate age from mtime
|
|
147
|
+
const ageSeconds = Math.floor((Date.now() - entryStat.mtimeMs) / 1000);
|
|
148
|
+
age = formatAge(ageSeconds);
|
|
149
|
+
|
|
150
|
+
// Format: "name/ (2d ago)" or "name (just now)"
|
|
151
|
+
const line = age ? `${entry}${suffix} (${age})` : entry + suffix;
|
|
152
|
+
results.push(line);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (results.length === 0) {
|
|
156
|
+
return { content: [{ type: "text", text: "(empty directory)" }], details: {} };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Apply byte truncation (no line limit since we already have entry limit)
|
|
160
|
+
const rawOutput = results.join("\n");
|
|
161
|
+
const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
162
|
+
|
|
163
|
+
let output = truncation.content;
|
|
164
|
+
const details: LsToolDetails = {
|
|
165
|
+
entries: results,
|
|
166
|
+
dirCount,
|
|
167
|
+
fileCount,
|
|
168
|
+
};
|
|
169
|
+
const truncationReasons: Array<"entryLimit" | "byteLimit"> = [];
|
|
170
|
+
|
|
171
|
+
// Build notices
|
|
172
|
+
const notices: string[] = [];
|
|
173
|
+
|
|
174
|
+
if (entryLimitReached) {
|
|
175
|
+
notices.push(`${effectiveLimit} entries limit reached. Use limit=${effectiveLimit * 2} for more`);
|
|
176
|
+
details.entryLimitReached = effectiveLimit;
|
|
177
|
+
truncationReasons.push("entryLimit");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (truncation.truncated) {
|
|
181
|
+
notices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);
|
|
182
|
+
details.truncation = truncation;
|
|
183
|
+
truncationReasons.push("byteLimit");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (truncationReasons.length > 0) {
|
|
187
|
+
details.truncationReasons = truncationReasons;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (notices.length > 0) {
|
|
191
|
+
output += `\n\n[${notices.join(". ")}]`;
|
|
192
|
+
}
|
|
187
193
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
};
|
|
194
|
+
return {
|
|
195
|
+
content: [{ type: "text", text: output }],
|
|
196
|
+
details,
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
}
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
// =============================================================================
|
|
@@ -110,6 +110,11 @@ export class BiomeClient implements LinterClient {
|
|
|
110
110
|
private config: ServerConfig;
|
|
111
111
|
private cwd: string;
|
|
112
112
|
|
|
113
|
+
/** Factory method for creating BiomeClient instances */
|
|
114
|
+
static create(config: ServerConfig, cwd: string): LinterClient {
|
|
115
|
+
return new BiomeClient(config, cwd);
|
|
116
|
+
}
|
|
117
|
+
|
|
113
118
|
constructor(config: ServerConfig, cwd: string) {
|
|
114
119
|
this.config = config;
|
|
115
120
|
this.cwd = cwd;
|
|
@@ -198,10 +203,3 @@ export class BiomeClient implements LinterClient {
|
|
|
198
203
|
// Nothing to dispose for CLI client
|
|
199
204
|
}
|
|
200
205
|
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Factory function to create a Biome client.
|
|
204
|
-
*/
|
|
205
|
-
export function createBiomeClient(config: ServerConfig, cwd: string): LinterClient {
|
|
206
|
-
return new BiomeClient(config, cwd);
|
|
207
|
-
}
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* Different implementations can use LSP protocol, CLI tools, or other mechanisms.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export { BiomeClient
|
|
9
|
-
export {
|
|
8
|
+
export { BiomeClient } from "./biome-client";
|
|
9
|
+
export { LspLinterClient } from "./lsp-linter-client";
|
|
10
10
|
|
|
11
11
|
import type { LinterClient, ServerConfig } from "../types";
|
|
12
|
-
import {
|
|
12
|
+
import { LspLinterClient } from "./lsp-linter-client";
|
|
13
13
|
|
|
14
14
|
// Cache of linter clients by server name + cwd
|
|
15
15
|
const clientCache = new Map<string, LinterClient>();
|
|
@@ -31,7 +31,7 @@ export function getLinterClient(serverName: string, config: ServerConfig, cwd: s
|
|
|
31
31
|
client = config.createClient(config, cwd);
|
|
32
32
|
} else {
|
|
33
33
|
// Default to LSP
|
|
34
|
-
client =
|
|
34
|
+
client = LspLinterClient.create(config, cwd);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
clientCache.set(key, client);
|
|
@@ -26,6 +26,11 @@ export class LspLinterClient implements LinterClient {
|
|
|
26
26
|
private cwd: string;
|
|
27
27
|
private client: LspClient | null = null;
|
|
28
28
|
|
|
29
|
+
/** Factory method for creating LspLinterClient instances */
|
|
30
|
+
static create(config: ServerConfig, cwd: string): LinterClient {
|
|
31
|
+
return new LspLinterClient(config, cwd);
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
constructor(config: ServerConfig, cwd: string) {
|
|
30
35
|
this.config = config;
|
|
31
36
|
this.cwd = cwd;
|
|
@@ -89,10 +94,3 @@ export class LspLinterClient implements LinterClient {
|
|
|
89
94
|
// Client lifecycle is managed globally, nothing to dispose here
|
|
90
95
|
}
|
|
91
96
|
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Factory function to create an LSP linter client.
|
|
95
|
-
*/
|
|
96
|
-
export function createLspLinterClient(config: ServerConfig, cwd: string): LinterClient {
|
|
97
|
-
return new LspLinterClient(config, cwd);
|
|
98
|
-
}
|
|
@@ -4,7 +4,7 @@ import { YAML } from "bun";
|
|
|
4
4
|
import { globSync } from "glob";
|
|
5
5
|
import { getConfigDirPaths } from "../../../config";
|
|
6
6
|
import { logger } from "../../logger";
|
|
7
|
-
import {
|
|
7
|
+
import { BiomeClient } from "./clients/biome-client";
|
|
8
8
|
import DEFAULTS from "./defaults.json" with { type: "json" };
|
|
9
9
|
import type { ServerConfig } from "./types";
|
|
10
10
|
|
|
@@ -137,7 +137,7 @@ function applyRuntimeDefaults(servers: Record<string, ServerConfig>): Record<str
|
|
|
137
137
|
const updated: Record<string, ServerConfig> = { ...servers };
|
|
138
138
|
|
|
139
139
|
if (updated.biome) {
|
|
140
|
-
updated.biome = { ...updated.biome, createClient:
|
|
140
|
+
updated.biome = { ...updated.biome, createClient: BiomeClient.create };
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
if (updated.omnisharp?.args) {
|