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
@@ -1,5 +1,5 @@
1
1
  // src/daemon-client.ts
2
- import { connect as connect2 } from "node:net";
2
+ import { connect } from "node:net";
3
3
  import { homedir as homedir3 } from "node:os";
4
4
  import { join as join7 } from "node:path";
5
5
 
@@ -180,7 +180,7 @@ function isPlainRecord(value) {
180
180
  // src/ensure-daemon.ts
181
181
  import { spawn } from "node:child_process";
182
182
  import { closeSync as closeSync2, mkdirSync as mkdirSync2, openSync as openSync2 } from "node:fs";
183
- import { connect } from "node:net";
183
+ import { Socket } from "node:net";
184
184
  import { dirname as dirname3 } from "node:path";
185
185
  import { execPath } from "node:process";
186
186
 
@@ -587,42 +587,48 @@ class DaemonUnreachableError extends Error {
587
587
  async function ensureDaemonRunning(paths, deps = defaultEnsureDaemonDeps(), options = {}) {
588
588
  const readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
589
589
  const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
590
- if (await deps.probe(paths))
590
+ const signal = options.signal;
591
+ throwIfAborted(signal);
592
+ if (await awaitWithSignal(deps.probe(paths, signal), signal))
591
593
  return;
594
+ throwIfAborted(signal);
592
595
  deps.spawnDaemon(paths);
593
- await waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs);
596
+ await waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs, signal);
594
597
  }
595
- async function waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs) {
598
+ async function waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs, signal) {
596
599
  const deadline = deps.now() + readyTimeoutMs;
597
600
  for (;; ) {
598
- if (await deps.probe(paths))
601
+ throwIfAborted(signal);
602
+ if (await awaitWithSignal(deps.probe(paths, signal), signal))
599
603
  return;
600
604
  if (deps.now() >= deadline)
601
605
  throw new DaemonUnreachableError(paths.socket);
602
- await deps.sleep(pollIntervalMs);
606
+ await awaitWithSignal(deps.sleep(pollIntervalMs, signal), signal);
603
607
  }
604
608
  }
605
- async function probeDaemon(paths, timeoutMs = PROBE_TIMEOUT_MS) {
609
+ async function probeDaemon(paths, timeoutMs = PROBE_TIMEOUT_MS, signal) {
606
610
  const token = readAuthToken(paths);
607
611
  if (!token)
608
612
  return false;
609
- return await pingDaemon(paths, token, timeoutMs) !== null;
613
+ return await pingDaemon(paths, token, timeoutMs, signal) !== null;
610
614
  }
611
- function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS) {
615
+ function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS, signal) {
612
616
  return new Promise((resolve2) => {
613
- const socket = connect(paths.socket);
617
+ const socket = new Socket;
614
618
  let settled = false;
619
+ let timer;
615
620
  const finish = (value) => {
616
621
  if (settled)
617
622
  return;
618
623
  settled = true;
624
+ if (timer !== undefined)
625
+ clearTimeout(timer);
626
+ signal?.removeEventListener("abort", onAbort);
619
627
  socket.destroy();
620
628
  resolve2(value);
621
629
  };
622
- const timer = setTimeout(() => finish(null), timeoutMs);
623
- timer.unref?.();
630
+ const onAbort = () => finish(null);
624
631
  const decoder = createLineDecoder((message) => {
625
- clearTimeout(timer);
626
632
  finish(parsePingResponse(message));
627
633
  });
628
634
  socket.once("connect", () => {
@@ -630,9 +636,16 @@ function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS) {
630
636
  });
631
637
  socket.on("data", (chunk) => decoder.push(chunk));
632
638
  socket.once("error", () => {
633
- clearTimeout(timer);
634
639
  finish(null);
635
640
  });
641
+ timer = setTimeout(() => finish(null), timeoutMs);
642
+ timer.unref?.();
643
+ if (signal?.aborted) {
644
+ onAbort();
645
+ return;
646
+ }
647
+ signal?.addEventListener("abort", onAbort, { once: true });
648
+ socket.connect(paths.socket);
636
649
  });
637
650
  }
638
651
  function spawnDaemonProcess(paths) {
@@ -650,14 +663,62 @@ function spawnDaemonProcess(paths) {
650
663
  }
651
664
  function defaultEnsureDaemonDeps() {
652
665
  return {
653
- probe: (paths) => probeDaemon(paths),
666
+ probe: (paths, signal) => probeDaemon(paths, PROBE_TIMEOUT_MS, signal),
654
667
  spawnDaemon: (paths) => spawnDaemonProcess(paths),
655
- sleep: (ms) => new Promise((resolve2) => {
656
- setTimeout(resolve2, ms);
657
- }),
668
+ sleep: (ms, signal) => sleepWithSignal(ms, signal),
658
669
  now: () => Date.now()
659
670
  };
660
671
  }
672
+ function sleepWithSignal(ms, signal) {
673
+ return new Promise((resolve2) => {
674
+ let settled = false;
675
+ const finish = () => {
676
+ if (settled)
677
+ return;
678
+ settled = true;
679
+ clearTimeout(timer);
680
+ signal?.removeEventListener("abort", finish);
681
+ resolve2();
682
+ };
683
+ const timer = setTimeout(finish, ms);
684
+ if (signal?.aborted) {
685
+ finish();
686
+ return;
687
+ }
688
+ signal?.addEventListener("abort", finish, { once: true });
689
+ });
690
+ }
691
+ function awaitWithSignal(promise, signal) {
692
+ if (!signal)
693
+ return promise;
694
+ if (signal.aborted)
695
+ return Promise.reject(abortError(signal));
696
+ return new Promise((resolve2, reject) => {
697
+ let settled = false;
698
+ const finish = (run) => {
699
+ if (settled)
700
+ return;
701
+ settled = true;
702
+ signal.removeEventListener("abort", onAbort);
703
+ run();
704
+ };
705
+ const onAbort = () => finish(() => reject(abortError(signal)));
706
+ signal.addEventListener("abort", onAbort, { once: true });
707
+ promise.then((value) => finish(() => resolve2(value)), (error) => finish(() => reject(error)));
708
+ });
709
+ }
710
+ function throwIfAborted(signal) {
711
+ if (signal?.aborted)
712
+ throw abortError(signal);
713
+ }
714
+ function abortError(signal) {
715
+ const reason = signal.reason;
716
+ if (reason instanceof Error)
717
+ return reason;
718
+ const error = new Error(typeof reason === "string" ? reason : "daemon startup cancelled");
719
+ error.name = "AbortError";
720
+ return error;
721
+ }
661
722
  function parsePingResponse(message) {
662
723
  if (!message || typeof message !== "object" || Array.isArray(message))
663
724
  return null;
@@ -697,6 +758,9 @@ function errorResponse(id, code, message, data) {
697
758
  function jsonRpcId2(value) {
698
759
  return typeof value === "string" || typeof value === "number" || value === null ? value : null;
699
760
  }
761
+ function messageFromError(error) {
762
+ return error instanceof Error ? error.message : String(error);
763
+ }
700
764
  // ../mcp-stdio-core/src/transport.ts
701
765
  var HEADER_SEPARATOR = Buffer.from(`\r
702
766
  \r
@@ -719,16 +783,45 @@ async function* readStdioJsonRpcMessages(input) {
719
783
  yield parseJsonPayload(trailing, "line");
720
784
  }
721
785
  }
722
- function writeStdioJsonRpcResponse(output, response, responseMode) {
786
+ async function writeStdioJsonRpcResponse(output, response, responseMode) {
723
787
  const body = JSON.stringify(response);
724
- if (responseMode === "framed") {
725
- output.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r
788
+ const payload = responseMode === "framed" ? `Content-Length: ${Buffer.byteLength(body, "utf8")}\r
726
789
  \r
727
- ${body}`);
728
- return;
729
- }
730
- output.write(`${body}
731
- `);
790
+ ${body}` : `${body}
791
+ `;
792
+ await writeChunk(output, payload);
793
+ }
794
+ function writeChunk(output, chunk) {
795
+ return new Promise((resolve2, reject) => {
796
+ let settled = false;
797
+ const onError = (error) => {
798
+ if (settled)
799
+ return;
800
+ settled = true;
801
+ reject(error);
802
+ };
803
+ output.once("error", onError);
804
+ try {
805
+ output.write(chunk, (error) => {
806
+ if (settled)
807
+ return;
808
+ settled = true;
809
+ if (error) {
810
+ queueMicrotask(() => output.removeListener("error", onError));
811
+ reject(error);
812
+ return;
813
+ }
814
+ output.removeListener("error", onError);
815
+ resolve2();
816
+ });
817
+ } catch (error) {
818
+ output.removeListener("error", onError);
819
+ if (settled)
820
+ return;
821
+ settled = true;
822
+ reject(error);
823
+ }
824
+ });
732
825
  }
733
826
  function readNextMessage(buffer) {
734
827
  if (buffer.length === 0)
@@ -824,39 +917,69 @@ async function runJsonRpcStdioServer(config) {
824
917
  break;
825
918
  idleTimer.arm();
826
919
  if (message.kind === "parse_error") {
827
- handleParseError(message, config, log);
920
+ if (!await handleParseError(message, config, log))
921
+ break;
828
922
  continue;
829
923
  }
830
- await handleRequest(message, config, log);
924
+ if (!await handleRequest(message, config, log))
925
+ break;
831
926
  }
832
927
  } finally {
833
928
  idleTimer.clear();
834
929
  log("stdio_stopped");
835
930
  }
836
931
  }
837
- function handleParseError(message, config, log) {
932
+ async function handleParseError(message, config, log) {
838
933
  log("parse_error", { message: message.message });
839
934
  const response = config.parseErrorResponse?.(message.message) ?? errorResponse(null, -32700, "Parse error", message.message);
840
- if (response !== undefined) {
841
- writeStdioJsonRpcResponse(config.output, response, message.responseMode);
842
- }
935
+ if (response === undefined)
936
+ return true;
937
+ return writeResponse(response, {
938
+ output: config.output,
939
+ responseMode: message.responseMode,
940
+ log
941
+ });
843
942
  }
844
943
  async function handleRequest(message, config, log) {
845
944
  const parsed = message.payload;
846
945
  const id = isPlainRecord(parsed) ? jsonRpcId2(parsed["id"]) : null;
847
946
  const method = isPlainRecord(parsed) && typeof parsed["method"] === "string" ? parsed["method"] : null;
848
947
  log("request", { id: id === null ? null : String(id), method });
948
+ let response;
849
949
  try {
850
- const response = await config.handler(parsed, config.handlerOptions);
851
- if (response === undefined)
852
- return;
853
- writeStdioJsonRpcResponse(config.output, response, message.responseMode);
854
- log("response", { id: String(response.id), method, is_error: response.error !== undefined });
950
+ response = await config.handler(parsed, config.handlerOptions);
855
951
  } catch (error) {
856
952
  if (config.onHandlerError === undefined)
857
953
  throw error;
858
954
  config.onHandlerError(error);
955
+ return true;
859
956
  }
957
+ if (response === undefined)
958
+ return true;
959
+ if (!await writeResponse(response, {
960
+ output: config.output,
961
+ responseMode: message.responseMode,
962
+ log
963
+ }))
964
+ return false;
965
+ log("response", { id: String(response.id), method, is_error: response.error !== undefined });
966
+ return true;
967
+ }
968
+ async function writeResponse(response, context) {
969
+ try {
970
+ await writeStdioJsonRpcResponse(context.output, response, context.responseMode);
971
+ return true;
972
+ } catch (error) {
973
+ if (!isTerminalOutputError(error))
974
+ throw error;
975
+ context.log("output_error", { message: messageFromError(error) });
976
+ return false;
977
+ }
978
+ }
979
+ function isTerminalOutputError(error) {
980
+ if (!(error instanceof Error) || !("code" in error))
981
+ return false;
982
+ return error.code === "EPIPE" || error.code === "ERR_STREAM_DESTROYED" || error.code === "ERR_STREAM_WRITE_AFTER_END";
860
983
  }
861
984
  function createIdleTimer(idleTimeoutMs, log, onIdleTimeout) {
862
985
  let timer = null;
@@ -1065,7 +1188,7 @@ class JsonRpcConnection {
1065
1188
  settled = true;
1066
1189
  this.pendingRequests.delete(key);
1067
1190
  cleanup();
1068
- const rejectCancelled = () => reject(abortError(options.signal));
1191
+ const rejectCancelled = () => reject(abortError2(options.signal));
1069
1192
  if (!requestWritten) {
1070
1193
  cancelAfterWrite = true;
1071
1194
  rejectCancelled();
@@ -1269,7 +1392,7 @@ ${body}`;
1269
1392
  }
1270
1393
  }
1271
1394
  }
1272
- function abortError(signal) {
1395
+ function abortError2(signal) {
1273
1396
  const reason = signal?.reason;
1274
1397
  if (reason instanceof Error)
1275
1398
  return reason;
@@ -3547,11 +3670,11 @@ function waitForDiagnosticsActivity(wait, signal) {
3547
3670
  if (!signal)
3548
3671
  return wait;
3549
3672
  if (signal.aborted)
3550
- return Promise.reject(abortError2(signal));
3673
+ return Promise.reject(abortError3(signal));
3551
3674
  return new Promise((resolve7, reject) => {
3552
3675
  const onAbort = () => {
3553
3676
  signal.removeEventListener("abort", onAbort);
3554
- reject(abortError2(signal));
3677
+ reject(abortError3(signal));
3555
3678
  };
3556
3679
  signal.addEventListener("abort", onAbort, { once: true });
3557
3680
  wait.then(() => {
@@ -3586,7 +3709,7 @@ function preCommitAbortReason(source) {
3586
3709
  return reason;
3587
3710
  return new Error("LSP request cancelled before workspace edit commit");
3588
3711
  }
3589
- function abortError2(signal) {
3712
+ function abortError3(signal) {
3590
3713
  const reason = signal.reason;
3591
3714
  if (reason instanceof Error)
3592
3715
  return reason;
@@ -3621,7 +3744,7 @@ async function stopClientBestEffort(client) {
3621
3744
  reportBestEffortCleanupError("client stop", error);
3622
3745
  }
3623
3746
  }
3624
- function awaitWithSignal(promise, signal) {
3747
+ function awaitWithSignal2(promise, signal) {
3625
3748
  if (!signal)
3626
3749
  return promise;
3627
3750
  return new Promise((resolve7, reject) => {
@@ -3724,7 +3847,7 @@ class LspManager {
3724
3847
  if (managed.initPromise) {
3725
3848
  managed.pendingWaiters++;
3726
3849
  try {
3727
- await awaitWithSignal(managed.initPromise, signal);
3850
+ await awaitWithSignal2(managed.initPromise, signal);
3728
3851
  } catch (err) {
3729
3852
  managed.pendingWaiters--;
3730
3853
  await this.tryDeleteIfOrphaned(key, managed);
@@ -3762,7 +3885,7 @@ class LspManager {
3762
3885
  };
3763
3886
  this.clients.set(key, newManaged);
3764
3887
  try {
3765
- await awaitWithSignal(initPromise, signal);
3888
+ await awaitWithSignal2(initPromise, signal);
3766
3889
  } catch (err) {
3767
3890
  newManaged.pendingWaiters--;
3768
3891
  if (this.clients.get(key) === newManaged) {
@@ -4748,7 +4871,7 @@ async function aggregateDiagnosticsForDirectory(directory, extension, severity,
4748
4871
  const fileErrors = [];
4749
4872
  const maxConcurrency = Math.max(1, options.maxConcurrency ?? DIRECTORY_DIAGNOSTICS_MAX_CONCURRENCY);
4750
4873
  options.signal?.throwIfAborted();
4751
- const client = await manager.getClient(root, server2);
4874
+ const client = await manager.getClient(root, server2, options.signal);
4752
4875
  try {
4753
4876
  let nextIndex = 0;
4754
4877
  const workers = Array.from({ length: Math.min(maxConcurrency, filesToProcess.length) }, async () => {
@@ -5618,14 +5741,14 @@ class DaemonRequestCancelledError extends DaemonRequestError {
5618
5741
  async function callToolViaDaemon(name, args, options) {
5619
5742
  const context = requireContext(options.context);
5620
5743
  const paths = options.paths ?? daemonPaths();
5621
- const ensure = options.ensure ?? ensureDaemonRunning;
5744
+ const ensure = options.ensure ?? ((ensurePaths, signal) => ensureDaemonRunning(ensurePaths, undefined, signal === undefined ? {} : { signal }));
5622
5745
  const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
5623
5746
  const requestArgs = withContext(args, context);
5624
5747
  let lastError;
5625
5748
  let authRefreshUsed = false;
5626
5749
  for (let attempt = 0;attempt < 3; attempt += 1) {
5627
5750
  try {
5628
- await ensure(paths);
5751
+ await ensureDaemonAvailable(paths, ensure, options.signal);
5629
5752
  const token = readAuthToken(paths);
5630
5753
  if (!token)
5631
5754
  throw new DaemonRequestError("daemon auth token missing", false);
@@ -5667,6 +5790,27 @@ function requireContext(context) {
5667
5790
  function withContext(args, context) {
5668
5791
  return { ...args, [CONTEXT_KEY]: context };
5669
5792
  }
5793
+ function ensureDaemonAvailable(paths, ensure, signal) {
5794
+ if (!signal)
5795
+ return ensure(paths);
5796
+ return new Promise((resolve9, reject) => {
5797
+ let settled = false;
5798
+ const finish = (run) => {
5799
+ if (settled)
5800
+ return;
5801
+ settled = true;
5802
+ signal.removeEventListener("abort", onAbort);
5803
+ run();
5804
+ };
5805
+ const onAbort = () => finish(() => reject(new DaemonRequestCancelledError(false)));
5806
+ if (signal.aborted) {
5807
+ onAbort();
5808
+ return;
5809
+ }
5810
+ signal.addEventListener("abort", onAbort, { once: true });
5811
+ Promise.resolve().then(() => ensure(paths, signal)).then(() => finish(() => resolve9()), (error) => finish(() => reject(signal.aborted ? new DaemonRequestCancelledError(false) : error)));
5812
+ });
5813
+ }
5670
5814
  function daemonUnreachableResult(paths, error) {
5671
5815
  const text2 = [
5672
5816
  `LSP daemon unreachable: ${errorText(error)}.`,
@@ -5680,7 +5824,7 @@ function daemonUnreachableResult(paths, error) {
5680
5824
  }
5681
5825
  function sendToolCall(paths, token, name, args, options) {
5682
5826
  return new Promise((resolve9, reject) => {
5683
- const socket = connect2(paths.socket);
5827
+ const socket = connect(paths.socket);
5684
5828
  const requestId = allocateProxyRequestId();
5685
5829
  let settled = false;
5686
5830
  let requestWritten = false;
@@ -1,5 +1,5 @@
1
- import type { ToolExecutionResult } from "@oh-my-opencode/lsp-core/tools";
2
1
  import { type LspRequestContext } from "@oh-my-opencode/lsp-core/request-context";
2
+ import type { ToolExecutionResult } from "@oh-my-opencode/lsp-core/tools";
3
3
  import { type DaemonPaths } from "./paths.js";
4
4
  export declare class DaemonRequestError extends Error {
5
5
  readonly requestWritten: boolean;
@@ -17,7 +17,7 @@ export interface CallToolOptions {
17
17
  paths?: DaemonPaths;
18
18
  requestTimeoutMs?: number;
19
19
  signal?: AbortSignal;
20
- ensure?: (paths: DaemonPaths) => Promise<void>;
20
+ ensure?: (paths: DaemonPaths, signal?: AbortSignal) => Promise<void>;
21
21
  }
22
22
  export declare function callToolViaDaemon(name: string, args: Record<string, unknown>, options: CallToolOptions): Promise<ToolExecutionResult>;
23
23
  export declare function callDiagnosticsViaDaemon(filePath: string, options: CallToolOptions): Promise<ToolExecutionResult>;
@@ -32,14 +32,15 @@ export class DaemonRequestCancelledError extends DaemonRequestError {
32
32
  export async function callToolViaDaemon(name, args, options) {
33
33
  const context = requireContext(options.context);
34
34
  const paths = options.paths ?? daemonPaths();
35
- const ensure = options.ensure ?? ensureDaemonRunning;
35
+ const ensure = options.ensure ??
36
+ ((ensurePaths, signal) => ensureDaemonRunning(ensurePaths, undefined, signal === undefined ? {} : { signal }));
36
37
  const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
37
38
  const requestArgs = withContext(args, context);
38
39
  let lastError;
39
40
  let authRefreshUsed = false;
40
41
  for (let attempt = 0; attempt < 3; attempt += 1) {
41
42
  try {
42
- await ensure(paths);
43
+ await ensureDaemonAvailable(paths, ensure, options.signal);
43
44
  const token = readAuthToken(paths);
44
45
  if (!token)
45
46
  throw new DaemonRequestError("daemon auth token missing", false);
@@ -82,6 +83,29 @@ function requireContext(context) {
82
83
  function withContext(args, context) {
83
84
  return { ...args, [CONTEXT_KEY]: context };
84
85
  }
86
+ function ensureDaemonAvailable(paths, ensure, signal) {
87
+ if (!signal)
88
+ return ensure(paths);
89
+ return new Promise((resolve, reject) => {
90
+ let settled = false;
91
+ const finish = (run) => {
92
+ if (settled)
93
+ return;
94
+ settled = true;
95
+ signal.removeEventListener("abort", onAbort);
96
+ run();
97
+ };
98
+ const onAbort = () => finish(() => reject(new DaemonRequestCancelledError(false)));
99
+ if (signal.aborted) {
100
+ onAbort();
101
+ return;
102
+ }
103
+ signal.addEventListener("abort", onAbort, { once: true });
104
+ Promise.resolve()
105
+ .then(() => ensure(paths, signal))
106
+ .then(() => finish(() => resolve()), (error) => finish(() => reject(signal.aborted ? new DaemonRequestCancelledError(false) : error)));
107
+ });
108
+ }
85
109
  function daemonUnreachableResult(paths, error) {
86
110
  const text = [
87
111
  `LSP daemon unreachable: ${errorText(error)}.`,
@@ -6,18 +6,19 @@ export declare class DaemonUnreachableError extends Error {
6
6
  constructor(socketPath: string);
7
7
  }
8
8
  export interface EnsureDaemonDeps {
9
- probe(paths: DaemonPaths): Promise<boolean>;
9
+ probe(paths: DaemonPaths, signal?: AbortSignal): Promise<boolean>;
10
10
  spawnDaemon(paths: DaemonPaths): void;
11
- sleep(ms: number): Promise<void>;
11
+ sleep(ms: number, signal?: AbortSignal): Promise<void>;
12
12
  now(): number;
13
13
  }
14
14
  export interface EnsureDaemonOptions {
15
15
  readyTimeoutMs?: number;
16
16
  pollIntervalMs?: number;
17
+ readonly signal?: AbortSignal;
17
18
  }
18
19
  export declare function ensureDaemonRunning(paths: DaemonPaths, deps?: EnsureDaemonDeps, options?: EnsureDaemonOptions): Promise<void>;
19
- export declare function probeDaemon(paths: DaemonPaths, timeoutMs?: number): Promise<boolean>;
20
- export declare function pingDaemon(paths: DaemonPaths, token: string, timeoutMs?: number): Promise<OwnerPing | null>;
20
+ export declare function probeDaemon(paths: DaemonPaths, timeoutMs?: number, signal?: AbortSignal): Promise<boolean>;
21
+ export declare function pingDaemon(paths: DaemonPaths, token: string, timeoutMs?: number, signal?: AbortSignal): Promise<OwnerPing | null>;
21
22
  export declare function spawnDaemonProcess(paths: DaemonPaths): void;
22
23
  export declare function resolveDaemonCliPath(env?: NodeJS.ProcessEnv, defaults?: DaemonRuntimeDefaults): string;
23
24
  export declare function defaultEnsureDaemonDeps(): EnsureDaemonDeps;
@@ -1,6 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { closeSync, mkdirSync, openSync } from "node:fs";
3
- import { connect } from "node:net";
3
+ import { Socket } from "node:net";
4
4
  import { dirname } from "node:path";
5
5
  import { execPath } from "node:process";
6
6
  import { authEnvelope, readAuthToken } from "./ipc-protocol.js";
@@ -20,42 +20,48 @@ export class DaemonUnreachableError extends Error {
20
20
  export async function ensureDaemonRunning(paths, deps = defaultEnsureDaemonDeps(), options = {}) {
21
21
  const readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
22
22
  const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
23
- if (await deps.probe(paths))
23
+ const signal = options.signal;
24
+ throwIfAborted(signal);
25
+ if (await awaitWithSignal(deps.probe(paths, signal), signal))
24
26
  return;
27
+ throwIfAborted(signal);
25
28
  deps.spawnDaemon(paths);
26
- await waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs);
29
+ await waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs, signal);
27
30
  }
28
- async function waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs) {
31
+ async function waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs, signal) {
29
32
  const deadline = deps.now() + readyTimeoutMs;
30
33
  for (;;) {
31
- if (await deps.probe(paths))
34
+ throwIfAborted(signal);
35
+ if (await awaitWithSignal(deps.probe(paths, signal), signal))
32
36
  return;
33
37
  if (deps.now() >= deadline)
34
38
  throw new DaemonUnreachableError(paths.socket);
35
- await deps.sleep(pollIntervalMs);
39
+ await awaitWithSignal(deps.sleep(pollIntervalMs, signal), signal);
36
40
  }
37
41
  }
38
- export async function probeDaemon(paths, timeoutMs = PROBE_TIMEOUT_MS) {
42
+ export async function probeDaemon(paths, timeoutMs = PROBE_TIMEOUT_MS, signal) {
39
43
  const token = readAuthToken(paths);
40
44
  if (!token)
41
45
  return false;
42
- return (await pingDaemon(paths, token, timeoutMs)) !== null;
46
+ return (await pingDaemon(paths, token, timeoutMs, signal)) !== null;
43
47
  }
44
- export function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS) {
48
+ export function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS, signal) {
45
49
  return new Promise((resolve) => {
46
- const socket = connect(paths.socket);
50
+ const socket = new Socket();
47
51
  let settled = false;
52
+ let timer;
48
53
  const finish = (value) => {
49
54
  if (settled)
50
55
  return;
51
56
  settled = true;
57
+ if (timer !== undefined)
58
+ clearTimeout(timer);
59
+ signal?.removeEventListener("abort", onAbort);
52
60
  socket.destroy();
53
61
  resolve(value);
54
62
  };
55
- const timer = setTimeout(() => finish(null), timeoutMs);
56
- timer.unref?.();
63
+ const onAbort = () => finish(null);
57
64
  const decoder = createLineDecoder((message) => {
58
- clearTimeout(timer);
59
65
  finish(parsePingResponse(message));
60
66
  });
61
67
  socket.once("connect", () => {
@@ -63,9 +69,16 @@ export function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS) {
63
69
  });
64
70
  socket.on("data", (chunk) => decoder.push(chunk));
65
71
  socket.once("error", () => {
66
- clearTimeout(timer);
67
72
  finish(null);
68
73
  });
74
+ timer = setTimeout(() => finish(null), timeoutMs);
75
+ timer.unref?.();
76
+ if (signal?.aborted) {
77
+ onAbort();
78
+ return;
79
+ }
80
+ signal?.addEventListener("abort", onAbort, { once: true });
81
+ socket.connect(paths.socket);
69
82
  });
70
83
  }
71
84
  export function spawnDaemonProcess(paths) {
@@ -87,14 +100,62 @@ export function resolveDaemonCliPath(env = process.env, defaults = packagedRunti
87
100
  }
88
101
  export function defaultEnsureDaemonDeps() {
89
102
  return {
90
- probe: (paths) => probeDaemon(paths),
103
+ probe: (paths, signal) => probeDaemon(paths, PROBE_TIMEOUT_MS, signal),
91
104
  spawnDaemon: (paths) => spawnDaemonProcess(paths),
92
- sleep: (ms) => new Promise((resolve) => {
93
- setTimeout(resolve, ms);
94
- }),
105
+ sleep: (ms, signal) => sleepWithSignal(ms, signal),
95
106
  now: () => Date.now(),
96
107
  };
97
108
  }
109
+ function sleepWithSignal(ms, signal) {
110
+ return new Promise((resolve) => {
111
+ let settled = false;
112
+ const finish = () => {
113
+ if (settled)
114
+ return;
115
+ settled = true;
116
+ clearTimeout(timer);
117
+ signal?.removeEventListener("abort", finish);
118
+ resolve();
119
+ };
120
+ const timer = setTimeout(finish, ms);
121
+ if (signal?.aborted) {
122
+ finish();
123
+ return;
124
+ }
125
+ signal?.addEventListener("abort", finish, { once: true });
126
+ });
127
+ }
128
+ function awaitWithSignal(promise, signal) {
129
+ if (!signal)
130
+ return promise;
131
+ if (signal.aborted)
132
+ return Promise.reject(abortError(signal));
133
+ return new Promise((resolve, reject) => {
134
+ let settled = false;
135
+ const finish = (run) => {
136
+ if (settled)
137
+ return;
138
+ settled = true;
139
+ signal.removeEventListener("abort", onAbort);
140
+ run();
141
+ };
142
+ const onAbort = () => finish(() => reject(abortError(signal)));
143
+ signal.addEventListener("abort", onAbort, { once: true });
144
+ promise.then((value) => finish(() => resolve(value)), (error) => finish(() => reject(error)));
145
+ });
146
+ }
147
+ function throwIfAborted(signal) {
148
+ if (signal?.aborted)
149
+ throw abortError(signal);
150
+ }
151
+ function abortError(signal) {
152
+ const reason = signal.reason;
153
+ if (reason instanceof Error)
154
+ return reason;
155
+ const error = new Error(typeof reason === "string" ? reason : "daemon startup cancelled");
156
+ error.name = "AbortError";
157
+ return error;
158
+ }
98
159
  function parsePingResponse(message) {
99
160
  if (!message || typeof message !== "object" || Array.isArray(message))
100
161
  return null;