oh-my-opencode 4.18.1 → 4.18.2

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.
Files changed (116) hide show
  1. package/.agents/skills/codex-qa/scripts/lsp-e2e.sh +39 -16
  2. package/.agents/skills/opencode-qa/scripts/lsp-e2e.sh +111 -28
  3. package/bin/AGENTS.md +33 -0
  4. package/dist/cli/index.js +57 -18
  5. package/dist/cli-node/index.js +57 -18
  6. package/dist/hooks/anthropic-context-window-limit-recovery/empty-content-recovery-sdk.d.ts +6 -0
  7. package/dist/hooks/category-skill-reminder/hook.d.ts +9 -1
  8. package/dist/hooks/comment-checker/hook.d.ts +8 -1
  9. package/dist/hooks/todo-continuation-enforcer/types.d.ts +3 -0
  10. package/dist/index.js +560 -333
  11. package/dist/plugin/messages-transform.d.ts +1 -0
  12. package/dist/plugin-handlers/prometheus-agent-config-builder.d.ts +2 -0
  13. package/dist/tui.js +43 -4
  14. package/package.json +13 -13
  15. package/packages/git-bash-mcp/dist/cli.js +81 -19
  16. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +4 -4
  17. package/packages/lsp-core/src/lsp/directory-diagnostics.test.ts +118 -1
  18. package/packages/lsp-core/src/lsp/directory-diagnostics.ts +1 -1
  19. package/packages/lsp-daemon/dist/cli.js +262 -63
  20. package/packages/lsp-daemon/dist/client.js +194 -50
  21. package/packages/lsp-daemon/dist/daemon-client.d.ts +2 -2
  22. package/packages/lsp-daemon/dist/daemon-client.js +26 -2
  23. package/packages/lsp-daemon/dist/ensure-daemon.d.ts +5 -4
  24. package/packages/lsp-daemon/dist/ensure-daemon.js +79 -18
  25. package/packages/lsp-daemon/dist/index.js +262 -63
  26. package/packages/lsp-daemon/dist/proxy.d.ts +2 -0
  27. package/packages/lsp-daemon/dist/proxy.js +79 -23
  28. package/packages/lsp-tools-mcp/dist/cli.js +82 -20
  29. package/packages/lsp-tools-mcp/dist/mcp.js +82 -20
  30. package/packages/lsp-tools-mcp/dist/tools.js +1 -1
  31. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  32. package/packages/omo-codex/plugin/.mcp.json +2 -1
  33. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  34. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  35. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +100 -28
  36. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +100 -28
  37. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  38. package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +21 -9
  39. package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +35 -0
  40. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +69 -0
  41. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +57 -1
  42. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  43. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  44. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  45. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  46. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  47. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  48. package/packages/omo-codex/plugin/components/lsp/.mcp.json +2 -1
  49. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  50. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +113 -31
  51. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  52. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  53. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +1 -0
  54. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.5.md +1 -1
  55. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +5 -3
  56. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  57. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  58. package/packages/omo-codex/plugin/components/start-work-continuation/README.md +2 -2
  59. package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +3 -3
  60. package/packages/omo-codex/plugin/components/start-work-continuation/dist/cli.js +2 -2
  61. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  62. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  63. package/packages/omo-codex/plugin/components/start-work-continuation/src/boulder-reader.ts +1 -1
  64. package/packages/omo-codex/plugin/components/start-work-continuation/src/codex-hook.ts +1 -1
  65. package/packages/omo-codex/plugin/components/start-work-continuation/test/boulder-reader.test.ts +15 -1
  66. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +20 -0
  67. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  68. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  69. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  70. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  71. package/packages/omo-codex/plugin/components/ultrawork/directive.md +50 -33
  72. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  73. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  74. package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +50 -33
  75. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +50 -33
  76. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +3 -3
  77. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +5 -6
  78. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  79. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  80. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +1 -1
  81. package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +5 -6
  82. package/packages/omo-codex/plugin/components/ulw-loop/test/stop-resume-hook.test.ts +2 -2
  83. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  84. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  85. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  86. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  87. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  88. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  89. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  90. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  91. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  92. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  93. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  94. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  95. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  96. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  97. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  98. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  99. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  100. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  101. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  102. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  103. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  104. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  105. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  106. package/packages/omo-codex/plugin/package-lock.json +13 -13
  107. package/packages/omo-codex/plugin/package.json +1 -1
  108. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +10 -2
  109. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +7 -0
  110. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +3 -2
  111. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +50 -33
  112. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +1 -1
  113. package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -0
  114. package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +7 -0
  115. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +35 -4
  116. package/packages/omo-codex/scripts/install-dist/install-local.mjs +1 -1
@@ -21,6 +21,9 @@ function errorResponse(id, code, message, data) {
21
21
  function jsonRpcId(value) {
22
22
  return typeof value === "string" || typeof value === "number" || value === null ? value : null;
23
23
  }
24
+ function messageFromError(error) {
25
+ return error instanceof Error ? error.message : String(error);
26
+ }
24
27
  // ../mcp-stdio-core/src/transport.ts
25
28
  var HEADER_SEPARATOR = Buffer.from(`\r
26
29
  \r
@@ -43,16 +46,45 @@ async function* readStdioJsonRpcMessages(input) {
43
46
  yield parseJsonPayload(trailing, "line");
44
47
  }
45
48
  }
46
- function writeStdioJsonRpcResponse(output, response, responseMode) {
49
+ async function writeStdioJsonRpcResponse(output, response, responseMode) {
47
50
  const body = JSON.stringify(response);
48
- if (responseMode === "framed") {
49
- output.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r
51
+ const payload = responseMode === "framed" ? `Content-Length: ${Buffer.byteLength(body, "utf8")}\r
50
52
  \r
51
- ${body}`);
52
- return;
53
- }
54
- output.write(`${body}
55
- `);
53
+ ${body}` : `${body}
54
+ `;
55
+ await writeChunk(output, payload);
56
+ }
57
+ function writeChunk(output, chunk) {
58
+ return new Promise((resolve, reject) => {
59
+ let settled = false;
60
+ const onError = (error) => {
61
+ if (settled)
62
+ return;
63
+ settled = true;
64
+ reject(error);
65
+ };
66
+ output.once("error", onError);
67
+ try {
68
+ output.write(chunk, (error) => {
69
+ if (settled)
70
+ return;
71
+ settled = true;
72
+ if (error) {
73
+ queueMicrotask(() => output.removeListener("error", onError));
74
+ reject(error);
75
+ return;
76
+ }
77
+ output.removeListener("error", onError);
78
+ resolve();
79
+ });
80
+ } catch (error) {
81
+ output.removeListener("error", onError);
82
+ if (settled)
83
+ return;
84
+ settled = true;
85
+ reject(error);
86
+ }
87
+ });
56
88
  }
57
89
  function readNextMessage(buffer) {
58
90
  if (buffer.length === 0)
@@ -148,39 +180,69 @@ async function runJsonRpcStdioServer(config) {
148
180
  break;
149
181
  idleTimer.arm();
150
182
  if (message.kind === "parse_error") {
151
- handleParseError(message, config, log);
183
+ if (!await handleParseError(message, config, log))
184
+ break;
152
185
  continue;
153
186
  }
154
- await handleRequest(message, config, log);
187
+ if (!await handleRequest(message, config, log))
188
+ break;
155
189
  }
156
190
  } finally {
157
191
  idleTimer.clear();
158
192
  log("stdio_stopped");
159
193
  }
160
194
  }
161
- function handleParseError(message, config, log) {
195
+ async function handleParseError(message, config, log) {
162
196
  log("parse_error", { message: message.message });
163
197
  const response = config.parseErrorResponse?.(message.message) ?? errorResponse(null, -32700, "Parse error", message.message);
164
- if (response !== undefined) {
165
- writeStdioJsonRpcResponse(config.output, response, message.responseMode);
166
- }
198
+ if (response === undefined)
199
+ return true;
200
+ return writeResponse(response, {
201
+ output: config.output,
202
+ responseMode: message.responseMode,
203
+ log
204
+ });
167
205
  }
168
206
  async function handleRequest(message, config, log) {
169
207
  const parsed = message.payload;
170
208
  const id = isPlainRecord(parsed) ? jsonRpcId(parsed["id"]) : null;
171
209
  const method = isPlainRecord(parsed) && typeof parsed["method"] === "string" ? parsed["method"] : null;
172
210
  log("request", { id: id === null ? null : String(id), method });
211
+ let response;
173
212
  try {
174
- const response = await config.handler(parsed, config.handlerOptions);
175
- if (response === undefined)
176
- return;
177
- writeStdioJsonRpcResponse(config.output, response, message.responseMode);
178
- log("response", { id: String(response.id), method, is_error: response.error !== undefined });
213
+ response = await config.handler(parsed, config.handlerOptions);
179
214
  } catch (error) {
180
215
  if (config.onHandlerError === undefined)
181
216
  throw error;
182
217
  config.onHandlerError(error);
218
+ return true;
183
219
  }
220
+ if (response === undefined)
221
+ return true;
222
+ if (!await writeResponse(response, {
223
+ output: config.output,
224
+ responseMode: message.responseMode,
225
+ log
226
+ }))
227
+ return false;
228
+ log("response", { id: String(response.id), method, is_error: response.error !== undefined });
229
+ return true;
230
+ }
231
+ async function writeResponse(response, context) {
232
+ try {
233
+ await writeStdioJsonRpcResponse(context.output, response, context.responseMode);
234
+ return true;
235
+ } catch (error) {
236
+ if (!isTerminalOutputError(error))
237
+ throw error;
238
+ context.log("output_error", { message: messageFromError(error) });
239
+ return false;
240
+ }
241
+ }
242
+ function isTerminalOutputError(error) {
243
+ if (!(error instanceof Error) || !("code" in error))
244
+ return false;
245
+ return error.code === "EPIPE" || error.code === "ERR_STREAM_DESTROYED" || error.code === "ERR_STREAM_WRITE_AFTER_END";
184
246
  }
185
247
  function createIdleTimer(idleTimeoutMs, log, onIdleTimeout) {
186
248
  let timer = null;
@@ -4241,7 +4303,7 @@ async function aggregateDiagnosticsForDirectory(directory, extension, severity,
4241
4303
  const fileErrors = [];
4242
4304
  const maxConcurrency = Math.max(1, options.maxConcurrency ?? DIRECTORY_DIAGNOSTICS_MAX_CONCURRENCY);
4243
4305
  options.signal?.throwIfAborted();
4244
- const client = await manager.getClient(root, server2);
4306
+ const client = await manager.getClient(root, server2, options.signal);
4245
4307
  try {
4246
4308
  let nextIndex = 0;
4247
4309
  const workers = Array.from({ length: Math.min(maxConcurrency, filesToProcess.length) }, async () => {
@@ -4999,14 +5061,14 @@ function requestedProtocolVersion(params) {
4999
5061
  }
5000
5062
 
5001
5063
  // src/daemon-client.ts
5002
- import { connect as connect2 } from "node:net";
5064
+ import { connect } from "node:net";
5003
5065
  import { homedir as homedir3 } from "node:os";
5004
5066
  import { join as join7 } from "node:path";
5005
5067
 
5006
5068
  // src/ensure-daemon.ts
5007
5069
  import { spawn as spawn2 } from "node:child_process";
5008
5070
  import { closeSync as closeSync2, mkdirSync as mkdirSync3, openSync as openSync2 } from "node:fs";
5009
- import { connect } from "node:net";
5071
+ import { Socket } from "node:net";
5010
5072
  import { dirname as dirname8 } from "node:path";
5011
5073
  import { execPath } from "node:process";
5012
5074
 
@@ -5413,42 +5475,48 @@ class DaemonUnreachableError extends Error {
5413
5475
  async function ensureDaemonRunning(paths, deps = defaultEnsureDaemonDeps(), options = {}) {
5414
5476
  const readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
5415
5477
  const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
5416
- if (await deps.probe(paths))
5478
+ const signal = options.signal;
5479
+ throwIfAborted(signal);
5480
+ if (await awaitWithSignal2(deps.probe(paths, signal), signal))
5417
5481
  return;
5482
+ throwIfAborted(signal);
5418
5483
  deps.spawnDaemon(paths);
5419
- await waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs);
5484
+ await waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs, signal);
5420
5485
  }
5421
- async function waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs) {
5486
+ async function waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs, signal) {
5422
5487
  const deadline = deps.now() + readyTimeoutMs;
5423
5488
  for (;; ) {
5424
- if (await deps.probe(paths))
5489
+ throwIfAborted(signal);
5490
+ if (await awaitWithSignal2(deps.probe(paths, signal), signal))
5425
5491
  return;
5426
5492
  if (deps.now() >= deadline)
5427
5493
  throw new DaemonUnreachableError(paths.socket);
5428
- await deps.sleep(pollIntervalMs);
5494
+ await awaitWithSignal2(deps.sleep(pollIntervalMs, signal), signal);
5429
5495
  }
5430
5496
  }
5431
- async function probeDaemon(paths, timeoutMs = PROBE_TIMEOUT_MS) {
5497
+ async function probeDaemon(paths, timeoutMs = PROBE_TIMEOUT_MS, signal) {
5432
5498
  const token = readAuthToken(paths);
5433
5499
  if (!token)
5434
5500
  return false;
5435
- return await pingDaemon(paths, token, timeoutMs) !== null;
5501
+ return await pingDaemon(paths, token, timeoutMs, signal) !== null;
5436
5502
  }
5437
- function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS) {
5503
+ function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS, signal) {
5438
5504
  return new Promise((resolve9) => {
5439
- const socket = connect(paths.socket);
5505
+ const socket = new Socket;
5440
5506
  let settled = false;
5507
+ let timer;
5441
5508
  const finish = (value) => {
5442
5509
  if (settled)
5443
5510
  return;
5444
5511
  settled = true;
5512
+ if (timer !== undefined)
5513
+ clearTimeout(timer);
5514
+ signal?.removeEventListener("abort", onAbort);
5445
5515
  socket.destroy();
5446
5516
  resolve9(value);
5447
5517
  };
5448
- const timer = setTimeout(() => finish(null), timeoutMs);
5449
- timer.unref?.();
5518
+ const onAbort = () => finish(null);
5450
5519
  const decoder = createLineDecoder((message) => {
5451
- clearTimeout(timer);
5452
5520
  finish(parsePingResponse(message));
5453
5521
  });
5454
5522
  socket.once("connect", () => {
@@ -5456,9 +5524,16 @@ function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS) {
5456
5524
  });
5457
5525
  socket.on("data", (chunk) => decoder.push(chunk));
5458
5526
  socket.once("error", () => {
5459
- clearTimeout(timer);
5460
5527
  finish(null);
5461
5528
  });
5529
+ timer = setTimeout(() => finish(null), timeoutMs);
5530
+ timer.unref?.();
5531
+ if (signal?.aborted) {
5532
+ onAbort();
5533
+ return;
5534
+ }
5535
+ signal?.addEventListener("abort", onAbort, { once: true });
5536
+ socket.connect(paths.socket);
5462
5537
  });
5463
5538
  }
5464
5539
  function spawnDaemonProcess(paths) {
@@ -5476,14 +5551,62 @@ function spawnDaemonProcess(paths) {
5476
5551
  }
5477
5552
  function defaultEnsureDaemonDeps() {
5478
5553
  return {
5479
- probe: (paths) => probeDaemon(paths),
5554
+ probe: (paths, signal) => probeDaemon(paths, PROBE_TIMEOUT_MS, signal),
5480
5555
  spawnDaemon: (paths) => spawnDaemonProcess(paths),
5481
- sleep: (ms) => new Promise((resolve9) => {
5482
- setTimeout(resolve9, ms);
5483
- }),
5556
+ sleep: (ms, signal) => sleepWithSignal(ms, signal),
5484
5557
  now: () => Date.now()
5485
5558
  };
5486
5559
  }
5560
+ function sleepWithSignal(ms, signal) {
5561
+ return new Promise((resolve9) => {
5562
+ let settled = false;
5563
+ const finish = () => {
5564
+ if (settled)
5565
+ return;
5566
+ settled = true;
5567
+ clearTimeout(timer);
5568
+ signal?.removeEventListener("abort", finish);
5569
+ resolve9();
5570
+ };
5571
+ const timer = setTimeout(finish, ms);
5572
+ if (signal?.aborted) {
5573
+ finish();
5574
+ return;
5575
+ }
5576
+ signal?.addEventListener("abort", finish, { once: true });
5577
+ });
5578
+ }
5579
+ function awaitWithSignal2(promise, signal) {
5580
+ if (!signal)
5581
+ return promise;
5582
+ if (signal.aborted)
5583
+ return Promise.reject(abortError3(signal));
5584
+ return new Promise((resolve9, reject) => {
5585
+ let settled = false;
5586
+ const finish = (run) => {
5587
+ if (settled)
5588
+ return;
5589
+ settled = true;
5590
+ signal.removeEventListener("abort", onAbort);
5591
+ run();
5592
+ };
5593
+ const onAbort = () => finish(() => reject(abortError3(signal)));
5594
+ signal.addEventListener("abort", onAbort, { once: true });
5595
+ promise.then((value) => finish(() => resolve9(value)), (error) => finish(() => reject(error)));
5596
+ });
5597
+ }
5598
+ function throwIfAborted(signal) {
5599
+ if (signal?.aborted)
5600
+ throw abortError3(signal);
5601
+ }
5602
+ function abortError3(signal) {
5603
+ const reason = signal.reason;
5604
+ if (reason instanceof Error)
5605
+ return reason;
5606
+ const error = new Error(typeof reason === "string" ? reason : "daemon startup cancelled");
5607
+ error.name = "AbortError";
5608
+ return error;
5609
+ }
5487
5610
  function parsePingResponse(message) {
5488
5611
  if (!message || typeof message !== "object" || Array.isArray(message))
5489
5612
  return null;
@@ -5627,14 +5750,14 @@ class DaemonRequestCancelledError extends DaemonRequestError {
5627
5750
  async function callToolViaDaemon(name, args, options) {
5628
5751
  const context = requireContext(options.context);
5629
5752
  const paths = options.paths ?? daemonPaths();
5630
- const ensure = options.ensure ?? ensureDaemonRunning;
5753
+ const ensure = options.ensure ?? ((ensurePaths, signal) => ensureDaemonRunning(ensurePaths, undefined, signal === undefined ? {} : { signal }));
5631
5754
  const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
5632
5755
  const requestArgs = withContext(args, context);
5633
5756
  let lastError;
5634
5757
  let authRefreshUsed = false;
5635
5758
  for (let attempt = 0;attempt < 3; attempt += 1) {
5636
5759
  try {
5637
- await ensure(paths);
5760
+ await ensureDaemonAvailable(paths, ensure, options.signal);
5638
5761
  const token = readAuthToken(paths);
5639
5762
  if (!token)
5640
5763
  throw new DaemonRequestError("daemon auth token missing", false);
@@ -5676,6 +5799,27 @@ function requireContext(context) {
5676
5799
  function withContext(args, context) {
5677
5800
  return { ...args, [CONTEXT_KEY]: context };
5678
5801
  }
5802
+ function ensureDaemonAvailable(paths, ensure, signal) {
5803
+ if (!signal)
5804
+ return ensure(paths);
5805
+ return new Promise((resolve9, reject) => {
5806
+ let settled = false;
5807
+ const finish = (run) => {
5808
+ if (settled)
5809
+ return;
5810
+ settled = true;
5811
+ signal.removeEventListener("abort", onAbort);
5812
+ run();
5813
+ };
5814
+ const onAbort = () => finish(() => reject(new DaemonRequestCancelledError(false)));
5815
+ if (signal.aborted) {
5816
+ onAbort();
5817
+ return;
5818
+ }
5819
+ signal.addEventListener("abort", onAbort, { once: true });
5820
+ Promise.resolve().then(() => ensure(paths, signal)).then(() => finish(() => resolve9()), (error) => finish(() => reject(signal.aborted ? new DaemonRequestCancelledError(false) : error)));
5821
+ });
5822
+ }
5679
5823
  function daemonUnreachableResult(paths, error) {
5680
5824
  const text2 = [
5681
5825
  `LSP daemon unreachable: ${errorText(error)}.`,
@@ -5689,7 +5833,7 @@ function daemonUnreachableResult(paths, error) {
5689
5833
  }
5690
5834
  function sendToolCall(paths, token, name, args, options) {
5691
5835
  return new Promise((resolve9, reject) => {
5692
- const socket = connect2(paths.socket);
5836
+ const socket = connect(paths.socket);
5693
5837
  const requestId = allocateProxyRequestId();
5694
5838
  let settled = false;
5695
5839
  let requestWritten = false;
@@ -5790,38 +5934,93 @@ function errorText(error) {
5790
5934
  }
5791
5935
 
5792
5936
  // src/proxy.ts
5937
+ var DEFAULT_STARTUP_TIMEOUT_MS = 1e4;
5793
5938
  async function runMcpStdioProxy(options = {}) {
5794
5939
  const input = options.input ?? process.stdin;
5795
5940
  const output = options.output ?? process.stdout;
5796
- const paths = options.paths ?? daemonPaths();
5797
- const env = options.env ?? process.env;
5798
- const cwd = options.cwd ?? inferOpenCodeProjectCwd(env["LSP_TOOLS_MCP_PROJECT_CONFIG"]);
5799
- const contextEnv = cwd === undefined ? env : canonicalizeContextEnv(env);
5800
- const contextInput = {
5801
- env: contextEnv,
5802
- ...cwd === undefined ? {} : { cwd },
5803
- ...options.homeDir === undefined ? {} : { homeDir: options.homeDir }
5941
+ const stderr = options.stderr ?? process.stderr;
5942
+ const startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
5943
+ const lifecycleController = new AbortController;
5944
+ const abortFromInput = () => lifecycleController.abort();
5945
+ let startupExpired = false;
5946
+ let startupTimer;
5947
+ const clearStartupWatchdog = () => {
5948
+ if (startupTimer === undefined)
5949
+ return;
5950
+ clearTimeout(startupTimer);
5951
+ startupTimer = undefined;
5804
5952
  };
5805
- const context = options.context ?? createStandaloneMcpRequestContext(contextInput);
5806
- const callOptions = { paths, context, ...options.ensure ? { ensure: options.ensure } : {} };
5807
- await runJsonRpcStdioServer({
5808
- input,
5809
- output,
5810
- idleTimeoutMs: 0,
5811
- handler: (request, requestOptions) => runWithRequestContext(context, () => handleProxyRequest(request, requestOptions)),
5812
- handlerOptions: callOptions,
5813
- onHandlerError: (error) => {
5814
- process.stderr.write(`[lsp-daemon] proxy error: ${error instanceof Error ? error.message : String(error)}
5953
+ input.once("end", abortFromInput);
5954
+ input.once("close", abortFromInput);
5955
+ if (startupTimeoutMs > 0) {
5956
+ startupTimer = setTimeout(() => {
5957
+ startupExpired = true;
5958
+ input.destroy();
5959
+ try {
5960
+ stderr.write(`[lsp-daemon] no MCP request received within ${startupTimeoutMs}ms; exiting
5815
5961
  `);
5816
- }
5817
- });
5962
+ } catch (error) {
5963
+ if (stderr !== process.stderr) {
5964
+ process.stderr.write(`[lsp-daemon] startup diagnostic failed: ${error instanceof Error ? error.message : String(error)}
5965
+ `);
5966
+ }
5967
+ }
5968
+ }, startupTimeoutMs);
5969
+ }
5970
+ try {
5971
+ const paths = options.paths ?? daemonPaths();
5972
+ const env = options.env ?? process.env;
5973
+ const cwd = options.cwd ?? inferOpenCodeProjectCwd(env["LSP_TOOLS_MCP_PROJECT_CONFIG"]);
5974
+ const contextEnv = cwd === undefined ? env : canonicalizeContextEnv(env);
5975
+ const contextInput = {
5976
+ env: contextEnv,
5977
+ ...cwd === undefined ? {} : { cwd },
5978
+ ...options.homeDir === undefined ? {} : { homeDir: options.homeDir }
5979
+ };
5980
+ const context = options.context ?? createStandaloneMcpRequestContext(contextInput);
5981
+ const callOptions = {
5982
+ paths,
5983
+ context,
5984
+ ...options.ensure ? { ensure: options.ensure } : {},
5985
+ signal: lifecycleController.signal
5986
+ };
5987
+ await runJsonRpcStdioServer({
5988
+ input,
5989
+ output,
5990
+ idleTimeoutMs: 0,
5991
+ handler: (request, requestOptions) => {
5992
+ clearStartupWatchdog();
5993
+ return runWithRequestContext(context, () => handleProxyRequest(request, requestOptions));
5994
+ },
5995
+ handlerOptions: callOptions,
5996
+ onHandlerError: (error) => {
5997
+ stderr.write(`[lsp-daemon] proxy error: ${error instanceof Error ? error.message : String(error)}
5998
+ `);
5999
+ }
6000
+ });
6001
+ } catch (error) {
6002
+ if (!startupExpired || !isPrematureCloseError(error))
6003
+ throw error;
6004
+ } finally {
6005
+ if (startupTimer !== undefined)
6006
+ clearTimeout(startupTimer);
6007
+ input.removeListener("end", abortFromInput);
6008
+ input.removeListener("close", abortFromInput);
6009
+ }
6010
+ }
6011
+ function isPrematureCloseError(error) {
6012
+ return error instanceof Error && Reflect.get(error, "code") === "ERR_STREAM_PREMATURE_CLOSE";
5818
6013
  }
5819
6014
  async function handleProxyRequest(parsed, callOptions) {
5820
6015
  const toolCall = asToolCall(parsed);
5821
6016
  if (!toolCall)
5822
6017
  return handleLspMcpRequest(parsed);
5823
6018
  const result = await callToolViaDaemon(toolCall.name, toolCall.args, callOptions);
5824
- return successResponse(toolCall.id, { content: result.content, isError: result.isError ?? false, details: result.details });
6019
+ return successResponse(toolCall.id, {
6020
+ content: result.content,
6021
+ isError: result.isError ?? false,
6022
+ details: result.details
6023
+ });
5825
6024
  }
5826
6025
  function asToolCall(parsed) {
5827
6026
  if (!isPlainRecord(parsed) || parsed["method"] !== "tools/call")