oh-my-opencode 4.17.1 → 4.18.1
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/.agents/skills/codex-qa/SKILL.md +2 -0
- package/.agents/skills/codex-qa/scripts/lsp-e2e.sh +3654 -0
- package/.agents/skills/opencode-qa/scripts/lsp-e2e.sh +3071 -0
- package/.agents/skills/work-with-pr/SKILL.md +16 -37
- package/.agents/skills/work-with-pr-workspace/evals/evals.json +3 -3
- package/.opencode/skills/work-with-pr/SKILL.md +16 -37
- package/.opencode/skills/work-with-pr-workspace/evals/evals.json +3 -3
- package/dist/cli/get-local-version/types.d.ts +1 -1
- package/dist/cli/index.js +498 -165
- package/dist/cli-node/index.js +498 -165
- package/dist/index.js +425 -392
- package/dist/skills/frontend/SKILL.md +1 -1
- package/dist/skills/frontend/references/design/README.md +9 -0
- package/dist/skills/frontend/references/design/design-system-architecture.md +4 -2
- package/dist/skills/frontend/references/design/layout-skill.md +107 -0
- package/dist/skills/programming/SKILL.md +12 -2
- package/package.json +17 -16
- package/packages/lsp-core/package.json +4 -0
- package/packages/lsp-core/src/index.ts +1 -0
- package/packages/lsp-core/src/lsp/cleanup-errors.test.ts +18 -0
- package/packages/lsp-core/src/lsp/cleanup-errors.ts +12 -3
- package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +261 -0
- package/packages/lsp-core/src/lsp/client-wrapper.test.ts +63 -0
- package/packages/lsp-core/src/lsp/client-wrapper.ts +35 -5
- package/packages/lsp-core/src/lsp/client.ts +262 -80
- package/packages/lsp-core/src/lsp/config-loader.ts +5 -17
- package/packages/lsp-core/src/lsp/connection.ts +12 -6
- package/packages/lsp-core/src/lsp/directory-diagnostics.test.ts +104 -0
- package/packages/lsp-core/src/lsp/directory-diagnostics.ts +60 -27
- package/packages/lsp-core/src/lsp/errors.ts +11 -0
- package/packages/lsp-core/src/lsp/fixtures/diagnostics-freshness-contract-probe.ts +283 -0
- package/packages/lsp-core/src/lsp/fixtures/workspace-edit-contract-probe.ts +196 -0
- package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +215 -0
- package/packages/lsp-core/src/lsp/formatters.ts +3 -0
- package/packages/lsp-core/src/lsp/json-rpc-connection-cancellation.test.ts +97 -0
- package/packages/lsp-core/src/lsp/json-rpc-connection.ts +73 -5
- package/packages/lsp-core/src/lsp/server-install-state.ts +3 -6
- package/packages/lsp-core/src/lsp/transport-protocol.ts +52 -0
- package/packages/lsp-core/src/lsp/transport.ts +96 -70
- package/packages/lsp-core/src/lsp/workspace-apply-edit-failure.ts +19 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit-lease.integration.test.ts +214 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit-sync.integration.test.ts +113 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit-test-support.ts +163 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit.integration.test.ts +163 -0
- package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +67 -0
- package/packages/lsp-core/src/lsp/workspace-document-state.ts +368 -0
- package/packages/lsp-core/src/lsp/workspace-edit-adversarial.test.ts +113 -0
- package/packages/lsp-core/src/lsp/workspace-edit-commit.test.ts +140 -0
- package/packages/lsp-core/src/lsp/workspace-edit-commit.ts +220 -0
- package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.test.ts +56 -0
- package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.ts +30 -0
- package/packages/lsp-core/src/lsp/workspace-edit-fingerprint.ts +44 -0
- package/packages/lsp-core/src/lsp/workspace-edit-options.test.ts +147 -0
- package/packages/lsp-core/src/lsp/workspace-edit-parse-helpers.ts +59 -0
- package/packages/lsp-core/src/lsp/workspace-edit-parser.ts +130 -0
- package/packages/lsp-core/src/lsp/workspace-edit-path.ts +98 -0
- package/packages/lsp-core/src/lsp/workspace-edit-plan-types.ts +60 -0
- package/packages/lsp-core/src/lsp/workspace-edit-plan.ts +73 -0
- package/packages/lsp-core/src/lsp/workspace-edit-prevalidation.test.ts +174 -0
- package/packages/lsp-core/src/lsp/workspace-edit-resource-parser.ts +89 -0
- package/packages/lsp-core/src/lsp/workspace-edit-simulation.ts +183 -0
- package/packages/lsp-core/src/lsp/workspace-edit-snapshot.ts +53 -0
- package/packages/lsp-core/src/lsp/workspace-edit-text.ts +125 -0
- package/packages/lsp-core/src/lsp/workspace-edit-types.ts +121 -0
- package/packages/lsp-core/src/lsp/workspace-edit.characterization.test.ts +95 -0
- package/packages/lsp-core/src/lsp/workspace-edit.ts +49 -200
- package/packages/lsp-core/src/lsp/workspace-mutation-controller.ts +182 -0
- package/packages/lsp-core/src/mcp.ts +18 -7
- package/packages/lsp-core/src/missing-dependency-result.test.ts +105 -0
- package/packages/lsp-core/src/missing-dependency-result.ts +57 -0
- package/packages/lsp-core/src/post-edit/index.ts +1 -0
- package/packages/lsp-core/src/post-edit/orchestration.test.ts +157 -0
- package/packages/lsp-core/src/post-edit/orchestration.ts +178 -0
- package/packages/lsp-core/src/request-context.test.ts +171 -0
- package/packages/lsp-core/src/request-context.ts +222 -9
- package/packages/lsp-core/src/tool-surface.test.ts +4 -1
- package/packages/lsp-core/src/tools/diagnostics.ts +32 -13
- package/packages/lsp-core/src/tools/navigation.ts +12 -12
- package/packages/lsp-core/src/tools/rename.ts +10 -15
- package/packages/lsp-core/src/tools/symbols.ts +11 -11
- package/packages/lsp-core/src/tools/types.ts +2 -1
- package/packages/lsp-daemon/dist/cli.js +3114 -747
- package/packages/lsp-daemon/dist/client.d.ts +105 -0
- package/packages/lsp-daemon/dist/client.js +5851 -0
- package/packages/lsp-daemon/dist/daemon-client.d.ts +11 -6
- package/packages/lsp-daemon/dist/daemon-client.js +113 -30
- package/packages/lsp-daemon/dist/daemon-server.d.ts +1 -0
- package/packages/lsp-daemon/dist/daemon-server.js +40 -15
- package/packages/lsp-daemon/dist/ensure-daemon.d.ts +8 -7
- package/packages/lsp-daemon/dist/ensure-daemon.js +67 -44
- package/packages/lsp-daemon/dist/index.d.ts +2 -2
- package/packages/lsp-daemon/dist/index.js +2862 -754
- package/packages/lsp-daemon/dist/ipc-protocol.d.ts +46 -0
- package/packages/lsp-daemon/dist/ipc-protocol.js +187 -0
- package/packages/lsp-daemon/dist/lock.js +14 -4
- package/packages/lsp-daemon/dist/ownership.d.ts +49 -0
- package/packages/lsp-daemon/dist/ownership.js +168 -0
- package/packages/lsp-daemon/dist/paths.d.ts +33 -9
- package/packages/lsp-daemon/dist/paths.js +72 -33
- package/packages/lsp-daemon/dist/proxy.d.ts +3 -0
- package/packages/lsp-daemon/dist/proxy.js +54 -3
- package/packages/lsp-daemon/dist/request-routing.d.ts +7 -2
- package/packages/lsp-daemon/dist/request-routing.js +71 -22
- package/packages/lsp-daemon/dist/run-daemon.js +9 -2
- package/packages/lsp-daemon/dist/runtime-contract.d.ts +21 -0
- package/packages/lsp-daemon/dist/runtime-contract.js +58 -0
- package/packages/lsp-daemon/dist/socket-jsonrpc.js +6 -1
- package/packages/lsp-daemon/package.json +12 -3
- package/packages/lsp-tools-mcp/dist/cli.js +2115 -442
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +1741 -148
- package/packages/lsp-tools-mcp/dist/mcp.js +2127 -454
- package/packages/lsp-tools-mcp/dist/request-context.js +176 -6
- package/packages/lsp-tools-mcp/dist/tools.js +2118 -446
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- 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/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 +55 -0
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +2959 -944
- package/packages/omo-codex/plugin/components/lsp/dist/codex-hook-cli.js +0 -4
- package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.d.ts +5 -2
- package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.js +41 -62
- package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.d.ts +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +24 -15
- package/packages/omo-codex/plugin/components/lsp/dist/lsp-session-state.d.ts +3 -7
- package/packages/omo-codex/plugin/components/lsp/dist/lsp-session-state.js +23 -49
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +3 -2
- package/packages/omo-codex/plugin/components/lsp/scripts/build-lsp-daemon.mjs +31 -1
- package/packages/omo-codex/plugin/components/lsp/scripts/build-lsp-daemon.test.mjs +76 -0
- package/packages/omo-codex/plugin/components/lsp/scripts/build-runtime.mjs +201 -0
- package/packages/omo-codex/plugin/components/lsp/scripts/build-runtime.test.mjs +55 -0
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +0 -4
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +49 -71
- package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +26 -15
- package/packages/omo-codex/plugin/components/lsp/src/lsp-session-state.ts +26 -64
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-unavailable.test.ts +16 -17
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook.test.ts +30 -4
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +19 -5
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.5.md +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +8 -6
- 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/start-work-continuation/directive.md +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- 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/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +37 -10
- 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/ultrawork/skills/ultrawork/SKILL.md +37 -10
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +37 -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/skills/ulw-loop/SKILL.md +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +10 -9
- 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-start-work-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-start-work-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/package-lock.json +26 -14
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +2 -3
- package/packages/omo-codex/plugin/scripts/build-components.mjs +13 -1
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +1 -1
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +9 -0
- package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +4 -2
- package/packages/omo-codex/plugin/skills/frontend/references/design/layout-skill.md +107 -0
- package/packages/omo-codex/plugin/skills/programming/SKILL.md +12 -2
- package/packages/omo-codex/plugin/skills/start-work/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +37 -10
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +10 -9
- package/packages/omo-codex/plugin/test/aggregate-build.test.mjs +8 -0
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +128 -15
- package/packages/omo-codex/plugin/test/install-time-build-runtime.test.mjs +10 -0
- package/packages/omo-codex/plugin/test/lsp-prebuild-layouts.test.mjs +2 -0
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +1 -1
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +351 -74
- package/packages/shared-skills/skills/frontend/SKILL.md +1 -1
- package/packages/shared-skills/skills/frontend/references/design/README.md +9 -0
- package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +4 -2
- package/packages/shared-skills/skills/frontend/references/design/layout-skill.md +107 -0
- package/packages/shared-skills/skills/programming/SKILL.md +12 -2
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, it } from "bun:test";
|
|
5
|
+
|
|
6
|
+
import { createStandaloneMcpRequestContext, runWithRequestContext } from "../request-context.js";
|
|
7
|
+
import { findWorkspaceRoot, resolvePathInsideContext } from "./client-wrapper.js";
|
|
8
|
+
import { LspInvalidPathError } from "./errors.js";
|
|
9
|
+
|
|
10
|
+
const tempDirectories: string[] = [];
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
for (const directory of tempDirectories.splice(0)) {
|
|
14
|
+
rmSync(directory, { recursive: true, force: true });
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function tempRoot(prefix: string): string {
|
|
19
|
+
const root = mkdtempSync(join(tmpdir(), prefix));
|
|
20
|
+
tempDirectories.push(root);
|
|
21
|
+
return root;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("LSP client path confinement", () => {
|
|
25
|
+
it("#given a relative file inside context cwd #when resolving workspace #then marker search stays inside cwd", () => {
|
|
26
|
+
const root = tempRoot("lsp-client-wrapper-root-");
|
|
27
|
+
mkdirSync(join(root, ".git"), { recursive: true });
|
|
28
|
+
mkdirSync(join(root, "src"), { recursive: true });
|
|
29
|
+
writeFileSync(join(root, "src", "file.ts"), "export const value = 1;\n");
|
|
30
|
+
|
|
31
|
+
const workspace = runWithRequestContext(createStandaloneMcpRequestContext({ cwd: root }), () =>
|
|
32
|
+
findWorkspaceRoot("src/file.ts"),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
expect(workspace).toBe(realpathSync(root));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("#given an absolute file outside context cwd #when resolving #then rejects before workspace inference", () => {
|
|
39
|
+
const root = tempRoot("lsp-client-wrapper-cwd-");
|
|
40
|
+
const outside = tempRoot("lsp-client-wrapper-outside-");
|
|
41
|
+
mkdirSync(join(outside, ".git"), { recursive: true });
|
|
42
|
+
writeFileSync(join(outside, "file.ts"), "export const outside = true;\n");
|
|
43
|
+
|
|
44
|
+
expect(() =>
|
|
45
|
+
runWithRequestContext(createStandaloneMcpRequestContext({ cwd: root }), () =>
|
|
46
|
+
findWorkspaceRoot(join(outside, "file.ts")),
|
|
47
|
+
),
|
|
48
|
+
).toThrow(LspInvalidPathError);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("#given a symlink inside cwd that points outside #when resolving #then rejects the escape", () => {
|
|
52
|
+
const root = tempRoot("lsp-client-wrapper-symlink-root-");
|
|
53
|
+
const outside = tempRoot("lsp-client-wrapper-symlink-outside-");
|
|
54
|
+
writeFileSync(join(outside, "file.ts"), "export const outside = true;\n");
|
|
55
|
+
symlinkSync(outside, join(root, "linked"));
|
|
56
|
+
|
|
57
|
+
expect(() =>
|
|
58
|
+
runWithRequestContext(createStandaloneMcpRequestContext({ cwd: root }), () =>
|
|
59
|
+
resolvePathInsideContext("linked/file.ts"),
|
|
60
|
+
),
|
|
61
|
+
).toThrow(LspInvalidPathError);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { existsSync, statSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
canonicalizeExistingOrNearestAncestor,
|
|
5
|
+
contextCwd,
|
|
6
|
+
isPathInside,
|
|
7
|
+
lspRequestContext,
|
|
8
|
+
} from "../request-context.js";
|
|
4
9
|
import type { LspClient } from "./client.js";
|
|
5
10
|
import { effectiveExtension } from "./effective-extension.js";
|
|
6
11
|
import {
|
|
@@ -26,7 +31,7 @@ export function isDirectoryPath(filePath: string): boolean {
|
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
export function findWorkspaceRoot(filePath: string): string {
|
|
29
|
-
const abs =
|
|
34
|
+
const abs = resolvePathInsideContext(filePath);
|
|
30
35
|
let dir = abs;
|
|
31
36
|
|
|
32
37
|
if (!isDirectoryPath(dir)) {
|
|
@@ -47,10 +52,22 @@ export function findWorkspaceRoot(filePath: string): string {
|
|
|
47
52
|
return dirname(abs);
|
|
48
53
|
}
|
|
49
54
|
|
|
55
|
+
export function resolvePathInsideContext(filePath: string): string {
|
|
56
|
+
const cwd = contextCwd();
|
|
57
|
+
const abs = resolve(cwd, filePath);
|
|
58
|
+
const canonical = canonicalizeExistingOrNearestAncestor(abs);
|
|
59
|
+
if (!isPathInside(cwd, canonical)) {
|
|
60
|
+
throw new LspInvalidPathError(`LSP file path must be inside request cwd: ${filePath}`);
|
|
61
|
+
}
|
|
62
|
+
return canonical;
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
export function formatServerLookupError(result: Exclude<ServerLookupResult, { status: "found" }>): string {
|
|
51
66
|
if (result.status === "not_installed") {
|
|
52
67
|
return formatNotInstalled(result);
|
|
53
68
|
}
|
|
69
|
+
const context = lspRequestContext();
|
|
70
|
+
const firstProjectConfigPath = context.projectConfigPaths[0] ?? "<project lsp config>";
|
|
54
71
|
|
|
55
72
|
return [
|
|
56
73
|
`No LSP server configured for extension: ${result.extension}`,
|
|
@@ -59,7 +76,7 @@ export function formatServerLookupError(result: Exclude<ServerLookupResult, { st
|
|
|
59
76
|
result.availableServers.length > 10 ? "..." : ""
|
|
60
77
|
}`,
|
|
61
78
|
"",
|
|
62
|
-
|
|
79
|
+
`Configure a custom server in '${firstProjectConfigPath}' or '${context.userConfigPath}':`,
|
|
63
80
|
" {",
|
|
64
81
|
' "lsp": {',
|
|
65
82
|
' "my-server": {',
|
|
@@ -75,6 +92,7 @@ function formatNotInstalled(result: Extract<ServerLookupResult, { status: "not_i
|
|
|
75
92
|
const { server, installHint } = result;
|
|
76
93
|
const extensions = server.extensions.join(", ");
|
|
77
94
|
const decision = loadInstallDecision(server.id)?.decision;
|
|
95
|
+
const context = lspRequestContext();
|
|
78
96
|
|
|
79
97
|
if (decision === "declined") {
|
|
80
98
|
return `LSP server '${server.id}' (${extensions}) is NOT INSTALLED; user previously declined installation — proceed without LSP.`;
|
|
@@ -95,6 +113,17 @@ function formatNotInstalled(result: Extract<ServerLookupResult, { status: "not_i
|
|
|
95
113
|
].join("\n");
|
|
96
114
|
}
|
|
97
115
|
|
|
116
|
+
if (!context.capabilities.installDecisionTool) {
|
|
117
|
+
return [
|
|
118
|
+
...header,
|
|
119
|
+
"To install, run:",
|
|
120
|
+
` ${installHint}`,
|
|
121
|
+
"",
|
|
122
|
+
"ACTION REQUIRED — ASK THE USER whether to install this LSP server.",
|
|
123
|
+
"Install-decision recording is unavailable in this harness; proceed without LSP if the user declines.",
|
|
124
|
+
].join("\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
98
127
|
return [
|
|
99
128
|
...header,
|
|
100
129
|
"To install, run:",
|
|
@@ -104,6 +133,7 @@ function formatNotInstalled(result: Extract<ServerLookupResult, { status: "not_i
|
|
|
104
133
|
"- If the user agrees: run the install command above, then retry this tool.",
|
|
105
134
|
"- If the user declines, OR has NOT explicitly asked for LSP installation:",
|
|
106
135
|
` call lsp_install_decision { server_id: "${server.id}", decision: "declined" },`,
|
|
136
|
+
` which writes to ${context.installDecisionsPath},`,
|
|
107
137
|
" then ignore this message and proceed WITHOUT LSP.",
|
|
108
138
|
].join("\n");
|
|
109
139
|
}
|
|
@@ -128,7 +158,7 @@ export async function withLspClient<T>(
|
|
|
128
158
|
toolName: string,
|
|
129
159
|
options: WithLspClientOptions = {},
|
|
130
160
|
): Promise<T> {
|
|
131
|
-
const absPath =
|
|
161
|
+
const absPath = resolvePathInsideContext(filePath);
|
|
132
162
|
|
|
133
163
|
if (isDirectoryPath(absPath)) {
|
|
134
164
|
throw new LspInvalidPathError(
|
|
@@ -140,7 +170,7 @@ export async function withLspClient<T>(
|
|
|
140
170
|
const ext = effectiveExtension(absPath);
|
|
141
171
|
const result = findServerForExtension(ext);
|
|
142
172
|
if (result.status !== "found") {
|
|
143
|
-
throw new LspServerLookupError(formatServerLookupError(result));
|
|
173
|
+
throw new LspServerLookupError(formatServerLookupError(result), result);
|
|
144
174
|
}
|
|
145
175
|
|
|
146
176
|
const server = result.server;
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import { resolve } from "node:path";
|
|
3
2
|
import { pathToFileURL } from "node:url";
|
|
4
3
|
|
|
5
|
-
import { contextCwd } from "../request-context.js";
|
|
6
4
|
import { LspClientConnection } from "./connection.js";
|
|
7
|
-
import {
|
|
8
|
-
import { getLanguageId } from "./language-mappings.js";
|
|
5
|
+
import type { LspClientTimeoutOptions } from "./transport.js";
|
|
9
6
|
import type {
|
|
10
7
|
Diagnostic,
|
|
11
8
|
DocumentSymbol,
|
|
@@ -14,105 +11,131 @@ import type {
|
|
|
14
11
|
PrepareRenameDefaultBehavior,
|
|
15
12
|
PrepareRenameResult,
|
|
16
13
|
Range,
|
|
14
|
+
ResolvedServer,
|
|
17
15
|
SymbolInfo,
|
|
18
16
|
WorkspaceEdit,
|
|
19
17
|
} from "./types.js";
|
|
18
|
+
import { LspRequestTimeoutError } from "./errors.js";
|
|
19
|
+
import { WorkspaceDocumentState } from "./workspace-document-state.js";
|
|
20
|
+
import type { LspRenameResult, WorkspaceEditCommitIo } from "./workspace-edit-types.js";
|
|
21
|
+
import { WorkspaceMutationController } from "./workspace-mutation-controller.js";
|
|
20
22
|
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
+
const DIAGNOSTICS_FRESHNESS_TIMEOUT_MS = 3_000;
|
|
24
|
+
const VERSIONLESS_PUBLISH_QUIESCENCE_MS = 250;
|
|
25
|
+
|
|
26
|
+
export interface LspDiagnosticsResult {
|
|
27
|
+
readonly items: Diagnostic[];
|
|
28
|
+
readonly transientError?: {
|
|
29
|
+
readonly kind: "freshness_timeout";
|
|
30
|
+
readonly message: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface LspClientOptions extends LspClientTimeoutOptions {
|
|
35
|
+
readonly diagnosticsFreshnessTimeoutMs?: number;
|
|
36
|
+
readonly versionlessPublishQuiescenceMs?: number;
|
|
37
|
+
}
|
|
23
38
|
|
|
24
39
|
export class LspClient extends LspClientConnection {
|
|
25
|
-
private readonly openedFiles = new Set<string>();
|
|
26
|
-
private readonly documentVersions = new Map<string, number>();
|
|
27
|
-
private readonly lastSyncedText = new Map<string, string>();
|
|
28
40
|
private readonly diagnosticPullErrors: Error[] = [];
|
|
41
|
+
private readonly documents: WorkspaceDocumentState;
|
|
42
|
+
private readonly workspaceMutations: WorkspaceMutationController;
|
|
43
|
+
private readonly diagnosticsFreshnessTimeoutMs: number;
|
|
44
|
+
|
|
45
|
+
constructor(root: string, server: ResolvedServer, options: LspClientOptions = {}) {
|
|
46
|
+
super(root, server, options);
|
|
47
|
+
this.diagnosticsFreshnessTimeoutMs =
|
|
48
|
+
options.diagnosticsFreshnessTimeoutMs ?? DIAGNOSTICS_FRESHNESS_TIMEOUT_MS;
|
|
49
|
+
this.documents = new WorkspaceDocumentState(
|
|
50
|
+
(method, params) => this.sendNotification(method, params),
|
|
51
|
+
(uri) => this.diagnosticsStore.delete(uri),
|
|
52
|
+
{
|
|
53
|
+
versionlessPublishQuiescenceMs:
|
|
54
|
+
options.versionlessPublishQuiescenceMs ?? VERSIONLESS_PUBLISH_QUIESCENCE_MS,
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
this.workspaceMutations = new WorkspaceMutationController(root, this.documents);
|
|
58
|
+
this.setWorkspaceApplyEditHandler((params) => this.workspaceMutations.handleApplyEdit(params));
|
|
59
|
+
}
|
|
29
60
|
|
|
30
61
|
getDiagnosticPullErrors(): readonly Error[] {
|
|
31
62
|
return this.diagnosticPullErrors;
|
|
32
63
|
}
|
|
33
64
|
|
|
34
65
|
async openFile(filePath: string): Promise<void> {
|
|
35
|
-
const absPath =
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (!this.openedFiles.has(absPath)) {
|
|
40
|
-
const ext = effectiveExtension(absPath);
|
|
41
|
-
const languageId = getLanguageId(ext);
|
|
42
|
-
const version = 1;
|
|
43
|
-
|
|
44
|
-
await this.sendNotification("textDocument/didOpen", {
|
|
45
|
-
textDocument: {
|
|
46
|
-
uri,
|
|
47
|
-
languageId,
|
|
48
|
-
version,
|
|
49
|
-
text,
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
this.openedFiles.add(absPath);
|
|
54
|
-
this.documentVersions.set(uri, version);
|
|
55
|
-
this.lastSyncedText.set(uri, text);
|
|
56
|
-
await new Promise((r) => setTimeout(r, POST_OPEN_DELAY_MS));
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
66
|
+
const absPath = this.resolveWorkspacePath(filePath);
|
|
67
|
+
await this.documents.openFile(absPath);
|
|
68
|
+
}
|
|
59
69
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
70
|
+
getOpenDocumentVersion(filePath: string): number | undefined {
|
|
71
|
+
return this.documents.getVersion(this.resolveWorkspacePath(filePath));
|
|
72
|
+
}
|
|
64
73
|
|
|
65
|
-
|
|
66
|
-
this.
|
|
67
|
-
|
|
74
|
+
override getStoredDiagnostics(uri: string): Diagnostic[] {
|
|
75
|
+
return [...this.documents.getStoredDiagnostics(uri)];
|
|
76
|
+
}
|
|
68
77
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
});
|
|
78
|
+
setWorkspaceEditIo(io: Partial<WorkspaceEditCommitIo>): void {
|
|
79
|
+
this.workspaceMutations.setIo(io);
|
|
80
|
+
}
|
|
73
81
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
protected override handlePublishDiagnostics(params: {
|
|
83
|
+
readonly uri: string;
|
|
84
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
85
|
+
readonly version?: number;
|
|
86
|
+
}): void {
|
|
87
|
+
super.handlePublishDiagnostics(params);
|
|
88
|
+
this.documents.recordPublishedDiagnostics(params);
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
async definition(
|
|
81
92
|
filePath: string,
|
|
82
93
|
line: number,
|
|
83
94
|
character: number,
|
|
95
|
+
signal?: AbortSignal,
|
|
84
96
|
): Promise<Location | LocationLink | Array<Location | LocationLink> | null> {
|
|
85
|
-
const absPath =
|
|
97
|
+
const absPath = this.resolveWorkspacePath(filePath);
|
|
86
98
|
await this.openFile(absPath);
|
|
99
|
+
const options = signal === undefined ? {} : { signal };
|
|
87
100
|
return this.sendRequest<Location | LocationLink | Array<Location | LocationLink> | null>(
|
|
88
101
|
"textDocument/definition",
|
|
89
102
|
{
|
|
90
103
|
textDocument: { uri: pathToFileURL(absPath).href },
|
|
91
104
|
position: { line: line - 1, character },
|
|
92
105
|
},
|
|
106
|
+
options,
|
|
93
107
|
);
|
|
94
108
|
}
|
|
95
109
|
|
|
96
|
-
async references(
|
|
97
|
-
|
|
110
|
+
async references(
|
|
111
|
+
filePath: string,
|
|
112
|
+
line: number,
|
|
113
|
+
character: number,
|
|
114
|
+
includeDeclaration = true,
|
|
115
|
+
signal?: AbortSignal,
|
|
116
|
+
): Promise<Location[]> {
|
|
117
|
+
const absPath = this.resolveWorkspacePath(filePath);
|
|
98
118
|
await this.openFile(absPath);
|
|
119
|
+
const options = signal === undefined ? {} : { signal };
|
|
99
120
|
return this.sendRequest<Location[]>("textDocument/references", {
|
|
100
121
|
textDocument: { uri: pathToFileURL(absPath).href },
|
|
101
122
|
position: { line: line - 1, character },
|
|
102
123
|
context: { includeDeclaration },
|
|
103
|
-
});
|
|
124
|
+
}, options);
|
|
104
125
|
}
|
|
105
126
|
|
|
106
|
-
async documentSymbols(filePath: string): Promise<Array<DocumentSymbol | SymbolInfo>> {
|
|
107
|
-
const absPath =
|
|
127
|
+
async documentSymbols(filePath: string, signal?: AbortSignal): Promise<Array<DocumentSymbol | SymbolInfo>> {
|
|
128
|
+
const absPath = this.resolveWorkspacePath(filePath);
|
|
108
129
|
await this.openFile(absPath);
|
|
130
|
+
const options = signal === undefined ? {} : { signal };
|
|
109
131
|
return this.sendRequest<Array<DocumentSymbol | SymbolInfo>>("textDocument/documentSymbol", {
|
|
110
132
|
textDocument: { uri: pathToFileURL(absPath).href },
|
|
111
|
-
});
|
|
133
|
+
}, options);
|
|
112
134
|
}
|
|
113
135
|
|
|
114
|
-
async workspaceSymbols(query: string): Promise<SymbolInfo[]> {
|
|
115
|
-
|
|
136
|
+
async workspaceSymbols(query: string, signal?: AbortSignal): Promise<SymbolInfo[]> {
|
|
137
|
+
const options = signal === undefined ? {} : { signal };
|
|
138
|
+
return this.sendRequest<SymbolInfo[]>("workspace/symbol", { query }, options);
|
|
116
139
|
}
|
|
117
140
|
|
|
118
141
|
private isUnsupportedDiagnosticPullError(error: unknown): boolean {
|
|
@@ -122,51 +145,210 @@ export class LspClient extends LspClientConnection {
|
|
|
122
145
|
return /unsupported|not supported|method not found|unknown request/i.test(error.message);
|
|
123
146
|
}
|
|
124
147
|
|
|
125
|
-
|
|
126
|
-
|
|
148
|
+
private freshnessTimeout(absPath: string): LspDiagnosticsResult {
|
|
149
|
+
return {
|
|
150
|
+
items: [],
|
|
151
|
+
transientError: {
|
|
152
|
+
kind: "freshness_timeout",
|
|
153
|
+
message: `Timed out waiting for fresh diagnostics for ${absPath} within ${this.diagnosticsFreshnessTimeoutMs}ms.`,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private parseDiagnosticPullReport(value: { items?: Diagnostic[]; kind?: string; resultId?: string }): {
|
|
159
|
+
readonly type: "full";
|
|
160
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
161
|
+
readonly resultId?: string;
|
|
162
|
+
} | {
|
|
163
|
+
readonly type: "unchanged";
|
|
164
|
+
readonly resultId?: string;
|
|
165
|
+
} {
|
|
166
|
+
if (value.kind === "unchanged") {
|
|
167
|
+
return {
|
|
168
|
+
type: "unchanged",
|
|
169
|
+
...(value.resultId === undefined ? {} : { resultId: value.resultId }),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
type: "full",
|
|
174
|
+
diagnostics: value.items ?? [],
|
|
175
|
+
...(value.resultId === undefined ? {} : { resultId: value.resultId }),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async diagnostics(filePath: string, signal?: AbortSignal): Promise<LspDiagnosticsResult> {
|
|
180
|
+
signal?.throwIfAborted();
|
|
181
|
+
const absPath = this.resolveWorkspacePath(filePath);
|
|
127
182
|
const uri = pathToFileURL(absPath).href;
|
|
128
183
|
await this.openFile(absPath);
|
|
129
|
-
|
|
184
|
+
const deadlineAt = Date.now() + this.diagnosticsFreshnessTimeoutMs;
|
|
130
185
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (!
|
|
140
|
-
this.
|
|
186
|
+
for (;;) {
|
|
187
|
+
signal?.throwIfAborted();
|
|
188
|
+
const snapshot = this.documents.captureDiagnosticSnapshot(absPath);
|
|
189
|
+
if (!snapshot) return this.freshnessTimeout(absPath);
|
|
190
|
+
const push = this.documents.resolvePushDiagnostics(snapshot);
|
|
191
|
+
if (push.status === "ready") return { items: [...push.diagnostics] };
|
|
192
|
+
|
|
193
|
+
let pushFallbackOnly = !this.isDiagnosticPullSupported();
|
|
194
|
+
if (!pushFallbackOnly) {
|
|
195
|
+
const cached = this.documents.getPullCache(snapshot);
|
|
196
|
+
try {
|
|
197
|
+
const remainingMs = deadlineAt - Date.now();
|
|
198
|
+
if (remainingMs <= 0) return this.freshnessTimeout(absPath);
|
|
199
|
+
const result = await this.sendRequest<{ items?: Diagnostic[]; kind?: string; resultId?: string }>(
|
|
200
|
+
"textDocument/diagnostic",
|
|
201
|
+
{
|
|
202
|
+
textDocument: { uri },
|
|
203
|
+
...(cached?.resultId === undefined ? {} : { previousResultId: cached.resultId }),
|
|
204
|
+
},
|
|
205
|
+
{ timeoutMs: remainingMs, ...(signal === undefined ? {} : { signal }) },
|
|
206
|
+
);
|
|
207
|
+
if (!this.documents.isCurrentSnapshot(snapshot)) continue;
|
|
208
|
+
const report = this.parseDiagnosticPullReport(result);
|
|
209
|
+
if (report.type === "full") {
|
|
210
|
+
this.documents.recordPullDiagnostics(snapshot, {
|
|
211
|
+
kind: "full",
|
|
212
|
+
diagnostics: report.diagnostics,
|
|
213
|
+
...(report.resultId === undefined ? {} : { resultId: report.resultId }),
|
|
214
|
+
});
|
|
215
|
+
return { items: [...report.diagnostics] };
|
|
216
|
+
}
|
|
217
|
+
if (
|
|
218
|
+
cached !== null &&
|
|
219
|
+
cached.documentVersion === snapshot.version &&
|
|
220
|
+
cached.resultId === report.resultId
|
|
221
|
+
) {
|
|
222
|
+
return { items: [...cached.diagnostics] };
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (this.isUnsupportedDiagnosticPullError(error)) {
|
|
226
|
+
this.setDiagnosticPullSupported(false);
|
|
227
|
+
pushFallbackOnly = true;
|
|
228
|
+
} else if (error instanceof LspRequestTimeoutError) {
|
|
229
|
+
pushFallbackOnly = true;
|
|
230
|
+
} else {
|
|
231
|
+
this.diagnosticPullErrors.push(error instanceof Error ? error : new Error(String(error)));
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
141
235
|
}
|
|
142
|
-
}
|
|
143
236
|
|
|
144
|
-
|
|
237
|
+
if (!pushFallbackOnly) continue;
|
|
238
|
+
|
|
239
|
+
const remainingMs = deadlineAt - Date.now();
|
|
240
|
+
if (remainingMs <= 0) return this.freshnessTimeout(absPath);
|
|
241
|
+
const waitMs = push.status === "wait" ? Math.min(push.waitMs, remainingMs) : remainingMs;
|
|
242
|
+
await waitForDiagnosticsActivity(this.documents.waitForDiagnosticsActivity(snapshot, waitMs), signal);
|
|
243
|
+
}
|
|
145
244
|
}
|
|
146
245
|
|
|
147
246
|
async prepareRename(
|
|
148
247
|
filePath: string,
|
|
149
248
|
line: number,
|
|
150
249
|
character: number,
|
|
250
|
+
signal?: AbortSignal,
|
|
151
251
|
): Promise<PrepareRenameResult | PrepareRenameDefaultBehavior | Range | null> {
|
|
152
|
-
const absPath =
|
|
252
|
+
const absPath = this.resolveWorkspacePath(filePath);
|
|
153
253
|
await this.openFile(absPath);
|
|
254
|
+
const options = signal === undefined ? {} : { signal };
|
|
154
255
|
return this.sendRequest<PrepareRenameResult | PrepareRenameDefaultBehavior | Range | null>(
|
|
155
256
|
"textDocument/prepareRename",
|
|
156
257
|
{
|
|
157
258
|
textDocument: { uri: pathToFileURL(absPath).href },
|
|
158
259
|
position: { line: line - 1, character },
|
|
159
260
|
},
|
|
261
|
+
options,
|
|
160
262
|
);
|
|
161
263
|
}
|
|
162
264
|
|
|
163
|
-
async rename(
|
|
164
|
-
|
|
265
|
+
async rename(
|
|
266
|
+
filePath: string,
|
|
267
|
+
line: number,
|
|
268
|
+
character: number,
|
|
269
|
+
newName: string,
|
|
270
|
+
signal?: AbortSignal,
|
|
271
|
+
): Promise<LspRenameResult> {
|
|
272
|
+
const absPath = this.resolveWorkspacePath(filePath);
|
|
165
273
|
await this.openFile(absPath);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
274
|
+
const acquired = this.workspaceMutations.acquire(signal);
|
|
275
|
+
if (!acquired.success) return { edit: null, apply: acquired.result };
|
|
276
|
+
const preCommitSignal = createPreCommitAbortSignal(signal, () =>
|
|
277
|
+
this.workspaceMutations.isBeforeCommit(acquired.lease),
|
|
278
|
+
);
|
|
279
|
+
try {
|
|
280
|
+
const renameParams = {
|
|
281
|
+
textDocument: { uri: pathToFileURL(absPath).href },
|
|
282
|
+
position: { line: line - 1, character },
|
|
283
|
+
newName,
|
|
284
|
+
};
|
|
285
|
+
const edit =
|
|
286
|
+
preCommitSignal === undefined
|
|
287
|
+
? await this.sendRequest<WorkspaceEdit | null>("textDocument/rename", renameParams)
|
|
288
|
+
: await this.sendRequest<WorkspaceEdit | null>("textDocument/rename", renameParams, {
|
|
289
|
+
signal: preCommitSignal.signal,
|
|
290
|
+
});
|
|
291
|
+
return await this.workspaceMutations.reconcileRename(acquired.lease, edit);
|
|
292
|
+
} finally {
|
|
293
|
+
preCommitSignal?.dispose();
|
|
294
|
+
this.workspaceMutations.release(acquired.lease);
|
|
295
|
+
}
|
|
171
296
|
}
|
|
297
|
+
|
|
298
|
+
private resolveWorkspacePath(filePath: string): string {
|
|
299
|
+
return resolve(this.root, filePath);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function waitForDiagnosticsActivity(wait: Promise<void>, signal: AbortSignal | undefined): Promise<void> {
|
|
304
|
+
if (!signal) return wait;
|
|
305
|
+
if (signal.aborted) return Promise.reject(abortError(signal));
|
|
306
|
+
return new Promise((resolve, reject) => {
|
|
307
|
+
const onAbort = (): void => {
|
|
308
|
+
signal.removeEventListener("abort", onAbort);
|
|
309
|
+
reject(abortError(signal));
|
|
310
|
+
};
|
|
311
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
312
|
+
wait.then(
|
|
313
|
+
() => {
|
|
314
|
+
signal.removeEventListener("abort", onAbort);
|
|
315
|
+
resolve();
|
|
316
|
+
},
|
|
317
|
+
(error) => {
|
|
318
|
+
signal.removeEventListener("abort", onAbort);
|
|
319
|
+
reject(error);
|
|
320
|
+
},
|
|
321
|
+
);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function createPreCommitAbortSignal(
|
|
326
|
+
source: AbortSignal | undefined,
|
|
327
|
+
isBeforeCommit: () => boolean,
|
|
328
|
+
): { readonly signal: AbortSignal; readonly dispose: () => void } | undefined {
|
|
329
|
+
if (!source) return undefined;
|
|
330
|
+
const controller = new AbortController();
|
|
331
|
+
const onAbort = (): void => {
|
|
332
|
+
if (isBeforeCommit() && !controller.signal.aborted) controller.abort(preCommitAbortReason(source));
|
|
333
|
+
};
|
|
334
|
+
if (source.aborted) onAbort();
|
|
335
|
+
else source.addEventListener("abort", onAbort, { once: true });
|
|
336
|
+
return {
|
|
337
|
+
signal: controller.signal,
|
|
338
|
+
dispose: () => source.removeEventListener("abort", onAbort),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function preCommitAbortReason(source: AbortSignal): Error {
|
|
343
|
+
const reason = source.reason;
|
|
344
|
+
if (reason instanceof Error && reason.name !== "AbortError") return reason;
|
|
345
|
+
return new Error("LSP request cancelled before workspace edit commit");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function abortError(signal: AbortSignal): Error {
|
|
349
|
+
const reason = signal.reason;
|
|
350
|
+
if (reason instanceof Error) return reason;
|
|
351
|
+
const error = new Error(typeof reason === "string" ? reason : "operation cancelled");
|
|
352
|
+
error.name = "AbortError";
|
|
353
|
+
return error;
|
|
172
354
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { delimiter, isAbsolute, join } from "node:path";
|
|
4
2
|
|
|
5
|
-
import {
|
|
3
|
+
import { lspRequestContext } from "../request-context.js";
|
|
6
4
|
import { BUILTIN_SERVERS } from "./server-definitions.js";
|
|
7
5
|
import type { ResolvedServer } from "./types.js";
|
|
8
6
|
|
|
@@ -27,27 +25,17 @@ export interface ServerWithSource extends ResolvedServer {
|
|
|
27
25
|
|
|
28
26
|
export function getConfigPaths(): { project: string; user: string } {
|
|
29
27
|
return {
|
|
30
|
-
project: getProjectConfigPaths()[0] ??
|
|
28
|
+
project: getProjectConfigPaths()[0] ?? lspRequestContext().projectConfigPaths[0] ?? "",
|
|
31
29
|
user: getUserConfigPath(),
|
|
32
30
|
};
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
function
|
|
36
|
-
return
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function getProjectConfigPaths(): string[] {
|
|
40
|
-
const projectOverride = contextEnv("LSP_TOOLS_MCP_PROJECT_CONFIG");
|
|
41
|
-
if (projectOverride) {
|
|
42
|
-
return projectOverride.split(delimiter).filter(Boolean).map(resolveProjectConfigPath);
|
|
43
|
-
}
|
|
44
|
-
return [join(contextCwd(), ".codex", "lsp-client.json")];
|
|
33
|
+
function getProjectConfigPaths(): readonly string[] {
|
|
34
|
+
return lspRequestContext().projectConfigPaths;
|
|
45
35
|
}
|
|
46
36
|
|
|
47
37
|
function getUserConfigPath(): string {
|
|
48
|
-
|
|
49
|
-
if (!userOverride) return join(homedir(), ".codex", "lsp-client.json");
|
|
50
|
-
return isAbsolute(userOverride) ? userOverride : join(homedir(), userOverride);
|
|
38
|
+
return lspRequestContext().userConfigPath;
|
|
51
39
|
}
|
|
52
40
|
|
|
53
41
|
function loadJsonFile(path: string): ConfigJson | null {
|