oh-my-opencode 5.0.0-beta.10 → 5.0.0-beta.12
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 +34 -86
- package/dist/cli-node/index.js +34 -86
- package/dist/index.js +80 -93
- package/dist/tools/delegate-task/builtin-categories.d.ts +1 -0
- package/dist/tools/delegate-task/builtin-category-definition.d.ts +1 -0
- package/dist/tools/delegate-task/tool-description.d.ts +3 -1
- package/dist/tui.js +19 -71
- package/package.json +14 -14
- package/packages/lsp-core/src/lsp/client-wrapper-outside-cwd.test.ts +78 -0
- package/packages/lsp-core/src/lsp/client-wrapper.test.ts +6 -6
- package/packages/lsp-core/src/lsp/client-wrapper.ts +5 -6
- package/packages/lsp-core/src/lsp/outside-context-workspace.ts +33 -0
- package/packages/lsp-core/src/lsp/workspace-markers.ts +1 -0
- package/packages/lsp-daemon/dist/cli.js +94 -59
- package/packages/lsp-daemon/dist/client.js +72 -37
- package/packages/lsp-daemon/dist/index.js +78 -43
- package/packages/lsp-tools-mcp/dist/cli.js +68 -33
- package/packages/lsp-tools-mcp/dist/mcp.js +68 -33
- package/packages/lsp-tools-mcp/dist/tools.js +68 -33
- 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/dist/cli.js +9 -0
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +9 -0
- 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 +83 -54
- 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/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/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/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- 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/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 +13 -13
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, 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, resolveReadablePathInsideContext, 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 read-only access outside request cwd", () => {
|
|
25
|
+
it("#given an absolute file outside cwd #when resolving a read path #then returns the canonical path", () => {
|
|
26
|
+
const root = tempRoot("lsp-outcwd-root-");
|
|
27
|
+
const outside = tempRoot("lsp-outcwd-outside-");
|
|
28
|
+
const outsideFile = join(outside, "probe.ts");
|
|
29
|
+
writeFileSync(outsideFile, "export const value = 1;\n");
|
|
30
|
+
|
|
31
|
+
const resolved = runWithRequestContext(createStandaloneMcpRequestContext({ cwd: root }), () =>
|
|
32
|
+
resolveReadablePathInsideContext(outsideFile),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
expect(resolved).toBe(realpathSync(outsideFile));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("#given an outside file inside its own marked project #when inferring workspace #then uses that project root", () => {
|
|
39
|
+
const root = tempRoot("lsp-outcwd-marker-root-");
|
|
40
|
+
const outside = tempRoot("lsp-outcwd-marker-outside-");
|
|
41
|
+
mkdirSync(join(outside, ".git"), { recursive: true });
|
|
42
|
+
mkdirSync(join(outside, "src"), { recursive: true });
|
|
43
|
+
const outsideFile = join(outside, "src", "probe.ts");
|
|
44
|
+
writeFileSync(outsideFile, "export const value = 1;\n");
|
|
45
|
+
|
|
46
|
+
const workspace = runWithRequestContext(createStandaloneMcpRequestContext({ cwd: root }), () =>
|
|
47
|
+
findWorkspaceRoot(outsideFile),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(workspace).toBe(realpathSync(outside));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("#given an outside file without any marker #when inferring workspace #then falls back to its own directory", () => {
|
|
54
|
+
const root = tempRoot("lsp-outcwd-nomarker-root-");
|
|
55
|
+
const outside = tempRoot("lsp-outcwd-nomarker-outside-");
|
|
56
|
+
const outsideFile = join(outside, "probe.ts");
|
|
57
|
+
writeFileSync(outsideFile, "export const value = 1;\n");
|
|
58
|
+
|
|
59
|
+
const workspace = runWithRequestContext(createStandaloneMcpRequestContext({ cwd: root }), () =>
|
|
60
|
+
findWorkspaceRoot(outsideFile),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
expect(workspace).toBe(realpathSync(outside));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("#given an outside file #when resolving a mutating path #then still rejects to keep writes confined", () => {
|
|
67
|
+
const root = tempRoot("lsp-outcwd-write-root-");
|
|
68
|
+
const outside = tempRoot("lsp-outcwd-write-outside-");
|
|
69
|
+
const outsideFile = join(outside, "probe.ts");
|
|
70
|
+
writeFileSync(outsideFile, "export const value = 1;\n");
|
|
71
|
+
|
|
72
|
+
expect(() =>
|
|
73
|
+
runWithRequestContext(createStandaloneMcpRequestContext({ cwd: root }), () =>
|
|
74
|
+
resolvePathInsideContext(outsideFile),
|
|
75
|
+
),
|
|
76
|
+
).toThrow(LspInvalidPathError);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -36,17 +36,17 @@ describe("LSP client path confinement", () => {
|
|
|
36
36
|
expect(workspace).toBe(realpathSync(root));
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
it("#given an absolute file outside context cwd #when resolving #then
|
|
39
|
+
it("#given an absolute file outside context cwd #when resolving #then infers the file's own project root", () => {
|
|
40
40
|
const root = tempRoot("lsp-client-wrapper-cwd-");
|
|
41
41
|
const outside = tempRoot("lsp-client-wrapper-outside-");
|
|
42
42
|
mkdirSync(join(outside, ".git"), { recursive: true });
|
|
43
43
|
writeFileSync(join(outside, "file.ts"), "export const outside = true;\n");
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
).
|
|
45
|
+
const workspace = runWithRequestContext(createStandaloneMcpRequestContext({ cwd: root }), () =>
|
|
46
|
+
findWorkspaceRoot(join(outside, "file.ts")),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(workspace).toBe(realpathSync(outside));
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
it("#given a symlink inside cwd that points outside #when resolving read path #then keeps lexical path and cwd root", () => {
|
|
@@ -16,11 +16,11 @@ import {
|
|
|
16
16
|
LspServerLookupError,
|
|
17
17
|
} from "./errors.js";
|
|
18
18
|
import { getLspManager, type LspManager } from "./manager.js";
|
|
19
|
+
import { findWorkspaceRootOutsideContext } from "./outside-context-workspace.js";
|
|
19
20
|
import { loadInstallDecision } from "./server-install-state.js";
|
|
20
21
|
import { findServerForExtension } from "./server-resolution.js";
|
|
21
22
|
import type { ServerLookupResult } from "./types.js";
|
|
22
|
-
|
|
23
|
-
const WORKSPACE_MARKERS = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
|
|
23
|
+
import { WORKSPACE_MARKERS } from "./workspace-markers.js";
|
|
24
24
|
|
|
25
25
|
export function isDirectoryPath(filePath: string): boolean {
|
|
26
26
|
try {
|
|
@@ -39,6 +39,8 @@ export function findWorkspaceRoot(filePath: string): string {
|
|
|
39
39
|
dir = dirname(dir);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
if (!isPathInside(cwd, abs)) return findWorkspaceRootOutsideContext(dir);
|
|
43
|
+
|
|
42
44
|
const fallbackRoot = nearestExistingDirectoryInsideContext(dir, cwd) ?? cwd;
|
|
43
45
|
while (isPathInside(cwd, dir)) {
|
|
44
46
|
const canonicalDir = existingDirectoryInsideContext(dir, cwd);
|
|
@@ -64,10 +66,7 @@ export function resolveReadablePathInsideContext(filePath: string): string {
|
|
|
64
66
|
const rebased = rebaseThroughCanonicalAncestor(abs, cwd);
|
|
65
67
|
if (rebased !== undefined) return rebased;
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
if (isPathInside(cwd, canonical)) return canonical;
|
|
69
|
-
|
|
70
|
-
throw new LspInvalidPathError(`LSP file path must be inside request cwd: ${filePath}`);
|
|
69
|
+
return canonicalizeExistingOrNearestAncestor(abs);
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
export function resolvePathInsideContext(filePath: string): string {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { WORKSPACE_MARKERS } from "./workspace-markers.js";
|
|
5
|
+
|
|
6
|
+
/** Read-only tools accept out-of-cwd files, so cwd cannot bound this search; the fallback keeps the server scoped to the file's own directory. */
|
|
7
|
+
export function findWorkspaceRootOutsideContext(directory: string): string {
|
|
8
|
+
const marked = nearestMarkedAncestor(directory);
|
|
9
|
+
if (marked !== undefined) return marked;
|
|
10
|
+
return realpathSync(nearestExistingAncestor(directory));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function nearestMarkedAncestor(directory: string): string | undefined {
|
|
14
|
+
let current = directory;
|
|
15
|
+
for (;;) {
|
|
16
|
+
if (existsSync(current) && WORKSPACE_MARKERS.some((marker) => existsSync(join(current, marker)))) {
|
|
17
|
+
return realpathSync(current);
|
|
18
|
+
}
|
|
19
|
+
const parent = dirname(current);
|
|
20
|
+
if (parent === current) return undefined;
|
|
21
|
+
current = parent;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function nearestExistingAncestor(directory: string): string {
|
|
26
|
+
let current = directory;
|
|
27
|
+
while (!existsSync(current)) {
|
|
28
|
+
const parent = dirname(current);
|
|
29
|
+
if (parent === current) return current;
|
|
30
|
+
current = parent;
|
|
31
|
+
}
|
|
32
|
+
return current;
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const WORKSPACE_MARKERS = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"] as const;
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import { argv, stderr } from "node:process";
|
|
5
5
|
|
|
6
6
|
// src/proxy.ts
|
|
7
|
-
import { existsSync as
|
|
8
|
-
import { basename as basename4, delimiter as delimiter4, dirname as
|
|
7
|
+
import { existsSync as existsSync13, realpathSync as realpathSync6 } from "node:fs";
|
|
8
|
+
import { basename as basename4, delimiter as delimiter4, dirname as dirname10, isAbsolute as isAbsolute5 } from "node:path";
|
|
9
9
|
|
|
10
10
|
// ../mcp-stdio-core/src/record.ts
|
|
11
11
|
function isPlainRecord(value) {
|
|
@@ -315,8 +315,8 @@ function createIdleTimer(idleTimeoutMs, log, onTimeout) {
|
|
|
315
315
|
};
|
|
316
316
|
}
|
|
317
317
|
// ../lsp-core/src/lsp/client-wrapper.ts
|
|
318
|
-
import { existsSync as
|
|
319
|
-
import { basename as basename3, dirname as
|
|
318
|
+
import { existsSync as existsSync10, realpathSync as realpathSync5, statSync as statSync3 } from "node:fs";
|
|
319
|
+
import { basename as basename3, dirname as dirname7, join as join5, resolve as resolve7 } from "node:path";
|
|
320
320
|
|
|
321
321
|
// ../lsp-core/src/request-context.ts
|
|
322
322
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -3500,15 +3500,52 @@ async function disposeDefaultLspManager() {
|
|
|
3500
3500
|
}
|
|
3501
3501
|
}
|
|
3502
3502
|
|
|
3503
|
+
// ../lsp-core/src/lsp/outside-context-workspace.ts
|
|
3504
|
+
import { existsSync as existsSync6, realpathSync as realpathSync4 } from "node:fs";
|
|
3505
|
+
import { dirname as dirname5, join as join3 } from "node:path";
|
|
3506
|
+
|
|
3507
|
+
// ../lsp-core/src/lsp/workspace-markers.ts
|
|
3508
|
+
var WORKSPACE_MARKERS = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
|
|
3509
|
+
|
|
3510
|
+
// ../lsp-core/src/lsp/outside-context-workspace.ts
|
|
3511
|
+
function findWorkspaceRootOutsideContext(directory) {
|
|
3512
|
+
const marked = nearestMarkedAncestor(directory);
|
|
3513
|
+
if (marked !== undefined)
|
|
3514
|
+
return marked;
|
|
3515
|
+
return realpathSync4(nearestExistingAncestor(directory));
|
|
3516
|
+
}
|
|
3517
|
+
function nearestMarkedAncestor(directory) {
|
|
3518
|
+
let current = directory;
|
|
3519
|
+
for (;; ) {
|
|
3520
|
+
if (existsSync6(current) && WORKSPACE_MARKERS.some((marker) => existsSync6(join3(current, marker)))) {
|
|
3521
|
+
return realpathSync4(current);
|
|
3522
|
+
}
|
|
3523
|
+
const parent = dirname5(current);
|
|
3524
|
+
if (parent === current)
|
|
3525
|
+
return;
|
|
3526
|
+
current = parent;
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
function nearestExistingAncestor(directory) {
|
|
3530
|
+
let current = directory;
|
|
3531
|
+
while (!existsSync6(current)) {
|
|
3532
|
+
const parent = dirname5(current);
|
|
3533
|
+
if (parent === current)
|
|
3534
|
+
return current;
|
|
3535
|
+
current = parent;
|
|
3536
|
+
}
|
|
3537
|
+
return current;
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3503
3540
|
// ../lsp-core/src/lsp/server-install-state.ts
|
|
3504
|
-
import { existsSync as
|
|
3505
|
-
import { dirname as
|
|
3541
|
+
import { existsSync as existsSync7, mkdirSync, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3542
|
+
import { dirname as dirname6 } from "node:path";
|
|
3506
3543
|
function getInstallDecisionsPath() {
|
|
3507
3544
|
return lspRequestContext().installDecisionsPath;
|
|
3508
3545
|
}
|
|
3509
3546
|
function loadInstallDecisions() {
|
|
3510
3547
|
const path = getInstallDecisionsPath();
|
|
3511
|
-
if (!
|
|
3548
|
+
if (!existsSync7(path))
|
|
3512
3549
|
return {};
|
|
3513
3550
|
try {
|
|
3514
3551
|
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
@@ -3530,7 +3567,7 @@ function isInstallDecision(value) {
|
|
|
3530
3567
|
}
|
|
3531
3568
|
function writeInstallDecisions(decisions) {
|
|
3532
3569
|
const path = getInstallDecisionsPath();
|
|
3533
|
-
mkdirSync(
|
|
3570
|
+
mkdirSync(dirname6(path), { recursive: true });
|
|
3534
3571
|
const tmpPath = `${path}.tmp`;
|
|
3535
3572
|
writeFileSync2(tmpPath, `${JSON.stringify(decisions, null, 2)}
|
|
3536
3573
|
`, "utf8");
|
|
@@ -3549,7 +3586,7 @@ function isRecord5(value) {
|
|
|
3549
3586
|
}
|
|
3550
3587
|
|
|
3551
3588
|
// ../lsp-core/src/lsp/config-loader.ts
|
|
3552
|
-
import { existsSync as
|
|
3589
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4 } from "node:fs";
|
|
3553
3590
|
|
|
3554
3591
|
// ../lsp-core/src/lsp/server-definitions.ts
|
|
3555
3592
|
var LSP_INSTALL_HINTS = {
|
|
@@ -3706,7 +3743,7 @@ function getUserConfigPath() {
|
|
|
3706
3743
|
return lspRequestContext().userConfigPath;
|
|
3707
3744
|
}
|
|
3708
3745
|
function loadJsonFile(path) {
|
|
3709
|
-
if (!
|
|
3746
|
+
if (!existsSync8(path))
|
|
3710
3747
|
return null;
|
|
3711
3748
|
try {
|
|
3712
3749
|
const parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
@@ -3894,8 +3931,8 @@ function getDisabledServerIds() {
|
|
|
3894
3931
|
}
|
|
3895
3932
|
|
|
3896
3933
|
// ../lsp-core/src/lsp/server-installation.ts
|
|
3897
|
-
import { existsSync as
|
|
3898
|
-
import { delimiter as delimiter3, join as
|
|
3934
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
3935
|
+
import { delimiter as delimiter3, join as join4 } from "node:path";
|
|
3899
3936
|
function isServerInstalled(command, _workingDirectory) {
|
|
3900
3937
|
if (command.length === 0)
|
|
3901
3938
|
return false;
|
|
@@ -3903,7 +3940,7 @@ function isServerInstalled(command, _workingDirectory) {
|
|
|
3903
3940
|
if (!cmd)
|
|
3904
3941
|
return false;
|
|
3905
3942
|
if (cmd.includes("/") || cmd.includes("\\")) {
|
|
3906
|
-
if (
|
|
3943
|
+
if (existsSync9(cmd))
|
|
3907
3944
|
return true;
|
|
3908
3945
|
}
|
|
3909
3946
|
const isWindows = process.platform === "win32";
|
|
@@ -3924,7 +3961,7 @@ function isServerInstalled(command, _workingDirectory) {
|
|
|
3924
3961
|
const paths = pathEnv.split(delimiter3);
|
|
3925
3962
|
for (const p of paths) {
|
|
3926
3963
|
for (const suffix of exts) {
|
|
3927
|
-
if (
|
|
3964
|
+
if (existsSync9(join4(p, cmd + suffix))) {
|
|
3928
3965
|
return true;
|
|
3929
3966
|
}
|
|
3930
3967
|
}
|
|
@@ -4020,7 +4057,6 @@ function getAllServers() {
|
|
|
4020
4057
|
}
|
|
4021
4058
|
|
|
4022
4059
|
// ../lsp-core/src/lsp/client-wrapper.ts
|
|
4023
|
-
var WORKSPACE_MARKERS = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
|
|
4024
4060
|
function isDirectoryPath(filePath) {
|
|
4025
4061
|
try {
|
|
4026
4062
|
return statSync3(filePath).isDirectory();
|
|
@@ -4033,21 +4069,23 @@ function findWorkspaceRoot(filePath) {
|
|
|
4033
4069
|
const abs = resolveReadablePathInsideContext(filePath);
|
|
4034
4070
|
let dir = abs;
|
|
4035
4071
|
if (!isDirectoryPath(dir)) {
|
|
4036
|
-
dir =
|
|
4072
|
+
dir = dirname7(dir);
|
|
4037
4073
|
}
|
|
4074
|
+
if (!isPathInside(cwd, abs))
|
|
4075
|
+
return findWorkspaceRootOutsideContext(dir);
|
|
4038
4076
|
const fallbackRoot = nearestExistingDirectoryInsideContext(dir, cwd) ?? cwd;
|
|
4039
4077
|
while (isPathInside(cwd, dir)) {
|
|
4040
4078
|
const canonicalDir = existingDirectoryInsideContext(dir, cwd);
|
|
4041
4079
|
if (canonicalDir !== undefined) {
|
|
4042
4080
|
for (const marker of WORKSPACE_MARKERS) {
|
|
4043
|
-
if (
|
|
4081
|
+
if (existsSync10(join5(dir, marker))) {
|
|
4044
4082
|
return canonicalDir;
|
|
4045
4083
|
}
|
|
4046
4084
|
}
|
|
4047
4085
|
}
|
|
4048
4086
|
if (dir === cwd)
|
|
4049
4087
|
break;
|
|
4050
|
-
dir =
|
|
4088
|
+
dir = dirname7(dir);
|
|
4051
4089
|
}
|
|
4052
4090
|
return fallbackRoot;
|
|
4053
4091
|
}
|
|
@@ -4059,10 +4097,7 @@ function resolveReadablePathInsideContext(filePath) {
|
|
|
4059
4097
|
const rebased = rebaseThroughCanonicalAncestor(abs, cwd);
|
|
4060
4098
|
if (rebased !== undefined)
|
|
4061
4099
|
return rebased;
|
|
4062
|
-
|
|
4063
|
-
if (isPathInside(cwd, canonical))
|
|
4064
|
-
return canonical;
|
|
4065
|
-
throw new LspInvalidPathError(`LSP file path must be inside request cwd: ${filePath}`);
|
|
4100
|
+
return canonicalizeExistingOrNearestAncestor(abs);
|
|
4066
4101
|
}
|
|
4067
4102
|
function resolvePathInsideContext(filePath) {
|
|
4068
4103
|
const cwd = contextCwd();
|
|
@@ -4077,12 +4112,12 @@ function rebaseThroughCanonicalAncestor(path, cwd) {
|
|
|
4077
4112
|
let current = path;
|
|
4078
4113
|
const suffix = [];
|
|
4079
4114
|
while (true) {
|
|
4080
|
-
if (
|
|
4081
|
-
const canonical =
|
|
4115
|
+
if (existsSync10(current)) {
|
|
4116
|
+
const canonical = realpathSync5(current);
|
|
4082
4117
|
if (isPathInside(cwd, canonical))
|
|
4083
|
-
return suffix.length === 0 ? canonical :
|
|
4118
|
+
return suffix.length === 0 ? canonical : join5(canonical, ...suffix);
|
|
4084
4119
|
}
|
|
4085
|
-
const parent =
|
|
4120
|
+
const parent = dirname7(current);
|
|
4086
4121
|
if (parent === current)
|
|
4087
4122
|
return;
|
|
4088
4123
|
suffix.unshift(basename3(current));
|
|
@@ -4090,9 +4125,9 @@ function rebaseThroughCanonicalAncestor(path, cwd) {
|
|
|
4090
4125
|
}
|
|
4091
4126
|
}
|
|
4092
4127
|
function existingDirectoryInsideContext(directory, cwd) {
|
|
4093
|
-
if (!
|
|
4128
|
+
if (!existsSync10(directory))
|
|
4094
4129
|
return;
|
|
4095
|
-
const canonical =
|
|
4130
|
+
const canonical = realpathSync5(directory);
|
|
4096
4131
|
if (!statSync3(canonical).isDirectory())
|
|
4097
4132
|
return;
|
|
4098
4133
|
return isPathInside(cwd, canonical) ? canonical : undefined;
|
|
@@ -4105,7 +4140,7 @@ function nearestExistingDirectoryInsideContext(directory, cwd) {
|
|
|
4105
4140
|
return canonical;
|
|
4106
4141
|
if (current === cwd)
|
|
4107
4142
|
return;
|
|
4108
|
-
current =
|
|
4143
|
+
current = dirname7(current);
|
|
4109
4144
|
}
|
|
4110
4145
|
return;
|
|
4111
4146
|
}
|
|
@@ -4223,8 +4258,8 @@ async function withLspClient(filePath, fn, toolName, options = {}) {
|
|
|
4223
4258
|
}
|
|
4224
4259
|
|
|
4225
4260
|
// ../lsp-core/src/lsp/directory-diagnostics.ts
|
|
4226
|
-
import { existsSync as
|
|
4227
|
-
import { join as
|
|
4261
|
+
import { existsSync as existsSync11, lstatSync as lstatSync4, readdirSync as readdirSync3 } from "node:fs";
|
|
4262
|
+
import { join as join6, resolve as resolve8 } from "node:path";
|
|
4228
4263
|
|
|
4229
4264
|
// ../lsp-core/src/lsp/formatters.ts
|
|
4230
4265
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
@@ -4354,7 +4389,7 @@ function collectFilesWithExtension(dir, extension, maxFiles) {
|
|
|
4354
4389
|
for (const entry of entries) {
|
|
4355
4390
|
if (files.length >= maxFiles)
|
|
4356
4391
|
return;
|
|
4357
|
-
const fullPath =
|
|
4392
|
+
const fullPath = join6(currentDir, entry);
|
|
4358
4393
|
let stat;
|
|
4359
4394
|
try {
|
|
4360
4395
|
stat = lstatSync4(fullPath);
|
|
@@ -4380,7 +4415,7 @@ async function aggregateDiagnosticsForDirectory(directory, extension, severity,
|
|
|
4380
4415
|
throw new LspInvalidPathError(`Extension must start with a dot (e.g., ".ts", not "${extension}"). Use ".${extension}" instead.`);
|
|
4381
4416
|
}
|
|
4382
4417
|
const absDir = resolve8(options.workspaceRoot ?? contextCwd(), directory);
|
|
4383
|
-
if (!
|
|
4418
|
+
if (!existsSync11(absDir)) {
|
|
4384
4419
|
throw new LspInvalidPathError(`Directory does not exist: ${absDir}`);
|
|
4385
4420
|
}
|
|
4386
4421
|
const serverResult = options.server === undefined ? findServerForExtension(extension) : { status: "found", server: options.server };
|
|
@@ -4467,7 +4502,7 @@ async function aggregateDiagnosticsForDirectory(directory, extension, severity,
|
|
|
4467
4502
|
|
|
4468
4503
|
// ../lsp-core/src/lsp/infer-extension.ts
|
|
4469
4504
|
import { lstatSync as lstatSync5, readdirSync as readdirSync4 } from "node:fs";
|
|
4470
|
-
import { join as
|
|
4505
|
+
import { join as join7 } from "node:path";
|
|
4471
4506
|
var SKIP_DIRECTORIES2 = new Set(["node_modules", ".git", "dist", "build", ".next", "out"]);
|
|
4472
4507
|
var MAX_SCAN_ENTRIES = 500;
|
|
4473
4508
|
function inferExtensionFromDirectory(directory) {
|
|
@@ -4485,7 +4520,7 @@ function inferExtensionFromDirectory(directory) {
|
|
|
4485
4520
|
for (const entry of entries) {
|
|
4486
4521
|
if (scanned >= MAX_SCAN_ENTRIES)
|
|
4487
4522
|
return;
|
|
4488
|
-
const fullPath =
|
|
4523
|
+
const fullPath = join7(dir, entry);
|
|
4489
4524
|
let stat;
|
|
4490
4525
|
try {
|
|
4491
4526
|
stat = lstatSync5(fullPath);
|
|
@@ -5167,7 +5202,7 @@ function requestedProtocolVersion(params) {
|
|
|
5167
5202
|
// src/daemon-client.ts
|
|
5168
5203
|
import { connect } from "node:net";
|
|
5169
5204
|
import { homedir as homedir3 } from "node:os";
|
|
5170
|
-
import { join as
|
|
5205
|
+
import { join as join8 } from "node:path";
|
|
5171
5206
|
|
|
5172
5207
|
// src/daemon-request-error.ts
|
|
5173
5208
|
class DaemonRequestError extends Error {
|
|
@@ -5246,9 +5281,9 @@ function errorText(error) {
|
|
|
5246
5281
|
|
|
5247
5282
|
// src/ensure-daemon.ts
|
|
5248
5283
|
import { spawn as spawn2 } from "node:child_process";
|
|
5249
|
-
import { closeSync as closeSync2, existsSync as
|
|
5284
|
+
import { closeSync as closeSync2, existsSync as existsSync12, mkdirSync as mkdirSync3, openSync as openSync2, writeSync as writeSync2 } from "node:fs";
|
|
5250
5285
|
import { Socket } from "node:net";
|
|
5251
|
-
import { dirname as
|
|
5286
|
+
import { dirname as dirname9, isAbsolute as isAbsolute4 } from "node:path";
|
|
5252
5287
|
import { argv0, execPath } from "node:process";
|
|
5253
5288
|
|
|
5254
5289
|
// src/ipc-protocol.ts
|
|
@@ -5266,7 +5301,7 @@ import {
|
|
|
5266
5301
|
unlinkSync,
|
|
5267
5302
|
writeSync
|
|
5268
5303
|
} from "node:fs";
|
|
5269
|
-
import { dirname as
|
|
5304
|
+
import { dirname as dirname8 } from "node:path";
|
|
5270
5305
|
var OMO_DAEMON_PROTOCOL_VERSION = 1;
|
|
5271
5306
|
var AUTH_ERROR_CODE = -32001;
|
|
5272
5307
|
var PROTOCOL_ERROR_CODE = -32002;
|
|
@@ -5376,7 +5411,7 @@ function setPrivateFileMode(path) {
|
|
|
5376
5411
|
chmodSync(path, 384);
|
|
5377
5412
|
}
|
|
5378
5413
|
function createAuthToken(paths) {
|
|
5379
|
-
ensurePrivateDirectory(
|
|
5414
|
+
ensurePrivateDirectory(dirname8(paths.auth));
|
|
5380
5415
|
const token = randomBytes(AUTH_TOKEN_BYTES).toString("base64url");
|
|
5381
5416
|
let fd;
|
|
5382
5417
|
try {
|
|
@@ -5716,7 +5751,7 @@ function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS, signal) {
|
|
|
5716
5751
|
});
|
|
5717
5752
|
}
|
|
5718
5753
|
function spawnDaemonProcess(paths) {
|
|
5719
|
-
mkdirSync3(
|
|
5754
|
+
mkdirSync3(dirname9(paths.log), { recursive: true });
|
|
5720
5755
|
const logFd = openSync2(paths.log, "a");
|
|
5721
5756
|
try {
|
|
5722
5757
|
const child = spawn2(resolveDaemonNodeExecutable(), [paths.cliPath, "daemon"], {
|
|
@@ -5735,7 +5770,7 @@ function spawnDaemonProcess(paths) {
|
|
|
5735
5770
|
throw error;
|
|
5736
5771
|
}
|
|
5737
5772
|
}
|
|
5738
|
-
function resolveDaemonNodeExecutable(cachedExecPath = execPath, originalArgv0 = argv0, pathExists =
|
|
5773
|
+
function resolveDaemonNodeExecutable(cachedExecPath = execPath, originalArgv0 = argv0, pathExists = existsSync12) {
|
|
5739
5774
|
if (pathExists(cachedExecPath))
|
|
5740
5775
|
return cachedExecPath;
|
|
5741
5776
|
if (isAbsolute4(originalArgv0) && pathExists(originalArgv0))
|
|
@@ -5955,9 +5990,9 @@ function currentRequestContext(env = process.env) {
|
|
|
5955
5990
|
const home = env["HOME"] ?? homedir3();
|
|
5956
5991
|
return parseLspRequestContext({
|
|
5957
5992
|
cwd,
|
|
5958
|
-
projectConfigPaths: [
|
|
5959
|
-
userConfigPath:
|
|
5960
|
-
installDecisionsPath:
|
|
5993
|
+
projectConfigPaths: [join8(cwd, ".codex", "lsp-client.json")],
|
|
5994
|
+
userConfigPath: join8(home, ".codex", "lsp-client.json"),
|
|
5995
|
+
installDecisionsPath: join8(home, ".codex", "lsp-install-decisions.json"),
|
|
5961
5996
|
capabilities: { installDecisionTool: true }
|
|
5962
5997
|
});
|
|
5963
5998
|
}
|
|
@@ -6212,18 +6247,18 @@ function canonicalizePathList(value) {
|
|
|
6212
6247
|
return value.split(delimiter4).map((entry) => canonicalizePath(entry) ?? entry).join(delimiter4);
|
|
6213
6248
|
}
|
|
6214
6249
|
function canonicalizePath(value) {
|
|
6215
|
-
if (value === undefined || !isAbsolute5(value) || !
|
|
6250
|
+
if (value === undefined || !isAbsolute5(value) || !existsSync13(value))
|
|
6216
6251
|
return value;
|
|
6217
|
-
return
|
|
6252
|
+
return realpathSync6(value);
|
|
6218
6253
|
}
|
|
6219
6254
|
function projectRootFromOpenCodeConfigPath(path2) {
|
|
6220
6255
|
if (basename4(path2) !== "lsp.json" && basename4(path2) !== "lsp-client.json")
|
|
6221
6256
|
return;
|
|
6222
|
-
const configDir =
|
|
6257
|
+
const configDir = dirname10(path2);
|
|
6223
6258
|
const configDirName = basename4(configDir);
|
|
6224
6259
|
if (configDirName !== ".opencode" && configDirName !== ".omo")
|
|
6225
6260
|
return;
|
|
6226
|
-
return
|
|
6261
|
+
return dirname10(configDir);
|
|
6227
6262
|
}
|
|
6228
6263
|
|
|
6229
6264
|
// src/daemon-server.ts
|
|
@@ -6232,12 +6267,12 @@ import { createServer as createServer2 } from "node:net";
|
|
|
6232
6267
|
|
|
6233
6268
|
// src/ownership.ts
|
|
6234
6269
|
import { randomUUID } from "node:crypto";
|
|
6235
|
-
import { existsSync as
|
|
6236
|
-
import { dirname as
|
|
6270
|
+
import { existsSync as existsSync14, lstatSync as lstatSync7, readFileSync as readFileSync7, statSync as statSync5, unlinkSync as unlinkSync3 } from "node:fs";
|
|
6271
|
+
import { dirname as dirname12 } from "node:path";
|
|
6237
6272
|
|
|
6238
6273
|
// src/lock.ts
|
|
6239
6274
|
import { closeSync as closeSync3, mkdirSync as mkdirSync4, openSync as openSync3, readFileSync as readFileSync6, unlinkSync as unlinkSync2, writeSync as writeSync3 } from "node:fs";
|
|
6240
|
-
import { dirname as
|
|
6275
|
+
import { dirname as dirname11 } from "node:path";
|
|
6241
6276
|
function isProcessAlive2(pid) {
|
|
6242
6277
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
6243
6278
|
return false;
|
|
@@ -6259,7 +6294,7 @@ function readLockPid(lockPath) {
|
|
|
6259
6294
|
}
|
|
6260
6295
|
}
|
|
6261
6296
|
function tryAcquireLock(lockPath, ownerPid = process.pid) {
|
|
6262
|
-
mkdirSync4(
|
|
6297
|
+
mkdirSync4(dirname11(lockPath), { recursive: true });
|
|
6263
6298
|
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
6264
6299
|
const handle = writeLockFile(lockPath, ownerPid);
|
|
6265
6300
|
if (handle)
|
|
@@ -6340,7 +6375,7 @@ async function acquireStartupLease(paths, pingOwner) {
|
|
|
6340
6375
|
function ensureDaemonDirectories(paths) {
|
|
6341
6376
|
ensurePrivateDirectory(paths.dir);
|
|
6342
6377
|
if (process.platform !== "win32")
|
|
6343
|
-
ensurePrivateDirectory(
|
|
6378
|
+
ensurePrivateDirectory(dirname12(paths.socket));
|
|
6344
6379
|
}
|
|
6345
6380
|
function readDaemonOwner(paths) {
|
|
6346
6381
|
try {
|
|
@@ -6420,7 +6455,7 @@ async function validateExistingOwner(paths, pingOwner) {
|
|
|
6420
6455
|
throw new DaemonStartupDeferredError("reachable_owner_mismatch");
|
|
6421
6456
|
}
|
|
6422
6457
|
function cleanupDeadOwner(paths, owner) {
|
|
6423
|
-
if (process.platform !== "win32" &&
|
|
6458
|
+
if (process.platform !== "win32" && existsSync14(owner.endpoint.path)) {
|
|
6424
6459
|
const stat = lstatSync7(owner.endpoint.path);
|
|
6425
6460
|
if (stat.isSocket())
|
|
6426
6461
|
unlinkSync3(owner.endpoint.path);
|
|
@@ -6471,7 +6506,7 @@ function parseEndpoint(value) {
|
|
|
6471
6506
|
// src/version-reap.ts
|
|
6472
6507
|
import { execFile } from "node:child_process";
|
|
6473
6508
|
import { readdir, readFile, rm } from "node:fs/promises";
|
|
6474
|
-
import { basename as basename5, dirname as
|
|
6509
|
+
import { basename as basename5, dirname as dirname13, join as join9 } from "node:path";
|
|
6475
6510
|
var TERM_GRACE_MS = 5000;
|
|
6476
6511
|
var KILL_GRACE_MS = 1000;
|
|
6477
6512
|
var VERSION_ENTRY_PATTERN = /^v([A-Za-z0-9][A-Za-z0-9._+-]{0,127})$/;
|
|
@@ -6492,7 +6527,7 @@ async function attestDaemonCliProcess(pid, platform, deps = {}) {
|
|
|
6492
6527
|
return isNodeCliDaemonCommand(command.trim());
|
|
6493
6528
|
}
|
|
6494
6529
|
async function reapStaleDaemonVersions(ownPaths, deps = {}) {
|
|
6495
|
-
const baseDir =
|
|
6530
|
+
const baseDir = dirname13(ownPaths.dir);
|
|
6496
6531
|
const platform = deps.platform ?? process.platform;
|
|
6497
6532
|
const isAlive = deps.isAlive ?? isProcessAlive2;
|
|
6498
6533
|
const attest = deps.attest ?? ((pid) => attestDaemonCliProcess(pid, platform));
|
|
@@ -6511,7 +6546,7 @@ async function reapStaleDaemonVersions(ownPaths, deps = {}) {
|
|
|
6511
6546
|
const version = parseVersionEntry(entry.name);
|
|
6512
6547
|
if (version === null || !entry.isDirectory())
|
|
6513
6548
|
continue;
|
|
6514
|
-
const versionDir =
|
|
6549
|
+
const versionDir = join9(baseDir, entry.name);
|
|
6515
6550
|
const siblingPaths = daemonPaths({ [OMO_LSP_DAEMON_DIR]: baseDir }, { cliPath: ownPaths.cliPath, version });
|
|
6516
6551
|
results.push(await reapOneVersion({
|
|
6517
6552
|
version,
|
|
@@ -6535,7 +6570,7 @@ async function reapOneVersion(context) {
|
|
|
6535
6570
|
const { version, versionDir } = context;
|
|
6536
6571
|
const owner = context.readOwner(context.siblingPaths);
|
|
6537
6572
|
if (!owner) {
|
|
6538
|
-
const lockPid = readLockPid(
|
|
6573
|
+
const lockPid = readLockPid(join9(versionDir, "daemon.lock"));
|
|
6539
6574
|
if (lockPid !== null && context.isAlive(lockPid)) {
|
|
6540
6575
|
context.log(`reap: sparing v${version}: owner metadata missing but daemon.lock is held by live pid ${lockPid}`);
|
|
6541
6576
|
return { version, status: "spared", reason: `owner metadata missing but lock held by live pid ${lockPid}` };
|