oh-my-opencode 5.0.0-beta.21 → 5.0.0-beta.23
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/dist/cli/index.js +290 -288
- package/dist/cli-node/index.js +290 -288
- package/dist/index.js +1675 -1657
- package/dist/skills/debugging/SKILL.md +2 -0
- package/dist/skills/debugging/references/methodology/00-setup.md +12 -0
- package/dist/skills/debugging/references/methodology/02-investigate.md +21 -0
- package/dist/skills/debugging/references/runtimes/go.md +15 -1
- package/dist/skills/debugging/references/runtimes/native-binary.md +38 -0
- package/dist/skills/debugging/references/runtimes/node.md +25 -0
- package/dist/skills/debugging/references/runtimes/python.md +13 -0
- package/dist/skills/debugging/references/runtimes/rust.md +41 -0
- package/dist/skills/debugging/references/scripts/dap.mjs +267 -0
- package/dist/skills/debugging/references/scripts/dap.test.ts +86 -0
- package/dist/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
- package/dist/skills/debugging/references/tools/dap.md +103 -0
- package/dist/skills/debugging/references/tools/frida.md +193 -0
- package/dist/tools/delegate-task/types.d.ts +3 -0
- package/dist/tui.js +131 -147
- package/package.json +18 -15
- package/packages/lsp-core/src/index.ts +1 -0
- package/packages/lsp-core/src/lsp/client-wrapper.ts +25 -5
- package/packages/lsp-core/src/lsp/client.ts +26 -0
- package/packages/lsp-core/src/lsp/connection.ts +16 -0
- package/packages/lsp-core/src/lsp/constants.ts +1 -0
- package/packages/lsp-core/src/lsp/format-document.test.ts +143 -0
- package/packages/lsp-core/src/lsp/format-document.ts +90 -0
- package/packages/lsp-core/src/lsp/manager-max-clients.test.ts +151 -0
- package/packages/lsp-core/src/lsp/manager.ts +34 -1
- package/packages/lsp-core/src/lsp/server-definitions.ts +29 -0
- package/packages/lsp-core/src/lsp/server-installation.test.ts +376 -0
- package/packages/lsp-core/src/lsp/server-installation.ts +200 -27
- package/packages/lsp-core/src/lsp/server-resolution-local-binary.test.ts +170 -0
- package/packages/lsp-core/src/lsp/server-resolution.ts +18 -5
- package/packages/lsp-core/src/lsp/transport.ts +9 -0
- package/packages/lsp-core/src/lsp/types.ts +8 -0
- package/packages/lsp-core/src/tool-surface.test.ts +13 -1
- package/packages/lsp-core/src/tools/definitions.ts +14 -0
- package/packages/lsp-core/src/tools/format.test.ts +56 -0
- package/packages/lsp-core/src/tools/format.ts +56 -0
- package/packages/lsp-core/src/tools/index.ts +1 -0
- package/packages/lsp-core/src/tools/types.ts +10 -0
- package/packages/lsp-daemon/dist/cli.js +432 -144
- package/packages/lsp-daemon/dist/client.d.ts +10 -0
- package/packages/lsp-daemon/dist/client.js +401 -107
- package/packages/lsp-daemon/dist/daemon-client.d.ts +1 -0
- package/packages/lsp-daemon/dist/daemon-client.js +3 -0
- package/packages/lsp-daemon/dist/index.js +395 -107
- package/packages/lsp-daemon/package.json +1 -1
- package/packages/lsp-tools-mcp/dist/cli.js +384 -99
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +53 -0
- package/packages/lsp-tools-mcp/dist/mcp.js +384 -99
- package/packages/lsp-tools-mcp/dist/tools.js +385 -99
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +2 -2
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +69 -35
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +21 -2
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +404 -121
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/dist/cli.js +3 -3
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +3 -3
- package/packages/omo-codex/plugin/components/rules/test/post-compact-budget.test.ts +4 -4
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +30 -15
- package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +30 -15
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +5 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +11 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +22 -10
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +5 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +13 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli.ts +3 -5
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-entrypoint.test.ts +26 -2
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/model-catalog.json +3 -3
- package/packages/omo-codex/plugin/package-lock.json +13 -13
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/catalog.mjs +3 -3
- package/packages/omo-codex/plugin/skills/debugging/SKILL.md +2 -0
- package/packages/omo-codex/plugin/skills/debugging/references/methodology/00-setup.md +12 -0
- package/packages/omo-codex/plugin/skills/debugging/references/methodology/02-investigate.md +21 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/go.md +15 -1
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/native-binary.md +38 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/node.md +25 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/python.md +13 -0
- package/packages/omo-codex/plugin/skills/debugging/references/runtimes/rust.md +41 -0
- package/packages/omo-codex/plugin/skills/debugging/references/scripts/dap.mjs +267 -0
- package/packages/omo-codex/plugin/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
- package/packages/omo-codex/plugin/skills/debugging/references/tools/dap.md +103 -0
- package/packages/omo-codex/plugin/skills/debugging/references/tools/frida.md +193 -0
- package/packages/omo-codex/plugin/test/aggregate-model-catalog.test.mjs +1 -1
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +2 -2
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +8 -8
- package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +2 -2
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +38 -23
- package/packages/shared-skills/skills/debugging/SKILL.md +2 -0
- package/packages/shared-skills/skills/debugging/references/methodology/00-setup.md +12 -0
- package/packages/shared-skills/skills/debugging/references/methodology/02-investigate.md +21 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/go.md +15 -1
- package/packages/shared-skills/skills/debugging/references/runtimes/native-binary.md +38 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/node.md +25 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/python.md +13 -0
- package/packages/shared-skills/skills/debugging/references/runtimes/rust.md +41 -0
- package/packages/shared-skills/skills/debugging/references/scripts/dap.mjs +267 -0
- package/packages/shared-skills/skills/debugging/references/scripts/dap.test.ts +86 -0
- package/packages/shared-skills/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
- package/packages/shared-skills/skills/debugging/references/tools/dap.md +103 -0
- package/packages/shared-skills/skills/debugging/references/tools/frida.md +193 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
5
|
+
|
|
6
|
+
import { runWithRequestContext } from "../request-context.js";
|
|
7
|
+
import { formatServerLookupError } from "./client-wrapper.js";
|
|
8
|
+
import { __resetServerBinaryResolutionCacheForTests } from "./server-installation.js";
|
|
9
|
+
import { findServerForExtension } from "./server-resolution.js";
|
|
10
|
+
|
|
11
|
+
const tempDirectories: string[] = [];
|
|
12
|
+
let previousPath: string | undefined;
|
|
13
|
+
|
|
14
|
+
function makeTempRoot(prefix: string): string {
|
|
15
|
+
const root = mkdtempSync(join(tmpdir(), prefix));
|
|
16
|
+
tempDirectories.push(root);
|
|
17
|
+
return root;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writeExecutable(path: string): string {
|
|
21
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
22
|
+
writeFileSync(path, "#!/bin/sh\nexit 0\n");
|
|
23
|
+
chmodSync(path, 0o755);
|
|
24
|
+
return path;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function contextFor(cwd: string) {
|
|
28
|
+
return {
|
|
29
|
+
cwd,
|
|
30
|
+
projectConfigPaths: [join(cwd, ".codex", "lsp-client.json")],
|
|
31
|
+
userConfigPath: join(cwd, "home-config", "lsp-client.json"),
|
|
32
|
+
installDecisionsPath: join(cwd, "home-config", "lsp-install-decisions.json"),
|
|
33
|
+
capabilities: { installDecisionTool: true },
|
|
34
|
+
} as const;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
previousPath = process.env["PATH"];
|
|
39
|
+
__resetServerBinaryResolutionCacheForTests();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
if (previousPath === undefined) delete process.env["PATH"];
|
|
44
|
+
else process.env["PATH"] = previousPath;
|
|
45
|
+
for (const directory of tempDirectories.splice(0)) {
|
|
46
|
+
rmSync(directory, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
__resetServerBinaryResolutionCacheForTests();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("findServerForExtension local binary substitution", () => {
|
|
52
|
+
test("#given a repo-local biome #when looking up a typescript file #then the command spawns the absolute local path", () => {
|
|
53
|
+
// given
|
|
54
|
+
const root = makeTempRoot("lsp-resolution-local-");
|
|
55
|
+
writeFileSync(join(root, "package.json"), "{}\n");
|
|
56
|
+
const binaryPath = writeExecutable(join(root, "node_modules", ".bin", "biome"));
|
|
57
|
+
process.env["PATH"] = join(root, "definitely-empty");
|
|
58
|
+
|
|
59
|
+
// when
|
|
60
|
+
const result = runWithRequestContext(contextFor(root), () => findServerForExtension(".ts"));
|
|
61
|
+
|
|
62
|
+
// then
|
|
63
|
+
expect(result.status).toBe("found");
|
|
64
|
+
if (result.status !== "found") throw new Error("expected a found server");
|
|
65
|
+
expect(result.server.command[0]).toBe(binaryPath);
|
|
66
|
+
expect(result.server.command.slice(1)).toEqual(["lsp-proxy", "--stdio"]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("#given a PATH-only server #when looking up #then the resolved PATH entry is substituted", () => {
|
|
70
|
+
// given
|
|
71
|
+
const root = makeTempRoot("lsp-resolution-path-root-");
|
|
72
|
+
writeFileSync(join(root, "package.json"), "{}\n");
|
|
73
|
+
const pathDir = makeTempRoot("lsp-resolution-path-");
|
|
74
|
+
const pathBinary = writeExecutable(join(pathDir, "biome"));
|
|
75
|
+
process.env["PATH"] = pathDir;
|
|
76
|
+
|
|
77
|
+
// when
|
|
78
|
+
const result = runWithRequestContext(contextFor(root), () => findServerForExtension(".ts"));
|
|
79
|
+
|
|
80
|
+
// then
|
|
81
|
+
expect(result.status).toBe("found");
|
|
82
|
+
if (result.status !== "found") throw new Error("expected a found server");
|
|
83
|
+
expect(result.server.command[0]).toBe(pathBinary);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("#given no server anywhere #when looking up #then the not_installed result is preserved", () => {
|
|
87
|
+
// given
|
|
88
|
+
const root = makeTempRoot("lsp-resolution-missing-");
|
|
89
|
+
process.env["PATH"] = join(root, "definitely-empty");
|
|
90
|
+
|
|
91
|
+
// when
|
|
92
|
+
const result = runWithRequestContext(contextFor(root), () => findServerForExtension(".ts"));
|
|
93
|
+
|
|
94
|
+
// then
|
|
95
|
+
expect(result.status).toBe("not_installed");
|
|
96
|
+
if (result.status !== "not_installed") throw new Error("expected not_installed");
|
|
97
|
+
expect(result.installHint.length).toBeGreaterThan(0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("#given an unknown extension #when looking up #then the not_configured result is preserved", () => {
|
|
101
|
+
// given
|
|
102
|
+
const root = makeTempRoot("lsp-resolution-unknown-");
|
|
103
|
+
process.env["PATH"] = join(root, "definitely-empty");
|
|
104
|
+
|
|
105
|
+
// when
|
|
106
|
+
const result = runWithRequestContext(contextFor(root), () => findServerForExtension(".unknown-ext"));
|
|
107
|
+
|
|
108
|
+
// then
|
|
109
|
+
expect(result.status).toBe("not_configured");
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("not-installed message repo-local guidance", () => {
|
|
114
|
+
test("#given a missing biome #when formatting the message #then the repo-local install is suggested before the global hint", () => {
|
|
115
|
+
// given: .css is served by biome alone, so the lookup cannot fall through to typescript
|
|
116
|
+
const root = makeTempRoot("lsp-message-biome-");
|
|
117
|
+
process.env["PATH"] = join(root, "definitely-empty");
|
|
118
|
+
const result = runWithRequestContext(contextFor(root), () => findServerForExtension(".css"));
|
|
119
|
+
if (result.status !== "not_installed") throw new Error("expected not_installed");
|
|
120
|
+
expect(result.server.id).toBe("biome");
|
|
121
|
+
|
|
122
|
+
// when
|
|
123
|
+
const message = runWithRequestContext(contextFor(root), () => formatServerLookupError(result));
|
|
124
|
+
|
|
125
|
+
// then
|
|
126
|
+
expect(message).toContain("bun add -d @biomejs/biome");
|
|
127
|
+
expect(message).toContain("npm install -g @biomejs/biome");
|
|
128
|
+
expect(message.indexOf("bun add -d @biomejs/biome")).toBeLessThan(message.indexOf("npm install -g @biomejs/biome"));
|
|
129
|
+
expect(message).toContain("ACTION REQUIRED");
|
|
130
|
+
expect(message).toContain("NOT INSTALLED");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("#given a server with no repo-local route #when formatting the message #then only the global hint is shown", () => {
|
|
134
|
+
// given
|
|
135
|
+
const root = makeTempRoot("lsp-message-global-");
|
|
136
|
+
process.env["PATH"] = join(root, "definitely-empty");
|
|
137
|
+
const result = runWithRequestContext(contextFor(root), () => findServerForExtension(".rs"));
|
|
138
|
+
if (result.status !== "not_installed") throw new Error("expected not_installed");
|
|
139
|
+
|
|
140
|
+
// when
|
|
141
|
+
const message = runWithRequestContext(contextFor(root), () => formatServerLookupError(result));
|
|
142
|
+
|
|
143
|
+
// then
|
|
144
|
+
expect(message).toContain("To install, run:");
|
|
145
|
+
expect(message).not.toContain("THIS repository");
|
|
146
|
+
expect(message).toContain("ACTION REQUIRED");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("#given a pre-authorized install #when formatting the message #then both install routes stay visible", () => {
|
|
150
|
+
// given
|
|
151
|
+
const root = makeTempRoot("lsp-message-allowed-");
|
|
152
|
+
process.env["PATH"] = join(root, "definitely-empty");
|
|
153
|
+
const context = contextFor(root);
|
|
154
|
+
mkdirSync(join(root, "home-config"), { recursive: true });
|
|
155
|
+
writeFileSync(
|
|
156
|
+
context.installDecisionsPath,
|
|
157
|
+
`${JSON.stringify({ biome: { decision: "allowed", decidedAt: new Date().toISOString() } })}\n`,
|
|
158
|
+
);
|
|
159
|
+
const result = runWithRequestContext(context, () => findServerForExtension(".css"));
|
|
160
|
+
if (result.status !== "not_installed") throw new Error("expected not_installed");
|
|
161
|
+
|
|
162
|
+
// when
|
|
163
|
+
const message = runWithRequestContext(context, () => formatServerLookupError(result));
|
|
164
|
+
|
|
165
|
+
// then
|
|
166
|
+
expect(message).toContain("pre-authorized");
|
|
167
|
+
expect(message).toContain("bun add -d @biomejs/biome");
|
|
168
|
+
expect(message).toContain("npm install -g @biomejs/biome");
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -1,16 +1,28 @@
|
|
|
1
|
+
import { contextCwd } from "../request-context.js";
|
|
1
2
|
import { getDisabledServerIds, getMergedServers } from "./config-loader.js";
|
|
2
3
|
import { BUILTIN_SERVERS, LSP_INSTALL_HINTS } from "./server-definitions.js";
|
|
3
|
-
import { isServerInstalled } from "./server-installation.js";
|
|
4
|
+
import { isServerInstalled, resolveServerBinary } from "./server-installation.js";
|
|
4
5
|
import type { ServerLookupResult } from "./types.js";
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Replaces the command name with the absolute binary path so the client spawns the exact
|
|
9
|
+
* binary that was resolved -- in particular a repo-local one that is absent from PATH.
|
|
10
|
+
*/
|
|
11
|
+
function withResolvedCommand(command: string[], binaryPath: string): string[] {
|
|
12
|
+
return [binaryPath, ...command.slice(1)];
|
|
13
|
+
}
|
|
14
|
+
|
|
6
15
|
export function findServerForExtension(ext: string): ServerLookupResult {
|
|
7
16
|
const servers = getMergedServers();
|
|
17
|
+
const workingDirectory = contextCwd();
|
|
8
18
|
|
|
9
19
|
for (const server of servers) {
|
|
10
|
-
if (server.extensions.includes(ext)
|
|
20
|
+
if (!server.extensions.includes(ext)) continue;
|
|
21
|
+
const binaryPath = resolveServerBinary(server.command, workingDirectory);
|
|
22
|
+
if (binaryPath !== null) {
|
|
11
23
|
const resolvedServer = {
|
|
12
24
|
id: server.id,
|
|
13
|
-
command: server.command,
|
|
25
|
+
command: withResolvedCommand(server.command, binaryPath),
|
|
14
26
|
extensions: server.extensions,
|
|
15
27
|
priority: server.priority,
|
|
16
28
|
};
|
|
@@ -70,6 +82,7 @@ export interface ServerStatus {
|
|
|
70
82
|
export function getAllServers(): ServerStatus[] {
|
|
71
83
|
const servers = getMergedServers();
|
|
72
84
|
const disabled = getDisabledServerIds();
|
|
85
|
+
const workingDirectory = contextCwd();
|
|
73
86
|
|
|
74
87
|
const result: ServerStatus[] = [];
|
|
75
88
|
const seen = new Set<string>();
|
|
@@ -78,7 +91,7 @@ export function getAllServers(): ServerStatus[] {
|
|
|
78
91
|
if (seen.has(server.id)) continue;
|
|
79
92
|
result.push({
|
|
80
93
|
id: server.id,
|
|
81
|
-
installed: isServerInstalled(server.command),
|
|
94
|
+
installed: isServerInstalled(server.command, workingDirectory),
|
|
82
95
|
extensions: server.extensions,
|
|
83
96
|
disabled: false,
|
|
84
97
|
source: server.source,
|
|
@@ -92,7 +105,7 @@ export function getAllServers(): ServerStatus[] {
|
|
|
92
105
|
const builtin = BUILTIN_SERVERS[id];
|
|
93
106
|
result.push({
|
|
94
107
|
id,
|
|
95
|
-
installed: builtin ? isServerInstalled(builtin.command) : false,
|
|
108
|
+
installed: builtin ? isServerInstalled(builtin.command, workingDirectory) : false,
|
|
96
109
|
extensions: builtin?.extensions ?? [],
|
|
97
110
|
disabled: true,
|
|
98
111
|
source: "disabled",
|
|
@@ -37,6 +37,7 @@ export class LspClientTransport {
|
|
|
37
37
|
protected readonly initializeTimeoutMs: number;
|
|
38
38
|
private workspaceApplyEditHandler: WorkspaceApplyEditHandler | null = null;
|
|
39
39
|
private diagnosticPullSupported = false;
|
|
40
|
+
private documentFormattingSupported = false;
|
|
40
41
|
|
|
41
42
|
constructor(
|
|
42
43
|
protected readonly root: string,
|
|
@@ -71,6 +72,14 @@ export class LspClientTransport {
|
|
|
71
72
|
return this.diagnosticPullSupported;
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
protected setDocumentFormattingSupported(supported: boolean): void {
|
|
76
|
+
this.documentFormattingSupported = supported;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
isDocumentFormattingSupported(): boolean {
|
|
80
|
+
return this.documentFormattingSupported;
|
|
81
|
+
}
|
|
82
|
+
|
|
74
83
|
protected handlePublishDiagnostics(params: {
|
|
75
84
|
readonly uri: string;
|
|
76
85
|
readonly diagnostics: readonly Diagnostic[];
|
|
@@ -72,6 +72,14 @@ export interface Diagnostic {
|
|
|
72
72
|
message: string;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
export interface FormattingOptions {
|
|
76
|
+
tabSize: number;
|
|
77
|
+
insertSpaces: boolean;
|
|
78
|
+
trimTrailingWhitespace?: boolean;
|
|
79
|
+
insertFinalNewline?: boolean;
|
|
80
|
+
trimFinalNewlines?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
export interface TextDocumentIdentifier {
|
|
76
84
|
uri: string;
|
|
77
85
|
}
|
|
@@ -104,6 +104,18 @@ const expectedToolSurface = [
|
|
|
104
104
|
required: ["filePath", "line", "character", "newName"],
|
|
105
105
|
},
|
|
106
106
|
},
|
|
107
|
+
{
|
|
108
|
+
name: "format",
|
|
109
|
+
title: "LSP Format",
|
|
110
|
+
description: "Format a source file with its language server and write the returned edits to disk.",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: {
|
|
114
|
+
filePath: { type: "string", description: "Source file path to format." },
|
|
115
|
+
},
|
|
116
|
+
required: ["filePath"],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
107
119
|
{
|
|
108
120
|
name: "install_decision",
|
|
109
121
|
title: "LSP Install Decision",
|
|
@@ -128,7 +140,7 @@ const expectedToolSurface = [
|
|
|
128
140
|
];
|
|
129
141
|
|
|
130
142
|
describe("LSP core tool surface", () => {
|
|
131
|
-
test("#given tool descriptors #when listed #then the public
|
|
143
|
+
test("#given tool descriptors #when listed #then the public nine-tool schemas are pinned", () => {
|
|
132
144
|
// given / when
|
|
133
145
|
const surface = LSP_MCP_TOOLS.map((tool) => ({
|
|
134
146
|
name: tool.name,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { executeLspDiagnostics } from "./diagnostics.js";
|
|
2
|
+
import { executeLspFormat } from "./format.js";
|
|
2
3
|
import { executeLspInstallDecision } from "./install-decision.js";
|
|
3
4
|
import { executeLspFindReferences, executeLspGotoDefinition } from "./navigation.js";
|
|
4
5
|
import { executeLspPrepareRename, executeLspRename } from "./rename.js";
|
|
@@ -116,6 +117,19 @@ export const LSP_MCP_TOOLS: LspMcpTool[] = [
|
|
|
116
117
|
),
|
|
117
118
|
execute: executeLspRename,
|
|
118
119
|
},
|
|
120
|
+
{
|
|
121
|
+
name: "format",
|
|
122
|
+
aliases: ["lsp_format"],
|
|
123
|
+
title: "LSP Format",
|
|
124
|
+
description: "Format a source file with its language server and write the returned edits to disk.",
|
|
125
|
+
inputSchema: objectSchema(
|
|
126
|
+
{
|
|
127
|
+
filePath: { type: "string", description: "Source file path to format." },
|
|
128
|
+
},
|
|
129
|
+
["filePath"],
|
|
130
|
+
),
|
|
131
|
+
execute: executeLspFormat,
|
|
132
|
+
},
|
|
119
133
|
{
|
|
120
134
|
name: "install_decision",
|
|
121
135
|
aliases: ["lsp_install_decision"],
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { afterEach, describe, expect, it } from "bun:test";
|
|
6
|
+
|
|
7
|
+
import { parseLspRequestContext, runWithRequestContext } from "../request-context.js";
|
|
8
|
+
import { executeLspFormat } from "./format.js";
|
|
9
|
+
import type { LspFormatDetails } from "./types.js";
|
|
10
|
+
|
|
11
|
+
const workspaces: string[] = [];
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
for (const workspace of workspaces.splice(0)) rmSync(workspace, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function workspaceWith(fileName: string, content: string): { readonly root: string; readonly filePath: string } {
|
|
18
|
+
const root = mkdtempSync(join(tmpdir(), "lsp-format-tool-"));
|
|
19
|
+
workspaces.push(root);
|
|
20
|
+
const filePath = join(root, fileName);
|
|
21
|
+
writeFileSync(filePath, content, "utf-8");
|
|
22
|
+
return { root, filePath };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function contextFor(root: string) {
|
|
26
|
+
return parseLspRequestContext({
|
|
27
|
+
cwd: root,
|
|
28
|
+
projectConfigPaths: [join(root, "lsp.json")],
|
|
29
|
+
userConfigPath: join(root, "user-lsp.json"),
|
|
30
|
+
installDecisionsPath: join(root, "install-decisions.json"),
|
|
31
|
+
capabilities: { installDecisionTool: false },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe("executeLspFormat", () => {
|
|
36
|
+
it("#given an extension with no configured server #when format runs #then the missing dependency result is returned and the file is untouched", async () => {
|
|
37
|
+
// given
|
|
38
|
+
const original = "value\n";
|
|
39
|
+
const { root, filePath } = workspaceWith("module.wat", original);
|
|
40
|
+
|
|
41
|
+
// when
|
|
42
|
+
const result = await runWithRequestContext(contextFor(root), () => executeLspFormat({ filePath }));
|
|
43
|
+
|
|
44
|
+
// then
|
|
45
|
+
const details = result.details as LspFormatDetails;
|
|
46
|
+
expect(details.errorKind).toBe("missing_dependency");
|
|
47
|
+
expect(details.status).toBe("unavailable");
|
|
48
|
+
expect(result.content[0]?.text).toContain("No LSP server configured for extension: .wat");
|
|
49
|
+
expect(readFileSync(filePath)).toEqual(Buffer.from(original));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("#given a missing filePath argument #when format runs #then the parameter contract is enforced", async () => {
|
|
53
|
+
// given / when / then
|
|
54
|
+
await expect(executeLspFormat({})).rejects.toThrow(/filePath/);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { withLspClient } from "../lsp/client-wrapper.js";
|
|
2
|
+
import { type FormatDocumentResult, formatDocumentWithClient } from "../lsp/format-document.js";
|
|
3
|
+
import { missingDependencyResult } from "../missing-dependency-result.js";
|
|
4
|
+
import { clientOptions, requireString } from "./parameters.js";
|
|
5
|
+
import { text } from "./result.js";
|
|
6
|
+
import type { LspFormatDetails, ToolExecutionResult } from "./types.js";
|
|
7
|
+
|
|
8
|
+
export async function executeLspFormat(
|
|
9
|
+
params: Record<string, unknown>,
|
|
10
|
+
signal?: AbortSignal,
|
|
11
|
+
): Promise<ToolExecutionResult> {
|
|
12
|
+
const filePath = requireString(params, "filePath");
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const result = await withLspClient(
|
|
16
|
+
filePath,
|
|
17
|
+
async (client, _workspaceRoot, resolvedFilePath) =>
|
|
18
|
+
formatDocumentWithClient(client, resolvedFilePath, signal === undefined ? {} : { signal }),
|
|
19
|
+
"format",
|
|
20
|
+
clientOptions(signal),
|
|
21
|
+
);
|
|
22
|
+
return text(describeResult(filePath, result), formatDetails(filePath, result));
|
|
23
|
+
} catch (error) {
|
|
24
|
+
const missingDependency = missingDependencyResult(error, {
|
|
25
|
+
filePath,
|
|
26
|
+
status: "unavailable",
|
|
27
|
+
reason: "server_unavailable",
|
|
28
|
+
linesAdded: 0,
|
|
29
|
+
linesRemoved: 0,
|
|
30
|
+
} satisfies Omit<LspFormatDetails, "error" | "errorKind">);
|
|
31
|
+
if (missingDependency) return missingDependency;
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function formatDetails(filePath: string, result: FormatDocumentResult): LspFormatDetails {
|
|
37
|
+
if (result.status === "unavailable") {
|
|
38
|
+
return { filePath, status: "unavailable", reason: result.reason, linesAdded: 0, linesRemoved: 0 };
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
filePath,
|
|
42
|
+
status: result.status,
|
|
43
|
+
linesAdded: result.linesAdded,
|
|
44
|
+
linesRemoved: result.linesRemoved,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function describeResult(filePath: string, result: FormatDocumentResult): string {
|
|
49
|
+
if (result.status === "unavailable") {
|
|
50
|
+
return `Formatting unavailable for ${filePath}: the language server does not advertise documentFormattingProvider.`;
|
|
51
|
+
}
|
|
52
|
+
if (result.status === "unchanged") {
|
|
53
|
+
return `Already formatted: ${filePath}`;
|
|
54
|
+
}
|
|
55
|
+
return `Formatted ${filePath} (+${result.linesAdded}/-${result.linesRemoved} lines)`;
|
|
56
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { executeLspDiagnostics } from "./diagnostics.js";
|
|
2
2
|
export { LSP_MCP_TOOLS } from "./definitions.js";
|
|
3
|
+
export { executeLspFormat } from "./format.js";
|
|
3
4
|
export { executeLspInstallDecision } from "./install-decision.js";
|
|
4
5
|
export { executeLspFindReferences, executeLspGotoDefinition } from "./navigation.js";
|
|
5
6
|
export { isRecord } from "./parameters.js";
|
|
@@ -93,6 +93,16 @@ export interface LspPrepareRenameDetails {
|
|
|
93
93
|
errorKind?: "missing_dependency";
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
export interface LspFormatDetails {
|
|
97
|
+
filePath: string;
|
|
98
|
+
status: "formatted" | "unchanged" | "unavailable";
|
|
99
|
+
reason?: "capability_not_advertised" | "server_unavailable";
|
|
100
|
+
linesAdded: number;
|
|
101
|
+
linesRemoved: number;
|
|
102
|
+
error?: string;
|
|
103
|
+
errorKind?: "missing_dependency";
|
|
104
|
+
}
|
|
105
|
+
|
|
96
106
|
export interface LspRenameDetails {
|
|
97
107
|
filePath: string;
|
|
98
108
|
line: number;
|