oh-my-opencode 5.0.0-beta.80 → 5.0.0-beta.81
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 +6830 -3346
- package/dist/cli-node/index.js +6830 -3346
- package/dist/features/team-mode/tools/lifecycle-inline-spec.d.ts +1 -1
- package/dist/index.js +4304 -1671
- package/dist/oh-my-opencode.schema.json +3 -7
- package/dist/skills/frontend/SKILL.md +1 -0
- package/dist/skills/frontend/references/design/README.md +3 -2
- package/dist/skills/frontend/references/design/design-system-architecture.md +1 -0
- package/dist/skills/frontend/references/perfection/README.md +1 -0
- package/dist/skills/ultimate-browsing/engine/templates/package.json +1 -1
- package/dist/tui.js +2546 -1338
- package/package.json +31 -29
- package/packages/lsp-daemon/dist/cli.js +591 -555
- package/packages/lsp-daemon/dist/client.js +34 -5
- package/packages/lsp-daemon/dist/daemon-client.d.ts +2 -0
- package/packages/lsp-daemon/dist/daemon-client.js +20 -3
- package/packages/lsp-daemon/dist/ensure-daemon.js +17 -3
- package/packages/lsp-daemon/dist/index.js +34 -5
- package/packages/lsp-daemon/package.json +4 -3
- package/packages/lsp-tools-mcp/package.json +4 -3
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +2537 -1310
- 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/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +5 -4
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +2 -2
- 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 +5 -4
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +34 -5
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +5 -4
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +5 -4
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +4 -3
- package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +4944 -2767
- package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +4944 -2767
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +5 -4
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +5 -4
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +5 -4
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +5 -4
- 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-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/post-tool-use-recording-spawn-admission.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-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-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 +481 -322
- package/packages/omo-codex/plugin/package.json +5 -1
- package/packages/omo-codex/plugin/shared/package.json +1 -1
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +1 -0
- package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +3 -2
- package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +1 -0
- package/packages/omo-codex/plugin/skills/frontend/references/perfection/README.md +1 -0
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/package.json +1 -1
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +7594 -4136
- package/packages/shared-skills/skills/frontend/SKILL.md +1 -0
- package/packages/shared-skills/skills/frontend/references/design/README.md +3 -2
- package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +1 -0
- package/packages/shared-skills/skills/frontend/references/perfection/README.md +1 -0
- package/packages/shared-skills/skills/ultimate-browsing/engine/templates/package.json +1 -1
|
@@ -3,14 +3,450 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { argv, stderr } from "node:process";
|
|
5
5
|
|
|
6
|
-
// src/
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
// src/ownership.ts
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { existsSync, lstatSync as lstatSync2, readFileSync as readFileSync3, statSync, unlinkSync as unlinkSync3 } from "node:fs";
|
|
9
|
+
import { dirname as dirname3 } from "node:path";
|
|
10
|
+
|
|
11
|
+
// src/lock.ts
|
|
12
|
+
import { closeSync, mkdirSync, openSync, readFileSync, unlinkSync, writeSync } from "node:fs";
|
|
13
|
+
import { dirname } from "node:path";
|
|
14
|
+
function isProcessAlive(pid) {
|
|
15
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
16
|
+
return false;
|
|
17
|
+
try {
|
|
18
|
+
process.kill(pid, 0);
|
|
19
|
+
return true;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error instanceof Error)
|
|
22
|
+
return errorCode(error) === "EPERM";
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function readLockPid(lockPath) {
|
|
27
|
+
try {
|
|
28
|
+
const pid = Number.parseInt(readFileSync(lockPath, "utf8").trim(), 10);
|
|
29
|
+
return Number.isInteger(pid) ? pid : null;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function tryAcquireLock(lockPath, ownerPid = process.pid) {
|
|
35
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
36
|
+
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
37
|
+
const handle = writeLockFile(lockPath, ownerPid);
|
|
38
|
+
if (handle)
|
|
39
|
+
return handle;
|
|
40
|
+
if (!reapStaleLock(lockPath))
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function writeLockFile(lockPath, ownerPid) {
|
|
46
|
+
try {
|
|
47
|
+
const fd = openSync(lockPath, "wx", 384);
|
|
48
|
+
writeSync(fd, `${ownerPid}
|
|
49
|
+
`);
|
|
50
|
+
closeSync(fd);
|
|
51
|
+
return { release: () => unlinkQuietly(lockPath) };
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (errorCode(error) === "EEXIST")
|
|
54
|
+
return null;
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function reapStaleLock(lockPath) {
|
|
59
|
+
const pid = readLockPid(lockPath);
|
|
60
|
+
if (pid !== null && isProcessAlive(pid))
|
|
61
|
+
return false;
|
|
62
|
+
unlinkQuietly(lockPath);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
function unlinkQuietly(path) {
|
|
66
|
+
try {
|
|
67
|
+
unlinkSync(path);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (error instanceof Error)
|
|
70
|
+
return;
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function errorCode(error) {
|
|
75
|
+
if (!error || typeof error !== "object" || !("code" in error))
|
|
76
|
+
return;
|
|
77
|
+
const code = Reflect.get(error, "code");
|
|
78
|
+
return typeof code === "string" ? code : undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/ipc-protocol.ts
|
|
82
|
+
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
83
|
+
import {
|
|
84
|
+
chmodSync,
|
|
85
|
+
closeSync as closeSync2,
|
|
86
|
+
constants,
|
|
87
|
+
fchmodSync,
|
|
88
|
+
fstatSync,
|
|
89
|
+
lstatSync,
|
|
90
|
+
mkdirSync as mkdirSync2,
|
|
91
|
+
openSync as openSync2,
|
|
92
|
+
readFileSync as readFileSync2,
|
|
93
|
+
unlinkSync as unlinkSync2,
|
|
94
|
+
writeSync as writeSync2
|
|
95
|
+
} from "node:fs";
|
|
96
|
+
import { dirname as dirname2 } from "node:path";
|
|
9
97
|
|
|
10
98
|
// ../mcp-stdio-core/src/record.ts
|
|
11
99
|
function isPlainRecord(value) {
|
|
12
100
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
101
|
}
|
|
102
|
+
|
|
103
|
+
// src/ipc-protocol.ts
|
|
104
|
+
var OMO_DAEMON_PROTOCOL_VERSION = 1;
|
|
105
|
+
var AUTH_ERROR_CODE = -32001;
|
|
106
|
+
var PROTOCOL_ERROR_CODE = -32002;
|
|
107
|
+
var AUTH_TOKEN_BYTES = 32;
|
|
108
|
+
|
|
109
|
+
class UnsafePrivateDirectoryError extends Error {
|
|
110
|
+
constructor(path, reason) {
|
|
111
|
+
super(`unsafe private directory ${path}: ${reason}`);
|
|
112
|
+
this.path = path;
|
|
113
|
+
this.reason = reason;
|
|
114
|
+
this.name = "UnsafePrivateDirectoryError";
|
|
115
|
+
this.code = "unsafe_private_directory";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function authEnvelope(token) {
|
|
119
|
+
return { protocolVersion: OMO_DAEMON_PROTOCOL_VERSION, token };
|
|
120
|
+
}
|
|
121
|
+
function readAuthToken(paths) {
|
|
122
|
+
try {
|
|
123
|
+
const token = readFileSync2(paths.auth, "utf8").trim();
|
|
124
|
+
return token.length > 0 ? token : null;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error instanceof Error)
|
|
127
|
+
return null;
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function readOrCreateAuthToken(paths) {
|
|
132
|
+
const existing = readAuthToken(paths);
|
|
133
|
+
if (existing)
|
|
134
|
+
return existing;
|
|
135
|
+
return createAuthToken(paths);
|
|
136
|
+
}
|
|
137
|
+
function rotateAuthToken(paths) {
|
|
138
|
+
try {
|
|
139
|
+
unlinkSync2(paths.auth);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (!(error instanceof Error))
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
return createAuthToken(paths);
|
|
145
|
+
}
|
|
146
|
+
function authenticateMessage(raw, expectedToken) {
|
|
147
|
+
const id = jsonRpcId(raw);
|
|
148
|
+
if (!isPlainRecord(raw))
|
|
149
|
+
return authError(id);
|
|
150
|
+
const params = raw["params"];
|
|
151
|
+
if (!isPlainRecord(params))
|
|
152
|
+
return authError(id);
|
|
153
|
+
const envelope = params["_omo"];
|
|
154
|
+
if (!isPlainRecord(envelope))
|
|
155
|
+
return authError(id);
|
|
156
|
+
const protocolVersion = envelope["protocolVersion"];
|
|
157
|
+
if (protocolVersion !== OMO_DAEMON_PROTOCOL_VERSION)
|
|
158
|
+
return protocolError(id);
|
|
159
|
+
const token = envelope["token"];
|
|
160
|
+
if (typeof token !== "string" || !tokenMatches(token, expectedToken))
|
|
161
|
+
return authError(id);
|
|
162
|
+
const cleanParams = { ...params };
|
|
163
|
+
delete cleanParams["_omo"];
|
|
164
|
+
return { input: { ...raw, params: cleanParams }, id, method: typeof raw["method"] === "string" ? raw["method"] : undefined };
|
|
165
|
+
}
|
|
166
|
+
function isAuthErrorResponse(message) {
|
|
167
|
+
if (!isPlainRecord(message))
|
|
168
|
+
return false;
|
|
169
|
+
const error = message["error"];
|
|
170
|
+
if (!isPlainRecord(error))
|
|
171
|
+
return false;
|
|
172
|
+
const data = error["data"];
|
|
173
|
+
return error["code"] === AUTH_ERROR_CODE && isPlainRecord(data) && data["code"] === "daemon_authentication_failed";
|
|
174
|
+
}
|
|
175
|
+
function writePrivateFile(path, data) {
|
|
176
|
+
const fd = openSync2(path, "w", 384);
|
|
177
|
+
try {
|
|
178
|
+
writeSync2(fd, data);
|
|
179
|
+
} finally {
|
|
180
|
+
closeSync2(fd);
|
|
181
|
+
}
|
|
182
|
+
setPrivateFileMode(path);
|
|
183
|
+
}
|
|
184
|
+
function ensurePrivateDirectory(path, options = {}) {
|
|
185
|
+
try {
|
|
186
|
+
mkdirSync2(path, { recursive: true, mode: 448 });
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (errorCode2(error) !== "EEXIST")
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
if (process.platform === "win32")
|
|
192
|
+
return;
|
|
193
|
+
const before = validatePrivateDirectory(path, options);
|
|
194
|
+
const fd = openSync2(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
195
|
+
try {
|
|
196
|
+
fchmodSync(fd, 448);
|
|
197
|
+
const after = validatePrivateDirectory(path, options);
|
|
198
|
+
const openStats = fstatSync(fd);
|
|
199
|
+
if (!sameDirectory(before, after) || !sameDirectory(before, openStats)) {
|
|
200
|
+
throw new UnsafePrivateDirectoryError(path, "changed_during_chmod");
|
|
201
|
+
}
|
|
202
|
+
} finally {
|
|
203
|
+
closeSync2(fd);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function setPrivateFileMode(path) {
|
|
207
|
+
if (process.platform !== "win32")
|
|
208
|
+
chmodSync(path, 384);
|
|
209
|
+
}
|
|
210
|
+
function createAuthToken(paths) {
|
|
211
|
+
ensurePrivateDirectory(dirname2(paths.auth));
|
|
212
|
+
const token = randomBytes(AUTH_TOKEN_BYTES).toString("base64url");
|
|
213
|
+
let fd;
|
|
214
|
+
try {
|
|
215
|
+
fd = openSync2(paths.auth, "wx", 384);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (errorCode2(error) === "EEXIST") {
|
|
218
|
+
const existing = readAuthToken(paths);
|
|
219
|
+
if (existing)
|
|
220
|
+
return existing;
|
|
221
|
+
}
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
writeSync2(fd, `${token}
|
|
226
|
+
`);
|
|
227
|
+
} finally {
|
|
228
|
+
closeSync2(fd);
|
|
229
|
+
}
|
|
230
|
+
setPrivateFileMode(paths.auth);
|
|
231
|
+
return token;
|
|
232
|
+
}
|
|
233
|
+
function errorCode2(error) {
|
|
234
|
+
if (!error || typeof error !== "object" || !("code" in error))
|
|
235
|
+
return;
|
|
236
|
+
const code = Reflect.get(error, "code");
|
|
237
|
+
return typeof code === "string" ? code : undefined;
|
|
238
|
+
}
|
|
239
|
+
function validatePrivateDirectory(path, options) {
|
|
240
|
+
const stats = options.lstat ? options.lstat(path) : lstatPrivateDirectory(path);
|
|
241
|
+
if (stats.isSymbolicLink())
|
|
242
|
+
throw new UnsafePrivateDirectoryError(path, "symlink");
|
|
243
|
+
if (!stats.isDirectory())
|
|
244
|
+
throw new UnsafePrivateDirectoryError(path, "not_directory");
|
|
245
|
+
const currentUid = options.currentUid ? options.currentUid() : process.getuid?.();
|
|
246
|
+
if (currentUid !== undefined && stats.uid !== currentUid) {
|
|
247
|
+
throw new UnsafePrivateDirectoryError(path, "wrong_owner");
|
|
248
|
+
}
|
|
249
|
+
return stats;
|
|
250
|
+
}
|
|
251
|
+
function lstatPrivateDirectory(path) {
|
|
252
|
+
return lstatSync(path);
|
|
253
|
+
}
|
|
254
|
+
function sameDirectory(a, b) {
|
|
255
|
+
return a.dev === b.dev && a.ino === b.ino;
|
|
256
|
+
}
|
|
257
|
+
function tokenMatches(candidate, expected) {
|
|
258
|
+
const candidateBytes = Buffer.from(candidate);
|
|
259
|
+
const expectedBytes = Buffer.from(expected);
|
|
260
|
+
return candidateBytes.length === expectedBytes.length && timingSafeEqual(candidateBytes, expectedBytes);
|
|
261
|
+
}
|
|
262
|
+
function jsonRpcId(raw) {
|
|
263
|
+
if (!isPlainRecord(raw))
|
|
264
|
+
return null;
|
|
265
|
+
const id = raw["id"];
|
|
266
|
+
return typeof id === "string" || typeof id === "number" || id === null ? id : null;
|
|
267
|
+
}
|
|
268
|
+
function authError(id) {
|
|
269
|
+
return {
|
|
270
|
+
jsonrpc: "2.0",
|
|
271
|
+
id,
|
|
272
|
+
error: { code: AUTH_ERROR_CODE, message: "daemon authentication failed", data: { code: "daemon_authentication_failed" } }
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function protocolError(id) {
|
|
276
|
+
return {
|
|
277
|
+
jsonrpc: "2.0",
|
|
278
|
+
id,
|
|
279
|
+
error: { code: PROTOCOL_ERROR_CODE, message: "daemon protocol mismatch", data: { code: "daemon_protocol_mismatch" } }
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/ownership.ts
|
|
284
|
+
class DaemonAlreadyRunningError extends Error {
|
|
285
|
+
constructor() {
|
|
286
|
+
super(...arguments);
|
|
287
|
+
this.name = "DaemonAlreadyRunningError";
|
|
288
|
+
this.code = "daemon_already_running";
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
class DaemonStartupDeferredError extends Error {
|
|
293
|
+
constructor(reason) {
|
|
294
|
+
super(`LSP daemon startup deferred: ${reason}`);
|
|
295
|
+
this.reason = reason;
|
|
296
|
+
this.name = "DaemonStartupDeferredError";
|
|
297
|
+
this.code = "daemon_startup_deferred";
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async function acquireStartupLease(paths, pingOwner) {
|
|
301
|
+
ensureDaemonDirectories(paths);
|
|
302
|
+
const lock = tryAcquireLock(paths.lock);
|
|
303
|
+
if (!lock) {
|
|
304
|
+
const token = readAuthToken(paths);
|
|
305
|
+
if (token && await pingOwner(token))
|
|
306
|
+
throw new DaemonAlreadyRunningError("LSP daemon already running");
|
|
307
|
+
throw new DaemonStartupDeferredError("startup_lock_busy");
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
const token = await validateExistingOwner(paths, pingOwner);
|
|
311
|
+
return { lock, token, owner: newOwner(paths) };
|
|
312
|
+
} catch (error) {
|
|
313
|
+
lock.release();
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function ensureDaemonDirectories(paths) {
|
|
318
|
+
ensurePrivateDirectory(paths.dir);
|
|
319
|
+
if (process.platform !== "win32")
|
|
320
|
+
ensurePrivateDirectory(dirname3(paths.socket));
|
|
321
|
+
}
|
|
322
|
+
function readDaemonOwner(paths) {
|
|
323
|
+
try {
|
|
324
|
+
return parseOwner(JSON.parse(readFileSync3(paths.owner, "utf8")));
|
|
325
|
+
} catch (error) {
|
|
326
|
+
if (error instanceof Error)
|
|
327
|
+
return null;
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function writeDaemonOwner(paths, owner) {
|
|
332
|
+
writePrivateFile(paths.pid, `${owner.pid}
|
|
333
|
+
`);
|
|
334
|
+
writePrivateFile(paths.endpoint, owner.endpoint.path);
|
|
335
|
+
writePrivateFile(paths.owner, `${JSON.stringify(owner)}
|
|
336
|
+
`);
|
|
337
|
+
}
|
|
338
|
+
function removeDaemonMetadataForOwner(paths, owner) {
|
|
339
|
+
const current = readDaemonOwner(paths);
|
|
340
|
+
if (!current || !sameOwner(current, owner))
|
|
341
|
+
return;
|
|
342
|
+
unlinkQuietly(paths.socket);
|
|
343
|
+
unlinkQuietly(paths.pid);
|
|
344
|
+
unlinkQuietly(paths.endpoint);
|
|
345
|
+
unlinkQuietly(paths.owner);
|
|
346
|
+
}
|
|
347
|
+
function endpointIdentity(endpointPath) {
|
|
348
|
+
if (process.platform === "win32")
|
|
349
|
+
return { kind: "windows", path: endpointPath };
|
|
350
|
+
try {
|
|
351
|
+
const stats = statSync(endpointPath);
|
|
352
|
+
return { kind: "unix", path: endpointPath, dev: stats.dev, ino: stats.ino };
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (error instanceof Error)
|
|
355
|
+
return { kind: "missing", path: endpointPath };
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function sameEndpoint(a, b) {
|
|
360
|
+
if (a.kind !== b.kind || a.path !== b.path)
|
|
361
|
+
return false;
|
|
362
|
+
switch (a.kind) {
|
|
363
|
+
case "unix":
|
|
364
|
+
return b.kind === "unix" && a.dev === b.dev && a.ino === b.ino;
|
|
365
|
+
case "windows":
|
|
366
|
+
return true;
|
|
367
|
+
case "missing":
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function sameOwner(a, b) {
|
|
372
|
+
return a.pid === b.pid && a.nonce === b.nonce && sameEndpoint(a.endpoint, b.endpoint);
|
|
373
|
+
}
|
|
374
|
+
async function validateExistingOwner(paths, pingOwner) {
|
|
375
|
+
let token = readOrCreateAuthToken(paths);
|
|
376
|
+
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
377
|
+
const owner = readDaemonOwner(paths);
|
|
378
|
+
if (!owner)
|
|
379
|
+
return token;
|
|
380
|
+
const ping = await pingOwner(token);
|
|
381
|
+
if (ping && owner.nonce === ping.nonce && sameEndpoint(owner.endpoint, ping.endpoint)) {
|
|
382
|
+
throw new DaemonAlreadyRunningError("LSP daemon already running");
|
|
383
|
+
}
|
|
384
|
+
if (ping)
|
|
385
|
+
continue;
|
|
386
|
+
if (isProcessAlive(owner.pid))
|
|
387
|
+
throw new DaemonStartupDeferredError("owner_pid_live_unreachable");
|
|
388
|
+
const reread = readDaemonOwner(paths);
|
|
389
|
+
if (!reread || !sameOwner(reread, owner)) {
|
|
390
|
+
throw new DaemonStartupDeferredError("owner_changed_during_cleanup");
|
|
391
|
+
}
|
|
392
|
+
cleanupDeadOwner(paths, owner);
|
|
393
|
+
token = rotateAuthToken(paths);
|
|
394
|
+
return token;
|
|
395
|
+
}
|
|
396
|
+
throw new DaemonStartupDeferredError("reachable_owner_mismatch");
|
|
397
|
+
}
|
|
398
|
+
function cleanupDeadOwner(paths, owner) {
|
|
399
|
+
if (process.platform !== "win32" && existsSync(owner.endpoint.path)) {
|
|
400
|
+
const stat = lstatSync2(owner.endpoint.path);
|
|
401
|
+
if (stat.isSocket())
|
|
402
|
+
unlinkSync3(owner.endpoint.path);
|
|
403
|
+
}
|
|
404
|
+
unlinkQuietly(paths.pid);
|
|
405
|
+
unlinkQuietly(paths.endpoint);
|
|
406
|
+
unlinkQuietly(paths.owner);
|
|
407
|
+
}
|
|
408
|
+
function newOwner(paths) {
|
|
409
|
+
return {
|
|
410
|
+
pid: process.pid,
|
|
411
|
+
nonce: randomUUID(),
|
|
412
|
+
startedAt: new Date().toISOString(),
|
|
413
|
+
endpoint: endpointIdentity(paths.socket)
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function parseOwner(value) {
|
|
417
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
418
|
+
return null;
|
|
419
|
+
const pid = Reflect.get(value, "pid");
|
|
420
|
+
const nonce = Reflect.get(value, "nonce");
|
|
421
|
+
const startedAt = Reflect.get(value, "startedAt");
|
|
422
|
+
const endpoint = parseEndpoint(Reflect.get(value, "endpoint"));
|
|
423
|
+
if (typeof pid !== "number" || typeof nonce !== "string" || typeof startedAt !== "string" || !endpoint)
|
|
424
|
+
return null;
|
|
425
|
+
return { pid, nonce, startedAt, endpoint };
|
|
426
|
+
}
|
|
427
|
+
function parseEndpoint(value) {
|
|
428
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
429
|
+
return null;
|
|
430
|
+
const kind = Reflect.get(value, "kind");
|
|
431
|
+
const path = Reflect.get(value, "path");
|
|
432
|
+
if (typeof path !== "string")
|
|
433
|
+
return null;
|
|
434
|
+
if (kind === undefined)
|
|
435
|
+
return endpointIdentity(path);
|
|
436
|
+
if (kind === "windows")
|
|
437
|
+
return { kind, path };
|
|
438
|
+
if (kind === "missing")
|
|
439
|
+
return { kind, path };
|
|
440
|
+
const dev = Reflect.get(value, "dev");
|
|
441
|
+
const ino = Reflect.get(value, "ino");
|
|
442
|
+
if (kind === "unix" && typeof dev === "number" && typeof ino === "number")
|
|
443
|
+
return { kind, path, dev, ino };
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/proxy.ts
|
|
448
|
+
import { existsSync as existsSync14, realpathSync as realpathSync6 } from "node:fs";
|
|
449
|
+
import { basename as basename4, delimiter as delimiter4, dirname as dirname13, isAbsolute as isAbsolute6 } from "node:path";
|
|
14
450
|
// ../mcp-stdio-core/src/responses.ts
|
|
15
451
|
function successResponse(id, result) {
|
|
16
452
|
return { jsonrpc: "2.0", id, result };
|
|
@@ -18,7 +454,7 @@ function successResponse(id, result) {
|
|
|
18
454
|
function errorResponse(id, code, message, data) {
|
|
19
455
|
return { jsonrpc: "2.0", id, error: data === undefined ? { code, message } : { code, message, data } };
|
|
20
456
|
}
|
|
21
|
-
function
|
|
457
|
+
function jsonRpcId2(value) {
|
|
22
458
|
return typeof value === "string" || typeof value === "number" || value === null ? value : null;
|
|
23
459
|
}
|
|
24
460
|
function messageFromError(error) {
|
|
@@ -169,7 +605,7 @@ function bufferFromChunk(chunk) {
|
|
|
169
605
|
var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60000;
|
|
170
606
|
var DEFAULT_PARENT_POLL_INTERVAL_MS = 30000;
|
|
171
607
|
var noopLog = () => {};
|
|
172
|
-
function
|
|
608
|
+
function isProcessAlive2(pid) {
|
|
173
609
|
try {
|
|
174
610
|
process.kill(pid, 0);
|
|
175
611
|
return true;
|
|
@@ -229,7 +665,7 @@ async function handleParseError(message, config, log) {
|
|
|
229
665
|
}
|
|
230
666
|
async function handleRequest(message, config, log) {
|
|
231
667
|
const parsed = message.payload;
|
|
232
|
-
const id = isPlainRecord(parsed) ?
|
|
668
|
+
const id = isPlainRecord(parsed) ? jsonRpcId2(parsed["id"]) : null;
|
|
233
669
|
const method = isPlainRecord(parsed) && typeof parsed["method"] === "string" ? parsed["method"] : null;
|
|
234
670
|
log("request", { id: id === null ? null : String(id), method });
|
|
235
671
|
let response;
|
|
@@ -278,7 +714,7 @@ function createParentWatchdog(config, onDeadParent) {
|
|
|
278
714
|
if (pollIntervalMs <= 0)
|
|
279
715
|
return { clear: () => {} };
|
|
280
716
|
const parentPid = config.parentPid ?? process.ppid;
|
|
281
|
-
const probeAlive = config.probeAlive ??
|
|
717
|
+
const probeAlive = config.probeAlive ?? isProcessAlive2;
|
|
282
718
|
let fired = false;
|
|
283
719
|
const timer = setInterval(() => {
|
|
284
720
|
if (fired)
|
|
@@ -320,14 +756,14 @@ function createIdleTimer(idleTimeoutMs, log, onTimeout) {
|
|
|
320
756
|
};
|
|
321
757
|
}
|
|
322
758
|
// ../lsp-core/src/lsp/client-wrapper.ts
|
|
323
|
-
import { existsSync as
|
|
324
|
-
import { basename as basename3, dirname as
|
|
759
|
+
import { existsSync as existsSync11, realpathSync as realpathSync5, statSync as statSync4 } from "node:fs";
|
|
760
|
+
import { basename as basename3, dirname as dirname11, join as join5, resolve as resolve8 } from "node:path";
|
|
325
761
|
|
|
326
762
|
// ../lsp-core/src/request-context.ts
|
|
327
763
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
328
|
-
import { existsSync, realpathSync, statSync } from "node:fs";
|
|
764
|
+
import { existsSync as existsSync2, realpathSync, statSync as statSync2 } from "node:fs";
|
|
329
765
|
import { homedir } from "node:os";
|
|
330
|
-
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
766
|
+
import { basename, delimiter, dirname as dirname4, isAbsolute, join, relative, resolve } from "node:path";
|
|
331
767
|
|
|
332
768
|
class LspRequestContextParseError extends Error {
|
|
333
769
|
constructor(code, message) {
|
|
@@ -413,7 +849,7 @@ function translateHomeConfigEnv(value, home, fallback) {
|
|
|
413
849
|
}
|
|
414
850
|
function canonicalCwd(cwd) {
|
|
415
851
|
const resolved = resolve(cwd);
|
|
416
|
-
if (!
|
|
852
|
+
if (!existsSync2(resolved) || !statSync2(resolved).isDirectory()) {
|
|
417
853
|
throw new LspRequestContextParseError("invalid_cwd", `LSP request cwd must be an existing directory: ${cwd}`);
|
|
418
854
|
}
|
|
419
855
|
return realpathSync(resolved);
|
|
@@ -428,7 +864,7 @@ function canonicalizeExistingOrNearestAncestor(path) {
|
|
|
428
864
|
} catch (error) {
|
|
429
865
|
if (!isMissingPathError(error))
|
|
430
866
|
throw error;
|
|
431
|
-
const parent =
|
|
867
|
+
const parent = dirname4(current);
|
|
432
868
|
if (parent === current)
|
|
433
869
|
throw error;
|
|
434
870
|
suffix.unshift(basename(current));
|
|
@@ -472,7 +908,7 @@ function isPathInside(parent, child) {
|
|
|
472
908
|
return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute(relativePath);
|
|
473
909
|
}
|
|
474
910
|
function isMissingPathError(error) {
|
|
475
|
-
const code =
|
|
911
|
+
const code = errorCode3(error);
|
|
476
912
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
477
913
|
}
|
|
478
914
|
function rejectUnknownFields(value, allowed, scope) {
|
|
@@ -484,7 +920,7 @@ function rejectUnknownFields(value, allowed, scope) {
|
|
|
484
920
|
function isRecord(value) {
|
|
485
921
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
486
922
|
}
|
|
487
|
-
function
|
|
923
|
+
function errorCode3(error) {
|
|
488
924
|
if (!error || typeof error !== "object" || !("code" in error))
|
|
489
925
|
return;
|
|
490
926
|
const code = Reflect.get(error, "code");
|
|
@@ -939,7 +1375,7 @@ function toError(error) {
|
|
|
939
1375
|
|
|
940
1376
|
// ../lsp-core/src/lsp/process.ts
|
|
941
1377
|
import { spawn, spawnSync } from "node:child_process";
|
|
942
|
-
import { existsSync as
|
|
1378
|
+
import { existsSync as existsSync3, statSync as statSync3 } from "node:fs";
|
|
943
1379
|
import { delimiter as delimiter2, join as join2 } from "node:path";
|
|
944
1380
|
function isMissingProcessError(error) {
|
|
945
1381
|
if (!(error instanceof Error) || !("code" in error))
|
|
@@ -953,10 +1389,10 @@ function reportKillError(context, error) {
|
|
|
953
1389
|
}
|
|
954
1390
|
function validateCwd(cwd) {
|
|
955
1391
|
try {
|
|
956
|
-
if (!
|
|
1392
|
+
if (!existsSync3(cwd)) {
|
|
957
1393
|
return { valid: false, error: `Working directory does not exist: ${cwd}` };
|
|
958
1394
|
}
|
|
959
|
-
const stats =
|
|
1395
|
+
const stats = statSync3(cwd);
|
|
960
1396
|
if (!stats.isDirectory()) {
|
|
961
1397
|
return { valid: false, error: `Path is not a directory: ${cwd}` };
|
|
962
1398
|
}
|
|
@@ -1041,7 +1477,7 @@ function resolveWindowsCommand(command, env) {
|
|
|
1041
1477
|
for (const baseDirectory of baseDirectories) {
|
|
1042
1478
|
for (const extension of extensions) {
|
|
1043
1479
|
const candidate = baseDirectory ? join2(baseDirectory, `${command}${extension}`) : `${command}${extension}`;
|
|
1044
|
-
if (
|
|
1480
|
+
if (existsSync3(candidate))
|
|
1045
1481
|
return candidate;
|
|
1046
1482
|
}
|
|
1047
1483
|
}
|
|
@@ -1460,7 +1896,7 @@ class LspClientConnection extends LspClientTransport {
|
|
|
1460
1896
|
}
|
|
1461
1897
|
|
|
1462
1898
|
// ../lsp-core/src/lsp/workspace-document-state.ts
|
|
1463
|
-
import { readFileSync, realpathSync as realpathSync2 } from "node:fs";
|
|
1899
|
+
import { readFileSync as readFileSync4, realpathSync as realpathSync2 } from "node:fs";
|
|
1464
1900
|
import { relative as relative2, resolve as resolve2 } from "node:path";
|
|
1465
1901
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
1466
1902
|
|
|
@@ -1689,7 +2125,7 @@ class WorkspaceDocumentState {
|
|
|
1689
2125
|
await existingOpen;
|
|
1690
2126
|
return this.openFile(path);
|
|
1691
2127
|
}
|
|
1692
|
-
const text =
|
|
2128
|
+
const text = readFileSync4(path, "utf-8");
|
|
1693
2129
|
const existing = this.openDocuments.get(path);
|
|
1694
2130
|
if (!existing)
|
|
1695
2131
|
return this.openDocumentSingleFlight(path, text);
|
|
@@ -1848,7 +2284,7 @@ class WorkspaceDocumentState {
|
|
|
1848
2284
|
const state = this.openDocuments.get(mutation.path);
|
|
1849
2285
|
if (state) {
|
|
1850
2286
|
await this.closeDocument(state);
|
|
1851
|
-
await this.openDocumentSingleFlight(mutation.path,
|
|
2287
|
+
await this.openDocumentSingleFlight(mutation.path, readFileSync4(mutation.path, "utf-8"));
|
|
1852
2288
|
} else {
|
|
1853
2289
|
watched.push({ uri: pathToFileURL2(mutation.path).href, type: mutation.replaced ? 2 : 1 });
|
|
1854
2290
|
}
|
|
@@ -1860,7 +2296,7 @@ class WorkspaceDocumentState {
|
|
|
1860
2296
|
await this.closeDocument(state);
|
|
1861
2297
|
for (const state of moved) {
|
|
1862
2298
|
const path = movedPath(state.path, mutation.oldPath, mutation.newPath);
|
|
1863
|
-
await this.openDocumentSingleFlight(path,
|
|
2299
|
+
await this.openDocumentSingleFlight(path, readFileSync4(path, "utf-8"));
|
|
1864
2300
|
}
|
|
1865
2301
|
if (moved.length === 0) {
|
|
1866
2302
|
watched.push({ uri: pathToFileURL2(mutation.oldPath).href, type: 3 });
|
|
@@ -1936,11 +2372,11 @@ function workspaceApplyEditConcurrentFailureReason(phase) {
|
|
|
1936
2372
|
}
|
|
1937
2373
|
|
|
1938
2374
|
// ../lsp-core/src/lsp/workspace-edit-commit.ts
|
|
1939
|
-
import { existsSync as
|
|
2375
|
+
import { existsSync as existsSync5, lstatSync as lstatSync4, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
1940
2376
|
|
|
1941
2377
|
// ../lsp-core/src/lsp/workspace-edit-path.ts
|
|
1942
|
-
import { existsSync as
|
|
1943
|
-
import { dirname as
|
|
2378
|
+
import { existsSync as existsSync4, lstatSync as lstatSync3, readFileSync as readFileSync5, readdirSync, realpathSync as realpathSync3 } from "node:fs";
|
|
2379
|
+
import { dirname as dirname5, isAbsolute as isAbsolute2, relative as relative3, resolve as resolve3 } from "node:path";
|
|
1944
2380
|
import { fileURLToPath } from "node:url";
|
|
1945
2381
|
|
|
1946
2382
|
class WorkspaceEditPathError extends Error {
|
|
@@ -1957,8 +2393,8 @@ function isPathInsideWorkspace(filePath, workspaceRoot) {
|
|
|
1957
2393
|
}
|
|
1958
2394
|
function canonicalizeMissingPath(filePath) {
|
|
1959
2395
|
let ancestor = filePath;
|
|
1960
|
-
while (!
|
|
1961
|
-
const parent =
|
|
2396
|
+
while (!existsSync4(ancestor)) {
|
|
2397
|
+
const parent = dirname5(ancestor);
|
|
1962
2398
|
if (parent === ancestor)
|
|
1963
2399
|
throw new WorkspaceEditPathError(filePath, "no existing ancestor");
|
|
1964
2400
|
ancestor = parent;
|
|
@@ -1968,14 +2404,14 @@ function canonicalizeMissingPath(filePath) {
|
|
|
1968
2404
|
function canonicalWorkspaceRoot(workspaceRoot) {
|
|
1969
2405
|
try {
|
|
1970
2406
|
const canonical = realpathSync3(resolve3(workspaceRoot));
|
|
1971
|
-
if (!
|
|
2407
|
+
if (!lstatSync3(canonical).isDirectory()) {
|
|
1972
2408
|
return { success: false, error: `workspace root is not a directory: ${workspaceRoot}` };
|
|
1973
2409
|
}
|
|
1974
2410
|
return {
|
|
1975
2411
|
success: true,
|
|
1976
2412
|
path: canonical,
|
|
1977
2413
|
requestedPath: resolve3(workspaceRoot),
|
|
1978
|
-
followedSymbolicLink:
|
|
2414
|
+
followedSymbolicLink: existsSync4(resolve3(workspaceRoot)) && lstatSync3(resolve3(workspaceRoot)).isSymbolicLink()
|
|
1979
2415
|
};
|
|
1980
2416
|
} catch (error) {
|
|
1981
2417
|
const detail = error instanceof Error ? error.message : String(error);
|
|
@@ -1995,7 +2431,7 @@ function uriToCanonicalWorkspacePath(uri, workspaceRoot) {
|
|
|
1995
2431
|
return { success: false, error: `non-file URI ${uri}: ${detail}` };
|
|
1996
2432
|
}
|
|
1997
2433
|
try {
|
|
1998
|
-
const canonical =
|
|
2434
|
+
const canonical = existsSync4(requestedPath) ? realpathSync3(requestedPath) : canonicalizeMissingPath(requestedPath);
|
|
1999
2435
|
if (!isPathInsideWorkspace(canonical, workspaceRoot)) {
|
|
2000
2436
|
return { success: false, error: `${requestedPath}: outside workspace ${workspaceRoot}` };
|
|
2001
2437
|
}
|
|
@@ -2003,7 +2439,7 @@ function uriToCanonicalWorkspacePath(uri, workspaceRoot) {
|
|
|
2003
2439
|
success: true,
|
|
2004
2440
|
path: canonical,
|
|
2005
2441
|
requestedPath,
|
|
2006
|
-
followedSymbolicLink:
|
|
2442
|
+
followedSymbolicLink: existsSync4(requestedPath) && lstatSync3(requestedPath).isSymbolicLink()
|
|
2007
2443
|
};
|
|
2008
2444
|
} catch (error) {
|
|
2009
2445
|
const detail = error instanceof Error ? error.message : String(error);
|
|
@@ -2011,11 +2447,11 @@ function uriToCanonicalWorkspacePath(uri, workspaceRoot) {
|
|
|
2011
2447
|
}
|
|
2012
2448
|
}
|
|
2013
2449
|
function snapshotPath(path, includeChildren) {
|
|
2014
|
-
if (!
|
|
2450
|
+
if (!existsSync4(path))
|
|
2015
2451
|
return { kind: "missing" };
|
|
2016
|
-
const stats =
|
|
2452
|
+
const stats = lstatSync3(path);
|
|
2017
2453
|
if (stats.isFile())
|
|
2018
|
-
return { kind: "file", content:
|
|
2454
|
+
return { kind: "file", content: readFileSync5(path, "utf-8") };
|
|
2019
2455
|
if (stats.isDirectory()) {
|
|
2020
2456
|
return includeChildren ? { kind: "directory", children: readdirSync(path).sort() } : { kind: "directory" };
|
|
2021
2457
|
}
|
|
@@ -2131,7 +2567,7 @@ function commitOperation(context, operation) {
|
|
|
2131
2567
|
}
|
|
2132
2568
|
if (operation.kind === "rename") {
|
|
2133
2569
|
if (operation.replaceDestination) {
|
|
2134
|
-
const targetKind =
|
|
2570
|
+
const targetKind = existsSync5(operation.newPath) && lstatSync4(operation.newPath).isDirectory() ? "directory" : "file";
|
|
2135
2571
|
io.remove(operation.newPath, targetKind === "directory");
|
|
2136
2572
|
accumulator.mutations.push({ kind: "delete", path: operation.newPath, targetKind });
|
|
2137
2573
|
addModifiedPath(accumulator.filesModified, reportedPath(plan, operation.newPath));
|
|
@@ -2495,7 +2931,7 @@ function parseDocumentChange(input) {
|
|
|
2495
2931
|
}
|
|
2496
2932
|
|
|
2497
2933
|
// ../lsp-core/src/lsp/workspace-edit-simulation.ts
|
|
2498
|
-
import { dirname as
|
|
2934
|
+
import { dirname as dirname6, relative as relative4, resolve as resolve4 } from "node:path";
|
|
2499
2935
|
|
|
2500
2936
|
// ../lsp-core/src/lsp/workspace-edit-text.ts
|
|
2501
2937
|
function comparePosition(left, right) {
|
|
@@ -2619,7 +3055,7 @@ function virtualDirectoryHasChildren(virtual, path) {
|
|
|
2619
3055
|
return false;
|
|
2620
3056
|
}
|
|
2621
3057
|
function requireVirtualParent(virtual, path, changeIndex) {
|
|
2622
|
-
if (virtual.get(
|
|
3058
|
+
if (virtual.get(dirname6(path))?.kind !== "directory") {
|
|
2623
3059
|
throw new WorkspaceEditValidationError(changeIndex, `parent directory does not exist for ${path}`);
|
|
2624
3060
|
}
|
|
2625
3061
|
}
|
|
@@ -2739,8 +3175,8 @@ function simulateDelete(operation, virtual) {
|
|
|
2739
3175
|
}
|
|
2740
3176
|
|
|
2741
3177
|
// ../lsp-core/src/lsp/workspace-edit-snapshot.ts
|
|
2742
|
-
import { existsSync as
|
|
2743
|
-
import { dirname as
|
|
3178
|
+
import { existsSync as existsSync6, lstatSync as lstatSync5, readdirSync as readdirSync2 } from "node:fs";
|
|
3179
|
+
import { dirname as dirname7, resolve as resolve5 } from "node:path";
|
|
2744
3180
|
class WorkspaceSnapshotBuilder {
|
|
2745
3181
|
constructor(workspaceRoot) {
|
|
2746
3182
|
this.workspaceRoot = workspaceRoot;
|
|
@@ -2774,9 +3210,9 @@ class WorkspaceSnapshotBuilder {
|
|
|
2774
3210
|
}
|
|
2775
3211
|
if (candidate === this.workspaceRoot)
|
|
2776
3212
|
break;
|
|
2777
|
-
candidate =
|
|
3213
|
+
candidate = dirname7(candidate);
|
|
2778
3214
|
}
|
|
2779
|
-
if (!includeChildren || !
|
|
3215
|
+
if (!includeChildren || !existsSync6(path) || !lstatSync5(path).isDirectory())
|
|
2780
3216
|
return;
|
|
2781
3217
|
for (const child of readdirSync2(path))
|
|
2782
3218
|
this.add(resolve5(path, child), true);
|
|
@@ -3641,8 +4077,8 @@ async function disposeDefaultLspManager() {
|
|
|
3641
4077
|
}
|
|
3642
4078
|
|
|
3643
4079
|
// ../lsp-core/src/lsp/outside-context-workspace.ts
|
|
3644
|
-
import { existsSync as
|
|
3645
|
-
import { dirname as
|
|
4080
|
+
import { existsSync as existsSync7, realpathSync as realpathSync4 } from "node:fs";
|
|
4081
|
+
import { dirname as dirname8, join as join3 } from "node:path";
|
|
3646
4082
|
|
|
3647
4083
|
// ../lsp-core/src/lsp/workspace-markers.ts
|
|
3648
4084
|
var GIT_WORKSPACE_MARKER = ".git";
|
|
@@ -3666,10 +4102,10 @@ function findWorkspaceRootOutsideContext(directory) {
|
|
|
3666
4102
|
function nearestMarkedAncestor(directory) {
|
|
3667
4103
|
let current = directory;
|
|
3668
4104
|
for (;; ) {
|
|
3669
|
-
if (
|
|
4105
|
+
if (existsSync7(current) && WORKSPACE_MARKERS.some((marker) => existsSync7(join3(current, marker)))) {
|
|
3670
4106
|
return realpathSync4(current);
|
|
3671
4107
|
}
|
|
3672
|
-
const parent =
|
|
4108
|
+
const parent = dirname8(current);
|
|
3673
4109
|
if (parent === current)
|
|
3674
4110
|
return;
|
|
3675
4111
|
current = parent;
|
|
@@ -3677,8 +4113,8 @@ function nearestMarkedAncestor(directory) {
|
|
|
3677
4113
|
}
|
|
3678
4114
|
function nearestExistingAncestor(directory) {
|
|
3679
4115
|
let current = directory;
|
|
3680
|
-
while (!
|
|
3681
|
-
const parent =
|
|
4116
|
+
while (!existsSync7(current)) {
|
|
4117
|
+
const parent = dirname8(current);
|
|
3682
4118
|
if (parent === current)
|
|
3683
4119
|
return current;
|
|
3684
4120
|
current = parent;
|
|
@@ -3854,17 +4290,17 @@ var BUILTIN_SERVERS = {
|
|
|
3854
4290
|
};
|
|
3855
4291
|
|
|
3856
4292
|
// ../lsp-core/src/lsp/server-install-state.ts
|
|
3857
|
-
import { existsSync as
|
|
3858
|
-
import { dirname as
|
|
4293
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4294
|
+
import { dirname as dirname9 } from "node:path";
|
|
3859
4295
|
function getInstallDecisionsPath() {
|
|
3860
4296
|
return lspRequestContext().installDecisionsPath;
|
|
3861
4297
|
}
|
|
3862
4298
|
function loadInstallDecisions() {
|
|
3863
4299
|
const path = getInstallDecisionsPath();
|
|
3864
|
-
if (!
|
|
4300
|
+
if (!existsSync8(path))
|
|
3865
4301
|
return {};
|
|
3866
4302
|
try {
|
|
3867
|
-
const parsed = JSON.parse(
|
|
4303
|
+
const parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
3868
4304
|
return isInstallDecisions(parsed) ? parsed : {};
|
|
3869
4305
|
} catch {
|
|
3870
4306
|
return {};
|
|
@@ -3883,7 +4319,7 @@ function isInstallDecision(value) {
|
|
|
3883
4319
|
}
|
|
3884
4320
|
function writeInstallDecisions(decisions) {
|
|
3885
4321
|
const path = getInstallDecisionsPath();
|
|
3886
|
-
|
|
4322
|
+
mkdirSync3(dirname9(path), { recursive: true });
|
|
3887
4323
|
const tmpPath = `${path}.tmp`;
|
|
3888
4324
|
writeFileSync2(tmpPath, `${JSON.stringify(decisions, null, 2)}
|
|
3889
4325
|
`, "utf8");
|
|
@@ -3902,7 +4338,7 @@ function isRecord5(value) {
|
|
|
3902
4338
|
}
|
|
3903
4339
|
|
|
3904
4340
|
// ../lsp-core/src/lsp/config-loader.ts
|
|
3905
|
-
import { existsSync as
|
|
4341
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
|
|
3906
4342
|
function getProjectConfigPaths() {
|
|
3907
4343
|
return lspRequestContext().projectConfigPaths;
|
|
3908
4344
|
}
|
|
@@ -3910,10 +4346,10 @@ function getUserConfigPath() {
|
|
|
3910
4346
|
return lspRequestContext().userConfigPath;
|
|
3911
4347
|
}
|
|
3912
4348
|
function loadJsonFile(path) {
|
|
3913
|
-
if (!
|
|
4349
|
+
if (!existsSync9(path))
|
|
3914
4350
|
return null;
|
|
3915
4351
|
try {
|
|
3916
|
-
const parsed = JSON.parse(
|
|
4352
|
+
const parsed = JSON.parse(readFileSync7(path, "utf-8"));
|
|
3917
4353
|
return isConfigJson(parsed) ? parsed : null;
|
|
3918
4354
|
} catch {
|
|
3919
4355
|
return null;
|
|
@@ -4098,8 +4534,8 @@ function getDisabledServerIds() {
|
|
|
4098
4534
|
}
|
|
4099
4535
|
|
|
4100
4536
|
// ../lsp-core/src/lsp/server-installation.ts
|
|
4101
|
-
import { existsSync as
|
|
4102
|
-
import { delimiter as delimiter3, dirname as
|
|
4537
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
4538
|
+
import { delimiter as delimiter3, dirname as dirname10, isAbsolute as isAbsolute3, join as join4, resolve as resolve7 } from "node:path";
|
|
4103
4539
|
var LOCAL_BIN_RULES = [
|
|
4104
4540
|
{
|
|
4105
4541
|
markers: ["package.json", "bun.lock", "bun.lockb", "package-lock.json", "yarn.lock", "pnpm-lock.yaml"],
|
|
@@ -4139,7 +4575,7 @@ var resolutionCache = new Map;
|
|
|
4139
4575
|
var probeCount = 0;
|
|
4140
4576
|
function probe(path) {
|
|
4141
4577
|
probeCount += 1;
|
|
4142
|
-
return
|
|
4578
|
+
return existsSync10(path);
|
|
4143
4579
|
}
|
|
4144
4580
|
function executableSuffixes(platform, pathExt) {
|
|
4145
4581
|
if (platform !== "win32")
|
|
@@ -4173,7 +4609,7 @@ function resolveLocal(command, workingDirectory, suffixes, stopAt) {
|
|
|
4173
4609
|
return null;
|
|
4174
4610
|
if (boundary !== undefined && current === boundary)
|
|
4175
4611
|
return null;
|
|
4176
|
-
const parent =
|
|
4612
|
+
const parent = dirname10(current);
|
|
4177
4613
|
if (parent === current)
|
|
4178
4614
|
return null;
|
|
4179
4615
|
current = parent;
|
|
@@ -4328,7 +4764,7 @@ function getAllServers() {
|
|
|
4328
4764
|
// ../lsp-core/src/lsp/client-wrapper.ts
|
|
4329
4765
|
function isDirectoryPath(filePath) {
|
|
4330
4766
|
try {
|
|
4331
|
-
return
|
|
4767
|
+
return statSync4(filePath).isDirectory();
|
|
4332
4768
|
} catch {
|
|
4333
4769
|
return false;
|
|
4334
4770
|
}
|
|
@@ -4338,7 +4774,7 @@ function findWorkspaceRoot(filePath) {
|
|
|
4338
4774
|
const abs = resolveReadablePathInsideContext(filePath);
|
|
4339
4775
|
let dir = abs;
|
|
4340
4776
|
if (!isDirectoryPath(dir)) {
|
|
4341
|
-
dir =
|
|
4777
|
+
dir = dirname11(dir);
|
|
4342
4778
|
}
|
|
4343
4779
|
if (!isPathInside(cwd, abs))
|
|
4344
4780
|
return findWorkspaceRootOutsideContext(dir);
|
|
@@ -4347,7 +4783,7 @@ function findWorkspaceRoot(filePath) {
|
|
|
4347
4783
|
while (isPathInside(cwd, dir)) {
|
|
4348
4784
|
const canonicalDir = existingDirectoryInsideContext(dir, cwd);
|
|
4349
4785
|
if (canonicalDir !== undefined) {
|
|
4350
|
-
if (
|
|
4786
|
+
if (existsSync11(join5(dir, GIT_WORKSPACE_MARKER))) {
|
|
4351
4787
|
return canonicalDir;
|
|
4352
4788
|
}
|
|
4353
4789
|
if (nearestPackageRoot === undefined && hasProjectWorkspaceMarker(dir)) {
|
|
@@ -4356,12 +4792,12 @@ function findWorkspaceRoot(filePath) {
|
|
|
4356
4792
|
}
|
|
4357
4793
|
if (dir === cwd)
|
|
4358
4794
|
break;
|
|
4359
|
-
dir =
|
|
4795
|
+
dir = dirname11(dir);
|
|
4360
4796
|
}
|
|
4361
4797
|
return nearestPackageRoot ?? fallbackRoot;
|
|
4362
4798
|
}
|
|
4363
4799
|
function hasProjectWorkspaceMarker(directory) {
|
|
4364
|
-
return PROJECT_WORKSPACE_MARKERS.some((marker) =>
|
|
4800
|
+
return PROJECT_WORKSPACE_MARKERS.some((marker) => existsSync11(join5(directory, marker)));
|
|
4365
4801
|
}
|
|
4366
4802
|
function resolveReadablePathInsideContext(filePath) {
|
|
4367
4803
|
const cwd = contextCwd();
|
|
@@ -4386,12 +4822,12 @@ function rebaseThroughCanonicalAncestor(path, cwd) {
|
|
|
4386
4822
|
let current = path;
|
|
4387
4823
|
const suffix = [];
|
|
4388
4824
|
while (true) {
|
|
4389
|
-
if (
|
|
4825
|
+
if (existsSync11(current)) {
|
|
4390
4826
|
const canonical = realpathSync5(current);
|
|
4391
4827
|
if (isPathInside(cwd, canonical))
|
|
4392
4828
|
return suffix.length === 0 ? canonical : join5(canonical, ...suffix);
|
|
4393
4829
|
}
|
|
4394
|
-
const parent =
|
|
4830
|
+
const parent = dirname11(current);
|
|
4395
4831
|
if (parent === current)
|
|
4396
4832
|
return;
|
|
4397
4833
|
suffix.unshift(basename3(current));
|
|
@@ -4399,10 +4835,10 @@ function rebaseThroughCanonicalAncestor(path, cwd) {
|
|
|
4399
4835
|
}
|
|
4400
4836
|
}
|
|
4401
4837
|
function existingDirectoryInsideContext(directory, cwd) {
|
|
4402
|
-
if (!
|
|
4838
|
+
if (!existsSync11(directory))
|
|
4403
4839
|
return;
|
|
4404
4840
|
const canonical = realpathSync5(directory);
|
|
4405
|
-
if (!
|
|
4841
|
+
if (!statSync4(canonical).isDirectory())
|
|
4406
4842
|
return;
|
|
4407
4843
|
return isPathInside(cwd, canonical) ? canonical : undefined;
|
|
4408
4844
|
}
|
|
@@ -4414,7 +4850,7 @@ function nearestExistingDirectoryInsideContext(directory, cwd) {
|
|
|
4414
4850
|
return canonical;
|
|
4415
4851
|
if (current === cwd)
|
|
4416
4852
|
return;
|
|
4417
|
-
current =
|
|
4853
|
+
current = dirname11(current);
|
|
4418
4854
|
}
|
|
4419
4855
|
return;
|
|
4420
4856
|
}
|
|
@@ -4544,7 +4980,7 @@ async function withLspClient(filePath, fn, toolName, options = {}) {
|
|
|
4544
4980
|
}
|
|
4545
4981
|
|
|
4546
4982
|
// ../lsp-core/src/lsp/directory-diagnostics.ts
|
|
4547
|
-
import { existsSync as
|
|
4983
|
+
import { existsSync as existsSync12, lstatSync as lstatSync6, readdirSync as readdirSync3 } from "node:fs";
|
|
4548
4984
|
import { join as join6, resolve as resolve9 } from "node:path";
|
|
4549
4985
|
|
|
4550
4986
|
// ../lsp-core/src/lsp/formatters.ts
|
|
@@ -4678,7 +5114,7 @@ function collectFilesWithExtension(dir, extension, maxFiles) {
|
|
|
4678
5114
|
const fullPath = join6(currentDir, entry);
|
|
4679
5115
|
let stat;
|
|
4680
5116
|
try {
|
|
4681
|
-
stat =
|
|
5117
|
+
stat = lstatSync6(fullPath);
|
|
4682
5118
|
} catch {
|
|
4683
5119
|
continue;
|
|
4684
5120
|
}
|
|
@@ -4701,7 +5137,7 @@ async function aggregateDiagnosticsForDirectory(directory, extension, severity,
|
|
|
4701
5137
|
throw new LspInvalidPathError(`Extension must start with a dot (e.g., ".ts", not "${extension}"). Use ".${extension}" instead.`);
|
|
4702
5138
|
}
|
|
4703
5139
|
const absDir = resolve9(options.workspaceRoot ?? contextCwd(), directory);
|
|
4704
|
-
if (!
|
|
5140
|
+
if (!existsSync12(absDir)) {
|
|
4705
5141
|
throw new LspInvalidPathError(`Directory does not exist: ${absDir}`);
|
|
4706
5142
|
}
|
|
4707
5143
|
const serverResult = options.server === undefined ? findServerForExtension(extension) : { status: "found", server: options.server };
|
|
@@ -4787,7 +5223,7 @@ async function aggregateDiagnosticsForDirectory(directory, extension, severity,
|
|
|
4787
5223
|
}
|
|
4788
5224
|
|
|
4789
5225
|
// ../lsp-core/src/lsp/infer-extension.ts
|
|
4790
|
-
import { lstatSync as
|
|
5226
|
+
import { lstatSync as lstatSync7, readdirSync as readdirSync4 } from "node:fs";
|
|
4791
5227
|
import { join as join7 } from "node:path";
|
|
4792
5228
|
var SKIP_DIRECTORIES2 = new Set(["node_modules", ".git", "dist", "build", ".next", "out"]);
|
|
4793
5229
|
var MAX_SCAN_ENTRIES = 500;
|
|
@@ -4809,7 +5245,7 @@ function inferExtensionFromDirectory(directory) {
|
|
|
4809
5245
|
const fullPath = join7(dir, entry);
|
|
4810
5246
|
let stat;
|
|
4811
5247
|
try {
|
|
4812
|
-
stat =
|
|
5248
|
+
stat = lstatSync7(fullPath);
|
|
4813
5249
|
} catch {
|
|
4814
5250
|
continue;
|
|
4815
5251
|
}
|
|
@@ -5058,7 +5494,7 @@ async function executeLspDiagnostics(params, signal) {
|
|
|
5058
5494
|
}
|
|
5059
5495
|
}
|
|
5060
5496
|
// ../lsp-core/src/lsp/format-document.ts
|
|
5061
|
-
import { readFileSync as
|
|
5497
|
+
import { readFileSync as readFileSync8, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5062
5498
|
var DEFAULT_FORMATTING_OPTIONS = {
|
|
5063
5499
|
tabSize: 4,
|
|
5064
5500
|
insertSpaces: false,
|
|
@@ -5073,7 +5509,7 @@ async function formatDocumentWithClient(client, filePath, options = {}) {
|
|
|
5073
5509
|
return { status: "unavailable", reason: "capability_not_advertised" };
|
|
5074
5510
|
if (edits.length === 0)
|
|
5075
5511
|
return UNCHANGED;
|
|
5076
|
-
const before =
|
|
5512
|
+
const before = readFileSync8(filePath, "utf-8");
|
|
5077
5513
|
const normalized = normalizeTextEdits(before, edits, 0);
|
|
5078
5514
|
if (normalized.text === before)
|
|
5079
5515
|
return UNCHANGED;
|
|
@@ -5101,7 +5537,7 @@ function writeAtomically(filePath, content) {
|
|
|
5101
5537
|
renameSync3(tempPath, filePath);
|
|
5102
5538
|
} catch (error) {
|
|
5103
5539
|
try {
|
|
5104
|
-
|
|
5540
|
+
unlinkSync4(tempPath);
|
|
5105
5541
|
} catch {}
|
|
5106
5542
|
throw error;
|
|
5107
5543
|
}
|
|
@@ -5528,7 +5964,7 @@ async function handleLspMcpRequest(input, options = {}) {
|
|
|
5528
5964
|
if (!isPlainRecord(input)) {
|
|
5529
5965
|
return errorResponse(null, -32600, "Invalid Request");
|
|
5530
5966
|
}
|
|
5531
|
-
const id =
|
|
5967
|
+
const id = jsonRpcId2(input["id"]);
|
|
5532
5968
|
const method = input["method"];
|
|
5533
5969
|
if (method === "notifications/initialized")
|
|
5534
5970
|
return;
|
|
@@ -5589,6 +6025,7 @@ function requestedProtocolVersion(params) {
|
|
|
5589
6025
|
import { connect } from "node:net";
|
|
5590
6026
|
import { homedir as homedir3 } from "node:os";
|
|
5591
6027
|
import { join as join8 } from "node:path";
|
|
6028
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
5592
6029
|
|
|
5593
6030
|
// src/daemon-request-error.ts
|
|
5594
6031
|
class DaemonRequestError extends Error {
|
|
@@ -5646,225 +6083,30 @@ function daemonTimedOutResult(paths, timeoutMs) {
|
|
|
5646
6083
|
`Logs: ${paths.log}`
|
|
5647
6084
|
].join(`
|
|
5648
6085
|
`);
|
|
5649
|
-
return { content: [{ type: "text", text }], isError: true };
|
|
5650
|
-
}
|
|
5651
|
-
function daemonUnreachableResult(paths, error) {
|
|
5652
|
-
const text = [
|
|
5653
|
-
`LSP daemon unreachable: ${errorText(error)}.`,
|
|
5654
|
-
"The MCP server is a thin proxy and never runs language servers in-process.",
|
|
5655
|
-
`Socket: ${paths.socket}`,
|
|
5656
|
-
`Logs: ${paths.log}`,
|
|
5657
|
-
"The daemon is auto-started on demand and will be retried on the next request."
|
|
5658
|
-
].join(`
|
|
5659
|
-
`);
|
|
5660
|
-
return { content: [{ type: "text", text }], isError: true };
|
|
5661
|
-
}
|
|
5662
|
-
function errorText(error) {
|
|
5663
|
-
return error instanceof Error ? error.message : String(error);
|
|
5664
|
-
}
|
|
5665
|
-
|
|
5666
|
-
// src/ensure-daemon.ts
|
|
5667
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
5668
|
-
import { closeSync as closeSync2, existsSync as existsSync12, mkdirSync as mkdirSync3, openSync as openSync2, writeSync as writeSync2 } from "node:fs";
|
|
5669
|
-
import { Socket } from "node:net";
|
|
5670
|
-
import { dirname as dirname10, isAbsolute as isAbsolute5 } from "node:path";
|
|
5671
|
-
import { argv0, execPath } from "node:process";
|
|
5672
|
-
|
|
5673
|
-
// src/ipc-protocol.ts
|
|
5674
|
-
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
5675
|
-
import {
|
|
5676
|
-
chmodSync,
|
|
5677
|
-
closeSync,
|
|
5678
|
-
constants,
|
|
5679
|
-
fchmodSync,
|
|
5680
|
-
fstatSync,
|
|
5681
|
-
lstatSync as lstatSync6,
|
|
5682
|
-
mkdirSync as mkdirSync2,
|
|
5683
|
-
openSync,
|
|
5684
|
-
readFileSync as readFileSync6,
|
|
5685
|
-
unlinkSync as unlinkSync2,
|
|
5686
|
-
writeSync
|
|
5687
|
-
} from "node:fs";
|
|
5688
|
-
import { dirname as dirname9 } from "node:path";
|
|
5689
|
-
var OMO_DAEMON_PROTOCOL_VERSION = 1;
|
|
5690
|
-
var AUTH_ERROR_CODE = -32001;
|
|
5691
|
-
var PROTOCOL_ERROR_CODE = -32002;
|
|
5692
|
-
var AUTH_TOKEN_BYTES = 32;
|
|
5693
|
-
|
|
5694
|
-
class UnsafePrivateDirectoryError extends Error {
|
|
5695
|
-
constructor(path, reason) {
|
|
5696
|
-
super(`unsafe private directory ${path}: ${reason}`);
|
|
5697
|
-
this.path = path;
|
|
5698
|
-
this.reason = reason;
|
|
5699
|
-
this.name = "UnsafePrivateDirectoryError";
|
|
5700
|
-
this.code = "unsafe_private_directory";
|
|
5701
|
-
}
|
|
5702
|
-
}
|
|
5703
|
-
function authEnvelope(token) {
|
|
5704
|
-
return { protocolVersion: OMO_DAEMON_PROTOCOL_VERSION, token };
|
|
5705
|
-
}
|
|
5706
|
-
function readAuthToken(paths) {
|
|
5707
|
-
try {
|
|
5708
|
-
const token = readFileSync6(paths.auth, "utf8").trim();
|
|
5709
|
-
return token.length > 0 ? token : null;
|
|
5710
|
-
} catch (error) {
|
|
5711
|
-
if (error instanceof Error)
|
|
5712
|
-
return null;
|
|
5713
|
-
throw error;
|
|
5714
|
-
}
|
|
5715
|
-
}
|
|
5716
|
-
function readOrCreateAuthToken(paths) {
|
|
5717
|
-
const existing = readAuthToken(paths);
|
|
5718
|
-
if (existing)
|
|
5719
|
-
return existing;
|
|
5720
|
-
return createAuthToken(paths);
|
|
5721
|
-
}
|
|
5722
|
-
function rotateAuthToken(paths) {
|
|
5723
|
-
try {
|
|
5724
|
-
unlinkSync2(paths.auth);
|
|
5725
|
-
} catch (error) {
|
|
5726
|
-
if (!(error instanceof Error))
|
|
5727
|
-
throw error;
|
|
5728
|
-
}
|
|
5729
|
-
return createAuthToken(paths);
|
|
5730
|
-
}
|
|
5731
|
-
function authenticateMessage(raw, expectedToken) {
|
|
5732
|
-
const id = jsonRpcId2(raw);
|
|
5733
|
-
if (!isPlainRecord(raw))
|
|
5734
|
-
return authError(id);
|
|
5735
|
-
const params = raw["params"];
|
|
5736
|
-
if (!isPlainRecord(params))
|
|
5737
|
-
return authError(id);
|
|
5738
|
-
const envelope = params["_omo"];
|
|
5739
|
-
if (!isPlainRecord(envelope))
|
|
5740
|
-
return authError(id);
|
|
5741
|
-
const protocolVersion = envelope["protocolVersion"];
|
|
5742
|
-
if (protocolVersion !== OMO_DAEMON_PROTOCOL_VERSION)
|
|
5743
|
-
return protocolError(id);
|
|
5744
|
-
const token = envelope["token"];
|
|
5745
|
-
if (typeof token !== "string" || !tokenMatches(token, expectedToken))
|
|
5746
|
-
return authError(id);
|
|
5747
|
-
const cleanParams = { ...params };
|
|
5748
|
-
delete cleanParams["_omo"];
|
|
5749
|
-
return { input: { ...raw, params: cleanParams }, id, method: typeof raw["method"] === "string" ? raw["method"] : undefined };
|
|
5750
|
-
}
|
|
5751
|
-
function isAuthErrorResponse(message) {
|
|
5752
|
-
if (!isPlainRecord(message))
|
|
5753
|
-
return false;
|
|
5754
|
-
const error = message["error"];
|
|
5755
|
-
if (!isPlainRecord(error))
|
|
5756
|
-
return false;
|
|
5757
|
-
const data = error["data"];
|
|
5758
|
-
return error["code"] === AUTH_ERROR_CODE && isPlainRecord(data) && data["code"] === "daemon_authentication_failed";
|
|
5759
|
-
}
|
|
5760
|
-
function writePrivateFile(path, data) {
|
|
5761
|
-
const fd = openSync(path, "w", 384);
|
|
5762
|
-
try {
|
|
5763
|
-
writeSync(fd, data);
|
|
5764
|
-
} finally {
|
|
5765
|
-
closeSync(fd);
|
|
5766
|
-
}
|
|
5767
|
-
setPrivateFileMode(path);
|
|
5768
|
-
}
|
|
5769
|
-
function ensurePrivateDirectory(path, options = {}) {
|
|
5770
|
-
try {
|
|
5771
|
-
mkdirSync2(path, { recursive: true, mode: 448 });
|
|
5772
|
-
} catch (error) {
|
|
5773
|
-
if (errorCode2(error) !== "EEXIST")
|
|
5774
|
-
throw error;
|
|
5775
|
-
}
|
|
5776
|
-
if (process.platform === "win32")
|
|
5777
|
-
return;
|
|
5778
|
-
const before = validatePrivateDirectory(path, options);
|
|
5779
|
-
const fd = openSync(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
5780
|
-
try {
|
|
5781
|
-
fchmodSync(fd, 448);
|
|
5782
|
-
const after = validatePrivateDirectory(path, options);
|
|
5783
|
-
const openStats = fstatSync(fd);
|
|
5784
|
-
if (!sameDirectory(before, after) || !sameDirectory(before, openStats)) {
|
|
5785
|
-
throw new UnsafePrivateDirectoryError(path, "changed_during_chmod");
|
|
5786
|
-
}
|
|
5787
|
-
} finally {
|
|
5788
|
-
closeSync(fd);
|
|
5789
|
-
}
|
|
5790
|
-
}
|
|
5791
|
-
function setPrivateFileMode(path) {
|
|
5792
|
-
if (process.platform !== "win32")
|
|
5793
|
-
chmodSync(path, 384);
|
|
5794
|
-
}
|
|
5795
|
-
function createAuthToken(paths) {
|
|
5796
|
-
ensurePrivateDirectory(dirname9(paths.auth));
|
|
5797
|
-
const token = randomBytes(AUTH_TOKEN_BYTES).toString("base64url");
|
|
5798
|
-
let fd;
|
|
5799
|
-
try {
|
|
5800
|
-
fd = openSync(paths.auth, "wx", 384);
|
|
5801
|
-
} catch (error) {
|
|
5802
|
-
if (errorCode2(error) === "EEXIST") {
|
|
5803
|
-
const existing = readAuthToken(paths);
|
|
5804
|
-
if (existing)
|
|
5805
|
-
return existing;
|
|
5806
|
-
}
|
|
5807
|
-
throw error;
|
|
5808
|
-
}
|
|
5809
|
-
try {
|
|
5810
|
-
writeSync(fd, `${token}
|
|
5811
|
-
`);
|
|
5812
|
-
} finally {
|
|
5813
|
-
closeSync(fd);
|
|
5814
|
-
}
|
|
5815
|
-
setPrivateFileMode(paths.auth);
|
|
5816
|
-
return token;
|
|
5817
|
-
}
|
|
5818
|
-
function errorCode2(error) {
|
|
5819
|
-
if (!error || typeof error !== "object" || !("code" in error))
|
|
5820
|
-
return;
|
|
5821
|
-
const code = Reflect.get(error, "code");
|
|
5822
|
-
return typeof code === "string" ? code : undefined;
|
|
5823
|
-
}
|
|
5824
|
-
function validatePrivateDirectory(path, options) {
|
|
5825
|
-
const stats = options.lstat ? options.lstat(path) : lstatPrivateDirectory(path);
|
|
5826
|
-
if (stats.isSymbolicLink())
|
|
5827
|
-
throw new UnsafePrivateDirectoryError(path, "symlink");
|
|
5828
|
-
if (!stats.isDirectory())
|
|
5829
|
-
throw new UnsafePrivateDirectoryError(path, "not_directory");
|
|
5830
|
-
const currentUid = options.currentUid ? options.currentUid() : process.getuid?.();
|
|
5831
|
-
if (currentUid !== undefined && stats.uid !== currentUid) {
|
|
5832
|
-
throw new UnsafePrivateDirectoryError(path, "wrong_owner");
|
|
5833
|
-
}
|
|
5834
|
-
return stats;
|
|
5835
|
-
}
|
|
5836
|
-
function lstatPrivateDirectory(path) {
|
|
5837
|
-
return lstatSync6(path);
|
|
5838
|
-
}
|
|
5839
|
-
function sameDirectory(a, b) {
|
|
5840
|
-
return a.dev === b.dev && a.ino === b.ino;
|
|
5841
|
-
}
|
|
5842
|
-
function tokenMatches(candidate, expected) {
|
|
5843
|
-
const candidateBytes = Buffer.from(candidate);
|
|
5844
|
-
const expectedBytes = Buffer.from(expected);
|
|
5845
|
-
return candidateBytes.length === expectedBytes.length && timingSafeEqual(candidateBytes, expectedBytes);
|
|
5846
|
-
}
|
|
5847
|
-
function jsonRpcId2(raw) {
|
|
5848
|
-
if (!isPlainRecord(raw))
|
|
5849
|
-
return null;
|
|
5850
|
-
const id = raw["id"];
|
|
5851
|
-
return typeof id === "string" || typeof id === "number" || id === null ? id : null;
|
|
5852
|
-
}
|
|
5853
|
-
function authError(id) {
|
|
5854
|
-
return {
|
|
5855
|
-
jsonrpc: "2.0",
|
|
5856
|
-
id,
|
|
5857
|
-
error: { code: AUTH_ERROR_CODE, message: "daemon authentication failed", data: { code: "daemon_authentication_failed" } }
|
|
5858
|
-
};
|
|
6086
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
5859
6087
|
}
|
|
5860
|
-
function
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
6088
|
+
function daemonUnreachableResult(paths, error) {
|
|
6089
|
+
const text = [
|
|
6090
|
+
`LSP daemon unreachable: ${errorText(error)}.`,
|
|
6091
|
+
"The MCP server is a thin proxy and never runs language servers in-process.",
|
|
6092
|
+
`Socket: ${paths.socket}`,
|
|
6093
|
+
`Logs: ${paths.log}`,
|
|
6094
|
+
"The daemon is auto-started on demand and will be retried on the next request."
|
|
6095
|
+
].join(`
|
|
6096
|
+
`);
|
|
6097
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
6098
|
+
}
|
|
6099
|
+
function errorText(error) {
|
|
6100
|
+
return error instanceof Error ? error.message : String(error);
|
|
5866
6101
|
}
|
|
5867
6102
|
|
|
6103
|
+
// src/ensure-daemon.ts
|
|
6104
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
6105
|
+
import { closeSync as closeSync3, existsSync as existsSync13, mkdirSync as mkdirSync4, openSync as openSync3, writeSync as writeSync3 } from "node:fs";
|
|
6106
|
+
import { Socket } from "node:net";
|
|
6107
|
+
import { dirname as dirname12, isAbsolute as isAbsolute5 } from "node:path";
|
|
6108
|
+
import { argv0, execPath } from "node:process";
|
|
6109
|
+
|
|
5868
6110
|
// src/paths.ts
|
|
5869
6111
|
import { createHash as createHash2 } from "node:crypto";
|
|
5870
6112
|
import { createRequire } from "node:module";
|
|
@@ -5873,7 +6115,7 @@ import * as path from "node:path";
|
|
|
5873
6115
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
5874
6116
|
|
|
5875
6117
|
// src/runtime-contract.ts
|
|
5876
|
-
import { statSync as
|
|
6118
|
+
import { statSync as statSync5 } from "node:fs";
|
|
5877
6119
|
import { isAbsolute as isAbsolute4 } from "node:path";
|
|
5878
6120
|
var OMO_LSP_DAEMON_DIR = "OMO_LSP_DAEMON_DIR";
|
|
5879
6121
|
var OMO_LSP_DAEMON_CLI = "OMO_LSP_DAEMON_CLI";
|
|
@@ -5921,7 +6163,7 @@ function resolveDaemonRuntime(env, defaults) {
|
|
|
5921
6163
|
}
|
|
5922
6164
|
let cliStats;
|
|
5923
6165
|
try {
|
|
5924
|
-
cliStats =
|
|
6166
|
+
cliStats = statSync5(cliOverride);
|
|
5925
6167
|
} catch (error) {
|
|
5926
6168
|
if (!(error instanceof Error))
|
|
5927
6169
|
throw error;
|
|
@@ -6055,9 +6297,11 @@ function createLineDecoder(onMessage, onParseError) {
|
|
|
6055
6297
|
}
|
|
6056
6298
|
|
|
6057
6299
|
// src/ensure-daemon.ts
|
|
6058
|
-
var PROBE_TIMEOUT_MS =
|
|
6300
|
+
var PROBE_TIMEOUT_MS = 2000;
|
|
6059
6301
|
var DEFAULT_READY_TIMEOUT_MS = 5000;
|
|
6060
6302
|
var DEFAULT_POLL_INTERVAL_MS = 100;
|
|
6303
|
+
var FAILED_SPAWN_COOLDOWN_MS = 5000;
|
|
6304
|
+
var failedSpawnUntil = new Map;
|
|
6061
6305
|
|
|
6062
6306
|
class DaemonUnreachableError extends Error {
|
|
6063
6307
|
constructor(socketPath) {
|
|
@@ -6070,11 +6314,22 @@ async function ensureDaemonRunning(paths, deps = defaultEnsureDaemonDeps(), opti
|
|
|
6070
6314
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
6071
6315
|
const signal = options.signal;
|
|
6072
6316
|
throwIfAborted(signal);
|
|
6073
|
-
if (await awaitWithSignal2(deps.probe(paths, signal), signal))
|
|
6317
|
+
if (await awaitWithSignal2(deps.probe(paths, signal), signal)) {
|
|
6318
|
+
failedSpawnUntil.delete(paths.socket);
|
|
6074
6319
|
return;
|
|
6320
|
+
}
|
|
6075
6321
|
throwIfAborted(signal);
|
|
6322
|
+
const retryAt = failedSpawnUntil.get(paths.socket);
|
|
6323
|
+
if (retryAt !== undefined && deps.now() < retryAt)
|
|
6324
|
+
throw new DaemonUnreachableError(paths.socket);
|
|
6325
|
+
failedSpawnUntil.delete(paths.socket);
|
|
6076
6326
|
deps.spawnDaemon(paths);
|
|
6077
|
-
|
|
6327
|
+
try {
|
|
6328
|
+
await waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs, signal);
|
|
6329
|
+
} catch (error) {
|
|
6330
|
+
failedSpawnUntil.set(paths.socket, deps.now() + FAILED_SPAWN_COOLDOWN_MS);
|
|
6331
|
+
throw error;
|
|
6332
|
+
}
|
|
6078
6333
|
}
|
|
6079
6334
|
async function waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs, signal) {
|
|
6080
6335
|
const deadline = deps.now() + readyTimeoutMs;
|
|
@@ -6130,8 +6385,8 @@ function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS, signal) {
|
|
|
6130
6385
|
});
|
|
6131
6386
|
}
|
|
6132
6387
|
function spawnDaemonProcess(paths, deps = {}) {
|
|
6133
|
-
|
|
6134
|
-
const logFd =
|
|
6388
|
+
mkdirSync4(dirname12(paths.log), { recursive: true });
|
|
6389
|
+
const logFd = openSync3(paths.log, "a");
|
|
6135
6390
|
try {
|
|
6136
6391
|
const spawnDaemonChild = deps.spawn ?? spawn2;
|
|
6137
6392
|
const executable = deps.resolveExecutable?.() ?? resolveDaemonNodeExecutable();
|
|
@@ -6141,19 +6396,19 @@ function spawnDaemonProcess(paths, deps = {}) {
|
|
|
6141
6396
|
windowsHide: true,
|
|
6142
6397
|
env: { ...process.env, BUN_BE_BUN: "1" }
|
|
6143
6398
|
});
|
|
6144
|
-
child.once("spawn", () =>
|
|
6399
|
+
child.once("spawn", () => closeSync3(logFd));
|
|
6145
6400
|
child.once("error", (error) => {
|
|
6146
|
-
|
|
6401
|
+
writeSync3(logFd, `[lsp-daemon] failed to spawn daemon: ${error.message}
|
|
6147
6402
|
`);
|
|
6148
|
-
|
|
6403
|
+
closeSync3(logFd);
|
|
6149
6404
|
});
|
|
6150
6405
|
child.unref();
|
|
6151
6406
|
} catch (error) {
|
|
6152
|
-
|
|
6407
|
+
closeSync3(logFd);
|
|
6153
6408
|
throw error;
|
|
6154
6409
|
}
|
|
6155
6410
|
}
|
|
6156
|
-
function resolveDaemonNodeExecutable(cachedExecPath = execPath, originalArgv0 = argv0, pathExists =
|
|
6411
|
+
function resolveDaemonNodeExecutable(cachedExecPath = execPath, originalArgv0 = argv0, pathExists = existsSync13) {
|
|
6157
6412
|
if (pathExists(cachedExecPath))
|
|
6158
6413
|
return cachedExecPath;
|
|
6159
6414
|
if (isAbsolute5(originalArgv0) && pathExists(originalArgv0))
|
|
@@ -6337,6 +6592,7 @@ function parseContext(value) {
|
|
|
6337
6592
|
|
|
6338
6593
|
// src/daemon-client.ts
|
|
6339
6594
|
var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
6595
|
+
var STARTUP_BACKOFF_MS = [0, 100, 300];
|
|
6340
6596
|
var nextProxyRequestId = 1;
|
|
6341
6597
|
async function callToolViaDaemon(name, args, options) {
|
|
6342
6598
|
const context = requireContext(options.context);
|
|
@@ -6344,17 +6600,31 @@ async function callToolViaDaemon(name, args, options) {
|
|
|
6344
6600
|
const ensure = options.ensure ?? ((ensurePaths, signal) => ensureDaemonRunning(ensurePaths, undefined, signal === undefined ? {} : { signal }));
|
|
6345
6601
|
const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
6346
6602
|
const requestArgs = withContext(args, context);
|
|
6603
|
+
const probe = options.probe ?? ((probePaths, signal) => probeDaemon(probePaths, undefined, signal));
|
|
6604
|
+
const sleep = options.sleep ?? ((ms, signal) => delay(ms, undefined, { signal }));
|
|
6347
6605
|
let lastError;
|
|
6348
6606
|
let authRefreshUsed = false;
|
|
6349
|
-
for (
|
|
6607
|
+
for (const [attempt, backoffMs] of STARTUP_BACKOFF_MS.entries()) {
|
|
6350
6608
|
try {
|
|
6351
|
-
|
|
6609
|
+
if (backoffMs > 0)
|
|
6610
|
+
await sleep(backoffMs, options.signal);
|
|
6611
|
+
if (options.signal?.aborted)
|
|
6612
|
+
throw new DaemonRequestCancelledError(false);
|
|
6613
|
+
if (attempt === 0) {
|
|
6614
|
+
await ensureDaemonAvailable(paths, ensure, options.signal);
|
|
6615
|
+
} else if (!await probe(paths, options.signal)) {
|
|
6616
|
+
throw new DaemonUnreachableError(paths.socket);
|
|
6617
|
+
}
|
|
6352
6618
|
const token = readAuthToken(paths);
|
|
6353
6619
|
if (!token)
|
|
6354
6620
|
throw new DaemonRequestError("daemon auth token missing", false);
|
|
6355
6621
|
const sendOptions = options.signal === undefined ? { timeoutMs } : { timeoutMs, signal: options.signal };
|
|
6356
6622
|
return await sendToolCall(paths, token, name, requestArgs, sendOptions);
|
|
6357
6623
|
} catch (error) {
|
|
6624
|
+
if (options.signal?.aborted) {
|
|
6625
|
+
lastError = new DaemonRequestCancelledError(false);
|
|
6626
|
+
break;
|
|
6627
|
+
}
|
|
6358
6628
|
lastError = error;
|
|
6359
6629
|
if (error instanceof DaemonAuthenticationRejectedError && !authRefreshUsed) {
|
|
6360
6630
|
authRefreshUsed = true;
|
|
@@ -6610,7 +6880,7 @@ function asToolCall(parsed) {
|
|
|
6610
6880
|
if (!isPlainRecord(params) || typeof params["name"] !== "string")
|
|
6611
6881
|
return null;
|
|
6612
6882
|
const args = params["arguments"];
|
|
6613
|
-
return { id:
|
|
6883
|
+
return { id: jsonRpcId2(parsed["id"]), name: params["name"], args: isPlainRecord(args) ? args : {} };
|
|
6614
6884
|
}
|
|
6615
6885
|
function inferOpenCodeProjectCwd(projectConfigEnv) {
|
|
6616
6886
|
if (!projectConfigEnv)
|
|
@@ -6636,263 +6906,24 @@ function canonicalizePathList(value) {
|
|
|
6636
6906
|
return value.split(delimiter4).map((entry) => canonicalizePath(entry) ?? entry).join(delimiter4);
|
|
6637
6907
|
}
|
|
6638
6908
|
function canonicalizePath(value) {
|
|
6639
|
-
if (value === undefined || !isAbsolute6(value) || !
|
|
6909
|
+
if (value === undefined || !isAbsolute6(value) || !existsSync14(value))
|
|
6640
6910
|
return value;
|
|
6641
6911
|
return realpathSync6(value);
|
|
6642
6912
|
}
|
|
6643
6913
|
function projectRootFromOpenCodeConfigPath(path) {
|
|
6644
6914
|
if (basename4(path) !== "lsp.json" && basename4(path) !== "lsp-client.json")
|
|
6645
6915
|
return;
|
|
6646
|
-
const configDir =
|
|
6916
|
+
const configDir = dirname13(path);
|
|
6647
6917
|
const configDirName = basename4(configDir);
|
|
6648
6918
|
if (configDirName !== ".opencode" && configDirName !== ".omo")
|
|
6649
6919
|
return;
|
|
6650
|
-
return
|
|
6920
|
+
return dirname13(configDir);
|
|
6651
6921
|
}
|
|
6652
6922
|
|
|
6653
6923
|
// src/daemon-server.ts
|
|
6654
6924
|
import { chmodSync as chmodSync2 } from "node:fs";
|
|
6655
6925
|
import { createServer as createServer2 } from "node:net";
|
|
6656
6926
|
|
|
6657
|
-
// src/ownership.ts
|
|
6658
|
-
import { randomUUID } from "node:crypto";
|
|
6659
|
-
import { existsSync as existsSync14, lstatSync as lstatSync7, readFileSync as readFileSync8, statSync as statSync5, unlinkSync as unlinkSync4 } from "node:fs";
|
|
6660
|
-
import { dirname as dirname13 } from "node:path";
|
|
6661
|
-
|
|
6662
|
-
// src/lock.ts
|
|
6663
|
-
import { closeSync as closeSync3, mkdirSync as mkdirSync4, openSync as openSync3, readFileSync as readFileSync7, unlinkSync as unlinkSync3, writeSync as writeSync3 } from "node:fs";
|
|
6664
|
-
import { dirname as dirname12 } from "node:path";
|
|
6665
|
-
function isProcessAlive2(pid) {
|
|
6666
|
-
if (!Number.isInteger(pid) || pid <= 0)
|
|
6667
|
-
return false;
|
|
6668
|
-
try {
|
|
6669
|
-
process.kill(pid, 0);
|
|
6670
|
-
return true;
|
|
6671
|
-
} catch (error) {
|
|
6672
|
-
if (error instanceof Error)
|
|
6673
|
-
return errorCode3(error) === "EPERM";
|
|
6674
|
-
throw error;
|
|
6675
|
-
}
|
|
6676
|
-
}
|
|
6677
|
-
function readLockPid(lockPath) {
|
|
6678
|
-
try {
|
|
6679
|
-
const pid = Number.parseInt(readFileSync7(lockPath, "utf8").trim(), 10);
|
|
6680
|
-
return Number.isInteger(pid) ? pid : null;
|
|
6681
|
-
} catch {
|
|
6682
|
-
return null;
|
|
6683
|
-
}
|
|
6684
|
-
}
|
|
6685
|
-
function tryAcquireLock(lockPath, ownerPid = process.pid) {
|
|
6686
|
-
mkdirSync4(dirname12(lockPath), { recursive: true });
|
|
6687
|
-
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
6688
|
-
const handle = writeLockFile(lockPath, ownerPid);
|
|
6689
|
-
if (handle)
|
|
6690
|
-
return handle;
|
|
6691
|
-
if (!reapStaleLock(lockPath))
|
|
6692
|
-
return null;
|
|
6693
|
-
}
|
|
6694
|
-
return null;
|
|
6695
|
-
}
|
|
6696
|
-
function writeLockFile(lockPath, ownerPid) {
|
|
6697
|
-
try {
|
|
6698
|
-
const fd = openSync3(lockPath, "wx", 384);
|
|
6699
|
-
writeSync3(fd, `${ownerPid}
|
|
6700
|
-
`);
|
|
6701
|
-
closeSync3(fd);
|
|
6702
|
-
return { release: () => unlinkQuietly(lockPath) };
|
|
6703
|
-
} catch (error) {
|
|
6704
|
-
if (errorCode3(error) === "EEXIST")
|
|
6705
|
-
return null;
|
|
6706
|
-
throw error;
|
|
6707
|
-
}
|
|
6708
|
-
}
|
|
6709
|
-
function reapStaleLock(lockPath) {
|
|
6710
|
-
const pid = readLockPid(lockPath);
|
|
6711
|
-
if (pid !== null && isProcessAlive2(pid))
|
|
6712
|
-
return false;
|
|
6713
|
-
unlinkQuietly(lockPath);
|
|
6714
|
-
return true;
|
|
6715
|
-
}
|
|
6716
|
-
function unlinkQuietly(path) {
|
|
6717
|
-
try {
|
|
6718
|
-
unlinkSync3(path);
|
|
6719
|
-
} catch (error) {
|
|
6720
|
-
if (error instanceof Error)
|
|
6721
|
-
return;
|
|
6722
|
-
throw error;
|
|
6723
|
-
}
|
|
6724
|
-
}
|
|
6725
|
-
function errorCode3(error) {
|
|
6726
|
-
if (!error || typeof error !== "object" || !("code" in error))
|
|
6727
|
-
return;
|
|
6728
|
-
const code = Reflect.get(error, "code");
|
|
6729
|
-
return typeof code === "string" ? code : undefined;
|
|
6730
|
-
}
|
|
6731
|
-
|
|
6732
|
-
// src/ownership.ts
|
|
6733
|
-
class DaemonAlreadyRunningError extends Error {
|
|
6734
|
-
constructor() {
|
|
6735
|
-
super(...arguments);
|
|
6736
|
-
this.name = "DaemonAlreadyRunningError";
|
|
6737
|
-
this.code = "daemon_already_running";
|
|
6738
|
-
}
|
|
6739
|
-
}
|
|
6740
|
-
|
|
6741
|
-
class DaemonStartupDeferredError extends Error {
|
|
6742
|
-
constructor(reason) {
|
|
6743
|
-
super(`LSP daemon startup deferred: ${reason}`);
|
|
6744
|
-
this.reason = reason;
|
|
6745
|
-
this.name = "DaemonStartupDeferredError";
|
|
6746
|
-
this.code = "daemon_startup_deferred";
|
|
6747
|
-
}
|
|
6748
|
-
}
|
|
6749
|
-
async function acquireStartupLease(paths, pingOwner) {
|
|
6750
|
-
ensureDaemonDirectories(paths);
|
|
6751
|
-
const lock = tryAcquireLock(paths.lock);
|
|
6752
|
-
if (!lock) {
|
|
6753
|
-
const token = readAuthToken(paths);
|
|
6754
|
-
if (token && await pingOwner(token))
|
|
6755
|
-
throw new DaemonAlreadyRunningError("LSP daemon already running");
|
|
6756
|
-
throw new DaemonStartupDeferredError("startup_lock_busy");
|
|
6757
|
-
}
|
|
6758
|
-
try {
|
|
6759
|
-
const token = await validateExistingOwner(paths, pingOwner);
|
|
6760
|
-
return { lock, token, owner: newOwner(paths) };
|
|
6761
|
-
} catch (error) {
|
|
6762
|
-
lock.release();
|
|
6763
|
-
throw error;
|
|
6764
|
-
}
|
|
6765
|
-
}
|
|
6766
|
-
function ensureDaemonDirectories(paths) {
|
|
6767
|
-
ensurePrivateDirectory(paths.dir);
|
|
6768
|
-
if (process.platform !== "win32")
|
|
6769
|
-
ensurePrivateDirectory(dirname13(paths.socket));
|
|
6770
|
-
}
|
|
6771
|
-
function readDaemonOwner(paths) {
|
|
6772
|
-
try {
|
|
6773
|
-
return parseOwner(JSON.parse(readFileSync8(paths.owner, "utf8")));
|
|
6774
|
-
} catch (error) {
|
|
6775
|
-
if (error instanceof Error)
|
|
6776
|
-
return null;
|
|
6777
|
-
throw error;
|
|
6778
|
-
}
|
|
6779
|
-
}
|
|
6780
|
-
function writeDaemonOwner(paths, owner) {
|
|
6781
|
-
writePrivateFile(paths.pid, `${owner.pid}
|
|
6782
|
-
`);
|
|
6783
|
-
writePrivateFile(paths.endpoint, owner.endpoint.path);
|
|
6784
|
-
writePrivateFile(paths.owner, `${JSON.stringify(owner)}
|
|
6785
|
-
`);
|
|
6786
|
-
}
|
|
6787
|
-
function removeDaemonMetadataForOwner(paths, owner) {
|
|
6788
|
-
const current = readDaemonOwner(paths);
|
|
6789
|
-
if (!current || !sameOwner(current, owner))
|
|
6790
|
-
return;
|
|
6791
|
-
unlinkQuietly(paths.socket);
|
|
6792
|
-
unlinkQuietly(paths.pid);
|
|
6793
|
-
unlinkQuietly(paths.endpoint);
|
|
6794
|
-
unlinkQuietly(paths.owner);
|
|
6795
|
-
}
|
|
6796
|
-
function endpointIdentity(endpointPath) {
|
|
6797
|
-
if (process.platform === "win32")
|
|
6798
|
-
return { kind: "windows", path: endpointPath };
|
|
6799
|
-
try {
|
|
6800
|
-
const stats = statSync5(endpointPath);
|
|
6801
|
-
return { kind: "unix", path: endpointPath, dev: stats.dev, ino: stats.ino };
|
|
6802
|
-
} catch (error) {
|
|
6803
|
-
if (error instanceof Error)
|
|
6804
|
-
return { kind: "missing", path: endpointPath };
|
|
6805
|
-
throw error;
|
|
6806
|
-
}
|
|
6807
|
-
}
|
|
6808
|
-
function sameEndpoint(a, b) {
|
|
6809
|
-
if (a.kind !== b.kind || a.path !== b.path)
|
|
6810
|
-
return false;
|
|
6811
|
-
switch (a.kind) {
|
|
6812
|
-
case "unix":
|
|
6813
|
-
return b.kind === "unix" && a.dev === b.dev && a.ino === b.ino;
|
|
6814
|
-
case "windows":
|
|
6815
|
-
return true;
|
|
6816
|
-
case "missing":
|
|
6817
|
-
return true;
|
|
6818
|
-
}
|
|
6819
|
-
}
|
|
6820
|
-
function sameOwner(a, b) {
|
|
6821
|
-
return a.pid === b.pid && a.nonce === b.nonce && sameEndpoint(a.endpoint, b.endpoint);
|
|
6822
|
-
}
|
|
6823
|
-
async function validateExistingOwner(paths, pingOwner) {
|
|
6824
|
-
let token = readOrCreateAuthToken(paths);
|
|
6825
|
-
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
6826
|
-
const owner = readDaemonOwner(paths);
|
|
6827
|
-
if (!owner)
|
|
6828
|
-
return token;
|
|
6829
|
-
const ping = await pingOwner(token);
|
|
6830
|
-
if (ping && owner.nonce === ping.nonce && sameEndpoint(owner.endpoint, ping.endpoint)) {
|
|
6831
|
-
throw new DaemonAlreadyRunningError("LSP daemon already running");
|
|
6832
|
-
}
|
|
6833
|
-
if (ping)
|
|
6834
|
-
continue;
|
|
6835
|
-
if (isProcessAlive2(owner.pid))
|
|
6836
|
-
throw new DaemonStartupDeferredError("owner_pid_live_unreachable");
|
|
6837
|
-
const reread = readDaemonOwner(paths);
|
|
6838
|
-
if (!reread || !sameOwner(reread, owner)) {
|
|
6839
|
-
throw new DaemonStartupDeferredError("owner_changed_during_cleanup");
|
|
6840
|
-
}
|
|
6841
|
-
cleanupDeadOwner(paths, owner);
|
|
6842
|
-
token = rotateAuthToken(paths);
|
|
6843
|
-
return token;
|
|
6844
|
-
}
|
|
6845
|
-
throw new DaemonStartupDeferredError("reachable_owner_mismatch");
|
|
6846
|
-
}
|
|
6847
|
-
function cleanupDeadOwner(paths, owner) {
|
|
6848
|
-
if (process.platform !== "win32" && existsSync14(owner.endpoint.path)) {
|
|
6849
|
-
const stat = lstatSync7(owner.endpoint.path);
|
|
6850
|
-
if (stat.isSocket())
|
|
6851
|
-
unlinkSync4(owner.endpoint.path);
|
|
6852
|
-
}
|
|
6853
|
-
unlinkQuietly(paths.pid);
|
|
6854
|
-
unlinkQuietly(paths.endpoint);
|
|
6855
|
-
unlinkQuietly(paths.owner);
|
|
6856
|
-
}
|
|
6857
|
-
function newOwner(paths) {
|
|
6858
|
-
return {
|
|
6859
|
-
pid: process.pid,
|
|
6860
|
-
nonce: randomUUID(),
|
|
6861
|
-
startedAt: new Date().toISOString(),
|
|
6862
|
-
endpoint: endpointIdentity(paths.socket)
|
|
6863
|
-
};
|
|
6864
|
-
}
|
|
6865
|
-
function parseOwner(value) {
|
|
6866
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
6867
|
-
return null;
|
|
6868
|
-
const pid = Reflect.get(value, "pid");
|
|
6869
|
-
const nonce = Reflect.get(value, "nonce");
|
|
6870
|
-
const startedAt = Reflect.get(value, "startedAt");
|
|
6871
|
-
const endpoint = parseEndpoint(Reflect.get(value, "endpoint"));
|
|
6872
|
-
if (typeof pid !== "number" || typeof nonce !== "string" || typeof startedAt !== "string" || !endpoint)
|
|
6873
|
-
return null;
|
|
6874
|
-
return { pid, nonce, startedAt, endpoint };
|
|
6875
|
-
}
|
|
6876
|
-
function parseEndpoint(value) {
|
|
6877
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
6878
|
-
return null;
|
|
6879
|
-
const kind = Reflect.get(value, "kind");
|
|
6880
|
-
const path = Reflect.get(value, "path");
|
|
6881
|
-
if (typeof path !== "string")
|
|
6882
|
-
return null;
|
|
6883
|
-
if (kind === undefined)
|
|
6884
|
-
return endpointIdentity(path);
|
|
6885
|
-
if (kind === "windows")
|
|
6886
|
-
return { kind, path };
|
|
6887
|
-
if (kind === "missing")
|
|
6888
|
-
return { kind, path };
|
|
6889
|
-
const dev = Reflect.get(value, "dev");
|
|
6890
|
-
const ino = Reflect.get(value, "ino");
|
|
6891
|
-
if (kind === "unix" && typeof dev === "number" && typeof ino === "number")
|
|
6892
|
-
return { kind, path, dev, ino };
|
|
6893
|
-
return null;
|
|
6894
|
-
}
|
|
6895
|
-
|
|
6896
6927
|
// src/version-reap.ts
|
|
6897
6928
|
import { execFile } from "node:child_process";
|
|
6898
6929
|
import { readdir, readFile, rm } from "node:fs/promises";
|
|
@@ -6919,7 +6950,7 @@ async function attestDaemonCliProcess(pid, platform, deps = {}) {
|
|
|
6919
6950
|
async function reapStaleDaemonVersions(ownPaths, deps = {}) {
|
|
6920
6951
|
const baseDir = dirname14(ownPaths.dir);
|
|
6921
6952
|
const platform = deps.platform ?? process.platform;
|
|
6922
|
-
const isAlive = deps.isAlive ??
|
|
6953
|
+
const isAlive = deps.isAlive ?? isProcessAlive;
|
|
6923
6954
|
const attest = deps.attest ?? ((pid) => attestDaemonCliProcess(pid, platform));
|
|
6924
6955
|
const sendSignal = deps.sendSignal ?? defaultSendSignal;
|
|
6925
6956
|
const waitForExit = deps.waitForExit ?? defaultWaitForExit;
|
|
@@ -7059,7 +7090,7 @@ function defaultSendSignal(pid, signal) {
|
|
|
7059
7090
|
async function defaultWaitForExit(pid, timeoutMs) {
|
|
7060
7091
|
const deadline = Date.now() + timeoutMs;
|
|
7061
7092
|
for (;; ) {
|
|
7062
|
-
if (!
|
|
7093
|
+
if (!isProcessAlive(pid))
|
|
7063
7094
|
return true;
|
|
7064
7095
|
if (Date.now() >= deadline)
|
|
7065
7096
|
return false;
|
|
@@ -7229,7 +7260,12 @@ async function main() {
|
|
|
7229
7260
|
process.exitCode = 2;
|
|
7230
7261
|
}
|
|
7231
7262
|
main().catch((error) => {
|
|
7232
|
-
|
|
7263
|
+
if (error instanceof DaemonStartupDeferredError) {
|
|
7264
|
+
stderr.write(`[lsp-daemon] startup deferred: ${error.reason}
|
|
7265
|
+
`);
|
|
7266
|
+
} else {
|
|
7267
|
+
stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}
|
|
7233
7268
|
`);
|
|
7269
|
+
}
|
|
7234
7270
|
process.exitCode = 1;
|
|
7235
7271
|
});
|