refactor(daemon): require explicit serverId on all daemon ops

Move all server matching logic to the extension/CLI side. The daemon no
longer calls pickServer() — it receives an explicit serverId (or
serverIds[] for diagnostics) and uses it directly for cache lookup and
server spawning.

Key changes:
- request op requires serverId: string
- diagnostics op requires serverIds: string[] — daemon fans out in
  parallel via Promise.allSettled and returns grouped map
- formatDiagnostics() handles grouped results with per-server headers
  when multiple servers contribute (single-server omits header)
- CLI picks servers locally before calling daemon helpers
- New pickDiagnosticServers() in extension returns all available,
  non-disabled servers matching the file extension

This makes multi-server diagnostics (e.g., typescript-language-server +
oxlint) work naturally — the extension decides which servers to query,
the daemon just executes.
This commit is contained in:
2026-05-04 07:39:03 -04:00
parent d24e2e94f4
commit b9808a8b1f
5 changed files with 117 additions and 41 deletions

13
cli.ts
View File

@@ -2,7 +2,8 @@
import * as path from "node:path";
import { startClientForFile } from "./src/client.ts";
import { isLspCommand, listCommands, runCommand } from "./src/commands.ts";
import { pickServer } from "./src/root.ts";
import { pickServer, isServerAvailable } from "./src/root.ts";
import { servers } from "./server.ts";
import {
daemonDiagnostics,
daemonRequest,
@@ -104,14 +105,20 @@ async function runViaDaemon(
const filePath = path.resolve(fileArg);
let result: unknown;
if (cmdArg === "diagnostics") {
result = await daemonDiagnostics(filePath);
// Pick All Available Servers For Diagnostics
const ext = path.extname(filePath).replace(/^\./, "");
const serverIds = servers
.filter((s) => s.match.includes(ext) && isServerAvailable(s))
.map((s) => s.id);
result = await daemonDiagnostics(filePath, serverIds);
} else if (cmdArg in methodMap) {
const server = pickServer(filePath);
// References Default - Match commands.ts: include declaration unless
// caller explicitly overrode `context`.
if (cmdArg === "references" && !("context" in params)) {
params.context = { includeDeclaration: true };
}
result = await daemonRequest(filePath, methodMap[cmdArg], params);
result = await daemonRequest(filePath, server.id, methodMap[cmdArg], params);
} else {
process.stderr.write(
`Unknown command "${cmdArg}". Known: ${listCommands().join(", ")}\n`,