@oh-my-pi/pi-coding-agent 1.340.0 → 2.0.1337
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 +115 -1
- package/README.md +1 -1
- package/examples/custom-tools/subagent/index.ts +1 -1
- package/package.json +5 -3
- package/src/cli/args.ts +13 -6
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/list-models.ts +2 -2
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/session-picker.ts +2 -2
- package/src/cli.ts +1 -1
- package/src/config.ts +3 -3
- package/src/core/agent-session.ts +189 -29
- package/src/core/bash-executor.ts +50 -10
- package/src/core/compaction/branch-summarization.ts +5 -5
- package/src/core/compaction/compaction.ts +3 -3
- package/src/core/compaction/index.ts +3 -3
- package/src/core/custom-commands/bundled/review/index.ts +156 -0
- package/src/core/custom-commands/index.ts +15 -0
- package/src/core/custom-commands/loader.ts +232 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +3 -3
- package/src/core/custom-tools/loader.ts +10 -8
- package/src/core/custom-tools/types.ts +11 -6
- package/src/core/custom-tools/wrapper.ts +2 -1
- package/src/core/exec.ts +22 -12
- package/src/core/export-html/index.ts +5 -5
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +5 -5
- package/src/core/hooks/loader.ts +21 -16
- package/src/core/hooks/runner.ts +6 -6
- package/src/core/hooks/tool-wrapper.ts +2 -2
- package/src/core/hooks/types.ts +12 -15
- package/src/core/index.ts +6 -6
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +3 -3
- package/src/core/mcp/config.ts +1 -1
- package/src/core/mcp/index.ts +12 -12
- package/src/core/mcp/loader.ts +2 -2
- package/src/core/mcp/manager.ts +6 -6
- package/src/core/mcp/tool-bridge.ts +3 -3
- package/src/core/mcp/transports/http.ts +1 -1
- package/src/core/mcp/transports/index.ts +2 -2
- package/src/core/mcp/transports/stdio.ts +1 -1
- package/src/core/messages.ts +22 -0
- package/src/core/model-registry.ts +2 -2
- package/src/core/model-resolver.ts +103 -2
- package/src/core/plugins/doctor.ts +1 -1
- package/src/core/plugins/index.ts +6 -6
- package/src/core/plugins/installer.ts +4 -4
- package/src/core/plugins/loader.ts +4 -9
- package/src/core/plugins/manager.ts +5 -5
- package/src/core/plugins/paths.ts +3 -3
- package/src/core/sdk.ts +127 -52
- package/src/core/session-manager.ts +123 -20
- package/src/core/settings-manager.ts +106 -22
- package/src/core/skills.ts +5 -5
- package/src/core/slash-commands.ts +60 -45
- package/src/core/system-prompt.ts +6 -6
- package/src/core/title-generator.ts +94 -0
- package/src/core/tools/bash.ts +33 -157
- package/src/core/tools/context.ts +2 -2
- package/src/core/tools/edit-diff.ts +5 -5
- package/src/core/tools/edit.ts +60 -9
- package/src/core/tools/exa/company.ts +3 -3
- package/src/core/tools/exa/index.ts +16 -17
- package/src/core/tools/exa/linkedin.ts +3 -3
- package/src/core/tools/exa/mcp-client.ts +9 -9
- package/src/core/tools/exa/render.ts +5 -5
- package/src/core/tools/exa/researcher.ts +3 -3
- package/src/core/tools/exa/search.ts +6 -5
- package/src/core/tools/exa/types.ts +5 -6
- package/src/core/tools/exa/websets.ts +3 -3
- package/src/core/tools/find.ts +3 -3
- package/src/core/tools/grep.ts +6 -5
- package/src/core/tools/index.ts +114 -40
- package/src/core/tools/ls.ts +4 -4
- package/src/core/tools/lsp/client.ts +204 -108
- package/src/core/tools/lsp/config.ts +709 -35
- package/src/core/tools/lsp/edits.ts +2 -2
- package/src/core/tools/lsp/index.ts +432 -30
- package/src/core/tools/lsp/render.ts +2 -2
- package/src/core/tools/lsp/rust-analyzer.ts +3 -3
- package/src/core/tools/lsp/types.ts +5 -0
- package/src/core/tools/lsp/utils.ts +1 -1
- package/src/core/tools/notebook.ts +1 -1
- package/src/core/tools/output.ts +175 -0
- package/src/core/tools/read.ts +7 -7
- package/src/core/tools/renderers.ts +92 -13
- package/src/core/tools/review.ts +268 -0
- package/src/core/tools/task/agents.ts +1 -1
- package/src/core/tools/task/bundled-agents/explore.md +1 -1
- package/src/core/tools/task/bundled-agents/reviewer.md +53 -38
- package/src/core/tools/task/discovery.ts +2 -2
- package/src/core/tools/task/executor.ts +145 -28
- package/src/core/tools/task/index.ts +78 -30
- package/src/core/tools/task/model-resolver.ts +72 -13
- package/src/core/tools/task/parallel.ts +1 -1
- package/src/core/tools/task/render.ts +219 -30
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +36 -2
- package/src/core/tools/web-fetch.ts +5 -3
- package/src/core/tools/web-search/auth.ts +1 -1
- package/src/core/tools/web-search/index.ts +17 -15
- package/src/core/tools/web-search/providers/anthropic.ts +2 -2
- package/src/core/tools/web-search/providers/exa.ts +3 -5
- package/src/core/tools/web-search/providers/perplexity.ts +1 -1
- package/src/core/tools/web-search/render.ts +3 -3
- package/src/core/tools/write.ts +70 -7
- package/src/index.ts +33 -17
- package/src/main.ts +60 -34
- package/src/migrations.ts +3 -3
- package/src/modes/index.ts +5 -5
- package/src/modes/interactive/components/armin.ts +1 -1
- package/src/modes/interactive/components/assistant-message.ts +1 -1
- package/src/modes/interactive/components/bash-execution.ts +4 -4
- package/src/modes/interactive/components/bordered-loader.ts +2 -2
- package/src/modes/interactive/components/branch-summary-message.ts +2 -2
- package/src/modes/interactive/components/compaction-summary-message.ts +2 -2
- package/src/modes/interactive/components/diff.ts +1 -1
- package/src/modes/interactive/components/dynamic-border.ts +1 -1
- package/src/modes/interactive/components/footer.ts +5 -5
- package/src/modes/interactive/components/hook-editor.ts +2 -2
- package/src/modes/interactive/components/hook-input.ts +2 -2
- package/src/modes/interactive/components/hook-message.ts +3 -3
- package/src/modes/interactive/components/hook-selector.ts +2 -2
- package/src/modes/interactive/components/model-selector.ts +341 -41
- package/src/modes/interactive/components/oauth-selector.ts +3 -3
- package/src/modes/interactive/components/plugin-settings.ts +4 -4
- package/src/modes/interactive/components/queue-mode-selector.ts +2 -2
- package/src/modes/interactive/components/session-selector.ts +24 -11
- package/src/modes/interactive/components/settings-defs.ts +51 -3
- package/src/modes/interactive/components/settings-selector.ts +13 -16
- package/src/modes/interactive/components/show-images-selector.ts +2 -2
- package/src/modes/interactive/components/theme-selector.ts +2 -2
- package/src/modes/interactive/components/thinking-selector.ts +2 -2
- package/src/modes/interactive/components/tool-execution.ts +44 -8
- package/src/modes/interactive/components/tree-selector.ts +5 -5
- package/src/modes/interactive/components/user-message-selector.ts +2 -2
- package/src/modes/interactive/components/user-message.ts +1 -1
- package/src/modes/interactive/components/welcome.ts +42 -5
- package/src/modes/interactive/interactive-mode.ts +169 -48
- package/src/modes/interactive/theme/theme.ts +8 -7
- package/src/modes/print-mode.ts +4 -3
- package/src/modes/rpc/rpc-client.ts +4 -4
- package/src/modes/rpc/rpc-mode.ts +21 -11
- package/src/modes/rpc/rpc-types.ts +3 -3
- package/src/utils/changelog.ts +2 -2
- package/src/utils/clipboard.ts +1 -1
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +93 -13
- package/src/utils/tools-manager.ts +1 -1
- package/examples/custom-tools/subagent/agents/reviewer.md +0 -35
- package/src/core/tools/exa/logger.ts +0 -56
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import { applyWorkspaceEdit } from "./edits
|
|
2
|
+
import { applyWorkspaceEdit } from "./edits";
|
|
3
3
|
import type {
|
|
4
4
|
Diagnostic,
|
|
5
5
|
LspClient,
|
|
@@ -8,29 +8,43 @@ import type {
|
|
|
8
8
|
LspJsonRpcResponse,
|
|
9
9
|
ServerConfig,
|
|
10
10
|
WorkspaceEdit,
|
|
11
|
-
} from "./types
|
|
12
|
-
import { detectLanguageId, fileToUri } from "./utils
|
|
11
|
+
} from "./types";
|
|
12
|
+
import { detectLanguageId, fileToUri } from "./utils";
|
|
13
13
|
|
|
14
14
|
// =============================================================================
|
|
15
15
|
// Client State
|
|
16
16
|
// =============================================================================
|
|
17
17
|
|
|
18
18
|
const clients = new Map<string, LspClient>();
|
|
19
|
+
const clientLocks = new Map<string, Promise<LspClient>>();
|
|
20
|
+
const fileOperationLocks = new Map<string, Promise<void>>();
|
|
19
21
|
|
|
20
|
-
// Idle timeout
|
|
21
|
-
|
|
22
|
+
// Idle timeout configuration (disabled by default)
|
|
23
|
+
let idleTimeoutMs: number | null = null;
|
|
24
|
+
let idleCheckInterval: Timer | null = null;
|
|
22
25
|
const IDLE_CHECK_INTERVAL_MS = 60 * 1000;
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Configure the idle timeout for LSP clients.
|
|
29
|
+
* @param ms - Timeout in milliseconds, or null/undefined to disable
|
|
30
|
+
*/
|
|
31
|
+
export function setIdleTimeout(ms: number | null | undefined): void {
|
|
32
|
+
idleTimeoutMs = ms ?? null;
|
|
33
|
+
|
|
34
|
+
if (idleTimeoutMs && idleTimeoutMs > 0) {
|
|
35
|
+
startIdleChecker();
|
|
36
|
+
} else {
|
|
37
|
+
stopIdleChecker();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
26
40
|
|
|
27
41
|
function startIdleChecker(): void {
|
|
28
42
|
if (idleCheckInterval) return;
|
|
29
43
|
idleCheckInterval = setInterval(() => {
|
|
44
|
+
if (!idleTimeoutMs) return;
|
|
30
45
|
const now = Date.now();
|
|
31
46
|
for (const [key, client] of Array.from(clients.entries())) {
|
|
32
|
-
if (now - client.lastActivity >
|
|
33
|
-
console.log(`[LSP] Shutting down idle client: ${key}`);
|
|
47
|
+
if (now - client.lastActivity > idleTimeoutMs) {
|
|
34
48
|
shutdownClient(key);
|
|
35
49
|
}
|
|
36
50
|
}
|
|
@@ -98,6 +112,12 @@ const CLIENT_CAPABILITIES = {
|
|
|
98
112
|
properties: ["edit"],
|
|
99
113
|
},
|
|
100
114
|
},
|
|
115
|
+
formatting: {
|
|
116
|
+
dynamicRegistration: false,
|
|
117
|
+
},
|
|
118
|
+
rangeFormatting: {
|
|
119
|
+
dynamicRegistration: false,
|
|
120
|
+
},
|
|
101
121
|
publishDiagnostics: {
|
|
102
122
|
relatedInformation: true,
|
|
103
123
|
versionSupport: false,
|
|
@@ -215,13 +235,17 @@ async function startMessageReader(client: LspClient): Promise<void> {
|
|
|
215
235
|
const { done, value } = await reader.read();
|
|
216
236
|
if (done) break;
|
|
217
237
|
|
|
218
|
-
|
|
238
|
+
// Atomically update buffer before processing
|
|
239
|
+
const currentBuffer = concatBuffers(client.messageBuffer, value);
|
|
240
|
+
client.messageBuffer = currentBuffer;
|
|
219
241
|
|
|
220
242
|
// Process all complete messages in buffer
|
|
221
|
-
|
|
243
|
+
// Use local variable to avoid race with concurrent buffer updates
|
|
244
|
+
let workingBuffer = currentBuffer;
|
|
245
|
+
let parsed = parseMessage(workingBuffer);
|
|
222
246
|
while (parsed) {
|
|
223
247
|
const { message, remaining } = parsed;
|
|
224
|
-
|
|
248
|
+
workingBuffer = remaining;
|
|
225
249
|
|
|
226
250
|
// Route message
|
|
227
251
|
if ("id" in message && message.id !== undefined) {
|
|
@@ -245,8 +269,11 @@ async function startMessageReader(client: LspClient): Promise<void> {
|
|
|
245
269
|
}
|
|
246
270
|
}
|
|
247
271
|
|
|
248
|
-
parsed = parseMessage(
|
|
272
|
+
parsed = parseMessage(workingBuffer);
|
|
249
273
|
}
|
|
274
|
+
|
|
275
|
+
// Atomically commit processed buffer
|
|
276
|
+
client.messageBuffer = workingBuffer;
|
|
250
277
|
}
|
|
251
278
|
} catch (err) {
|
|
252
279
|
// Connection closed or error - reject all pending requests
|
|
@@ -350,77 +377,88 @@ async function sendResponse(
|
|
|
350
377
|
export async function getOrCreateClient(config: ServerConfig, cwd: string): Promise<LspClient> {
|
|
351
378
|
const key = `${config.command}:${cwd}`;
|
|
352
379
|
|
|
353
|
-
if
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
380
|
+
// Check if client already exists
|
|
381
|
+
const existingClient = clients.get(key);
|
|
382
|
+
if (existingClient) {
|
|
383
|
+
existingClient.lastActivity = Date.now();
|
|
384
|
+
return existingClient;
|
|
357
385
|
}
|
|
358
386
|
|
|
359
|
-
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
stderr: "pipe",
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
const client: LspClient = {
|
|
368
|
-
name: key,
|
|
369
|
-
cwd,
|
|
370
|
-
process: proc,
|
|
371
|
-
config,
|
|
372
|
-
requestId: 0,
|
|
373
|
-
diagnostics: new Map(),
|
|
374
|
-
openFiles: new Map(),
|
|
375
|
-
pendingRequests: new Map(),
|
|
376
|
-
messageBuffer: new Uint8Array(0),
|
|
377
|
-
isReading: false,
|
|
378
|
-
lastActivity: Date.now(),
|
|
379
|
-
};
|
|
380
|
-
clients.set(key, client);
|
|
387
|
+
// Check if another coroutine is already creating this client
|
|
388
|
+
const existingLock = clientLocks.get(key);
|
|
389
|
+
if (existingLock) {
|
|
390
|
+
return existingLock;
|
|
391
|
+
}
|
|
381
392
|
|
|
382
|
-
//
|
|
383
|
-
|
|
393
|
+
// Create new client with lock
|
|
394
|
+
const clientPromise = (async () => {
|
|
395
|
+
const args = config.args ?? [];
|
|
396
|
+
const command = config.resolvedCommand ?? config.command;
|
|
397
|
+
const proc = Bun.spawn([command, ...args], {
|
|
398
|
+
cwd,
|
|
399
|
+
stdin: "pipe",
|
|
400
|
+
stdout: "pipe",
|
|
401
|
+
stderr: "pipe",
|
|
402
|
+
});
|
|
384
403
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
404
|
+
const client: LspClient = {
|
|
405
|
+
name: key,
|
|
406
|
+
cwd,
|
|
407
|
+
process: proc,
|
|
408
|
+
config,
|
|
409
|
+
requestId: 0,
|
|
410
|
+
diagnostics: new Map(),
|
|
411
|
+
openFiles: new Map(),
|
|
412
|
+
pendingRequests: new Map(),
|
|
413
|
+
messageBuffer: new Uint8Array(0),
|
|
414
|
+
isReading: false,
|
|
415
|
+
lastActivity: Date.now(),
|
|
416
|
+
};
|
|
417
|
+
clients.set(key, client);
|
|
418
|
+
|
|
419
|
+
// Register crash recovery - remove client on process exit
|
|
420
|
+
proc.exited.then(() => {
|
|
421
|
+
clients.delete(key);
|
|
422
|
+
clientLocks.delete(key);
|
|
423
|
+
});
|
|
393
424
|
|
|
394
|
-
|
|
395
|
-
|
|
425
|
+
// Start background message reader
|
|
426
|
+
startMessageReader(client);
|
|
427
|
+
|
|
428
|
+
try {
|
|
429
|
+
// Send initialize request
|
|
430
|
+
const initResult = (await sendRequest(client, "initialize", {
|
|
431
|
+
processId: process.pid,
|
|
432
|
+
rootUri: fileToUri(cwd),
|
|
433
|
+
rootPath: cwd,
|
|
434
|
+
capabilities: CLIENT_CAPABILITIES,
|
|
435
|
+
initializationOptions: config.initOptions ?? {},
|
|
436
|
+
workspaceFolders: [{ uri: fileToUri(cwd), name: cwd.split("/").pop() ?? "workspace" }],
|
|
437
|
+
})) as { capabilities?: unknown };
|
|
438
|
+
|
|
439
|
+
if (!initResult) {
|
|
440
|
+
throw new Error("Failed to initialize LSP: no response");
|
|
441
|
+
}
|
|
396
442
|
|
|
397
|
-
|
|
398
|
-
// Send initialize request
|
|
399
|
-
const initResult = (await sendRequest(client, "initialize", {
|
|
400
|
-
processId: process.pid,
|
|
401
|
-
rootUri: fileToUri(cwd),
|
|
402
|
-
rootPath: cwd,
|
|
403
|
-
capabilities: CLIENT_CAPABILITIES,
|
|
404
|
-
initializationOptions: config.initOptions ?? {},
|
|
405
|
-
workspaceFolders: [{ uri: fileToUri(cwd), name: cwd.split("/").pop() ?? "workspace" }],
|
|
406
|
-
})) as { capabilities?: unknown };
|
|
407
|
-
|
|
408
|
-
if (!initResult) {
|
|
409
|
-
throw new Error("Failed to initialize LSP: no response");
|
|
410
|
-
}
|
|
443
|
+
client.serverCapabilities = initResult.capabilities as LspClient["serverCapabilities"];
|
|
411
444
|
|
|
412
|
-
|
|
445
|
+
// Send initialized notification
|
|
446
|
+
await sendNotification(client, "initialized", {});
|
|
413
447
|
|
|
414
|
-
|
|
415
|
-
|
|
448
|
+
return client;
|
|
449
|
+
} catch (err) {
|
|
450
|
+
// Clean up on initialization failure
|
|
451
|
+
clients.delete(key);
|
|
452
|
+
clientLocks.delete(key);
|
|
453
|
+
proc.kill();
|
|
454
|
+
throw err;
|
|
455
|
+
} finally {
|
|
456
|
+
clientLocks.delete(key);
|
|
457
|
+
}
|
|
458
|
+
})();
|
|
416
459
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
// Clean up on initialization failure
|
|
420
|
-
clients.delete(key);
|
|
421
|
-
proc.kill();
|
|
422
|
-
throw err;
|
|
423
|
-
}
|
|
460
|
+
clientLocks.set(key, clientPromise);
|
|
461
|
+
return clientPromise;
|
|
424
462
|
}
|
|
425
463
|
|
|
426
464
|
/**
|
|
@@ -429,24 +467,49 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string): Prom
|
|
|
429
467
|
*/
|
|
430
468
|
export async function ensureFileOpen(client: LspClient, filePath: string): Promise<void> {
|
|
431
469
|
const uri = fileToUri(filePath);
|
|
470
|
+
const lockKey = `${client.name}:${uri}`;
|
|
471
|
+
|
|
472
|
+
// Check if file is already open
|
|
432
473
|
if (client.openFiles.has(uri)) {
|
|
433
474
|
return;
|
|
434
475
|
}
|
|
435
476
|
|
|
436
|
-
|
|
437
|
-
const
|
|
477
|
+
// Check if another operation is already opening this file
|
|
478
|
+
const existingLock = fileOperationLocks.get(lockKey);
|
|
479
|
+
if (existingLock) {
|
|
480
|
+
await existingLock;
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
438
483
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
},
|
|
446
|
-
});
|
|
484
|
+
// Lock and open file
|
|
485
|
+
const openPromise = (async () => {
|
|
486
|
+
// Double-check after acquiring lock
|
|
487
|
+
if (client.openFiles.has(uri)) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
447
490
|
|
|
448
|
-
|
|
449
|
-
|
|
491
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
492
|
+
const languageId = detectLanguageId(filePath);
|
|
493
|
+
|
|
494
|
+
await sendNotification(client, "textDocument/didOpen", {
|
|
495
|
+
textDocument: {
|
|
496
|
+
uri,
|
|
497
|
+
languageId,
|
|
498
|
+
version: 1,
|
|
499
|
+
text: content,
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
client.openFiles.set(uri, { version: 1, languageId });
|
|
504
|
+
client.lastActivity = Date.now();
|
|
505
|
+
})();
|
|
506
|
+
|
|
507
|
+
fileOperationLocks.set(lockKey, openPromise);
|
|
508
|
+
try {
|
|
509
|
+
await openPromise;
|
|
510
|
+
} finally {
|
|
511
|
+
fileOperationLocks.delete(lockKey);
|
|
512
|
+
}
|
|
450
513
|
}
|
|
451
514
|
|
|
452
515
|
/**
|
|
@@ -455,27 +518,45 @@ export async function ensureFileOpen(client: LspClient, filePath: string): Promi
|
|
|
455
518
|
*/
|
|
456
519
|
export async function refreshFile(client: LspClient, filePath: string): Promise<void> {
|
|
457
520
|
const uri = fileToUri(filePath);
|
|
458
|
-
const
|
|
521
|
+
const lockKey = `${client.name}:${uri}`;
|
|
459
522
|
|
|
460
|
-
if
|
|
461
|
-
|
|
462
|
-
|
|
523
|
+
// Check if another operation is in progress
|
|
524
|
+
const existingLock = fileOperationLocks.get(lockKey);
|
|
525
|
+
if (existingLock) {
|
|
526
|
+
await existingLock;
|
|
463
527
|
}
|
|
464
528
|
|
|
465
|
-
|
|
466
|
-
|
|
529
|
+
// Lock and refresh file
|
|
530
|
+
const refreshPromise = (async () => {
|
|
531
|
+
const info = client.openFiles.get(uri);
|
|
467
532
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
533
|
+
if (!info) {
|
|
534
|
+
await ensureFileOpen(client, filePath);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
472
537
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
text: content,
|
|
476
|
-
});
|
|
538
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
539
|
+
const version = ++info.version;
|
|
477
540
|
|
|
478
|
-
|
|
541
|
+
await sendNotification(client, "textDocument/didChange", {
|
|
542
|
+
textDocument: { uri, version },
|
|
543
|
+
contentChanges: [{ text: content }],
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
await sendNotification(client, "textDocument/didSave", {
|
|
547
|
+
textDocument: { uri },
|
|
548
|
+
text: content,
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
client.lastActivity = Date.now();
|
|
552
|
+
})();
|
|
553
|
+
|
|
554
|
+
fileOperationLocks.set(lockKey, refreshPromise);
|
|
555
|
+
try {
|
|
556
|
+
await refreshPromise;
|
|
557
|
+
} finally {
|
|
558
|
+
fileOperationLocks.delete(lockKey);
|
|
559
|
+
}
|
|
479
560
|
}
|
|
480
561
|
|
|
481
562
|
/**
|
|
@@ -497,10 +578,6 @@ export function shutdownClient(key: string): void {
|
|
|
497
578
|
// Kill process
|
|
498
579
|
client.process.kill();
|
|
499
580
|
clients.delete(key);
|
|
500
|
-
|
|
501
|
-
if (clients.size === 0) {
|
|
502
|
-
stopIdleChecker();
|
|
503
|
-
}
|
|
504
581
|
}
|
|
505
582
|
|
|
506
583
|
// =============================================================================
|
|
@@ -511,7 +588,9 @@ export function shutdownClient(key: string): void {
|
|
|
511
588
|
* Send an LSP request and wait for response.
|
|
512
589
|
*/
|
|
513
590
|
export async function sendRequest(client: LspClient, method: string, params: unknown): Promise<unknown> {
|
|
591
|
+
// Atomically increment and capture request ID
|
|
514
592
|
const id = ++client.requestId;
|
|
593
|
+
|
|
515
594
|
const request: LspJsonRpcRequest = {
|
|
516
595
|
jsonrpc: "2.0",
|
|
517
596
|
id,
|
|
@@ -570,8 +649,6 @@ export async function sendNotification(client: LspClient, method: string, params
|
|
|
570
649
|
* Shutdown all LSP clients.
|
|
571
650
|
*/
|
|
572
651
|
export function shutdownAll(): void {
|
|
573
|
-
stopIdleChecker();
|
|
574
|
-
|
|
575
652
|
for (const client of Array.from(clients.values())) {
|
|
576
653
|
// Reject all pending requests
|
|
577
654
|
for (const pending of Array.from(client.pendingRequests.values())) {
|
|
@@ -587,6 +664,25 @@ export function shutdownAll(): void {
|
|
|
587
664
|
clients.clear();
|
|
588
665
|
}
|
|
589
666
|
|
|
667
|
+
/** Status of an LSP server */
|
|
668
|
+
export interface LspServerStatus {
|
|
669
|
+
name: string;
|
|
670
|
+
status: "connecting" | "ready" | "error";
|
|
671
|
+
fileTypes: string[];
|
|
672
|
+
error?: string;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Get status of all active LSP clients.
|
|
677
|
+
*/
|
|
678
|
+
export function getActiveClients(): LspServerStatus[] {
|
|
679
|
+
return Array.from(clients.values()).map((client) => ({
|
|
680
|
+
name: client.config.command,
|
|
681
|
+
status: "ready" as const,
|
|
682
|
+
fileTypes: client.config.fileTypes,
|
|
683
|
+
}));
|
|
684
|
+
}
|
|
685
|
+
|
|
590
686
|
// =============================================================================
|
|
591
687
|
// Process Cleanup
|
|
592
688
|
// =============================================================================
|