merge PR #5746 via eval/pr-5746: fix(lsp): returned null for missing configuration

This commit is contained in:
can1357
2026-07-17 04:40:04 +02:00
3 changed files with 101 additions and 1 deletions
+3
View File
@@ -68,6 +68,9 @@
### Fixed
- Fixed the `write` approval gate misclassifying `xd://` device writes as `exec` when the mounted tool declared a function-valued (argument-dependent) `approval`: the gate discarded the function and never decoded the device JSON payload, so read/write device operations prompted in non-yolo modes their approval mode permits. It now parses valid object payloads and evaluates the mounted tool's normal approval decision, while malformed JSON, non-object payloads, and unknown devices still fall back to `exec` and prompt ([#5727](https://github.com/can1357/oh-my-pi/issues/5727)).
### Fixed
- Fixed custom LSP servers such as `roslyn-language-server` crashing after initialization when they request unconfigured `workspace/configuration` sections; missing settings now receive the spec-required `null` instead of `{}` ([#5745](https://github.com/can1357/oh-my-pi/issues/5745)).
## [17.0.1] - 2026-07-16
+1 -1
View File
@@ -428,7 +428,7 @@ async function handleConfigurationRequest(client: LspClient, message: LspJsonRpc
const items = params?.items ?? [];
const result = items.map(item => {
const section = item.section ?? "";
return client.config.settings?.[section] ?? {};
return client.config.settings?.[section] ?? null;
});
await sendResponse(client, message.id, result, "workspace/configuration");
}
@@ -429,6 +429,103 @@ describe("lsp regressions", () => {
}
});
it("answers missing workspace configuration sections with null in request order", async () => {
const tempDir = TempDir.createSync("@omp-lsp-configuration-null-");
try {
const server = installFakeLsp((message, srv) => {
if (message.method === "initialize") {
srv.send({ jsonrpc: "2.0", id: message.id, result: { capabilities: {} } });
} else if (message.method === "initialized") {
srv.send({
jsonrpc: "2.0",
id: 5745,
method: "workspace/configuration",
params: {
items: [
{ section: "razor.format.attribute_indent_style" },
{ section: "html.auto_closing_tags" },
{},
],
},
});
} else if (message.method === "shutdown") {
srv.send({ jsonrpc: "2.0", id: message.id, result: null });
} else if (message.method === "exit") {
srv.exit(0);
}
});
const config: ServerConfig = {
command: "fake-lsp",
fileTypes: ["cs"],
rootMarkers: [],
settings: { "html.auto_closing_tags": true },
};
await lspClient.getOrCreateClient(config, tempDir.path(), 1_000);
const response = await server.waitFor(message => message.id === 5745 && message.method === undefined);
expect(response.result).toEqual([null, true, null]);
} finally {
await lspClient.shutdownAll();
tempDir.removeSync();
}
});
it("keeps the session alive when configuration is pulled after didChangeConfiguration", async () => {
const tempDir = TempDir.createSync("@omp-lsp-configuration-session-");
let configurationAccepted = false;
try {
const server = installFakeLsp((message, srv) => {
if (message.method === "initialize") {
srv.send({ jsonrpc: "2.0", id: message.id, result: { capabilities: { hoverProvider: true } } });
} else if (message.method === "workspace/didChangeConfiguration") {
srv.send({
jsonrpc: "2.0",
id: "roslyn-config",
method: "workspace/configuration",
params: { items: [{ section: "razor.format.attribute_indent_style" }] },
});
} else if (message.id === "roslyn-config" && message.method === undefined) {
if (Array.isArray(message.result) && message.result[0] === null) {
configurationAccepted = true;
} else {
srv.exit(-6);
}
} else if (message.method === "textDocument/hover" && configurationAccepted) {
srv.send({ jsonrpc: "2.0", id: message.id, result: { contents: "string C.Target" } });
} else if (message.method === "shutdown") {
srv.send({ jsonrpc: "2.0", id: message.id, result: null });
} else if (message.method === "exit") {
srv.exit(0);
}
});
const config: ServerConfig = {
command: "fake-lsp",
fileTypes: ["cs"],
rootMarkers: [],
};
const client = await lspClient.getOrCreateClient(config, tempDir.path(), 1_000);
await server.waitFor(message => message.id === "roslyn-config" && message.method === undefined);
const result = await lspClient.sendRequest(
client,
"textDocument/hover",
{
textDocument: { uri: fileToUri(path.join(tempDir.path(), "Target.cs")) },
position: { line: 0, character: 6 },
},
undefined,
50,
);
expect(result).toEqual({ contents: "string C.Target" });
expect(configurationAccepted).toBe(true);
} finally {
await lspClient.shutdownAll();
tempDir.removeSync();
}
});
it("accepts dynamic capability registration before semantic requests", async () => {
const tempDir = TempDir.createSync("@omp-lsp-dynamic-registration-");
try {