oh-my-opencode 4.18.0 → 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 (226) hide show
  1. package/.agents/skills/codex-qa/scripts/lsp-e2e.sh +3677 -0
  2. package/.agents/skills/opencode-qa/scripts/lsp-e2e.sh +3154 -0
  3. package/.agents/skills/work-with-pr/SKILL.md +16 -37
  4. package/.agents/skills/work-with-pr-workspace/evals/evals.json +3 -3
  5. package/.opencode/skills/work-with-pr/SKILL.md +16 -37
  6. package/.opencode/skills/work-with-pr-workspace/evals/evals.json +3 -3
  7. package/bin/AGENTS.md +33 -0
  8. package/dist/cli/index.js +500 -158
  9. package/dist/cli-node/index.js +500 -158
  10. package/dist/hooks/anthropic-context-window-limit-recovery/empty-content-recovery-sdk.d.ts +6 -0
  11. package/dist/hooks/category-skill-reminder/hook.d.ts +9 -1
  12. package/dist/hooks/comment-checker/hook.d.ts +8 -1
  13. package/dist/hooks/todo-continuation-enforcer/types.d.ts +3 -0
  14. package/dist/index.js +956 -702
  15. package/dist/plugin/messages-transform.d.ts +1 -0
  16. package/dist/plugin-handlers/prometheus-agent-config-builder.d.ts +2 -0
  17. package/dist/tui.js +43 -4
  18. package/package.json +16 -16
  19. package/packages/git-bash-mcp/dist/cli.js +81 -19
  20. package/packages/lsp-core/package.json +4 -0
  21. package/packages/lsp-core/src/index.ts +1 -0
  22. package/packages/lsp-core/src/lsp/cleanup-errors.test.ts +18 -0
  23. package/packages/lsp-core/src/lsp/cleanup-errors.ts +12 -3
  24. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +261 -0
  25. package/packages/lsp-core/src/lsp/client-wrapper.test.ts +63 -0
  26. package/packages/lsp-core/src/lsp/client-wrapper.ts +35 -5
  27. package/packages/lsp-core/src/lsp/client.ts +262 -80
  28. package/packages/lsp-core/src/lsp/config-loader.ts +5 -17
  29. package/packages/lsp-core/src/lsp/connection.ts +12 -6
  30. package/packages/lsp-core/src/lsp/directory-diagnostics.test.ts +221 -0
  31. package/packages/lsp-core/src/lsp/directory-diagnostics.ts +61 -28
  32. package/packages/lsp-core/src/lsp/errors.ts +11 -0
  33. package/packages/lsp-core/src/lsp/fixtures/diagnostics-freshness-contract-probe.ts +283 -0
  34. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-contract-probe.ts +196 -0
  35. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +215 -0
  36. package/packages/lsp-core/src/lsp/formatters.ts +3 -0
  37. package/packages/lsp-core/src/lsp/json-rpc-connection-cancellation.test.ts +97 -0
  38. package/packages/lsp-core/src/lsp/json-rpc-connection.ts +73 -5
  39. package/packages/lsp-core/src/lsp/server-install-state.ts +3 -6
  40. package/packages/lsp-core/src/lsp/transport-protocol.ts +52 -0
  41. package/packages/lsp-core/src/lsp/transport.ts +96 -70
  42. package/packages/lsp-core/src/lsp/workspace-apply-edit-failure.ts +19 -0
  43. package/packages/lsp-core/src/lsp/workspace-apply-edit-lease.integration.test.ts +214 -0
  44. package/packages/lsp-core/src/lsp/workspace-apply-edit-sync.integration.test.ts +113 -0
  45. package/packages/lsp-core/src/lsp/workspace-apply-edit-test-support.ts +163 -0
  46. package/packages/lsp-core/src/lsp/workspace-apply-edit.integration.test.ts +163 -0
  47. package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +67 -0
  48. package/packages/lsp-core/src/lsp/workspace-document-state.ts +368 -0
  49. package/packages/lsp-core/src/lsp/workspace-edit-adversarial.test.ts +113 -0
  50. package/packages/lsp-core/src/lsp/workspace-edit-commit.test.ts +140 -0
  51. package/packages/lsp-core/src/lsp/workspace-edit-commit.ts +220 -0
  52. package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.test.ts +56 -0
  53. package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.ts +30 -0
  54. package/packages/lsp-core/src/lsp/workspace-edit-fingerprint.ts +44 -0
  55. package/packages/lsp-core/src/lsp/workspace-edit-options.test.ts +147 -0
  56. package/packages/lsp-core/src/lsp/workspace-edit-parse-helpers.ts +59 -0
  57. package/packages/lsp-core/src/lsp/workspace-edit-parser.ts +130 -0
  58. package/packages/lsp-core/src/lsp/workspace-edit-path.ts +98 -0
  59. package/packages/lsp-core/src/lsp/workspace-edit-plan-types.ts +60 -0
  60. package/packages/lsp-core/src/lsp/workspace-edit-plan.ts +73 -0
  61. package/packages/lsp-core/src/lsp/workspace-edit-prevalidation.test.ts +174 -0
  62. package/packages/lsp-core/src/lsp/workspace-edit-resource-parser.ts +89 -0
  63. package/packages/lsp-core/src/lsp/workspace-edit-simulation.ts +183 -0
  64. package/packages/lsp-core/src/lsp/workspace-edit-snapshot.ts +53 -0
  65. package/packages/lsp-core/src/lsp/workspace-edit-text.ts +125 -0
  66. package/packages/lsp-core/src/lsp/workspace-edit-types.ts +121 -0
  67. package/packages/lsp-core/src/lsp/workspace-edit.characterization.test.ts +95 -0
  68. package/packages/lsp-core/src/lsp/workspace-edit.ts +49 -200
  69. package/packages/lsp-core/src/lsp/workspace-mutation-controller.ts +182 -0
  70. package/packages/lsp-core/src/mcp.ts +18 -7
  71. package/packages/lsp-core/src/missing-dependency-result.test.ts +105 -0
  72. package/packages/lsp-core/src/missing-dependency-result.ts +57 -0
  73. package/packages/lsp-core/src/post-edit/index.ts +1 -0
  74. package/packages/lsp-core/src/post-edit/orchestration.test.ts +157 -0
  75. package/packages/lsp-core/src/post-edit/orchestration.ts +178 -0
  76. package/packages/lsp-core/src/request-context.test.ts +171 -0
  77. package/packages/lsp-core/src/request-context.ts +222 -9
  78. package/packages/lsp-core/src/tool-surface.test.ts +4 -1
  79. package/packages/lsp-core/src/tools/diagnostics.ts +32 -13
  80. package/packages/lsp-core/src/tools/navigation.ts +12 -12
  81. package/packages/lsp-core/src/tools/rename.ts +10 -15
  82. package/packages/lsp-core/src/tools/symbols.ts +11 -11
  83. package/packages/lsp-core/src/tools/types.ts +2 -1
  84. package/packages/lsp-daemon/dist/cli.js +3330 -764
  85. package/packages/lsp-daemon/dist/client.d.ts +105 -0
  86. package/packages/lsp-daemon/dist/client.js +5995 -0
  87. package/packages/lsp-daemon/dist/daemon-client.d.ts +12 -7
  88. package/packages/lsp-daemon/dist/daemon-client.js +139 -32
  89. package/packages/lsp-daemon/dist/daemon-server.d.ts +1 -0
  90. package/packages/lsp-daemon/dist/daemon-server.js +40 -15
  91. package/packages/lsp-daemon/dist/ensure-daemon.d.ts +10 -8
  92. package/packages/lsp-daemon/dist/ensure-daemon.js +135 -51
  93. package/packages/lsp-daemon/dist/index.d.ts +2 -2
  94. package/packages/lsp-daemon/dist/index.js +3093 -786
  95. package/packages/lsp-daemon/dist/ipc-protocol.d.ts +46 -0
  96. package/packages/lsp-daemon/dist/ipc-protocol.js +187 -0
  97. package/packages/lsp-daemon/dist/lock.js +14 -4
  98. package/packages/lsp-daemon/dist/ownership.d.ts +49 -0
  99. package/packages/lsp-daemon/dist/ownership.js +168 -0
  100. package/packages/lsp-daemon/dist/paths.d.ts +33 -9
  101. package/packages/lsp-daemon/dist/paths.js +72 -33
  102. package/packages/lsp-daemon/dist/proxy.d.ts +5 -0
  103. package/packages/lsp-daemon/dist/proxy.js +123 -16
  104. package/packages/lsp-daemon/dist/request-routing.d.ts +7 -2
  105. package/packages/lsp-daemon/dist/request-routing.js +71 -22
  106. package/packages/lsp-daemon/dist/run-daemon.js +9 -2
  107. package/packages/lsp-daemon/dist/runtime-contract.d.ts +21 -0
  108. package/packages/lsp-daemon/dist/runtime-contract.js +58 -0
  109. package/packages/lsp-daemon/dist/socket-jsonrpc.js +6 -1
  110. package/packages/lsp-daemon/package.json +12 -3
  111. package/packages/lsp-tools-mcp/dist/cli.js +2189 -454
  112. package/packages/lsp-tools-mcp/dist/lsp/manager.js +1741 -148
  113. package/packages/lsp-tools-mcp/dist/mcp.js +2206 -471
  114. package/packages/lsp-tools-mcp/dist/request-context.js +176 -6
  115. package/packages/lsp-tools-mcp/dist/tools.js +2119 -447
  116. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  117. package/packages/omo-codex/plugin/.mcp.json +2 -1
  118. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  119. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  120. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +100 -28
  121. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +100 -28
  122. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  123. package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +21 -9
  124. package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +35 -0
  125. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +69 -0
  126. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +57 -1
  127. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  128. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  129. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  130. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  131. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  132. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  133. package/packages/omo-codex/plugin/components/lsp/.mcp.json +2 -1
  134. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +55 -0
  135. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +3033 -936
  136. package/packages/omo-codex/plugin/components/lsp/dist/codex-hook-cli.js +0 -4
  137. package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.d.ts +5 -2
  138. package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.js +41 -62
  139. package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.d.ts +1 -1
  140. package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +24 -15
  141. package/packages/omo-codex/plugin/components/lsp/dist/lsp-session-state.d.ts +3 -7
  142. package/packages/omo-codex/plugin/components/lsp/dist/lsp-session-state.js +23 -49
  143. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  144. package/packages/omo-codex/plugin/components/lsp/package.json +3 -2
  145. package/packages/omo-codex/plugin/components/lsp/scripts/build-lsp-daemon.mjs +31 -1
  146. package/packages/omo-codex/plugin/components/lsp/scripts/build-lsp-daemon.test.mjs +76 -0
  147. package/packages/omo-codex/plugin/components/lsp/scripts/build-runtime.mjs +201 -0
  148. package/packages/omo-codex/plugin/components/lsp/scripts/build-runtime.test.mjs +55 -0
  149. package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +0 -4
  150. package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +49 -71
  151. package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +26 -15
  152. package/packages/omo-codex/plugin/components/lsp/src/lsp-session-state.ts +26 -64
  153. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-unavailable.test.ts +16 -17
  154. package/packages/omo-codex/plugin/components/lsp/test/codex-hook.test.ts +30 -4
  155. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +20 -5
  156. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.5.md +1 -1
  157. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +5 -3
  158. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  159. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  160. package/packages/omo-codex/plugin/components/start-work-continuation/README.md +2 -2
  161. package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +3 -3
  162. package/packages/omo-codex/plugin/components/start-work-continuation/dist/cli.js +2 -2
  163. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  164. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  165. package/packages/omo-codex/plugin/components/start-work-continuation/src/boulder-reader.ts +1 -1
  166. package/packages/omo-codex/plugin/components/start-work-continuation/src/codex-hook.ts +1 -1
  167. package/packages/omo-codex/plugin/components/start-work-continuation/test/boulder-reader.test.ts +15 -1
  168. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +20 -0
  169. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  170. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  171. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  172. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  173. package/packages/omo-codex/plugin/components/ultrawork/directive.md +50 -33
  174. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  175. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  176. package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +50 -33
  177. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +50 -33
  178. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +3 -3
  179. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +5 -6
  180. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  181. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  182. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +1 -1
  183. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +6 -5
  184. package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +5 -6
  185. package/packages/omo-codex/plugin/components/ulw-loop/test/stop-resume-hook.test.ts +2 -2
  186. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  187. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  188. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  189. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  190. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  191. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  192. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  193. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  194. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  195. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  196. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  197. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  198. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  199. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  200. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  201. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  202. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  203. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  204. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  205. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  206. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  207. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  208. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  209. package/packages/omo-codex/plugin/package-lock.json +26 -14
  210. package/packages/omo-codex/plugin/package.json +1 -1
  211. package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +2 -3
  212. package/packages/omo-codex/plugin/scripts/build-components.mjs +13 -1
  213. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +9 -1
  214. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +7 -0
  215. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +2 -1
  216. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +50 -33
  217. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
  218. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +6 -5
  219. package/packages/omo-codex/plugin/test/aggregate-build.test.mjs +8 -0
  220. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +128 -15
  221. package/packages/omo-codex/plugin/test/install-time-build-runtime.test.mjs +10 -0
  222. package/packages/omo-codex/plugin/test/lsp-prebuild-layouts.test.mjs +2 -0
  223. package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -0
  224. package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +7 -0
  225. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +34 -3
  226. package/packages/omo-codex/scripts/install-dist/install-local.mjs +328 -63
@@ -1,19 +1,24 @@
1
+ import { type LspRequestContext } from "@oh-my-opencode/lsp-core/request-context";
1
2
  import type { ToolExecutionResult } from "@oh-my-opencode/lsp-core/tools";
2
3
  import { type DaemonPaths } from "./paths.js";
3
4
  export declare class DaemonRequestError extends Error {
4
5
  readonly requestWritten: boolean;
5
6
  constructor(message: string, requestWritten: boolean);
6
7
  }
7
- export interface DaemonToolContext {
8
- cwd?: string;
9
- env?: Record<string, string>;
8
+ export declare class DaemonAuthenticationRejectedError extends DaemonRequestError {
9
+ constructor();
10
10
  }
11
+ export declare class DaemonRequestCancelledError extends DaemonRequestError {
12
+ constructor(requestWritten: boolean);
13
+ }
14
+ export type DaemonToolContext = LspRequestContext;
11
15
  export interface CallToolOptions {
12
- context?: DaemonToolContext;
16
+ context: DaemonToolContext;
13
17
  paths?: DaemonPaths;
14
18
  requestTimeoutMs?: number;
15
- ensure?: (paths: DaemonPaths) => Promise<void>;
19
+ signal?: AbortSignal;
20
+ ensure?: (paths: DaemonPaths, signal?: AbortSignal) => Promise<void>;
16
21
  }
17
- export declare function callToolViaDaemon(name: string, args: Record<string, unknown>, options?: CallToolOptions): Promise<ToolExecutionResult>;
18
- export declare function callDiagnosticsViaDaemon(filePath: string, options?: CallToolOptions): Promise<ToolExecutionResult>;
22
+ export declare function callToolViaDaemon(name: string, args: Record<string, unknown>, options: CallToolOptions): Promise<ToolExecutionResult>;
23
+ export declare function callDiagnosticsViaDaemon(filePath: string, options: CallToolOptions): Promise<ToolExecutionResult>;
19
24
  export declare function currentRequestContext(env?: NodeJS.ProcessEnv): DaemonToolContext;
@@ -1,11 +1,15 @@
1
1
  import { connect } from "node:net";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { parseLspRequestContext } from "@oh-my-opencode/lsp-core/request-context";
2
5
  import { isPlainRecord } from "@oh-my-opencode/mcp-stdio-core/record";
3
6
  import { ensureDaemonRunning } from "./ensure-daemon.js";
7
+ import { authEnvelope, isAuthErrorResponse, readAuthToken } from "./ipc-protocol.js";
4
8
  import { daemonPaths } from "./paths.js";
5
9
  import { CONTEXT_KEY } from "./request-routing.js";
6
10
  import { createLineDecoder, encodeJsonLine } from "./socket-jsonrpc.js";
7
11
  const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
8
- const REQUEST_ID = 1;
12
+ let nextProxyRequestId = 1;
9
13
  export class DaemonRequestError extends Error {
10
14
  constructor(message, requestWritten) {
11
15
  super(message);
@@ -13,47 +17,95 @@ export class DaemonRequestError extends Error {
13
17
  this.requestWritten = requestWritten;
14
18
  }
15
19
  }
16
- export async function callToolViaDaemon(name, args, options = {}) {
20
+ export class DaemonAuthenticationRejectedError extends DaemonRequestError {
21
+ constructor() {
22
+ super("daemon authentication failed before dispatch", true);
23
+ this.name = "DaemonAuthenticationRejectedError";
24
+ }
25
+ }
26
+ export class DaemonRequestCancelledError extends DaemonRequestError {
27
+ constructor(requestWritten) {
28
+ super("daemon request cancelled", requestWritten);
29
+ this.name = "DaemonRequestCancelledError";
30
+ }
31
+ }
32
+ export async function callToolViaDaemon(name, args, options) {
33
+ const context = requireContext(options.context);
17
34
  const paths = options.paths ?? daemonPaths();
18
- const ensure = options.ensure ?? ensureDaemonRunning;
35
+ const ensure = options.ensure ??
36
+ ((ensurePaths, signal) => ensureDaemonRunning(ensurePaths, undefined, signal === undefined ? {} : { signal }));
19
37
  const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
20
- const requestArgs = withContext(args, options.context);
38
+ const requestArgs = withContext(args, context);
21
39
  let lastError;
22
- for (let attempt = 0; attempt < 2; attempt += 1) {
40
+ let authRefreshUsed = false;
41
+ for (let attempt = 0; attempt < 3; attempt += 1) {
23
42
  try {
24
- await ensure(paths);
25
- return await sendToolCall(paths.socket, name, requestArgs, timeoutMs);
43
+ await ensureDaemonAvailable(paths, ensure, options.signal);
44
+ const token = readAuthToken(paths);
45
+ if (!token)
46
+ throw new DaemonRequestError("daemon auth token missing", false);
47
+ const sendOptions = options.signal === undefined ? { timeoutMs } : { timeoutMs, signal: options.signal };
48
+ return await sendToolCall(paths, token, name, requestArgs, sendOptions);
26
49
  }
27
50
  catch (error) {
28
51
  lastError = error;
29
- if (error instanceof DaemonRequestError && error.requestWritten)
52
+ if (error instanceof DaemonAuthenticationRejectedError && !authRefreshUsed) {
53
+ authRefreshUsed = true;
54
+ continue;
55
+ }
56
+ if (error instanceof DaemonRequestCancelledError)
57
+ break;
58
+ if (error instanceof DaemonRequestError && (error.requestWritten || !isRetryableTool(name)))
30
59
  break;
31
60
  }
32
61
  }
33
62
  return daemonUnreachableResult(paths, lastError);
34
63
  }
35
- export function callDiagnosticsViaDaemon(filePath, options = {}) {
64
+ export function callDiagnosticsViaDaemon(filePath, options) {
36
65
  return callToolViaDaemon("diagnostics", { filePath, severity: "error" }, options);
37
66
  }
38
- const FORWARDED_ENV_KEYS = [
39
- "LSP_TOOLS_MCP_PROJECT_CONFIG",
40
- "LSP_TOOLS_MCP_USER_CONFIG",
41
- "LSP_TOOLS_MCP_INSTALL_DECISIONS",
42
- ];
43
67
  export function currentRequestContext(env = process.env) {
44
- const forwarded = {};
45
- for (const key of FORWARDED_ENV_KEYS) {
46
- const value = env[key];
47
- if (value !== undefined)
48
- forwarded[key] = value;
49
- }
50
- return { cwd: process.cwd(), env: forwarded };
68
+ const cwd = process.cwd();
69
+ const home = env["HOME"] ?? homedir();
70
+ return parseLspRequestContext({
71
+ cwd,
72
+ projectConfigPaths: [join(cwd, ".codex", "lsp-client.json")],
73
+ userConfigPath: join(home, ".codex", "lsp-client.json"),
74
+ installDecisionsPath: join(home, ".codex", "lsp-install-decisions.json"),
75
+ capabilities: { installDecisionTool: true },
76
+ });
77
+ }
78
+ function requireContext(context) {
79
+ if (!context)
80
+ throw new DaemonRequestError("daemon tool context is required", false);
81
+ return parseLspRequestContext(context);
51
82
  }
52
83
  function withContext(args, context) {
53
- if (!context || (context.cwd === undefined && context.env === undefined))
54
- return args;
55
84
  return { ...args, [CONTEXT_KEY]: context };
56
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
+ }
57
109
  function daemonUnreachableResult(paths, error) {
58
110
  const text = [
59
111
  `LSP daemon unreachable: ${errorText(error)}.`,
@@ -64,39 +116,84 @@ function daemonUnreachableResult(paths, error) {
64
116
  ].join("\n");
65
117
  return { content: [{ type: "text", text }], isError: true };
66
118
  }
67
- function sendToolCall(socketPath, name, args, timeoutMs) {
119
+ function sendToolCall(paths, token, name, args, options) {
68
120
  return new Promise((resolve, reject) => {
69
- const socket = connect(socketPath);
121
+ const socket = connect(paths.socket);
122
+ const requestId = allocateProxyRequestId();
70
123
  let settled = false;
71
124
  let requestWritten = false;
125
+ let cancelAfterWrite = false;
126
+ const cancelPayload = () => encodeJsonLine({
127
+ jsonrpc: "2.0",
128
+ method: "$/cancelRequest",
129
+ params: { _omo: authEnvelope(token), id: requestId },
130
+ });
72
131
  const finish = (run) => {
73
132
  if (settled)
74
133
  return;
75
134
  settled = true;
76
135
  clearTimeout(timer);
77
- socket.destroy();
136
+ options.signal?.removeEventListener("abort", onAbort);
137
+ destroyAfterCancel();
78
138
  run();
79
139
  };
80
- const timer = setTimeout(() => finish(() => reject(new DaemonRequestError("daemon request timed out", requestWritten))), timeoutMs);
140
+ const sendCancel = () => {
141
+ if (!requestWritten) {
142
+ cancelAfterWrite = true;
143
+ return;
144
+ }
145
+ if (!socket.writable)
146
+ return;
147
+ socket.write(cancelPayload());
148
+ };
149
+ const destroyAfterCancel = () => {
150
+ socket.destroy();
151
+ };
152
+ const onAbort = () => {
153
+ sendCancel();
154
+ finish(() => reject(new DaemonRequestCancelledError(requestWritten)));
155
+ };
156
+ const timer = setTimeout(() => {
157
+ sendCancel();
158
+ finish(() => reject(new DaemonRequestError("daemon request timed out", requestWritten)));
159
+ }, options.timeoutMs);
81
160
  timer.unref();
161
+ if (options.signal?.aborted) {
162
+ onAbort();
163
+ return;
164
+ }
165
+ options.signal?.addEventListener("abort", onAbort, { once: true });
82
166
  const decoder = createLineDecoder((message) => {
83
- const result = toToolResult(message);
167
+ if (isAuthErrorResponse(message)) {
168
+ finish(() => reject(new DaemonAuthenticationRejectedError()));
169
+ return;
170
+ }
171
+ const result = toToolResult(message, requestId);
84
172
  if (result)
85
173
  finish(() => resolve(result));
86
174
  else
87
175
  finish(() => reject(new DaemonRequestError("invalid daemon response", requestWritten)));
88
176
  });
89
177
  socket.once("connect", () => {
90
- requestWritten = true;
91
- socket.write(encodeJsonLine({ jsonrpc: "2.0", id: REQUEST_ID, method: "tools/call", params: { name, arguments: args } }));
178
+ const payload = encodeJsonLine({
179
+ jsonrpc: "2.0",
180
+ id: requestId,
181
+ method: "tools/call",
182
+ params: { _omo: authEnvelope(token), name, arguments: args },
183
+ });
184
+ socket.write(payload, () => {
185
+ requestWritten = true;
186
+ if (cancelAfterWrite && socket.writable)
187
+ socket.write(cancelPayload());
188
+ });
92
189
  });
93
190
  socket.on("data", (chunk) => decoder.push(chunk));
94
191
  socket.once("error", (error) => finish(() => reject(new DaemonRequestError(error.message, requestWritten))));
95
192
  socket.once("close", () => finish(() => reject(new DaemonRequestError("daemon connection closed", requestWritten))));
96
193
  });
97
194
  }
98
- function toToolResult(message) {
99
- if (!isPlainRecord(message) || message["id"] !== REQUEST_ID)
195
+ function toToolResult(message, requestId) {
196
+ if (!isPlainRecord(message) || message["id"] !== requestId)
100
197
  return null;
101
198
  const result = message["result"];
102
199
  if (!isPlainRecord(result) || !Array.isArray(result["content"]))
@@ -107,6 +204,16 @@ function toToolResult(message) {
107
204
  details: result["details"],
108
205
  };
109
206
  }
207
+ function allocateProxyRequestId() {
208
+ const id = nextProxyRequestId;
209
+ nextProxyRequestId += 1;
210
+ if (nextProxyRequestId > Number.MAX_SAFE_INTEGER)
211
+ nextProxyRequestId = 1;
212
+ return id;
213
+ }
214
+ function isRetryableTool(name) {
215
+ return name !== "rename" && name !== "lsp_rename";
216
+ }
110
217
  function errorText(error) {
111
218
  return error instanceof Error ? error.message : String(error);
112
219
  }
@@ -1,5 +1,6 @@
1
1
  import { type Server } from "node:net";
2
2
  import type { DaemonPaths } from "./paths.js";
3
+ export { DaemonAlreadyRunningError, DaemonStartupDeferredError } from "./ownership.js";
3
4
  export interface DaemonServerOptions {
4
5
  idleShutdownMs?: number;
5
6
  idleCheckIntervalMs?: number;
@@ -1,41 +1,58 @@
1
- import { mkdirSync, writeFileSync } from "node:fs";
1
+ import { chmodSync } from "node:fs";
2
2
  import { createServer } from "node:net";
3
- import { join } from "node:path";
4
3
  import { disposeDefaultLspManager, getLspManager } from "@oh-my-opencode/lsp-core/lsp/manager";
5
- import { unlinkQuietly } from "./lock.js";
4
+ import { setPrivateFileMode } from "./ipc-protocol.js";
5
+ import { pingDaemon } from "./ensure-daemon.js";
6
+ import { acquireStartupLease, DaemonStartupDeferredError, endpointIdentity, removeDaemonMetadataForOwner, writeDaemonOwner, } from "./ownership.js";
6
7
  import { handleDaemonMessage } from "./request-routing.js";
7
8
  import { createLineDecoder, encodeJsonLine } from "./socket-jsonrpc.js";
9
+ export { DaemonAlreadyRunningError, DaemonStartupDeferredError } from "./ownership.js";
8
10
  const DEFAULT_IDLE_SHUTDOWN_MS = 30 * 60_000;
9
11
  const DEFAULT_IDLE_CHECK_INTERVAL_MS = 60_000;
10
12
  export async function startDaemonServer(paths, options = {}) {
11
13
  const idleShutdownMs = options.idleShutdownMs ?? DEFAULT_IDLE_SHUTDOWN_MS;
12
14
  const idleCheckIntervalMs = options.idleCheckIntervalMs ?? DEFAULT_IDLE_CHECK_INTERVAL_MS;
13
- mkdirSync(paths.dir, { recursive: true });
14
- unlinkQuietly(paths.socket);
15
+ const lease = await acquireStartupLease(paths, (token) => pingDaemon(paths, token));
15
16
  const connections = new Set();
16
17
  let lastActiveAt = Date.now();
17
18
  const touch = () => {
18
19
  lastActiveAt = Date.now();
19
20
  };
21
+ let routeOwner = lease.owner;
20
22
  const server = createServer((socket) => {
21
23
  connections.add(socket);
24
+ const activeRequests = new Map();
22
25
  touch();
23
26
  const decoder = createLineDecoder((message) => {
24
27
  touch();
25
- void respond(socket, message);
28
+ void respond(socket, message, lease.token, routeOwner, activeRequests);
26
29
  });
27
30
  socket.on("data", (chunk) => decoder.push(chunk));
28
31
  socket.on("error", () => socket.destroy());
29
32
  socket.on("close", () => {
33
+ for (const controller of activeRequests.values())
34
+ controller.abort();
35
+ activeRequests.clear();
30
36
  connections.delete(socket);
31
37
  touch();
32
38
  });
33
39
  });
34
40
  server.on("error", (error) => logServerError(error));
35
- const endpointPath = join(paths.dir, "daemon.endpoint");
36
- await listen(server, paths.socket);
37
- writeFileSync(paths.pid, `${process.pid}\n`);
38
- writeFileSync(endpointPath, paths.socket);
41
+ let owner;
42
+ try {
43
+ await listen(server, paths.socket);
44
+ if (process.platform !== "win32")
45
+ chmodSync(paths.socket, 0o600);
46
+ owner = { ...lease.owner, endpoint: endpointIdentity(paths.socket) };
47
+ routeOwner = owner;
48
+ writeDaemonOwner(paths, owner);
49
+ await assertSelfProbe(paths, lease.token, owner);
50
+ }
51
+ catch (error) {
52
+ lease.lock.release();
53
+ throw error;
54
+ }
55
+ lease.lock.release();
39
56
  let closed = false;
40
57
  const close = async () => {
41
58
  if (closed)
@@ -46,9 +63,7 @@ export async function startDaemonServer(paths, options = {}) {
46
63
  socket.destroy();
47
64
  connections.clear();
48
65
  await closeServer(server);
49
- unlinkQuietly(paths.socket);
50
- unlinkQuietly(paths.pid);
51
- unlinkQuietly(endpointPath);
66
+ removeDaemonMetadataForOwner(paths, owner);
52
67
  await disposeDefaultLspManager();
53
68
  };
54
69
  const idleTimer = setInterval(() => {
@@ -70,16 +85,26 @@ export async function startDaemonServer(paths, options = {}) {
70
85
  installSignalHandlers(close);
71
86
  return { server, close };
72
87
  }
73
- async function respond(socket, message) {
88
+ async function respond(socket, message, token, owner, activeRequests) {
74
89
  try {
75
- const response = await handleDaemonMessage(message);
90
+ const response = await handleDaemonMessage(message, { token, owner, activeRequests });
76
91
  if (response && socket.writable)
77
92
  socket.write(encodeJsonLine(response));
78
93
  }
79
94
  catch (error) {
95
+ if (!(error instanceof Error))
96
+ throw error;
80
97
  logServerError(error);
81
98
  }
82
99
  }
100
+ async function assertSelfProbe(paths, token, owner) {
101
+ setPrivateFileMode(paths.pid);
102
+ setPrivateFileMode(paths.endpoint);
103
+ setPrivateFileMode(paths.owner);
104
+ const ping = await pingDaemon(paths, token);
105
+ if (!ping || ping.nonce !== owner.nonce)
106
+ throw new DaemonStartupDeferredError("self_probe_failed");
107
+ }
83
108
  function listen(server, socketPath) {
84
109
  return new Promise((resolve, reject) => {
85
110
  const onError = (error) => reject(error);
@@ -1,22 +1,24 @@
1
- import { type LockHandle } from "./lock.js";
2
- import type { DaemonPaths } from "./paths.js";
1
+ import type { OwnerPing } from "./ownership.js";
2
+ import { type DaemonPaths } from "./paths.js";
3
+ import { type DaemonRuntimeDefaults } from "./runtime-contract.js";
4
+ export { InvalidRuntimeOverrideError, OMO_LSP_DAEMON_CLI, resolveDaemonRuntime } from "./runtime-contract.js";
3
5
  export declare class DaemonUnreachableError extends Error {
4
6
  constructor(socketPath: string);
5
7
  }
6
8
  export interface EnsureDaemonDeps {
7
- probe(socketPath: string): Promise<boolean>;
8
- acquireLock(lockPath: string): LockHandle | null;
9
- cleanupStaleSocket(socketPath: string): void;
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 probeSocket(socketPath: string, timeoutMs?: number): Promise<boolean>;
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>;
20
22
  export declare function spawnDaemonProcess(paths: DaemonPaths): void;
21
- export declare function resolveDaemonCliPath(env?: NodeJS.ProcessEnv): string;
23
+ export declare function resolveDaemonCliPath(env?: NodeJS.ProcessEnv, defaults?: DaemonRuntimeDefaults): string;
22
24
  export declare function defaultEnsureDaemonDeps(): EnsureDaemonDeps;
@@ -1,14 +1,16 @@
1
1
  import { spawn } from "node:child_process";
2
- import { closeSync, existsSync, mkdirSync, openSync } from "node:fs";
3
- import { connect } from "node:net";
2
+ import { closeSync, mkdirSync, openSync } from "node:fs";
3
+ import { Socket } from "node:net";
4
4
  import { dirname } from "node:path";
5
5
  import { execPath } from "node:process";
6
- import { fileURLToPath } from "node:url";
7
- import { tryAcquireLock, unlinkQuietly } from "./lock.js";
6
+ import { authEnvelope, readAuthToken } from "./ipc-protocol.js";
7
+ import { packagedRuntimeDefaults } from "./paths.js";
8
+ import { resolveDaemonRuntime } from "./runtime-contract.js";
9
+ import { createLineDecoder, encodeJsonLine } from "./socket-jsonrpc.js";
10
+ export { InvalidRuntimeOverrideError, OMO_LSP_DAEMON_CLI, resolveDaemonRuntime } from "./runtime-contract.js";
8
11
  const PROBE_TIMEOUT_MS = 500;
9
12
  const DEFAULT_READY_TIMEOUT_MS = 5_000;
10
13
  const DEFAULT_POLL_INTERVAL_MS = 100;
11
- const CODEX_LSP_DAEMON_CLI_ENV = "CODEX_LSP_DAEMON_CLI";
12
14
  export class DaemonUnreachableError extends Error {
13
15
  constructor(socketPath) {
14
16
  super(`LSP daemon did not become reachable at ${socketPath}`);
@@ -18,59 +20,72 @@ export class DaemonUnreachableError extends Error {
18
20
  export async function ensureDaemonRunning(paths, deps = defaultEnsureDaemonDeps(), options = {}) {
19
21
  const readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
20
22
  const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
21
- if (await deps.probe(paths.socket))
23
+ const signal = options.signal;
24
+ throwIfAborted(signal);
25
+ if (await awaitWithSignal(deps.probe(paths, signal), signal))
22
26
  return;
23
- const lock = deps.acquireLock(paths.lock);
24
- if (!lock) {
25
- await waitUntilReachable(paths.socket, deps, readyTimeoutMs, pollIntervalMs);
26
- return;
27
- }
28
- try {
29
- if (await deps.probe(paths.socket))
30
- return;
31
- deps.cleanupStaleSocket(paths.socket);
32
- deps.spawnDaemon(paths);
33
- await waitUntilReachable(paths.socket, deps, readyTimeoutMs, pollIntervalMs);
34
- }
35
- finally {
36
- lock.release();
37
- }
27
+ throwIfAborted(signal);
28
+ deps.spawnDaemon(paths);
29
+ await waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs, signal);
38
30
  }
39
- async function waitUntilReachable(socketPath, deps, readyTimeoutMs, pollIntervalMs) {
31
+ async function waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs, signal) {
40
32
  const deadline = deps.now() + readyTimeoutMs;
41
33
  for (;;) {
42
- if (await deps.probe(socketPath))
34
+ throwIfAborted(signal);
35
+ if (await awaitWithSignal(deps.probe(paths, signal), signal))
43
36
  return;
44
37
  if (deps.now() >= deadline)
45
- throw new DaemonUnreachableError(socketPath);
46
- await deps.sleep(pollIntervalMs);
38
+ throw new DaemonUnreachableError(paths.socket);
39
+ await awaitWithSignal(deps.sleep(pollIntervalMs, signal), signal);
47
40
  }
48
41
  }
49
- export function probeSocket(socketPath, timeoutMs = PROBE_TIMEOUT_MS) {
42
+ export async function probeDaemon(paths, timeoutMs = PROBE_TIMEOUT_MS, signal) {
43
+ const token = readAuthToken(paths);
44
+ if (!token)
45
+ return false;
46
+ return (await pingDaemon(paths, token, timeoutMs, signal)) !== null;
47
+ }
48
+ export function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS, signal) {
50
49
  return new Promise((resolve) => {
51
- const socket = connect(socketPath);
52
- const finish = (ok) => {
50
+ const socket = new Socket();
51
+ let settled = false;
52
+ let timer;
53
+ const finish = (value) => {
54
+ if (settled)
55
+ return;
56
+ settled = true;
57
+ if (timer !== undefined)
58
+ clearTimeout(timer);
59
+ signal?.removeEventListener("abort", onAbort);
53
60
  socket.destroy();
54
- resolve(ok);
61
+ resolve(value);
55
62
  };
56
- const timer = setTimeout(() => finish(false), timeoutMs);
57
- timer.unref?.();
63
+ const onAbort = () => finish(null);
64
+ const decoder = createLineDecoder((message) => {
65
+ finish(parsePingResponse(message));
66
+ });
58
67
  socket.once("connect", () => {
59
- clearTimeout(timer);
60
- finish(true);
68
+ socket.write(encodeJsonLine({ jsonrpc: "2.0", id: 1, method: "omo/ping", params: { _omo: authEnvelope(token) } }));
61
69
  });
70
+ socket.on("data", (chunk) => decoder.push(chunk));
62
71
  socket.once("error", () => {
63
- clearTimeout(timer);
64
- finish(false);
72
+ finish(null);
65
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);
66
82
  });
67
83
  }
68
84
  export function spawnDaemonProcess(paths) {
69
85
  mkdirSync(dirname(paths.log), { recursive: true });
70
86
  const logFd = openSync(paths.log, "a");
71
87
  try {
72
- const cliPath = resolveDaemonCliPath();
73
- const child = spawn(execPath, [cliPath, "daemon"], {
88
+ const child = spawn(execPath, [paths.cliPath, "daemon"], {
74
89
  detached: true,
75
90
  stdio: ["ignore", logFd, logFd],
76
91
  });
@@ -80,24 +95,93 @@ export function spawnDaemonProcess(paths) {
80
95
  closeSync(logFd);
81
96
  }
82
97
  }
83
- export function resolveDaemonCliPath(env = process.env) {
84
- const override = env[CODEX_LSP_DAEMON_CLI_ENV]?.trim();
85
- if (override)
86
- return override;
87
- return fileURLToPath(new URL("./cli.js", import.meta.url));
98
+ export function resolveDaemonCliPath(env = process.env, defaults = packagedRuntimeDefaults()) {
99
+ return resolveDaemonRuntime(env, defaults).cliPath;
88
100
  }
89
101
  export function defaultEnsureDaemonDeps() {
90
102
  return {
91
- probe: (socketPath) => probeSocket(socketPath),
92
- acquireLock: (lockPath) => tryAcquireLock(lockPath),
93
- cleanupStaleSocket: (socketPath) => {
94
- if (existsSync(socketPath))
95
- unlinkQuietly(socketPath);
96
- },
103
+ probe: (paths, signal) => probeDaemon(paths, PROBE_TIMEOUT_MS, signal),
97
104
  spawnDaemon: (paths) => spawnDaemonProcess(paths),
98
- sleep: (ms) => new Promise((resolve) => {
99
- setTimeout(resolve, ms);
100
- }),
105
+ sleep: (ms, signal) => sleepWithSignal(ms, signal),
101
106
  now: () => Date.now(),
102
107
  };
103
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
+ }
159
+ function parsePingResponse(message) {
160
+ if (!message || typeof message !== "object" || Array.isArray(message))
161
+ return null;
162
+ const result = Reflect.get(message, "result");
163
+ if (!result || typeof result !== "object" || Array.isArray(result))
164
+ return null;
165
+ const pid = Reflect.get(result, "pid");
166
+ const nonce = Reflect.get(result, "nonce");
167
+ const startedAt = Reflect.get(result, "startedAt");
168
+ const endpoint = Reflect.get(result, "endpoint");
169
+ if (typeof pid !== "number" || typeof nonce !== "string" || typeof startedAt !== "string")
170
+ return null;
171
+ if (!endpoint || typeof endpoint !== "object" || Array.isArray(endpoint))
172
+ return null;
173
+ const path = Reflect.get(endpoint, "path");
174
+ const kind = Reflect.get(endpoint, "kind");
175
+ if (typeof path !== "string")
176
+ return null;
177
+ if (kind === "windows")
178
+ return { pid, nonce, startedAt, endpoint: { kind, path } };
179
+ if (kind === "missing")
180
+ return { pid, nonce, startedAt, endpoint: { kind, path } };
181
+ const dev = Reflect.get(endpoint, "dev");
182
+ const ino = Reflect.get(endpoint, "ino");
183
+ if (kind === "unix" && typeof dev === "number" && typeof ino === "number") {
184
+ return { pid, nonce, startedAt, endpoint: { kind, path, dev, ino } };
185
+ }
186
+ return null;
187
+ }