@oh-my-pi/pi-coding-agent 3.30.0 → 3.31.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 +71 -0
- package/package.json +5 -5
- package/src/cli/args.ts +4 -0
- package/src/core/agent-session.ts +29 -2
- package/src/core/bash-executor.ts +2 -1
- package/src/core/custom-commands/bundled/review/index.ts +369 -14
- package/src/core/custom-commands/bundled/wt/index.ts +1 -1
- package/src/core/session-manager.ts +158 -246
- package/src/core/session-storage.ts +379 -0
- package/src/core/settings-manager.ts +155 -4
- package/src/core/system-prompt.ts +62 -64
- package/src/core/tools/ask.ts +5 -4
- package/src/core/tools/bash-interceptor.ts +26 -61
- package/src/core/tools/bash.ts +13 -8
- package/src/core/tools/edit-diff.ts +11 -4
- package/src/core/tools/edit.ts +7 -13
- package/src/core/tools/find.ts +111 -50
- package/src/core/tools/gemini-image.ts +128 -147
- package/src/core/tools/grep.ts +397 -415
- package/src/core/tools/index.test.ts +5 -1
- package/src/core/tools/index.ts +6 -8
- package/src/core/tools/ls.ts +12 -10
- package/src/core/tools/lsp/client.ts +58 -9
- package/src/core/tools/lsp/config.ts +205 -656
- package/src/core/tools/lsp/defaults.json +465 -0
- package/src/core/tools/lsp/index.ts +55 -32
- package/src/core/tools/lsp/rust-analyzer.ts +49 -10
- package/src/core/tools/lsp/types.ts +1 -0
- package/src/core/tools/lsp/utils.ts +1 -1
- package/src/core/tools/read.ts +150 -74
- package/src/core/tools/render-utils.ts +70 -10
- package/src/core/tools/review.ts +38 -126
- package/src/core/tools/task/artifacts.ts +5 -4
- package/src/core/tools/task/executor.ts +94 -83
- package/src/core/tools/task/index.ts +129 -92
- package/src/core/tools/task/parallel.ts +30 -3
- package/src/core/tools/task/render.ts +85 -39
- package/src/core/tools/task/types.ts +15 -6
- package/src/core/tools/task/worker.ts +124 -89
- package/src/core/tools/web-fetch.ts +112 -377
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/artifacthub.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/arxiv.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/aur.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/biorxiv.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/bluesky.ts +10 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/brew.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/cheatsh.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/chocolatey.ts +6 -1
- package/src/core/tools/web-scrapers/choosealicense.ts +110 -0
- package/src/core/tools/web-scrapers/cisa-kev.ts +100 -0
- package/src/core/tools/web-scrapers/clojars.ts +180 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/coingecko.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/crates-io.ts +7 -2
- package/src/core/tools/web-scrapers/crossref.ts +149 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/devto.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/discogs.ts +6 -1
- package/src/core/tools/web-scrapers/discourse.ts +221 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/dockerhub.ts +7 -3
- package/src/core/tools/web-scrapers/fdroid.ts +158 -0
- package/src/core/tools/web-scrapers/firefox-addons.ts +214 -0
- package/src/core/tools/web-scrapers/flathub.ts +239 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/github-gist.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/github.ts +63 -32
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/gitlab.ts +31 -19
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/go-pkg.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/hackage.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/hackernews.ts +18 -18
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/hex.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/huggingface.ts +10 -10
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/iacr.ts +8 -4
- package/src/core/tools/web-scrapers/index.ts +250 -0
- package/src/core/tools/web-scrapers/jetbrains-marketplace.ts +169 -0
- package/src/core/tools/web-scrapers/lemmy.ts +220 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/lobsters.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/mastodon.ts +11 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/maven.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/mdn.ts +2 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/metacpan.ts +13 -7
- package/src/core/tools/web-scrapers/musicbrainz.ts +273 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/npm.ts +12 -5
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/nuget.ts +9 -5
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/nvd.ts +6 -1
- package/src/core/tools/web-scrapers/ollama.ts +267 -0
- package/src/core/tools/web-scrapers/open-vsx.ts +119 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/opencorporates.ts +2 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/openlibrary.ts +18 -12
- package/src/core/tools/web-scrapers/orcid.ts +299 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/osv.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/packagist.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/pub-dev.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/pubmed.ts +8 -4
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/pypi.ts +7 -3
- package/src/core/tools/web-scrapers/rawg.ts +124 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/readthedocs.ts +7 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/reddit.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/repology.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/rfc.ts +7 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/rubygems.ts +6 -1
- package/src/core/tools/web-scrapers/searchcode.ts +217 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/sec-edgar.ts +6 -1
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/semantic-scholar.ts +2 -2
- package/src/core/tools/web-scrapers/snapcraft.ts +200 -0
- package/src/core/tools/web-scrapers/sourcegraph.ts +373 -0
- package/src/core/tools/web-scrapers/spdx.ts +121 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/spotify.ts +3 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/stackoverflow.ts +3 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/terraform.ts +11 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/tldr.ts +6 -2
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/twitter.ts +15 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/types.ts +98 -27
- package/src/core/tools/web-scrapers/utils.ts +162 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/vimeo.ts +3 -3
- package/src/core/tools/web-scrapers/vscode-marketplace.ts +195 -0
- package/src/core/tools/web-scrapers/w3c.ts +163 -0
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikidata.ts +13 -5
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikipedia.ts +7 -3
- package/src/core/tools/{web-fetch-handlers → web-scrapers}/youtube.ts +72 -20
- package/src/core/tools/write.ts +21 -18
- package/src/core/voice.ts +3 -2
- package/src/lib/worktree/collapse.ts +2 -1
- package/src/lib/worktree/git.ts +2 -18
- package/src/main.ts +59 -3
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +33 -19
- package/src/modes/interactive/components/extensions/extension-list.ts +15 -8
- package/src/modes/interactive/components/hook-editor.ts +2 -1
- package/src/modes/interactive/components/model-selector.ts +19 -4
- package/src/modes/interactive/interactive-mode.ts +41 -38
- package/src/modes/interactive/theme/theme.ts +58 -58
- package/src/modes/rpc/rpc-mode.ts +10 -9
- package/src/prompts/review-request.md +27 -0
- package/src/prompts/reviewer.md +64 -68
- package/src/prompts/tools/output.md +22 -3
- package/src/prompts/tools/task.md +32 -33
- package/src/utils/clipboard.ts +2 -1
- package/examples/extensions/subagent/agents/reviewer.md +0 -35
- package/src/core/tools/web-fetch-handlers/index.ts +0 -69
- package/src/core/tools/web-fetch-handlers/utils.ts +0 -91
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/academic.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/business.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/dev-platforms.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/documentation.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/finance-media.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/git-hosting.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/media.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-managers-2.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-managers.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-registries.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/research.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/security.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/social-extended.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/social.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/stackexchange.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/standards.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikipedia.test.ts +0 -0
- /package/src/core/tools/{web-fetch-handlers → web-scrapers}/youtube.test.ts +0 -0
|
@@ -110,6 +110,8 @@ describe("createTools", () => {
|
|
|
110
110
|
getEditFuzzyMatch: () => true,
|
|
111
111
|
getGitToolEnabled: () => false,
|
|
112
112
|
getBashInterceptorEnabled: () => true,
|
|
113
|
+
getBashInterceptorSimpleLsEnabled: () => true,
|
|
114
|
+
getBashInterceptorRules: () => [],
|
|
113
115
|
},
|
|
114
116
|
});
|
|
115
117
|
const tools = await createTools(session);
|
|
@@ -128,6 +130,8 @@ describe("createTools", () => {
|
|
|
128
130
|
getEditFuzzyMatch: () => true,
|
|
129
131
|
getGitToolEnabled: () => true,
|
|
130
132
|
getBashInterceptorEnabled: () => true,
|
|
133
|
+
getBashInterceptorSimpleLsEnabled: () => true,
|
|
134
|
+
getBashInterceptorRules: () => [],
|
|
131
135
|
},
|
|
132
136
|
});
|
|
133
137
|
const tools = await createTools(session);
|
|
@@ -183,6 +187,6 @@ describe("createTools", () => {
|
|
|
183
187
|
});
|
|
184
188
|
|
|
185
189
|
it("HIDDEN_TOOLS contains review tools", () => {
|
|
186
|
-
expect(Object.keys(HIDDEN_TOOLS).sort()).toEqual(["complete", "report_finding"
|
|
190
|
+
expect(Object.keys(HIDDEN_TOOLS).sort()).toEqual(["complete", "report_finding"]);
|
|
187
191
|
});
|
|
188
192
|
});
|
package/src/core/tools/index.ts
CHANGED
|
@@ -18,13 +18,12 @@ export {
|
|
|
18
18
|
type LspServerStatus,
|
|
19
19
|
type LspToolDetails,
|
|
20
20
|
type LspWarmupResult,
|
|
21
|
-
lspTool,
|
|
22
21
|
warmupLspServers,
|
|
23
22
|
} from "./lsp/index";
|
|
24
23
|
export { createNotebookTool, type NotebookToolDetails } from "./notebook";
|
|
25
24
|
export { createOutputTool, type OutputToolDetails } from "./output";
|
|
26
25
|
export { createReadTool, type ReadToolDetails } from "./read";
|
|
27
|
-
export { reportFindingTool,
|
|
26
|
+
export { reportFindingTool, type SubmitReviewDetails } from "./review";
|
|
28
27
|
export { filterRulebookRules, formatRulesForPrompt, type RulebookToolDetails } from "./rulebook";
|
|
29
28
|
export { BUNDLED_AGENTS, createTaskTool, taskTool } from "./task/index";
|
|
30
29
|
export type { TruncationResult } from "./truncate";
|
|
@@ -53,6 +52,7 @@ export { createWriteTool, type WriteToolDetails } from "./write";
|
|
|
53
52
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
54
53
|
import type { Rule } from "../../capability/rule";
|
|
55
54
|
import type { EventBus } from "../event-bus";
|
|
55
|
+
import type { BashInterceptorRule } from "../settings-manager";
|
|
56
56
|
import { createAskTool } from "./ask";
|
|
57
57
|
import { createBashTool } from "./bash";
|
|
58
58
|
import { createCompleteTool } from "./complete";
|
|
@@ -65,7 +65,7 @@ import { createLspTool } from "./lsp/index";
|
|
|
65
65
|
import { createNotebookTool } from "./notebook";
|
|
66
66
|
import { createOutputTool } from "./output";
|
|
67
67
|
import { createReadTool } from "./read";
|
|
68
|
-
import { reportFindingTool
|
|
68
|
+
import { reportFindingTool } from "./review";
|
|
69
69
|
import { createRulebookTool } from "./rulebook";
|
|
70
70
|
import { createTaskTool } from "./task/index";
|
|
71
71
|
import { createWebFetchTool } from "./web-fetch";
|
|
@@ -102,6 +102,8 @@ export interface ToolSession {
|
|
|
102
102
|
getEditFuzzyMatch(): boolean;
|
|
103
103
|
getGitToolEnabled(): boolean;
|
|
104
104
|
getBashInterceptorEnabled(): boolean;
|
|
105
|
+
getBashInterceptorSimpleLsEnabled(): boolean;
|
|
106
|
+
getBashInterceptorRules(): BashInterceptorRule[];
|
|
105
107
|
};
|
|
106
108
|
}
|
|
107
109
|
|
|
@@ -129,7 +131,6 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
129
131
|
export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
|
|
130
132
|
complete: createCompleteTool,
|
|
131
133
|
report_finding: () => reportFindingTool,
|
|
132
|
-
submit_review: () => submitReviewTool,
|
|
133
134
|
};
|
|
134
135
|
|
|
135
136
|
export type ToolName = keyof typeof BUILTIN_TOOLS;
|
|
@@ -147,10 +148,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
147
148
|
|
|
148
149
|
const entries = requestedTools
|
|
149
150
|
? requestedTools.filter((name) => name in allTools).map((name) => [name, allTools[name]] as const)
|
|
150
|
-
: [
|
|
151
|
-
...Object.entries(BUILTIN_TOOLS),
|
|
152
|
-
...(includeComplete ? ([["complete", HIDDEN_TOOLS.complete]] as const) : []),
|
|
153
|
-
];
|
|
151
|
+
: [...Object.entries(BUILTIN_TOOLS), ...(includeComplete ? ([["complete", HIDDEN_TOOLS.complete]] as const) : [])];
|
|
154
152
|
const results = await Promise.all(entries.map(([, factory]) => factory(session)));
|
|
155
153
|
const tools = results.filter((t): t is Tool => t !== null);
|
|
156
154
|
|
package/src/core/tools/ls.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
1
|
import nodePath from "node:path";
|
|
3
2
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
4
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
@@ -53,23 +52,25 @@ export function createLsTool(session: ToolSession): AgentTool<typeof lsSchema> {
|
|
|
53
52
|
const dirPath = resolveToCwd(path || ".", session.cwd);
|
|
54
53
|
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
|
55
54
|
|
|
56
|
-
// Check if path exists
|
|
57
|
-
|
|
55
|
+
// Check if path exists and is a directory
|
|
56
|
+
let dirStat: Awaited<ReturnType<Bun.BunFile["stat"]>>;
|
|
57
|
+
try {
|
|
58
|
+
dirStat = await Bun.file(dirPath).stat();
|
|
59
|
+
} catch {
|
|
58
60
|
throw new Error(`Path not found: ${dirPath}`);
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
const stat = statSync(dirPath);
|
|
63
|
-
if (!stat.isDirectory()) {
|
|
63
|
+
if (!dirStat.isDirectory()) {
|
|
64
64
|
throw new Error(`Not a directory: ${dirPath}`);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// Read directory entries
|
|
68
68
|
let entries: string[];
|
|
69
69
|
try {
|
|
70
|
-
entries =
|
|
71
|
-
} catch (
|
|
72
|
-
|
|
70
|
+
entries = await Array.fromAsync(new Bun.Glob("*").scan({ cwd: dirPath, dot: true, onlyFiles: false }));
|
|
71
|
+
} catch (error) {
|
|
72
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
73
|
+
throw new Error(`Cannot read directory: ${message}`);
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
// Sort alphabetically (case-insensitive)
|
|
@@ -82,6 +83,7 @@ export function createLsTool(session: ToolSession): AgentTool<typeof lsSchema> {
|
|
|
82
83
|
let fileCount = 0;
|
|
83
84
|
|
|
84
85
|
for (const entry of entries) {
|
|
86
|
+
signal?.throwIfAborted();
|
|
85
87
|
if (results.length >= effectiveLimit) {
|
|
86
88
|
entryLimitReached = true;
|
|
87
89
|
break;
|
|
@@ -92,7 +94,7 @@ export function createLsTool(session: ToolSession): AgentTool<typeof lsSchema> {
|
|
|
92
94
|
let age = "";
|
|
93
95
|
|
|
94
96
|
try {
|
|
95
|
-
const entryStat =
|
|
97
|
+
const entryStat = await Bun.file(fullPath).stat();
|
|
96
98
|
if (entryStat.isDirectory()) {
|
|
97
99
|
suffix = "/";
|
|
98
100
|
dirCount += 1;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { logger } from "../../logger";
|
|
2
3
|
import { applyWorkspaceEdit } from "./edits";
|
|
3
4
|
import type {
|
|
4
5
|
Diagnostic,
|
|
@@ -266,6 +267,7 @@ async function startMessageReader(client: LspClient): Promise<void> {
|
|
|
266
267
|
if (message.method === "textDocument/publishDiagnostics" && message.params) {
|
|
267
268
|
const params = message.params as { uri: string; diagnostics: Diagnostic[] };
|
|
268
269
|
client.diagnostics.set(params.uri, params.diagnostics);
|
|
270
|
+
client.diagnosticsVersion += 1;
|
|
269
271
|
}
|
|
270
272
|
}
|
|
271
273
|
|
|
@@ -363,7 +365,7 @@ async function sendResponse(
|
|
|
363
365
|
try {
|
|
364
366
|
await writeMessage(client.process.stdin as import("bun").FileSink, response);
|
|
365
367
|
} catch (err) {
|
|
366
|
-
|
|
368
|
+
logger.error("LSP failed to respond.", { method, error: String(err) });
|
|
367
369
|
}
|
|
368
370
|
}
|
|
369
371
|
|
|
@@ -408,6 +410,7 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string): Prom
|
|
|
408
410
|
config,
|
|
409
411
|
requestId: 0,
|
|
410
412
|
diagnostics: new Map(),
|
|
413
|
+
diagnosticsVersion: 0,
|
|
411
414
|
openFiles: new Map(),
|
|
412
415
|
pendingRequests: new Map(),
|
|
413
416
|
messageBuffer: new Uint8Array(0),
|
|
@@ -516,9 +519,15 @@ export async function ensureFileOpen(client: LspClient, filePath: string): Promi
|
|
|
516
519
|
* Sync in-memory content to the LSP client without reading from disk.
|
|
517
520
|
* Use this to provide instant feedback during edits before the file is saved.
|
|
518
521
|
*/
|
|
519
|
-
export async function syncContent(
|
|
522
|
+
export async function syncContent(
|
|
523
|
+
client: LspClient,
|
|
524
|
+
filePath: string,
|
|
525
|
+
content: string,
|
|
526
|
+
signal?: AbortSignal,
|
|
527
|
+
): Promise<void> {
|
|
520
528
|
const uri = fileToUri(filePath);
|
|
521
529
|
const lockKey = `${client.name}:${uri}`;
|
|
530
|
+
signal?.throwIfAborted();
|
|
522
531
|
|
|
523
532
|
const existingLock = fileOperationLocks.get(lockKey);
|
|
524
533
|
if (existingLock) {
|
|
@@ -534,6 +543,7 @@ export async function syncContent(client: LspClient, filePath: string, content:
|
|
|
534
543
|
if (!info) {
|
|
535
544
|
// Open file with provided content instead of reading from disk
|
|
536
545
|
const languageId = detectLanguageId(filePath);
|
|
546
|
+
signal?.throwIfAborted();
|
|
537
547
|
await sendNotification(client, "textDocument/didOpen", {
|
|
538
548
|
textDocument: {
|
|
539
549
|
uri,
|
|
@@ -548,6 +558,7 @@ export async function syncContent(client: LspClient, filePath: string, content:
|
|
|
548
558
|
}
|
|
549
559
|
|
|
550
560
|
const version = ++info.version;
|
|
561
|
+
signal?.throwIfAborted();
|
|
551
562
|
await sendNotification(client, "textDocument/didChange", {
|
|
552
563
|
textDocument: { uri, version },
|
|
553
564
|
contentChanges: [{ text: content }],
|
|
@@ -567,11 +578,12 @@ export async function syncContent(client: LspClient, filePath: string, content:
|
|
|
567
578
|
* Notify LSP that a file was saved.
|
|
568
579
|
* Assumes content was already synced via syncContent - just sends didSave.
|
|
569
580
|
*/
|
|
570
|
-
export async function notifySaved(client: LspClient, filePath: string): Promise<void> {
|
|
581
|
+
export async function notifySaved(client: LspClient, filePath: string, signal?: AbortSignal): Promise<void> {
|
|
571
582
|
const uri = fileToUri(filePath);
|
|
572
583
|
const info = client.openFiles.get(uri);
|
|
573
584
|
if (!info) return; // File not open, nothing to notify
|
|
574
585
|
|
|
586
|
+
signal?.throwIfAborted();
|
|
575
587
|
await sendNotification(client, "textDocument/didSave", {
|
|
576
588
|
textDocument: { uri },
|
|
577
589
|
});
|
|
@@ -653,9 +665,18 @@ export function shutdownClient(key: string): void {
|
|
|
653
665
|
/**
|
|
654
666
|
* Send an LSP request and wait for response.
|
|
655
667
|
*/
|
|
656
|
-
export async function sendRequest(
|
|
668
|
+
export async function sendRequest(
|
|
669
|
+
client: LspClient,
|
|
670
|
+
method: string,
|
|
671
|
+
params: unknown,
|
|
672
|
+
signal?: AbortSignal,
|
|
673
|
+
): Promise<unknown> {
|
|
657
674
|
// Atomically increment and capture request ID
|
|
658
675
|
const id = ++client.requestId;
|
|
676
|
+
if (signal?.aborted) {
|
|
677
|
+
const reason = signal.reason instanceof Error ? signal.reason : new Error("Operation aborted");
|
|
678
|
+
return Promise.reject(reason);
|
|
679
|
+
}
|
|
659
680
|
|
|
660
681
|
const request: LspJsonRpcRequest = {
|
|
661
682
|
jsonrpc: "2.0",
|
|
@@ -667,22 +688,49 @@ export async function sendRequest(client: LspClient, method: string, params: unk
|
|
|
667
688
|
client.lastActivity = Date.now();
|
|
668
689
|
|
|
669
690
|
return new Promise((resolve, reject) => {
|
|
691
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
692
|
+
const cleanup = () => {
|
|
693
|
+
if (signal) {
|
|
694
|
+
signal.removeEventListener("abort", abortHandler);
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
const abortHandler = () => {
|
|
698
|
+
if (client.pendingRequests.has(id)) {
|
|
699
|
+
client.pendingRequests.delete(id);
|
|
700
|
+
}
|
|
701
|
+
if (timeout) clearTimeout(timeout);
|
|
702
|
+
cleanup();
|
|
703
|
+
const reason = signal?.reason instanceof Error ? signal.reason : new Error("Operation aborted");
|
|
704
|
+
reject(reason);
|
|
705
|
+
};
|
|
706
|
+
|
|
670
707
|
// Set timeout
|
|
671
|
-
|
|
708
|
+
timeout = setTimeout(() => {
|
|
672
709
|
if (client.pendingRequests.has(id)) {
|
|
673
710
|
client.pendingRequests.delete(id);
|
|
674
|
-
|
|
711
|
+
const err = new Error(`LSP request ${method} timed out`);
|
|
712
|
+
cleanup();
|
|
713
|
+
reject(err);
|
|
675
714
|
}
|
|
676
715
|
}, 30000);
|
|
716
|
+
if (signal) {
|
|
717
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
718
|
+
if (signal.aborted) {
|
|
719
|
+
abortHandler();
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
677
723
|
|
|
678
724
|
// Register pending request with timeout wrapper
|
|
679
725
|
client.pendingRequests.set(id, {
|
|
680
726
|
resolve: (result) => {
|
|
681
|
-
clearTimeout(timeout);
|
|
727
|
+
if (timeout) clearTimeout(timeout);
|
|
728
|
+
cleanup();
|
|
682
729
|
resolve(result);
|
|
683
730
|
},
|
|
684
731
|
reject: (err) => {
|
|
685
|
-
clearTimeout(timeout);
|
|
732
|
+
if (timeout) clearTimeout(timeout);
|
|
733
|
+
cleanup();
|
|
686
734
|
reject(err);
|
|
687
735
|
},
|
|
688
736
|
method,
|
|
@@ -690,8 +738,9 @@ export async function sendRequest(client: LspClient, method: string, params: unk
|
|
|
690
738
|
|
|
691
739
|
// Write request
|
|
692
740
|
writeMessage(client.process.stdin as import("bun").FileSink, request).catch((err) => {
|
|
693
|
-
clearTimeout(timeout);
|
|
741
|
+
if (timeout) clearTimeout(timeout);
|
|
694
742
|
client.pendingRequests.delete(id);
|
|
743
|
+
cleanup();
|
|
695
744
|
reject(err);
|
|
696
745
|
});
|
|
697
746
|
});
|