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,80 +1,603 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // components/lsp/src/cli.ts
3
+ // src/cli.ts
4
4
  import { spawn as spawn3 } from "node:child_process";
5
5
  import { argv, execPath as execPath2, stderr } from "node:process";
6
6
 
7
- // components/lsp/src/codex-hook-cli.ts
7
+ // src/codex-hook-cli.ts
8
8
  import { stdin as processStdin } from "node:process";
9
9
 
10
- // ../../lsp-daemon/dist/index.js
11
- import { readFileSync } from "node:fs";
12
- import { resolve } from "node:path";
13
- import { pathToFileURL as pathToFileURL2 } from "node:url";
14
- import { AsyncLocalStorage } from "node:async_hooks";
15
- import { pathToFileURL } from "node:url";
16
- import { spawn, spawnSync } from "node:child_process";
17
- import { existsSync, statSync } from "node:fs";
18
- import { delimiter, join } from "node:path";
19
- import { basename, extname } from "node:path";
20
- import { connect as connect2 } from "node:net";
21
- import { spawn as spawn2 } from "node:child_process";
22
- import { closeSync as closeSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, openSync as openSync2 } from "node:fs";
10
+ // src/codex-hook.ts
11
+ import { readFileSync as readFileSync9, realpathSync as realpathSync6 } from "node:fs";
12
+ import { homedir as homedir3 } from "node:os";
13
+ import { join as join11, resolve as resolve11 } from "node:path";
14
+
15
+ // ../../../../lsp-daemon/dist/client.js
23
16
  import { connect } from "node:net";
24
- import { dirname as dirname2 } from "node:path";
17
+ import { AsyncLocalStorage } from "node:async_hooks";
18
+ import { existsSync, realpathSync, statSync } from "node:fs";
19
+ import { basename, delimiter, dirname, isAbsolute, join, relative, resolve } from "node:path";
20
+ import { spawn } from "node:child_process";
21
+ import { closeSync as closeSync2, mkdirSync as mkdirSync2, openSync as openSync2 } from "node:fs";
22
+ import { Socket } from "node:net";
23
+ import { dirname as dirname3 } from "node:path";
25
24
  import { execPath } from "node:process";
26
- import { fileURLToPath } from "node:url";
27
- import { closeSync, mkdirSync, openSync, readFileSync as readFileSync2, unlinkSync, writeSync } from "node:fs";
28
- import { dirname } from "node:path";
25
+ import {
26
+ chmodSync,
27
+ closeSync,
28
+ constants,
29
+ fchmodSync,
30
+ fstatSync,
31
+ lstatSync,
32
+ mkdirSync,
33
+ openSync,
34
+ readFileSync,
35
+ unlinkSync,
36
+ writeSync
37
+ } from "node:fs";
29
38
  import { createHash } from "node:crypto";
30
39
  import { createRequire } from "node:module";
31
- import { homedir, tmpdir } from "node:os";
32
- import { join as join2 } from "node:path";
33
- import { resolve as resolve4 } from "node:path";
34
- import { existsSync as existsSync6, statSync as statSync2 } from "node:fs";
35
- import { dirname as dirname4, join as join6, resolve as resolve2 } from "node:path";
36
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, writeFileSync } from "node:fs";
37
- import { homedir as homedir2 } from "node:os";
38
- import { dirname as dirname3, isAbsolute, join as join3 } from "node:path";
39
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
40
- import { homedir as homedir3 } from "node:os";
41
- import { delimiter as delimiter2, isAbsolute as isAbsolute2, join as join4 } from "node:path";
42
- import { existsSync as existsSync5 } from "node:fs";
43
- import { delimiter as delimiter3, join as join5 } from "node:path";
44
- import { existsSync as existsSync7, lstatSync, readdirSync } from "node:fs";
45
- import { join as join7, resolve as resolve3 } from "node:path";
40
+ import { homedir as homedir2, tmpdir, userInfo } from "node:os";
41
+ import * as path from "node:path";
42
+ import { fileURLToPath } from "node:url";
43
+ import { statSync as statSync2 } from "node:fs";
44
+ import { isAbsolute as isAbsolute2 } from "node:path";
45
+ import { existsSync as existsSync9, statSync as statSync4 } from "node:fs";
46
+ import { dirname as dirname8, join as join4, resolve as resolve7 } from "node:path";
47
+ import { basename as basename2, extname } from "node:path";
48
+ import { resolve as resolve6 } from "node:path";
49
+ import { pathToFileURL as pathToFileURL3 } from "node:url";
50
+ import { pathToFileURL } from "node:url";
51
+ import { spawn as spawn2, spawnSync } from "node:child_process";
52
+ import { existsSync as existsSync2, statSync as statSync3 } from "node:fs";
53
+ import { delimiter as delimiter2, join as join2 } from "node:path";
54
+ import { readFileSync as readFileSync2, realpathSync as realpathSync2 } from "node:fs";
55
+ import { relative as relative2, resolve as resolve2 } from "node:path";
56
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
57
+ import { existsSync as existsSync4, lstatSync as lstatSync3, renameSync, rmSync, writeFileSync } from "node:fs";
58
+ import { existsSync as existsSync3, lstatSync as lstatSync2, readFileSync as readFileSync3, readdirSync, realpathSync as realpathSync3 } from "node:fs";
59
+ import { dirname as dirname4, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve3 } from "node:path";
46
60
  import { fileURLToPath as fileURLToPath2 } from "node:url";
47
- import { lstatSync as lstatSync2, readdirSync as readdirSync2 } from "node:fs";
48
- import { join as join8 } from "node:path";
49
- import { existsSync as existsSync8, readFileSync as readFileSync5, realpathSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
50
- import { dirname as dirname5, isAbsolute as isAbsolute3, relative, resolve as resolve5 } from "node:path";
61
+ import { createHash as createHash2 } from "node:crypto";
62
+ import { dirname as dirname5, relative as relative4, resolve as resolve4 } from "node:path";
63
+ import { existsSync as existsSync5, lstatSync as lstatSync4, readdirSync as readdirSync2 } from "node:fs";
64
+ import { dirname as dirname6, resolve as resolve5 } from "node:path";
65
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
66
+ import { dirname as dirname7 } from "node:path";
67
+ import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
68
+ import { existsSync as existsSync8 } from "node:fs";
69
+ import { delimiter as delimiter3, join as join3 } from "node:path";
70
+ import { existsSync as existsSync10, lstatSync as lstatSync5, readdirSync as readdirSync3 } from "node:fs";
71
+ import { join as join5, resolve as resolve8 } from "node:path";
51
72
  import { fileURLToPath as fileURLToPath3 } from "node:url";
52
- function reportBestEffortCleanupError(operation, error) {
53
- if (process.env["CODEX_LSP_DEBUG_CLEANUP"] !== "1")
54
- return;
55
- const message = error instanceof Error ? error.message : String(error);
56
- console.error(`[codex-lsp] ignored ${operation} failure during cleanup: ${message}`);
73
+ import { lstatSync as lstatSync6, readdirSync as readdirSync4 } from "node:fs";
74
+ import { join as join6 } from "node:path";
75
+ import { statSync as statSync5 } from "node:fs";
76
+ import { isAbsolute as isAbsolute4 } from "node:path";
77
+
78
+ class LspRequestContextParseError extends Error {
79
+ code;
80
+ name = "LspRequestContextParseError";
81
+ constructor(code, message) {
82
+ super(message);
83
+ this.code = code;
84
+ }
85
+ }
86
+
87
+ class LspRequestContextUnavailableError extends Error {
88
+ name = "LspRequestContextUnavailableError";
89
+ constructor() {
90
+ super("LSP request context is required. Standalone MCP startup must install one with runWithRequestContext(createStandaloneMcpRequestContext()).");
91
+ }
57
92
  }
58
93
  var storage = new AsyncLocalStorage;
94
+ var CONTEXT_FIELDS = new Set(["cwd", "projectConfigPaths", "userConfigPath", "installDecisionsPath", "capabilities"]);
95
+ var CAPABILITY_FIELDS = new Set(["installDecisionTool"]);
96
+ function lspRequestContext() {
97
+ const context = storage.getStore();
98
+ if (!context)
99
+ throw new LspRequestContextUnavailableError;
100
+ return context;
101
+ }
59
102
  function contextCwd() {
60
- return storage.getStore()?.cwd ?? process.cwd();
103
+ return lspRequestContext().cwd;
104
+ }
105
+ function parseLspRequestContext(value) {
106
+ if (!isRecord(value)) {
107
+ throw new LspRequestContextParseError("invalid_context", "LSP request context must be an object.");
108
+ }
109
+ rejectUnknownFields(value, CONTEXT_FIELDS, "context");
110
+ const cwd = stringField(value, "cwd");
111
+ const projectConfigPaths = stringArrayField(value, "projectConfigPaths");
112
+ const userConfigPath = stringField(value, "userConfigPath");
113
+ const installDecisionsPath = stringField(value, "installDecisionsPath");
114
+ const capabilities = capabilitiesField(value["capabilities"]);
115
+ const canonical = canonicalCwd(cwd);
116
+ for (const path2 of projectConfigPaths) {
117
+ requireAbsolutePath(path2, "projectConfigPaths");
118
+ const projectPath = canonicalizeExistingOrNearestAncestor(path2);
119
+ if (!isPathInside(canonical, projectPath)) {
120
+ throw new LspRequestContextParseError("project_config_outside_cwd", `Project LSP config path must be inside cwd: ${path2}`);
121
+ }
122
+ }
123
+ requireAbsolutePath(userConfigPath, "userConfigPath");
124
+ requireAbsolutePath(installDecisionsPath, "installDecisionsPath");
125
+ return {
126
+ cwd: canonical,
127
+ projectConfigPaths: projectConfigPaths.map((path2) => canonicalizeExistingOrNearestAncestor(path2)),
128
+ userConfigPath,
129
+ installDecisionsPath,
130
+ capabilities
131
+ };
61
132
  }
62
- function contextEnv(key) {
63
- const store = storage.getStore();
64
- if (store?.env)
65
- return store.env[key];
66
- return process.env[key];
133
+ function canonicalCwd(cwd) {
134
+ const resolved = resolve(cwd);
135
+ if (!existsSync(resolved) || !statSync(resolved).isDirectory()) {
136
+ throw new LspRequestContextParseError("invalid_cwd", `LSP request cwd must be an existing directory: ${cwd}`);
137
+ }
138
+ return realpathSync(resolved);
139
+ }
140
+ function canonicalizeExistingOrNearestAncestor(path2) {
141
+ let current = resolve(path2);
142
+ const suffix = [];
143
+ while (true) {
144
+ try {
145
+ const existing = realpathSync(current);
146
+ return suffix.length === 0 ? existing : join(existing, ...suffix);
147
+ } catch (error) {
148
+ if (!isMissingPathError(error))
149
+ throw error;
150
+ const parent = dirname(current);
151
+ if (parent === current)
152
+ throw error;
153
+ suffix.unshift(basename(current));
154
+ current = parent;
155
+ }
156
+ }
157
+ }
158
+ function capabilitiesField(value) {
159
+ if (!isRecord(value)) {
160
+ throw new LspRequestContextParseError("invalid_capabilities", "LSP request capabilities must be an object.");
161
+ }
162
+ rejectUnknownFields(value, CAPABILITY_FIELDS, "capabilities");
163
+ const installDecisionTool = value["installDecisionTool"];
164
+ if (typeof installDecisionTool !== "boolean") {
165
+ throw new LspRequestContextParseError("invalid_install_decision_capability", "LSP request capabilities.installDecisionTool must be a boolean.");
166
+ }
167
+ return { installDecisionTool };
168
+ }
169
+ function stringField(value, field) {
170
+ const fieldValue = value[field];
171
+ if (typeof fieldValue !== "string" || fieldValue.length === 0) {
172
+ throw new LspRequestContextParseError("invalid_field", `LSP request context.${field} must be a non-empty string.`);
173
+ }
174
+ return fieldValue;
175
+ }
176
+ function stringArrayField(value, field) {
177
+ const fieldValue = value[field];
178
+ if (!Array.isArray(fieldValue) || !fieldValue.every((item) => typeof item === "string" && item.length > 0)) {
179
+ throw new LspRequestContextParseError("invalid_field", `LSP request context.${field} must be a non-empty string array.`);
180
+ }
181
+ return fieldValue;
182
+ }
183
+ function requireAbsolutePath(path2, field) {
184
+ if (!isAbsolute(path2)) {
185
+ throw new LspRequestContextParseError("relative_path", `LSP request context.${field} must be absolute: ${path2}`);
186
+ }
187
+ }
188
+ function isPathInside(parent, child) {
189
+ const childPath = resolve(child);
190
+ const relativePath = relative(parent, childPath);
191
+ return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute(relativePath);
192
+ }
193
+ function isMissingPathError(error) {
194
+ const code = errorCode(error);
195
+ return code === "ENOENT" || code === "ENOTDIR";
196
+ }
197
+ function rejectUnknownFields(value, allowed, scope) {
198
+ const unknown = Object.keys(value).filter((key) => !allowed.has(key));
199
+ if (unknown.length > 0) {
200
+ throw new LspRequestContextParseError("unknown_field", `Unknown LSP request ${scope} field: ${unknown.join(", ")}`);
201
+ }
202
+ }
203
+ function isRecord(value) {
204
+ return typeof value === "object" && value !== null && !Array.isArray(value);
205
+ }
206
+ function errorCode(error) {
207
+ if (!error || typeof error !== "object" || !("code" in error))
208
+ return;
209
+ const code = Reflect.get(error, "code");
210
+ return typeof code === "string" ? code : undefined;
211
+ }
212
+ function isPlainRecord(value) {
213
+ return typeof value === "object" && value !== null && !Array.isArray(value);
214
+ }
215
+ var OMO_DAEMON_PROTOCOL_VERSION = 1;
216
+ var AUTH_ERROR_CODE = -32001;
217
+ function authEnvelope(token) {
218
+ return { protocolVersion: OMO_DAEMON_PROTOCOL_VERSION, token };
219
+ }
220
+ function readAuthToken(paths) {
221
+ try {
222
+ const token = readFileSync(paths.auth, "utf8").trim();
223
+ return token.length > 0 ? token : null;
224
+ } catch (error) {
225
+ if (error instanceof Error)
226
+ return null;
227
+ throw error;
228
+ }
229
+ }
230
+ function isAuthErrorResponse(message) {
231
+ if (!isPlainRecord(message))
232
+ return false;
233
+ const error = message["error"];
234
+ if (!isPlainRecord(error))
235
+ return false;
236
+ const data = error["data"];
237
+ return error["code"] === AUTH_ERROR_CODE && isPlainRecord(data) && data["code"] === "daemon_authentication_failed";
238
+ }
239
+ var OMO_LSP_DAEMON_DIR = "OMO_LSP_DAEMON_DIR";
240
+ var OMO_LSP_DAEMON_CLI = "OMO_LSP_DAEMON_CLI";
241
+ var OMO_LSP_DAEMON_VERSION = "OMO_LSP_DAEMON_VERSION";
242
+ var DAEMON_VERSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
243
+
244
+ class InvalidRuntimeOverrideError extends Error {
245
+ code = "invalid_runtime_override";
246
+ reason;
247
+ constructor(reason, message) {
248
+ super(message);
249
+ this.name = "InvalidRuntimeOverrideError";
250
+ this.reason = reason;
251
+ }
252
+ }
253
+
254
+ class InvalidDaemonVersionError extends Error {
255
+ code = "invalid_daemon_version";
256
+ version;
257
+ constructor(version) {
258
+ super("LSP daemon version must match [A-Za-z0-9][A-Za-z0-9._+-]{0,127}");
259
+ this.name = "InvalidDaemonVersionError";
260
+ this.version = version;
261
+ }
262
+ }
263
+ function validateDaemonVersion(version) {
264
+ if (!DAEMON_VERSION_PATTERN.test(version))
265
+ throw new InvalidDaemonVersionError(version);
266
+ return version;
267
+ }
268
+ function resolveDaemonRuntime(env, defaults) {
269
+ const cliOverride = env[OMO_LSP_DAEMON_CLI];
270
+ const versionOverride = env[OMO_LSP_DAEMON_VERSION];
271
+ const hasCliOverride = cliOverride !== undefined;
272
+ const hasVersionOverride = versionOverride !== undefined;
273
+ if (hasCliOverride !== hasVersionOverride) {
274
+ throw new InvalidRuntimeOverrideError("paired_values_required", `${OMO_LSP_DAEMON_CLI} and ${OMO_LSP_DAEMON_VERSION} must be set together`);
275
+ }
276
+ if (!hasCliOverride || !hasVersionOverride) {
277
+ if (!isAbsolute2(defaults.cliPath)) {
278
+ throw new InvalidRuntimeOverrideError("packaged_cli_must_be_absolute", "Packaged LSP daemon CLI path must be absolute");
279
+ }
280
+ return { cliPath: defaults.cliPath, version: validateDaemonVersion(defaults.version) };
281
+ }
282
+ if (!isAbsolute2(cliOverride)) {
283
+ throw new InvalidRuntimeOverrideError("cli_must_be_absolute", `${OMO_LSP_DAEMON_CLI} must be an absolute path to an existing regular file`);
284
+ }
285
+ let cliStats;
286
+ try {
287
+ cliStats = statSync2(cliOverride);
288
+ } catch (error) {
289
+ if (!(error instanceof Error))
290
+ throw error;
291
+ throw new InvalidRuntimeOverrideError("cli_not_found", `${OMO_LSP_DAEMON_CLI} must name an existing regular file`);
292
+ }
293
+ if (!cliStats.isFile()) {
294
+ throw new InvalidRuntimeOverrideError("cli_not_file", `${OMO_LSP_DAEMON_CLI} must name an existing regular file`);
295
+ }
296
+ return { cliPath: cliOverride, version: validateDaemonVersion(versionOverride) };
297
+ }
298
+ var requireFromHere = createRequire(import.meta.url);
299
+ var MAX_SOCKET_PATH_LENGTH = 100;
300
+
301
+ class InvalidDaemonDirectoryError extends Error {
302
+ code = "invalid_daemon_directory";
303
+ directory;
304
+ constructor(directory) {
305
+ super(`${OMO_LSP_DAEMON_DIR} must be an absolute path`);
306
+ this.name = "InvalidDaemonDirectoryError";
307
+ this.directory = directory;
308
+ }
309
+ }
310
+ function resolveDaemonVersion(requireFn = requireFromHere) {
311
+ for (const candidate of ["./package.json", "../package.json"]) {
312
+ let loaded;
313
+ try {
314
+ loaded = requireFn(candidate);
315
+ } catch (error) {
316
+ if (!(error instanceof Error))
317
+ throw error;
318
+ continue;
319
+ }
320
+ if (typeof loaded === "object" && loaded !== null && "version" in loaded) {
321
+ const version = Reflect.get(loaded, "version");
322
+ if (typeof version === "string")
323
+ return validateDaemonVersion(version);
324
+ }
325
+ }
326
+ return "0";
327
+ }
328
+ function packagedRuntimeDefaults() {
329
+ return {
330
+ cliPath: fileURLToPath(new URL("./cli.js", import.meta.url)),
331
+ version: resolveDaemonVersion()
332
+ };
333
+ }
334
+ function daemonBaseDir(env = process.env, platform = defaultDaemonPlatform()) {
335
+ const override = env[OMO_LSP_DAEMON_DIR];
336
+ if (override !== undefined) {
337
+ if (!platform.path.isAbsolute(override))
338
+ throw new InvalidDaemonDirectoryError(override);
339
+ return platform.path.resolve(override);
340
+ }
341
+ return platform.path.resolve(platform.path.join(platform.homedir(), ".omo", "lsp-daemon"));
342
+ }
343
+ function daemonPaths(env = process.env, runtimeDefaults = packagedRuntimeDefaults(), platform = defaultDaemonPlatform()) {
344
+ const runtime = resolveDaemonRuntime(env, runtimeDefaults);
345
+ const baseDir = daemonBaseDir(env, platform);
346
+ const dir = platform.path.resolve(platform.path.join(baseDir, `v${runtime.version}`));
347
+ return {
348
+ version: runtime.version,
349
+ cliPath: runtime.cliPath,
350
+ dir,
351
+ socket: resolveSocketPath(dir, runtime.version, platform),
352
+ lock: platform.path.join(dir, "daemon.lock"),
353
+ pid: platform.path.join(dir, "daemon.pid"),
354
+ auth: platform.path.join(dir, "daemon.auth"),
355
+ endpoint: platform.path.join(dir, "daemon.endpoint"),
356
+ owner: platform.path.join(dir, "daemon.owner"),
357
+ log: platform.path.join(dir, "daemon.log")
358
+ };
359
+ }
360
+ function defaultDaemonPlatform() {
361
+ return {
362
+ platform: process.platform,
363
+ homedir: homedir2,
364
+ tmpdir,
365
+ getuid: () => typeof process.getuid === "function" ? process.getuid() : undefined,
366
+ username: () => userInfo().username,
367
+ path
368
+ };
369
+ }
370
+ function resolveSocketPath(dir, version, platform) {
371
+ const canonicalVersionDir = platform.path.resolve(dir);
372
+ if (platform.platform === "win32") {
373
+ const currentUserDiscriminator = `${platform.getuid() ?? "win"}:${platform.username()}:${platform.path.resolve(platform.homedir())}`;
374
+ const digest = shortDigest(`${canonicalVersionDir}\x00${currentUserDiscriminator}`);
375
+ return `\\\\.\\pipe\\omo-lsp-${version}-${digest}`;
376
+ }
377
+ const natural = platform.path.join(canonicalVersionDir, "daemon.sock");
378
+ if (natural.length < MAX_SOCKET_PATH_LENGTH)
379
+ return natural;
380
+ return platform.path.join(platform.tmpdir(), `omo-lsp-${version}-${shortDigest(canonicalVersionDir)}`, "daemon.sock");
381
+ }
382
+ function shortDigest(value) {
383
+ return createHash("sha256").update(value).digest("hex").slice(0, 16);
384
+ }
385
+ function encodeJsonLine(message) {
386
+ return `${JSON.stringify(message)}
387
+ `;
388
+ }
389
+ function createLineDecoder(onMessage, onParseError) {
390
+ let buffer = "";
391
+ return {
392
+ push(chunk) {
393
+ buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
394
+ let index = buffer.indexOf(`
395
+ `);
396
+ while (index !== -1) {
397
+ const raw = buffer.slice(0, index).trim();
398
+ buffer = buffer.slice(index + 1);
399
+ if (raw.length > 0) {
400
+ try {
401
+ onMessage(JSON.parse(raw));
402
+ } catch (error) {
403
+ if (error instanceof Error) {
404
+ onParseError?.(raw, error);
405
+ } else {
406
+ throw error;
407
+ }
408
+ }
409
+ }
410
+ index = buffer.indexOf(`
411
+ `);
412
+ }
413
+ }
414
+ };
415
+ }
416
+ var PROBE_TIMEOUT_MS = 500;
417
+ var DEFAULT_READY_TIMEOUT_MS = 5000;
418
+ var DEFAULT_POLL_INTERVAL_MS = 100;
419
+
420
+ class DaemonUnreachableError extends Error {
421
+ constructor(socketPath) {
422
+ super(`LSP daemon did not become reachable at ${socketPath}`);
423
+ this.name = "DaemonUnreachableError";
424
+ }
425
+ }
426
+ async function ensureDaemonRunning(paths, deps = defaultEnsureDaemonDeps(), options = {}) {
427
+ const readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
428
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
429
+ const signal = options.signal;
430
+ throwIfAborted(signal);
431
+ if (await awaitWithSignal(deps.probe(paths, signal), signal))
432
+ return;
433
+ throwIfAborted(signal);
434
+ deps.spawnDaemon(paths);
435
+ await waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs, signal);
436
+ }
437
+ async function waitUntilReachable(paths, deps, readyTimeoutMs, pollIntervalMs, signal) {
438
+ const deadline = deps.now() + readyTimeoutMs;
439
+ for (;; ) {
440
+ throwIfAborted(signal);
441
+ if (await awaitWithSignal(deps.probe(paths, signal), signal))
442
+ return;
443
+ if (deps.now() >= deadline)
444
+ throw new DaemonUnreachableError(paths.socket);
445
+ await awaitWithSignal(deps.sleep(pollIntervalMs, signal), signal);
446
+ }
447
+ }
448
+ async function probeDaemon(paths, timeoutMs = PROBE_TIMEOUT_MS, signal) {
449
+ const token = readAuthToken(paths);
450
+ if (!token)
451
+ return false;
452
+ return await pingDaemon(paths, token, timeoutMs, signal) !== null;
453
+ }
454
+ function pingDaemon(paths, token, timeoutMs = PROBE_TIMEOUT_MS, signal) {
455
+ return new Promise((resolve22) => {
456
+ const socket = new Socket;
457
+ let settled = false;
458
+ let timer;
459
+ const finish = (value) => {
460
+ if (settled)
461
+ return;
462
+ settled = true;
463
+ if (timer !== undefined)
464
+ clearTimeout(timer);
465
+ signal?.removeEventListener("abort", onAbort);
466
+ socket.destroy();
467
+ resolve22(value);
468
+ };
469
+ const onAbort = () => finish(null);
470
+ const decoder = createLineDecoder((message) => {
471
+ finish(parsePingResponse(message));
472
+ });
473
+ socket.once("connect", () => {
474
+ socket.write(encodeJsonLine({ jsonrpc: "2.0", id: 1, method: "omo/ping", params: { _omo: authEnvelope(token) } }));
475
+ });
476
+ socket.on("data", (chunk) => decoder.push(chunk));
477
+ socket.once("error", () => {
478
+ finish(null);
479
+ });
480
+ timer = setTimeout(() => finish(null), timeoutMs);
481
+ timer.unref?.();
482
+ if (signal?.aborted) {
483
+ onAbort();
484
+ return;
485
+ }
486
+ signal?.addEventListener("abort", onAbort, { once: true });
487
+ socket.connect(paths.socket);
488
+ });
489
+ }
490
+ function spawnDaemonProcess(paths) {
491
+ mkdirSync2(dirname3(paths.log), { recursive: true });
492
+ const logFd = openSync2(paths.log, "a");
493
+ try {
494
+ const child = spawn(execPath, [paths.cliPath, "daemon"], {
495
+ detached: true,
496
+ stdio: ["ignore", logFd, logFd]
497
+ });
498
+ child.unref();
499
+ } finally {
500
+ closeSync2(logFd);
501
+ }
502
+ }
503
+ function defaultEnsureDaemonDeps() {
504
+ return {
505
+ probe: (paths, signal) => probeDaemon(paths, PROBE_TIMEOUT_MS, signal),
506
+ spawnDaemon: (paths) => spawnDaemonProcess(paths),
507
+ sleep: (ms, signal) => sleepWithSignal(ms, signal),
508
+ now: () => Date.now()
509
+ };
510
+ }
511
+ function sleepWithSignal(ms, signal) {
512
+ return new Promise((resolve22) => {
513
+ let settled = false;
514
+ const finish = () => {
515
+ if (settled)
516
+ return;
517
+ settled = true;
518
+ clearTimeout(timer);
519
+ signal?.removeEventListener("abort", finish);
520
+ resolve22();
521
+ };
522
+ const timer = setTimeout(finish, ms);
523
+ if (signal?.aborted) {
524
+ finish();
525
+ return;
526
+ }
527
+ signal?.addEventListener("abort", finish, { once: true });
528
+ });
529
+ }
530
+ function awaitWithSignal(promise, signal) {
531
+ if (!signal)
532
+ return promise;
533
+ if (signal.aborted)
534
+ return Promise.reject(abortError(signal));
535
+ return new Promise((resolve22, reject) => {
536
+ let settled = false;
537
+ const finish = (run) => {
538
+ if (settled)
539
+ return;
540
+ settled = true;
541
+ signal.removeEventListener("abort", onAbort);
542
+ run();
543
+ };
544
+ const onAbort = () => finish(() => reject(abortError(signal)));
545
+ signal.addEventListener("abort", onAbort, { once: true });
546
+ promise.then((value) => finish(() => resolve22(value)), (error) => finish(() => reject(error)));
547
+ });
548
+ }
549
+ function throwIfAborted(signal) {
550
+ if (signal?.aborted)
551
+ throw abortError(signal);
552
+ }
553
+ function abortError(signal) {
554
+ const reason = signal.reason;
555
+ if (reason instanceof Error)
556
+ return reason;
557
+ const error = new Error(typeof reason === "string" ? reason : "daemon startup cancelled");
558
+ error.name = "AbortError";
559
+ return error;
560
+ }
561
+ function parsePingResponse(message) {
562
+ if (!message || typeof message !== "object" || Array.isArray(message))
563
+ return null;
564
+ const result = Reflect.get(message, "result");
565
+ if (!result || typeof result !== "object" || Array.isArray(result))
566
+ return null;
567
+ const pid = Reflect.get(result, "pid");
568
+ const nonce = Reflect.get(result, "nonce");
569
+ const startedAt = Reflect.get(result, "startedAt");
570
+ const endpoint = Reflect.get(result, "endpoint");
571
+ if (typeof pid !== "number" || typeof nonce !== "string" || typeof startedAt !== "string")
572
+ return null;
573
+ if (!endpoint || typeof endpoint !== "object" || Array.isArray(endpoint))
574
+ return null;
575
+ const path2 = Reflect.get(endpoint, "path");
576
+ const kind = Reflect.get(endpoint, "kind");
577
+ if (typeof path2 !== "string")
578
+ return null;
579
+ if (kind === "windows")
580
+ return { pid, nonce, startedAt, endpoint: { kind, path: path2 } };
581
+ if (kind === "missing")
582
+ return { pid, nonce, startedAt, endpoint: { kind, path: path2 } };
583
+ const dev = Reflect.get(endpoint, "dev");
584
+ const ino = Reflect.get(endpoint, "ino");
585
+ if (kind === "unix" && typeof dev === "number" && typeof ino === "number") {
586
+ return { pid, nonce, startedAt, endpoint: { kind, path: path2, dev, ino } };
587
+ }
588
+ return null;
589
+ }
590
+ var HEADER_SEPARATOR = Buffer.from(`\r
591
+ \r
592
+ `);
593
+ var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60000;
594
+ var BASENAME_EXTENSIONS = {
595
+ Dockerfile: ".dockerfile",
596
+ Containerfile: ".dockerfile"
597
+ };
598
+ function effectiveExtension(filePath) {
599
+ return BASENAME_EXTENSIONS[basename2(filePath)] ?? extname(filePath);
67
600
  }
68
- var DEFAULT_MAX_REFERENCES = 200;
69
- var DEFAULT_MAX_SYMBOLS = 200;
70
- var DEFAULT_MAX_DIAGNOSTICS = 200;
71
- var DEFAULT_MAX_DIRECTORY_FILES = 50;
72
- var REQUEST_TIMEOUT_MS = 15000;
73
- var INIT_TIMEOUT_MS = 60000;
74
- var IDLE_TIMEOUT_MS = 5 * 60000;
75
- var REAPER_INTERVAL_MS = 60000;
76
- var STOP_HARD_KILL_TIMEOUT_MS = 5000;
77
- var STOP_SIGKILL_GRACE_MS = 1000;
78
601
 
79
602
  class LspConnectionClosedError extends Error {
80
603
  serverId;
@@ -122,7 +645,12 @@ class LspInvalidPathError extends Error {
122
645
  }
123
646
 
124
647
  class LspServerLookupError extends Error {
648
+ lookup;
125
649
  name = "LspServerLookupError";
650
+ constructor(message, lookup) {
651
+ super(message);
652
+ this.lookup = lookup;
653
+ }
126
654
  }
127
655
 
128
656
  class LspServerInitializingError extends Error {
@@ -140,7 +668,25 @@ class LspProcessSpawnError extends Error {
140
668
  function isLspDeadConnectionError(err) {
141
669
  return err instanceof LspConnectionClosedError || err instanceof LspProcessExitedError;
142
670
  }
143
- var HEADER_SEPARATOR = `\r
671
+ function writeCleanupError(message) {
672
+ process.stderr.write(`${message}
673
+ `);
674
+ }
675
+ function reportBestEffortCleanupError(operation, error, logger = writeCleanupError) {
676
+ const message = error instanceof Error ? error.message : String(error);
677
+ logger(`[lsp] ignored ${operation} failure during cleanup: ${message}`);
678
+ }
679
+ var DEFAULT_MAX_REFERENCES = 200;
680
+ var DEFAULT_MAX_SYMBOLS = 200;
681
+ var DEFAULT_MAX_DIAGNOSTICS = 200;
682
+ var DEFAULT_MAX_DIRECTORY_FILES = 50;
683
+ var REQUEST_TIMEOUT_MS = 15000;
684
+ var INIT_TIMEOUT_MS = 60000;
685
+ var IDLE_TIMEOUT_MS = 5 * 60000;
686
+ var REAPER_INTERVAL_MS = 60000;
687
+ var STOP_HARD_KILL_TIMEOUT_MS = 5000;
688
+ var STOP_SIGKILL_GRACE_MS = 1000;
689
+ var HEADER_SEPARATOR2 = `\r
144
690
  \r
145
691
  `;
146
692
  var PARSE_ERROR = -32700;
@@ -186,28 +732,80 @@ class JsonRpcConnection {
186
732
  onError(handler) {
187
733
  this.errorHandlers.push(handler);
188
734
  }
189
- async sendRequest(method, params) {
735
+ async sendRequest(method, params, options = {}) {
190
736
  if (this.disposed)
191
737
  throw new Error("JSON-RPC connection is disposed");
192
738
  const id = this.nextRequestId;
193
739
  this.nextRequestId += 1;
740
+ const key = String(id);
194
741
  const message = params === undefined ? { jsonrpc: "2.0", id, method } : { jsonrpc: "2.0", id, method, params };
195
- const responsePromise = new Promise((resolve6, reject) => {
196
- this.pendingRequests.set(String(id), {
742
+ let requestWritten = false;
743
+ let cancelAfterWrite = false;
744
+ let settled = false;
745
+ const writeCancel = () => this.writeMessage({ jsonrpc: "2.0", method: "$/cancelRequest", params: { id } });
746
+ const responsePromise = new Promise((resolve22, reject) => {
747
+ const cleanup = () => {
748
+ options.signal?.removeEventListener("abort", onAbort);
749
+ };
750
+ const settleCancel = () => {
751
+ if (settled)
752
+ return;
753
+ settled = true;
754
+ this.pendingRequests.delete(key);
755
+ cleanup();
756
+ const rejectCancelled = () => reject(abortError2(options.signal));
757
+ if (!requestWritten) {
758
+ cancelAfterWrite = true;
759
+ rejectCancelled();
760
+ return;
761
+ }
762
+ writeCancel().then(rejectCancelled, (error) => {
763
+ this.emitError(toError(error));
764
+ rejectCancelled();
765
+ });
766
+ };
767
+ const onAbort = () => settleCancel();
768
+ this.pendingRequests.set(key, {
197
769
  resolve(result) {
198
- resolve6(result);
770
+ settled = true;
771
+ cleanup();
772
+ resolve22(result);
773
+ },
774
+ reject(error) {
775
+ settled = true;
776
+ cleanup();
777
+ reject(error);
199
778
  },
200
- reject
779
+ cleanup
201
780
  });
781
+ if (options.signal?.aborted) {
782
+ settleCancel();
783
+ return;
784
+ }
785
+ options.signal?.addEventListener("abort", onAbort, { once: true });
202
786
  });
787
+ if (settled)
788
+ return responsePromise;
203
789
  try {
204
790
  await this.writeMessage(message);
791
+ requestWritten = true;
792
+ if (cancelAfterWrite)
793
+ await writeCancel();
205
794
  } catch (error) {
206
- this.pendingRequests.delete(String(id));
795
+ if (settled)
796
+ return responsePromise;
797
+ const pending = this.pendingRequests.get(key);
798
+ if (pending) {
799
+ pending.cleanup();
800
+ this.pendingRequests.delete(key);
801
+ }
207
802
  throw error;
208
803
  }
209
804
  return responsePromise;
210
805
  }
806
+ pendingRequestCount() {
807
+ return this.pendingRequests.size;
808
+ }
211
809
  async sendNotification(method, params) {
212
810
  if (this.disposed)
213
811
  return;
@@ -224,6 +822,7 @@ class JsonRpcConnection {
224
822
  this.reader.off("error", this.handleStreamError);
225
823
  this.writer.off("error", this.handleStreamError);
226
824
  for (const pending of this.pendingRequests.values()) {
825
+ pending.cleanup();
227
826
  pending.reject(new Error("JSON-RPC connection disposed"));
228
827
  }
229
828
  this.pendingRequests.clear();
@@ -245,17 +844,17 @@ class JsonRpcConnection {
245
844
  };
246
845
  drainInputBuffer() {
247
846
  while (true) {
248
- const headerEnd = this.inputBuffer.indexOf(HEADER_SEPARATOR);
847
+ const headerEnd = this.inputBuffer.indexOf(HEADER_SEPARATOR2);
249
848
  if (headerEnd === -1)
250
849
  return;
251
850
  const headers = this.inputBuffer.subarray(0, headerEnd).toString("ascii");
252
- const contentLength = parseContentLength(headers);
851
+ const contentLength = parseContentLength2(headers);
253
852
  if (contentLength === null) {
254
853
  this.inputBuffer = Buffer.alloc(0);
255
854
  this.emitError(new Error("JSON-RPC message is missing Content-Length header"));
256
855
  return;
257
856
  }
258
- const bodyStart = headerEnd + Buffer.byteLength(HEADER_SEPARATOR);
857
+ const bodyStart = headerEnd + Buffer.byteLength(HEADER_SEPARATOR2);
259
858
  const bodyEnd = bodyStart + contentLength;
260
859
  if (this.inputBuffer.length < bodyEnd)
261
860
  return;
@@ -299,6 +898,7 @@ class JsonRpcConnection {
299
898
  if (!pending)
300
899
  return;
301
900
  this.pendingRequests.delete(String(id));
901
+ pending.cleanup();
302
902
  if ("error" in message) {
303
903
  pending.reject(jsonRpcErrorToError(message["error"]));
304
904
  return;
@@ -312,7 +912,11 @@ class JsonRpcConnection {
312
912
  try {
313
913
  handler(params);
314
914
  } catch (error) {
315
- this.emitError(toError(error));
915
+ if (error instanceof Error) {
916
+ this.emitError(error);
917
+ return;
918
+ }
919
+ this.emitError(new Error(String(error)));
316
920
  }
317
921
  }
318
922
  handleRequest(message) {
@@ -337,13 +941,13 @@ class JsonRpcConnection {
337
941
  const payload = `Content-Length: ${Buffer.byteLength(body, "utf8")}\r
338
942
  \r
339
943
  ${body}`;
340
- return new Promise((resolve6, reject) => {
944
+ return new Promise((resolve22, reject) => {
341
945
  this.writer.write(payload, (error) => {
342
946
  if (error) {
343
947
  reject(error);
344
948
  return;
345
949
  }
346
- resolve6();
950
+ resolve22();
347
951
  });
348
952
  });
349
953
  }
@@ -353,7 +957,15 @@ ${body}`;
353
957
  }
354
958
  }
355
959
  }
356
- function parseContentLength(headers) {
960
+ function abortError2(signal) {
961
+ const reason = signal?.reason;
962
+ if (reason instanceof Error)
963
+ return reason;
964
+ const error = new Error(typeof reason === "string" ? reason : "LSP request cancelled");
965
+ error.name = "AbortError";
966
+ return error;
967
+ }
968
+ function parseContentLength2(headers) {
357
969
  for (const line of headers.split(`\r
358
970
  `)) {
359
971
  const separatorIndex = line.indexOf(":");
@@ -401,10 +1013,10 @@ function reportKillError(context, error) {
401
1013
  }
402
1014
  function validateCwd(cwd) {
403
1015
  try {
404
- if (!existsSync(cwd)) {
1016
+ if (!existsSync2(cwd)) {
405
1017
  return { valid: false, error: `Working directory does not exist: ${cwd}` };
406
1018
  }
407
- const stats = statSync(cwd);
1019
+ const stats = statSync3(cwd);
408
1020
  if (!stats.isDirectory()) {
409
1021
  return { valid: false, error: `Path is not a directory: ${cwd}` };
410
1022
  }
@@ -417,9 +1029,9 @@ function validateCwd(cwd) {
417
1029
  }
418
1030
  }
419
1031
  function wrap(proc) {
420
- const exitedPromise = new Promise((resolve6) => {
421
- proc.once("close", (code) => resolve6(code ?? 0));
422
- proc.once("error", () => resolve6(1));
1032
+ const exitedPromise = new Promise((resolve22) => {
1033
+ proc.once("close", (code) => resolve22(code ?? 0));
1034
+ proc.once("error", () => resolve22(1));
423
1035
  });
424
1036
  if (!proc.stdin || !proc.stdout || !proc.stderr) {
425
1037
  throw new LspProcessSpawnError("Spawned process is missing one of stdin/stdout/stderr pipes");
@@ -473,7 +1085,7 @@ function isWindowsShellShim(command) {
473
1085
  return lowerCommand.endsWith(".cmd") || lowerCommand.endsWith(".bat");
474
1086
  }
475
1087
  function splitPath(pathValue, platform) {
476
- const separator = platform === "win32" ? ";" : delimiter;
1088
+ const separator = platform === "win32" ? ";" : delimiter2;
477
1089
  return pathValue.split(separator).filter(Boolean);
478
1090
  }
479
1091
  function getWindowsPathExtensions(env) {
@@ -488,8 +1100,8 @@ function resolveWindowsCommand(command, env) {
488
1100
  const extensions = getWindowsPathExtensions(env);
489
1101
  for (const baseDirectory of baseDirectories) {
490
1102
  for (const extension of extensions) {
491
- const candidate = baseDirectory ? join(baseDirectory, `${command}${extension}`) : `${command}${extension}`;
492
- if (existsSync(candidate))
1103
+ const candidate = baseDirectory ? join2(baseDirectory, `${command}${extension}`) : `${command}${extension}`;
1104
+ if (existsSync2(candidate))
493
1105
  return candidate;
494
1106
  }
495
1107
  }
@@ -523,7 +1135,7 @@ function spawnProcess(command, options) {
523
1135
  throw new LspProcessSpawnError("[lsp] empty command");
524
1136
  }
525
1137
  const preparedCommand = createSpawnCommand(command, process.platform, process.env["ComSpec"] ?? "cmd.exe", options.env);
526
- const proc = spawn(preparedCommand.command, preparedCommand.args, {
1138
+ const proc = spawn2(preparedCommand.command, preparedCommand.args, {
527
1139
  cwd: options.cwd,
528
1140
  env: options.env,
529
1141
  stdio: ["pipe", "pipe", "pipe"],
@@ -533,15 +1145,15 @@ function spawnProcess(command, options) {
533
1145
  });
534
1146
  return wrap(proc);
535
1147
  }
536
- function isRecord(value) {
1148
+ function isRecord2(value) {
537
1149
  return typeof value === "object" && value !== null && !Array.isArray(value);
538
1150
  }
539
1151
  function parseConfigurationItems(params) {
540
- if (!isRecord(params) || !Array.isArray(params["items"]))
1152
+ if (!isRecord2(params) || !Array.isArray(params["items"]))
541
1153
  return [];
542
1154
  const items = [];
543
1155
  for (const item of params["items"]) {
544
- if (!isRecord(item))
1156
+ if (!isRecord2(item))
545
1157
  continue;
546
1158
  const section = item["section"];
547
1159
  items.push(section === undefined || typeof section !== "string" ? {} : { section });
@@ -549,10 +1161,34 @@ function parseConfigurationItems(params) {
549
1161
  return items;
550
1162
  }
551
1163
  function parseDiagnosticsParams(params) {
552
- if (!isRecord(params) || typeof params["uri"] !== "string")
1164
+ if (!isRecord2(params) || typeof params["uri"] !== "string")
553
1165
  return null;
554
1166
  const diagnostics = Array.isArray(params["diagnostics"]) ? params["diagnostics"].filter(isDiagnostic) : [];
555
- return { uri: params["uri"], diagnostics };
1167
+ const version = typeof params["version"] === "number" ? params["version"] : undefined;
1168
+ return { uri: params["uri"], diagnostics, ...version === undefined ? {} : { version } };
1169
+ }
1170
+ function createLspSpawnEnv(_root, input) {
1171
+ return { ...input };
1172
+ }
1173
+ function isDiagnostic(value) {
1174
+ return isRecord2(value) && isRange(value["range"]) && typeof value["message"] === "string";
1175
+ }
1176
+ function isRange(value) {
1177
+ return isRecord2(value) && isPosition(value["start"]) && isPosition(value["end"]);
1178
+ }
1179
+ function isPosition(value) {
1180
+ return isRecord2(value) && typeof value["line"] === "number" && typeof value["character"] === "number";
1181
+ }
1182
+
1183
+ class LspClientNotStartedError extends Error {
1184
+ serverId;
1185
+ root;
1186
+ name = "LspClientNotStartedError";
1187
+ constructor(serverId, root) {
1188
+ super("LSP client not started");
1189
+ this.serverId = serverId;
1190
+ this.root = root;
1191
+ }
556
1192
  }
557
1193
 
558
1194
  class LspClientTransport {
@@ -565,9 +1201,11 @@ class LspClientTransport {
565
1201
  diagnosticsStore = new Map;
566
1202
  requestTimeoutMs;
567
1203
  initializeTimeoutMs;
568
- constructor(root, server, timeouts = {}) {
1204
+ workspaceApplyEditHandler = null;
1205
+ diagnosticPullSupported = false;
1206
+ constructor(root, server2, timeouts = {}) {
569
1207
  this.root = root;
570
- this.server = server;
1208
+ this.server = server2;
571
1209
  this.requestTimeoutMs = timeouts.requestTimeoutMs ?? REQUEST_TIMEOUT_MS;
572
1210
  this.initializeTimeoutMs = timeouts.initializeTimeoutMs ?? INIT_TIMEOUT_MS;
573
1211
  }
@@ -577,6 +1215,21 @@ class LspClientTransport {
577
1215
  command() {
578
1216
  return [...this.server.command];
579
1217
  }
1218
+ setWorkspaceApplyEditHandler(handler) {
1219
+ this.workspaceApplyEditHandler = handler;
1220
+ }
1221
+ hasWorkspaceApplyEditHandler() {
1222
+ return this.workspaceApplyEditHandler !== null;
1223
+ }
1224
+ setDiagnosticPullSupported(supported) {
1225
+ this.diagnosticPullSupported = supported;
1226
+ }
1227
+ isDiagnosticPullSupported() {
1228
+ return this.diagnosticPullSupported;
1229
+ }
1230
+ handlePublishDiagnostics(params) {
1231
+ this.diagnosticsStore.set(params.uri, [...params.diagnostics]);
1232
+ }
580
1233
  async start() {
581
1234
  const env = createLspSpawnEnv(this.root, {
582
1235
  ...process.env,
@@ -587,7 +1240,6 @@ class LspClientTransport {
587
1240
  env
588
1241
  });
589
1242
  this.startStderrReading();
590
- await new Promise((resolve6) => setTimeout(resolve6, 100));
591
1243
  if (this.proc.exitCode !== null) {
592
1244
  const stderr = this.stderrBuffer.join(`
593
1245
  `);
@@ -597,7 +1249,7 @@ class LspClientTransport {
597
1249
  this.connection.onNotification("textDocument/publishDiagnostics", (params) => {
598
1250
  const diagnosticsParams = parseDiagnosticsParams(params);
599
1251
  if (diagnosticsParams?.uri) {
600
- this.diagnosticsStore.set(diagnosticsParams.uri, diagnosticsParams.diagnostics);
1252
+ this.handlePublishDiagnostics(diagnosticsParams);
601
1253
  }
602
1254
  });
603
1255
  this.connection.onRequest("workspace/configuration", (params) => {
@@ -610,6 +1262,9 @@ class LspClientTransport {
610
1262
  });
611
1263
  this.connection.onRequest("client/registerCapability", () => null);
612
1264
  this.connection.onRequest("window/workDoneProgress/create", () => null);
1265
+ if (this.workspaceApplyEditHandler) {
1266
+ this.connection.onRequest("workspace/applyEdit", this.workspaceApplyEditHandler);
1267
+ }
613
1268
  this.connection.onClose(() => {
614
1269
  this.processExited = true;
615
1270
  });
@@ -638,30 +1293,25 @@ class LspClientTransport {
638
1293
  }
639
1294
  async sendRequest(method, ...args) {
640
1295
  if (!this.connection)
641
- throw new Error("LSP client not started");
1296
+ throw new LspClientNotStartedError(this.server.id, this.root);
642
1297
  if (this.processExited || this.proc && this.proc.exitCode !== null) {
643
1298
  const stderrTail = this.stderrBuffer.slice(-10).join(`
644
1299
  `);
645
1300
  throw new LspProcessExitedError(this.server.id, this.root, this.proc?.exitCode ?? null, stderrTail || undefined);
646
1301
  }
647
- const timeoutMs = args[1]?.timeoutMs ?? this.requestTimeoutMs;
648
- let timeoutHandle = null;
649
- const timeoutPromise = new Promise((_, reject) => {
650
- timeoutHandle = setTimeout(() => {
651
- const stderrTail = this.stderrBuffer.slice(-5).join(`
1302
+ const options = args[1];
1303
+ const timeoutMs = options?.timeoutMs ?? this.requestTimeoutMs;
1304
+ const timeoutController = new AbortController;
1305
+ const timeoutHandle = setTimeout(() => {
1306
+ const stderrTail = this.stderrBuffer.slice(-5).join(`
652
1307
  `);
653
- reject(new LspRequestTimeoutError(method, stderrTail || undefined));
654
- }, timeoutMs);
655
- });
1308
+ timeoutController.abort(new LspRequestTimeoutError(method, stderrTail || undefined));
1309
+ }, timeoutMs);
1310
+ const combinedSignal = combineAbortSignals(options?.signal, timeoutController.signal);
656
1311
  try {
657
- const requestPromise = args.length === 0 ? this.connection.sendRequest(method) : this.connection.sendRequest(method, args[0]);
658
- const result = await Promise.race([requestPromise, timeoutPromise]);
659
- if (timeoutHandle !== null)
660
- clearTimeout(timeoutHandle);
1312
+ const result = args.length === 0 ? await this.connection.sendRequest(method, undefined, { signal: combinedSignal.signal }) : await this.connection.sendRequest(method, args[0], { signal: combinedSignal.signal });
661
1313
  return result;
662
1314
  } catch (error) {
663
- if (timeoutHandle !== null)
664
- clearTimeout(timeoutHandle);
665
1315
  if (this.processExited || this.proc && this.proc.exitCode !== null) {
666
1316
  throw new LspProcessExitedError(this.server.id, this.root, this.proc?.exitCode ?? null, this.stderrBuffer.slice(-10).join(`
667
1317
  `) || undefined);
@@ -670,6 +1320,9 @@ class LspClientTransport {
670
1320
  throw new LspConnectionClosedError(this.server.id, this.root, error.message);
671
1321
  }
672
1322
  throw error;
1323
+ } finally {
1324
+ clearTimeout(timeoutHandle);
1325
+ combinedSignal.dispose();
673
1326
  }
674
1327
  }
675
1328
  async sendNotification(method, ...args) {
@@ -698,17 +1351,17 @@ class LspClientTransport {
698
1351
  try {
699
1352
  await this.sendRequest("shutdown");
700
1353
  } catch (error) {
701
- reportBestEffortCleanupError("shutdown request", error);
1354
+ reportBestEffortCleanupError("shutdown request", error instanceof Error ? error : String(error));
702
1355
  }
703
1356
  try {
704
1357
  await this.sendNotification("exit");
705
1358
  } catch (error) {
706
- reportBestEffortCleanupError("exit notification", error);
1359
+ reportBestEffortCleanupError("exit notification", error instanceof Error ? error : String(error));
707
1360
  }
708
1361
  try {
709
1362
  this.connection.dispose();
710
1363
  } catch (error) {
711
- reportBestEffortCleanupError("connection dispose", error);
1364
+ reportBestEffortCleanupError("connection dispose", error instanceof Error ? error : String(error));
712
1365
  }
713
1366
  this.connection = null;
714
1367
  }
@@ -719,8 +1372,8 @@ class LspClientTransport {
719
1372
  try {
720
1373
  proc.kill();
721
1374
  let timeoutId;
722
- const timeoutPromise = new Promise((resolve6) => {
723
- timeoutId = setTimeout(resolve6, STOP_HARD_KILL_TIMEOUT_MS);
1375
+ const timeoutPromise = new Promise((resolve22) => {
1376
+ timeoutId = setTimeout(resolve22, STOP_HARD_KILL_TIMEOUT_MS);
724
1377
  });
725
1378
  await Promise.race([
726
1379
  proc.exited.then(() => {
@@ -736,14 +1389,14 @@ class LspClientTransport {
736
1389
  proc.kill("SIGKILL");
737
1390
  await Promise.race([
738
1391
  proc.exited,
739
- new Promise((resolve6) => setTimeout(resolve6, STOP_SIGKILL_GRACE_MS))
1392
+ new Promise((resolve22) => setTimeout(resolve22, STOP_SIGKILL_GRACE_MS))
740
1393
  ]);
741
1394
  } catch (error) {
742
- reportBestEffortCleanupError("hard process kill", error);
1395
+ reportBestEffortCleanupError("hard process kill", error instanceof Error ? error : String(error));
743
1396
  }
744
1397
  }
745
1398
  } catch (error) {
746
- reportBestEffortCleanupError("process stop", error);
1399
+ reportBestEffortCleanupError("process stop", error instanceof Error ? error : String(error));
747
1400
  }
748
1401
  }
749
1402
  this.processExited = true;
@@ -753,24 +1406,43 @@ class LspClientTransport {
753
1406
  return this.diagnosticsStore.get(uri) ?? [];
754
1407
  }
755
1408
  }
756
- function createLspSpawnEnv(_root, input) {
757
- return { ...input };
758
- }
759
- function isDiagnostic(value) {
760
- return isRecord(value) && isRange(value["range"]) && typeof value["message"] === "string";
761
- }
762
- function isRange(value) {
763
- return isRecord(value) && isPosition(value["start"]) && isPosition(value["end"]);
1409
+ function combineAbortSignals(primary, secondary) {
1410
+ const controller = new AbortController;
1411
+ const abortFrom = (signal) => {
1412
+ if (!controller.signal.aborted)
1413
+ controller.abort(signal.reason);
1414
+ };
1415
+ const onPrimaryAbort = () => {
1416
+ if (primary)
1417
+ abortFrom(primary);
1418
+ };
1419
+ const onSecondaryAbort = () => abortFrom(secondary);
1420
+ if (primary?.aborted)
1421
+ abortFrom(primary);
1422
+ else
1423
+ primary?.addEventListener("abort", onPrimaryAbort, { once: true });
1424
+ if (secondary.aborted)
1425
+ abortFrom(secondary);
1426
+ else
1427
+ secondary.addEventListener("abort", onSecondaryAbort, { once: true });
1428
+ return {
1429
+ signal: controller.signal,
1430
+ dispose: () => {
1431
+ primary?.removeEventListener("abort", onPrimaryAbort);
1432
+ secondary.removeEventListener("abort", onSecondaryAbort);
1433
+ }
1434
+ };
764
1435
  }
765
- function isPosition(value) {
766
- return isRecord(value) && typeof value["line"] === "number" && typeof value["character"] === "number";
1436
+ function supportsDiagnosticPull(capabilities) {
1437
+ if (capabilities === undefined)
1438
+ return false;
1439
+ return Object.hasOwn(capabilities, "diagnosticProvider");
767
1440
  }
768
- var INITIALIZE_SETTLE_MS = 300;
769
1441
 
770
1442
  class LspClientConnection extends LspClientTransport {
771
1443
  async initialize() {
772
1444
  const rootUri = pathToFileURL(this.root).href;
773
- await this.sendRequest("initialize", {
1445
+ const result = await this.sendRequest("initialize", {
774
1446
  processId: process.pid,
775
1447
  rootUri,
776
1448
  rootPath: this.root,
@@ -784,8 +1456,7 @@ class LspClientConnection extends LspClientTransport {
784
1456
  publishDiagnostics: {},
785
1457
  rename: {
786
1458
  prepareSupport: true,
787
- prepareSupportDefaultBehavior: 1,
788
- honorsChangeAnnotations: true
1459
+ prepareSupportDefaultBehavior: 1
789
1460
  },
790
1461
  codeAction: {
791
1462
  codeActionLiteralSupport: {
@@ -814,28 +1485,22 @@ class LspClientConnection extends LspClientTransport {
814
1485
  symbol: {},
815
1486
  workspaceFolders: true,
816
1487
  configuration: true,
817
- applyEdit: true,
1488
+ ...this.hasWorkspaceApplyEditHandler() ? { applyEdit: true } : {},
818
1489
  workspaceEdit: {
819
- documentChanges: true
1490
+ documentChanges: true,
1491
+ resourceOperations: ["create", "rename", "delete"]
820
1492
  }
821
1493
  }
822
1494
  },
823
1495
  initializationOptions: this.server.initialization
824
1496
  }, { timeoutMs: this.initializeTimeoutMs });
1497
+ this.setDiagnosticPullSupported(supportsDiagnosticPull(result?.capabilities));
825
1498
  await this.sendNotification("initialized");
826
1499
  await this.sendNotification("workspace/didChangeConfiguration", {
827
1500
  settings: { json: { validate: { enable: true } } }
828
1501
  });
829
- await new Promise((r) => setTimeout(r, INITIALIZE_SETTLE_MS));
830
1502
  }
831
1503
  }
832
- var BASENAME_EXTENSIONS = {
833
- Dockerfile: ".dockerfile",
834
- Containerfile: ".dockerfile"
835
- };
836
- function effectiveExtension(filePath) {
837
- return BASENAME_EXTENSIONS[basename(filePath)] ?? extname(filePath);
838
- }
839
1504
  var SYMBOL_KIND_MAP = {
840
1505
  1: "File",
841
1506
  2: "Module",
@@ -1006,81 +1671,1384 @@ var EXT_TO_LANG = {
1006
1671
  function getLanguageId(ext) {
1007
1672
  return EXT_TO_LANG[ext] ?? "plaintext";
1008
1673
  }
1009
- var POST_OPEN_DELAY_MS = 1000;
1010
- var POST_DIAGNOSTICS_WAIT_MS = 500;
1674
+ var WATCHED_FILE_BATCH_SIZE = 128;
1675
+ var DEFAULT_VERSIONLESS_PUBLISH_QUIESCENCE_MS = 250;
1676
+ function canonicalPath(filePath) {
1677
+ const absolute = resolve2(filePath);
1678
+ try {
1679
+ return realpathSync2(absolute);
1680
+ } catch {
1681
+ return absolute;
1682
+ }
1683
+ }
1684
+ function isSameOrDescendant(candidate, parent) {
1685
+ const suffix = relative2(parent, candidate);
1686
+ return suffix === "" || !suffix.startsWith("..") && suffix !== "..";
1687
+ }
1688
+ function movedPath(candidate, oldPath, newPath) {
1689
+ const suffix = relative2(oldPath, candidate);
1690
+ return suffix === "" ? newPath : resolve2(newPath, suffix);
1691
+ }
1011
1692
 
1012
- class LspClient extends LspClientConnection {
1013
- openedFiles = new Set;
1014
- documentVersions = new Map;
1015
- lastSyncedText = new Map;
1016
- diagnosticPullErrors = [];
1017
- getDiagnosticPullErrors() {
1018
- return this.diagnosticPullErrors;
1693
+ class WorkspaceDocumentState {
1694
+ sendNotification;
1695
+ clearDiagnostics;
1696
+ openDocuments = new Map;
1697
+ openByUri = new Map;
1698
+ openPromises = new Map;
1699
+ now;
1700
+ versionlessPublishQuiescenceMs;
1701
+ constructor(sendNotification, clearDiagnostics, options = {}) {
1702
+ this.sendNotification = sendNotification;
1703
+ this.clearDiagnostics = clearDiagnostics;
1704
+ this.now = options.now ?? (() => Date.now());
1705
+ this.versionlessPublishQuiescenceMs = options.versionlessPublishQuiescenceMs ?? DEFAULT_VERSIONLESS_PUBLISH_QUIESCENCE_MS;
1019
1706
  }
1020
1707
  async openFile(filePath) {
1021
- const absPath = resolve(contextCwd(), filePath);
1022
- const uri = pathToFileURL2(absPath).href;
1023
- const text = readFileSync(absPath, "utf-8");
1024
- if (!this.openedFiles.has(absPath)) {
1025
- const ext = effectiveExtension(absPath);
1026
- const languageId = getLanguageId(ext);
1027
- const version = 1;
1028
- await this.sendNotification("textDocument/didOpen", {
1029
- textDocument: {
1030
- uri,
1031
- languageId,
1032
- version,
1033
- text
1708
+ const path2 = canonicalPath(filePath);
1709
+ const existingOpen = this.openPromises.get(path2);
1710
+ if (existingOpen) {
1711
+ await existingOpen;
1712
+ return this.openFile(path2);
1713
+ }
1714
+ const text = readFileSync2(path2, "utf-8");
1715
+ const existing = this.openDocuments.get(path2);
1716
+ if (!existing)
1717
+ return this.openDocumentSingleFlight(path2, text);
1718
+ if (existing.text === text)
1719
+ return;
1720
+ await this.changeDocument(existing, text);
1721
+ }
1722
+ getVersion(filePath) {
1723
+ return this.openDocuments.get(canonicalPath(filePath))?.version;
1724
+ }
1725
+ getStoredDiagnostics(uri) {
1726
+ const state = this.openByUri.get(uri);
1727
+ if (!state)
1728
+ return [];
1729
+ return state.lastPublish?.diagnostics ?? state.pullCache?.diagnostics ?? [];
1730
+ }
1731
+ captureDiagnosticSnapshot(filePath) {
1732
+ const state = this.openDocuments.get(canonicalPath(filePath));
1733
+ if (!state)
1734
+ return null;
1735
+ return {
1736
+ path: state.path,
1737
+ uri: state.uri,
1738
+ version: state.version,
1739
+ documentGeneration: state.generation,
1740
+ publishGeneration: state.publishGeneration
1741
+ };
1742
+ }
1743
+ isCurrentSnapshot(snapshot) {
1744
+ const state = this.openDocuments.get(snapshot.path);
1745
+ return state !== undefined && state.uri === snapshot.uri && state.version === snapshot.version && state.generation === snapshot.documentGeneration;
1746
+ }
1747
+ getPullCache(snapshot) {
1748
+ const state = this.openByUri.get(snapshot.uri);
1749
+ if (!state?.pullCache || state.pullCache.documentVersion !== snapshot.version)
1750
+ return null;
1751
+ return state.pullCache;
1752
+ }
1753
+ recordPullDiagnostics(snapshot, report) {
1754
+ const state = this.openByUri.get(snapshot.uri);
1755
+ if (!state)
1756
+ return;
1757
+ state.pullCache = {
1758
+ documentVersion: snapshot.version,
1759
+ diagnostics: [...report.diagnostics],
1760
+ ...report.resultId === undefined ? {} : { resultId: report.resultId }
1761
+ };
1762
+ }
1763
+ recordPublishedDiagnostics(params) {
1764
+ const state = this.openByUri.get(params.uri);
1765
+ if (!state)
1766
+ return;
1767
+ state.publishGeneration += 1;
1768
+ state.lastPublish = {
1769
+ diagnostics: [...params.diagnostics],
1770
+ publishGeneration: state.publishGeneration,
1771
+ documentGenerationAtArrival: state.generation,
1772
+ arrivedAt: this.now(),
1773
+ ...params.version === undefined ? {} : { version: params.version }
1774
+ };
1775
+ this.notifyWaiters(state);
1776
+ }
1777
+ resolvePushDiagnostics(snapshot) {
1778
+ const state = this.openByUri.get(snapshot.uri);
1779
+ if (!state?.lastPublish)
1780
+ return { status: "missing" };
1781
+ const publish = state.lastPublish;
1782
+ if (publish.version !== undefined) {
1783
+ return publish.version === snapshot.version ? { status: "ready", diagnostics: publish.diagnostics } : { status: "missing" };
1784
+ }
1785
+ if (publish.documentGenerationAtArrival < snapshot.documentGeneration)
1786
+ return { status: "missing" };
1787
+ const readyAt = publish.arrivedAt + this.versionlessPublishQuiescenceMs;
1788
+ const waitMs = Math.max(0, readyAt - this.now());
1789
+ return waitMs === 0 ? { status: "ready", diagnostics: publish.diagnostics } : { status: "wait", waitMs };
1790
+ }
1791
+ waitForDiagnosticsActivity(snapshot, timeoutMs) {
1792
+ const state = this.openByUri.get(snapshot.uri);
1793
+ if (!state || timeoutMs <= 0)
1794
+ return Promise.resolve();
1795
+ return new Promise((resolveActivity) => {
1796
+ let settled = false;
1797
+ const finish = () => {
1798
+ if (settled)
1799
+ return;
1800
+ settled = true;
1801
+ clearTimeout(timer);
1802
+ state.waiters.delete(finish);
1803
+ resolveActivity();
1804
+ };
1805
+ const timer = setTimeout(finish, timeoutMs);
1806
+ if (typeof timer.unref === "function")
1807
+ timer.unref();
1808
+ state.waiters.add(finish);
1809
+ });
1810
+ }
1811
+ validateVersions(operations) {
1812
+ const versions = new Map([...this.openDocuments].map(([path2, state]) => [path2, state.version]));
1813
+ for (const operation of operations) {
1814
+ if (operation.kind === "text") {
1815
+ const current = versions.get(operation.path);
1816
+ if (operation.documentVersion !== null && current !== operation.documentVersion) {
1817
+ const observed = current === undefined ? "closed document" : `open document version ${current}`;
1818
+ return {
1819
+ changeIndex: operation.changeIndex,
1820
+ message: `document version ${operation.documentVersion} does not match ${observed} for ${operation.path}`
1821
+ };
1822
+ }
1823
+ if (current !== undefined)
1824
+ versions.set(operation.path, current + 1);
1825
+ continue;
1826
+ }
1827
+ if (operation.kind === "rename") {
1828
+ const moved = [...versions].filter(([path2]) => isSameOrDescendant(path2, operation.oldPath));
1829
+ for (const [path2] of moved)
1830
+ versions.delete(path2);
1831
+ for (const [path2] of moved)
1832
+ versions.set(movedPath(path2, operation.oldPath, operation.newPath), 1);
1833
+ continue;
1834
+ }
1835
+ if (operation.kind === "delete") {
1836
+ for (const path2 of [...versions.keys()]) {
1837
+ if (isSameOrDescendant(path2, operation.path))
1838
+ versions.delete(path2);
1034
1839
  }
1840
+ continue;
1841
+ }
1842
+ if (operation.kind === "create" && operation.replaced && versions.has(operation.path)) {
1843
+ versions.set(operation.path, 1);
1844
+ }
1845
+ }
1846
+ return null;
1847
+ }
1848
+ async synchronize(delta) {
1849
+ const watched = [];
1850
+ for (const mutation of delta.operations)
1851
+ await this.synchronizeMutation(mutation, watched);
1852
+ for (let index = 0;index < watched.length; index += WATCHED_FILE_BATCH_SIZE) {
1853
+ await this.sendNotification("workspace/didChangeWatchedFiles", {
1854
+ changes: watched.slice(index, index + WATCHED_FILE_BATCH_SIZE)
1035
1855
  });
1036
- this.openedFiles.add(absPath);
1037
- this.documentVersions.set(uri, version);
1038
- this.lastSyncedText.set(uri, text);
1039
- await new Promise((r) => setTimeout(r, POST_OPEN_DELAY_MS));
1856
+ }
1857
+ }
1858
+ async synchronizeMutation(mutation, watched) {
1859
+ if (mutation.kind === "text") {
1860
+ const state = this.openDocuments.get(mutation.path);
1861
+ if (state)
1862
+ await this.changeDocument(state, mutation.afterText);
1863
+ else
1864
+ watched.push({ uri: pathToFileURL2(mutation.path).href, type: 2 });
1865
+ return;
1866
+ }
1867
+ if (mutation.kind === "create") {
1868
+ const state = this.openDocuments.get(mutation.path);
1869
+ if (state) {
1870
+ await this.closeDocument(state);
1871
+ await this.openDocumentSingleFlight(mutation.path, readFileSync2(mutation.path, "utf-8"));
1872
+ } else {
1873
+ watched.push({ uri: pathToFileURL2(mutation.path).href, type: mutation.replaced ? 2 : 1 });
1874
+ }
1040
1875
  return;
1041
1876
  }
1042
- const prevText = this.lastSyncedText.get(uri);
1043
- if (prevText === text) {
1877
+ if (mutation.kind === "rename") {
1878
+ const moved = [...this.openDocuments.values()].filter((state) => isSameOrDescendant(state.path, mutation.oldPath));
1879
+ for (const state of moved)
1880
+ await this.closeDocument(state);
1881
+ for (const state of moved) {
1882
+ const path2 = movedPath(state.path, mutation.oldPath, mutation.newPath);
1883
+ await this.openDocumentSingleFlight(path2, readFileSync2(path2, "utf-8"));
1884
+ }
1885
+ if (moved.length === 0) {
1886
+ watched.push({ uri: pathToFileURL2(mutation.oldPath).href, type: 3 });
1887
+ watched.push({ uri: pathToFileURL2(mutation.newPath).href, type: 1 });
1888
+ }
1044
1889
  return;
1045
1890
  }
1046
- const nextVersion = (this.documentVersions.get(uri) ?? 1) + 1;
1047
- this.documentVersions.set(uri, nextVersion);
1048
- this.lastSyncedText.set(uri, text);
1891
+ const removed = [...this.openDocuments.values()].filter((state) => isSameOrDescendant(state.path, mutation.path));
1892
+ for (const state of removed)
1893
+ await this.closeDocument(state);
1894
+ if (removed.length === 0)
1895
+ watched.push({ uri: pathToFileURL2(mutation.path).href, type: 3 });
1896
+ }
1897
+ async openDocumentSingleFlight(path2, text) {
1898
+ const existing = this.openPromises.get(path2);
1899
+ if (existing)
1900
+ return existing;
1901
+ const open = (async () => {
1902
+ const state = {
1903
+ path: path2,
1904
+ uri: pathToFileURL2(path2).href,
1905
+ languageId: getLanguageId(effectiveExtension(path2)),
1906
+ text,
1907
+ version: 1,
1908
+ generation: 1,
1909
+ publishGeneration: 0,
1910
+ waiters: new Set
1911
+ };
1912
+ this.openDocuments.set(path2, state);
1913
+ this.openByUri.set(state.uri, state);
1914
+ this.notifyWaiters(state);
1915
+ await this.sendNotification("textDocument/didOpen", {
1916
+ textDocument: { uri: state.uri, languageId: state.languageId, version: state.version, text }
1917
+ });
1918
+ })().finally(() => {
1919
+ this.openPromises.delete(path2);
1920
+ });
1921
+ this.openPromises.set(path2, open);
1922
+ return open;
1923
+ }
1924
+ async changeDocument(state, text) {
1925
+ state.text = text;
1926
+ state.version += 1;
1927
+ state.generation += 1;
1928
+ this.clearDiagnostics(state.uri);
1929
+ this.notifyWaiters(state);
1049
1930
  await this.sendNotification("textDocument/didChange", {
1050
- textDocument: { uri, version: nextVersion },
1931
+ textDocument: { uri: state.uri, version: state.version },
1051
1932
  contentChanges: [{ text }]
1052
1933
  });
1053
- await this.sendNotification("textDocument/didSave", {
1054
- textDocument: { uri },
1055
- text
1934
+ await this.sendNotification("textDocument/didSave", { textDocument: { uri: state.uri }, text });
1935
+ }
1936
+ async closeDocument(state) {
1937
+ this.openDocuments.delete(state.path);
1938
+ this.openByUri.delete(state.uri);
1939
+ this.clearDiagnostics(state.uri);
1940
+ this.notifyWaiters(state);
1941
+ await this.sendNotification("textDocument/didClose", { textDocument: { uri: state.uri } });
1942
+ }
1943
+ notifyWaiters(state) {
1944
+ for (const waiter of [...state.waiters])
1945
+ waiter();
1946
+ }
1947
+ }
1948
+ var CONCURRENT_FAILURE_REASON_BY_PHASE = {
1949
+ applying: "workspace/applyEdit is already in progress for this workspace mutation",
1950
+ settled: "workspace/applyEdit was already handled for this workspace mutation"
1951
+ };
1952
+ function workspaceApplyEditConcurrentFailureReason(phase) {
1953
+ return CONCURRENT_FAILURE_REASON_BY_PHASE[phase];
1954
+ }
1955
+
1956
+ class WorkspaceEditPathError extends Error {
1957
+ path;
1958
+ detail;
1959
+ name = "WorkspaceEditPathError";
1960
+ constructor(path2, detail) {
1961
+ super(`${detail}: ${path2}`);
1962
+ this.path = path2;
1963
+ this.detail = detail;
1964
+ }
1965
+ }
1966
+ function isPathInsideWorkspace(filePath, workspaceRoot) {
1967
+ const relativePath = relative3(workspaceRoot, filePath);
1968
+ return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute3(relativePath);
1969
+ }
1970
+ function canonicalizeMissingPath(filePath) {
1971
+ let ancestor = filePath;
1972
+ while (!existsSync3(ancestor)) {
1973
+ const parent = dirname4(ancestor);
1974
+ if (parent === ancestor)
1975
+ throw new WorkspaceEditPathError(filePath, "no existing ancestor");
1976
+ ancestor = parent;
1977
+ }
1978
+ return resolve3(realpathSync3(ancestor), relative3(ancestor, filePath));
1979
+ }
1980
+ function canonicalWorkspaceRoot(workspaceRoot) {
1981
+ try {
1982
+ const canonical = realpathSync3(resolve3(workspaceRoot));
1983
+ if (!lstatSync2(canonical).isDirectory()) {
1984
+ return { success: false, error: `workspace root is not a directory: ${workspaceRoot}` };
1985
+ }
1986
+ return {
1987
+ success: true,
1988
+ path: canonical,
1989
+ requestedPath: resolve3(workspaceRoot),
1990
+ followedSymbolicLink: existsSync3(resolve3(workspaceRoot)) && lstatSync2(resolve3(workspaceRoot)).isSymbolicLink()
1991
+ };
1992
+ } catch (error) {
1993
+ const detail = error instanceof Error ? error.message : String(error);
1994
+ return { success: false, error: `workspace root ${workspaceRoot}: ${detail}` };
1995
+ }
1996
+ }
1997
+ function uriToCanonicalWorkspacePath(uri, workspaceRoot) {
1998
+ let requestedPath;
1999
+ try {
2000
+ const parsed = new URL(uri);
2001
+ if (parsed.protocol !== "file:" || parsed.search !== "" || parsed.hash !== "") {
2002
+ return { success: false, error: `non-file URI ${uri}` };
2003
+ }
2004
+ requestedPath = resolve3(fileURLToPath2(parsed));
2005
+ } catch (error) {
2006
+ const detail = error instanceof Error ? error.message : String(error);
2007
+ return { success: false, error: `non-file URI ${uri}: ${detail}` };
2008
+ }
2009
+ try {
2010
+ const canonical = existsSync3(requestedPath) ? realpathSync3(requestedPath) : canonicalizeMissingPath(requestedPath);
2011
+ if (!isPathInsideWorkspace(canonical, workspaceRoot)) {
2012
+ return { success: false, error: `${requestedPath}: outside workspace ${workspaceRoot}` };
2013
+ }
2014
+ return {
2015
+ success: true,
2016
+ path: canonical,
2017
+ requestedPath,
2018
+ followedSymbolicLink: existsSync3(requestedPath) && lstatSync2(requestedPath).isSymbolicLink()
2019
+ };
2020
+ } catch (error) {
2021
+ const detail = error instanceof Error ? error.message : String(error);
2022
+ return { success: false, error: `${requestedPath}: ${detail}` };
2023
+ }
2024
+ }
2025
+ function snapshotPath(path2, includeChildren) {
2026
+ if (!existsSync3(path2))
2027
+ return { kind: "missing" };
2028
+ const stats = lstatSync2(path2);
2029
+ if (stats.isFile())
2030
+ return { kind: "file", content: readFileSync3(path2, "utf-8") };
2031
+ if (stats.isDirectory()) {
2032
+ return includeChildren ? { kind: "directory", children: readdirSync(path2).sort() } : { kind: "directory" };
2033
+ }
2034
+ throw new WorkspaceEditPathError(path2, "unsupported filesystem entry");
2035
+ }
2036
+ var DEFAULT_IO = {
2037
+ writeFile(path2, content) {
2038
+ writeFileSync(path2, content, "utf-8");
2039
+ },
2040
+ rename(oldPath, newPath) {
2041
+ renameSync(oldPath, newPath);
2042
+ },
2043
+ remove(path2, recursive) {
2044
+ rmSync(path2, { recursive, force: false });
2045
+ }
2046
+ };
2047
+ function snapshotsEqual(expected, actual) {
2048
+ if (expected.kind !== actual.kind)
2049
+ return false;
2050
+ if (expected.kind === "file" && actual.kind === "file")
2051
+ return expected.content === actual.content;
2052
+ if (expected.kind === "directory" && actual.kind === "directory" && expected.children !== undefined) {
2053
+ return JSON.stringify(expected.children) === JSON.stringify(actual.children);
2054
+ }
2055
+ return true;
2056
+ }
2057
+ function liveSnapshot(path2, expected) {
2058
+ return snapshotPath(path2, expected.kind === "directory" && expected.children !== undefined);
2059
+ }
2060
+ function firstOperationIndex(plan) {
2061
+ return plan.operations[0]?.changeIndex ?? 0;
2062
+ }
2063
+ function failedCommit(plan, failure) {
2064
+ const { message, changeIndex, mutations = [], filesModified = [], totalEdits = 0, lateAbort = false } = failure;
2065
+ return {
2066
+ result: {
2067
+ success: false,
2068
+ filesModified,
2069
+ totalEdits,
2070
+ errors: [`change ${changeIndex}: ${message}`],
2071
+ failedChange: changeIndex,
2072
+ ...lateAbort ? { lateAbort: true } : {}
2073
+ },
2074
+ delta: mutationDelta(mutations),
2075
+ fingerprint: plan.fingerprint
2076
+ };
2077
+ }
2078
+ function verifySnapshots(plan) {
2079
+ for (const [path2, expected] of plan.snapshots) {
2080
+ let actual;
2081
+ try {
2082
+ actual = liveSnapshot(path2, expected);
2083
+ } catch (error) {
2084
+ const changeIndex = plan.firstChangeByPath.get(path2) ?? firstOperationIndex(plan);
2085
+ const detail = error instanceof Error ? error.message : String(error);
2086
+ return failedCommit(plan, { message: `cannot verify snapshot for ${path2}: ${detail}`, changeIndex });
2087
+ }
2088
+ if (!snapshotsEqual(expected, actual)) {
2089
+ const changeIndex = plan.firstChangeByPath.get(path2) ?? firstOperationIndex(plan);
2090
+ return failedCommit(plan, { message: `workspace state changed before commit: ${path2}`, changeIndex });
2091
+ }
2092
+ }
2093
+ return null;
2094
+ }
2095
+ function addModifiedPath(paths, path2) {
2096
+ if (!paths.includes(path2))
2097
+ paths.push(path2);
2098
+ }
2099
+ function reportedPath(plan, path2) {
2100
+ return plan.reportedPathByCanonical.get(path2) ?? path2;
2101
+ }
2102
+ function changedPathsForMutation(mutation) {
2103
+ return mutation.kind === "rename" ? [mutation.oldPath, mutation.newPath] : [mutation.path];
2104
+ }
2105
+ function mutationDelta(operations) {
2106
+ const changedPaths = new Set;
2107
+ for (const operation of operations) {
2108
+ for (const path2 of changedPathsForMutation(operation))
2109
+ changedPaths.add(path2);
2110
+ }
2111
+ return { operations, changedPaths: [...changedPaths].sort() };
2112
+ }
2113
+ function resolveIo(overrides) {
2114
+ return {
2115
+ writeFile: overrides?.writeFile ?? DEFAULT_IO.writeFile,
2116
+ rename: overrides?.rename ?? DEFAULT_IO.rename,
2117
+ remove: overrides?.remove ?? DEFAULT_IO.remove
2118
+ };
2119
+ }
2120
+ function commitOperation(context, operation) {
2121
+ const { plan, io, accumulator } = context;
2122
+ if (operation.kind === "noop")
2123
+ return;
2124
+ if (operation.kind === "text") {
2125
+ io.writeFile(operation.path, operation.afterText);
2126
+ accumulator.mutations.push({
2127
+ kind: "text",
2128
+ path: operation.path,
2129
+ beforeText: operation.beforeText,
2130
+ afterText: operation.afterText
2131
+ });
2132
+ addModifiedPath(accumulator.filesModified, reportedPath(plan, operation.path));
2133
+ accumulator.totalEdits += operation.editCount;
2134
+ return;
2135
+ }
2136
+ if (operation.kind === "create") {
2137
+ io.writeFile(operation.path, "");
2138
+ accumulator.mutations.push({ kind: "create", path: operation.path, replaced: operation.replaced });
2139
+ addModifiedPath(accumulator.filesModified, reportedPath(plan, operation.path));
2140
+ return;
2141
+ }
2142
+ if (operation.kind === "rename") {
2143
+ if (operation.replaceDestination) {
2144
+ const targetKind = existsSync4(operation.newPath) && lstatSync3(operation.newPath).isDirectory() ? "directory" : "file";
2145
+ io.remove(operation.newPath, targetKind === "directory");
2146
+ accumulator.mutations.push({ kind: "delete", path: operation.newPath, targetKind });
2147
+ addModifiedPath(accumulator.filesModified, reportedPath(plan, operation.newPath));
2148
+ }
2149
+ io.rename(operation.oldPath, operation.newPath);
2150
+ accumulator.mutations.push({
2151
+ kind: "rename",
2152
+ oldPath: operation.oldPath,
2153
+ newPath: operation.newPath,
2154
+ sourceKind: operation.sourceKind
2155
+ });
2156
+ addModifiedPath(accumulator.filesModified, reportedPath(plan, operation.newPath));
2157
+ return;
2158
+ }
2159
+ io.remove(operation.path, operation.recursive);
2160
+ accumulator.mutations.push({
2161
+ kind: "delete",
2162
+ path: operation.path,
2163
+ targetKind: operation.targetKind
2164
+ });
2165
+ addModifiedPath(accumulator.filesModified, reportedPath(plan, operation.path));
2166
+ }
2167
+ function commitWorkspaceEditPlan(plan, options = {}) {
2168
+ if (options.signal?.aborted) {
2169
+ return failedCommit(plan, { message: "cancelled before commit", changeIndex: firstOperationIndex(plan) });
2170
+ }
2171
+ const stale = verifySnapshots(plan);
2172
+ if (stale)
2173
+ return stale;
2174
+ if (options.signal?.aborted) {
2175
+ return failedCommit(plan, { message: "cancelled before commit", changeIndex: firstOperationIndex(plan) });
2176
+ }
2177
+ const io = resolveIo(options.io);
2178
+ const accumulator = { mutations: [], filesModified: [], totalEdits: 0 };
2179
+ const context = { plan, io, accumulator };
2180
+ let lateAbort = false;
2181
+ for (const operation of plan.operations) {
2182
+ try {
2183
+ commitOperation(context, operation);
2184
+ } catch (error) {
2185
+ const detail = error instanceof Error ? error.message : String(error);
2186
+ return failedCommit(plan, {
2187
+ message: `I/O failure during ${operation.kind}: ${detail}`,
2188
+ changeIndex: operation.changeIndex,
2189
+ mutations: accumulator.mutations,
2190
+ filesModified: accumulator.filesModified,
2191
+ totalEdits: accumulator.totalEdits,
2192
+ lateAbort: lateAbort || options.signal?.aborted === true
2193
+ });
2194
+ }
2195
+ if (options.signal?.aborted)
2196
+ lateAbort = true;
2197
+ }
2198
+ const result = {
2199
+ success: true,
2200
+ filesModified: accumulator.filesModified,
2201
+ totalEdits: accumulator.totalEdits,
2202
+ errors: [],
2203
+ ...lateAbort ? { lateAbort: true } : {}
2204
+ };
2205
+ return { result, delta: mutationDelta(accumulator.mutations), fingerprint: plan.fingerprint };
2206
+ }
2207
+ function canonicalFingerprint(operations) {
2208
+ const canonical = operations.map((operation) => {
2209
+ switch (operation.kind) {
2210
+ case "text":
2211
+ return {
2212
+ kind: operation.kind,
2213
+ changeIndex: operation.changeIndex,
2214
+ path: operation.path,
2215
+ edits: operation.edits,
2216
+ version: operation.version
2217
+ };
2218
+ case "rename":
2219
+ return {
2220
+ kind: operation.kind,
2221
+ changeIndex: operation.changeIndex,
2222
+ oldPath: operation.oldPath,
2223
+ newPath: operation.newPath,
2224
+ overwrite: operation.overwrite,
2225
+ ignoreIfExists: operation.ignoreIfExists
2226
+ };
2227
+ case "create":
2228
+ return {
2229
+ kind: operation.kind,
2230
+ changeIndex: operation.changeIndex,
2231
+ path: operation.path,
2232
+ overwrite: operation.overwrite,
2233
+ ignoreIfExists: operation.ignoreIfExists
2234
+ };
2235
+ case "delete":
2236
+ return {
2237
+ kind: operation.kind,
2238
+ changeIndex: operation.changeIndex,
2239
+ path: operation.path,
2240
+ recursive: operation.recursive,
2241
+ ignoreIfNotExists: operation.ignoreIfNotExists
2242
+ };
2243
+ }
2244
+ });
2245
+ return createHash2("sha256").update(JSON.stringify(canonical)).digest("hex");
2246
+ }
2247
+
2248
+ class WorkspaceEditValidationError extends Error {
2249
+ changeIndex;
2250
+ detail;
2251
+ name = "WorkspaceEditValidationError";
2252
+ constructor(changeIndex, detail) {
2253
+ super(`change ${changeIndex}: ${detail}`);
2254
+ this.changeIndex = changeIndex;
2255
+ this.detail = detail;
2256
+ }
2257
+ }
2258
+ function isRecord3(value) {
2259
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2260
+ }
2261
+ function parsePosition(value) {
2262
+ if (!isRecord3(value) || typeof value["line"] !== "number" || typeof value["character"] !== "number") {
2263
+ return null;
2264
+ }
2265
+ return { line: value["line"], character: value["character"] };
2266
+ }
2267
+ function parseRange(value) {
2268
+ if (!isRecord3(value))
2269
+ return null;
2270
+ const start = parsePosition(value["start"]);
2271
+ const end = parsePosition(value["end"]);
2272
+ return start && end ? { start, end } : null;
2273
+ }
2274
+ function parseTextEdits(value, changeIndex) {
2275
+ if (!Array.isArray(value)) {
2276
+ throw new WorkspaceEditValidationError(changeIndex, "text edits must be an array");
2277
+ }
2278
+ const edits = [];
2279
+ for (const candidate of value) {
2280
+ if (!isRecord3(candidate) || typeof candidate["newText"] !== "string") {
2281
+ throw new WorkspaceEditValidationError(changeIndex, "text edit requires range and newText");
2282
+ }
2283
+ if ("annotationId" in candidate) {
2284
+ throw new WorkspaceEditValidationError(changeIndex, "annotated text edits are unsupported");
2285
+ }
2286
+ const range = parseRange(candidate["range"]);
2287
+ if (!range)
2288
+ throw new WorkspaceEditValidationError(changeIndex, "text edit range is malformed");
2289
+ edits.push({ range, newText: candidate["newText"] });
2290
+ }
2291
+ return edits;
2292
+ }
2293
+ function parseBooleanOption(options, key, changeIndex) {
2294
+ const value = options[key];
2295
+ if (value === undefined)
2296
+ return false;
2297
+ if (typeof value !== "boolean") {
2298
+ throw new WorkspaceEditValidationError(changeIndex, `${key} must be boolean`);
2299
+ }
2300
+ return value;
2301
+ }
2302
+ function parseOptions(value, allowed, changeIndex) {
2303
+ if (value === undefined)
2304
+ return {};
2305
+ if (!isRecord3(value))
2306
+ throw new WorkspaceEditValidationError(changeIndex, "resource options must be an object");
2307
+ for (const key of Object.keys(value)) {
2308
+ if (!allowed.includes(key))
2309
+ throw new WorkspaceEditValidationError(changeIndex, `unsupported resource option ${key}`);
2310
+ }
2311
+ const parsed = {};
2312
+ for (const key of allowed)
2313
+ parsed[key] = parseBooleanOption(value, key, changeIndex);
2314
+ return parsed;
2315
+ }
2316
+ function parseResourceChange(input) {
2317
+ const kind = input.change["kind"];
2318
+ if (kind === "create" || kind === "delete") {
2319
+ parseSinglePathResource(input, kind);
2320
+ return;
2321
+ }
2322
+ if (kind !== "rename") {
2323
+ throw new WorkspaceEditValidationError(input.changeIndex, `unsupported resource operation ${String(kind)}`);
2324
+ }
2325
+ parseRename(input);
2326
+ }
2327
+ function parseSinglePathResource(input, kind) {
2328
+ const { change, changeIndex, workspaceRoot, target } = input;
2329
+ if (typeof change["uri"] !== "string")
2330
+ throw new WorkspaceEditValidationError(changeIndex, `${kind}.uri is required`);
2331
+ const resolvedPath = uriToCanonicalWorkspacePath(change["uri"], workspaceRoot);
2332
+ if (!resolvedPath.success) {
2333
+ target.failures.push({ changeIndex, message: resolvedPath.error });
2334
+ return;
2335
+ }
2336
+ if (kind === "create") {
2337
+ const options2 = parseOptions(change["options"], ["overwrite", "ignoreIfExists"], changeIndex);
2338
+ target.operations.push({
2339
+ kind,
2340
+ changeIndex,
2341
+ path: resolvedPath.path,
2342
+ reportedPath: resolvedPath.requestedPath,
2343
+ overwrite: options2["overwrite"] ?? false,
2344
+ ignoreIfExists: options2["ignoreIfExists"] ?? false,
2345
+ followedSymbolicLink: resolvedPath.followedSymbolicLink
2346
+ });
2347
+ return;
2348
+ }
2349
+ const options = parseOptions(change["options"], ["recursive", "ignoreIfNotExists"], changeIndex);
2350
+ target.operations.push({
2351
+ kind,
2352
+ changeIndex,
2353
+ path: resolvedPath.path,
2354
+ reportedPath: resolvedPath.requestedPath,
2355
+ recursive: options["recursive"] ?? false,
2356
+ ignoreIfNotExists: options["ignoreIfNotExists"] ?? false,
2357
+ followedSymbolicLink: resolvedPath.followedSymbolicLink
2358
+ });
2359
+ }
2360
+ function parseRename(input) {
2361
+ const { change, changeIndex, workspaceRoot, target } = input;
2362
+ if (typeof change["oldUri"] !== "string" || typeof change["newUri"] !== "string") {
2363
+ throw new WorkspaceEditValidationError(changeIndex, "rename requires oldUri and newUri");
2364
+ }
2365
+ const oldPath = uriToCanonicalWorkspacePath(change["oldUri"], workspaceRoot);
2366
+ const newPath = uriToCanonicalWorkspacePath(change["newUri"], workspaceRoot);
2367
+ if (!oldPath.success || !newPath.success) {
2368
+ target.failures.push({
2369
+ changeIndex,
2370
+ message: !oldPath.success ? oldPath.error : !newPath.success ? newPath.error : "invalid rename path"
2371
+ });
2372
+ return;
2373
+ }
2374
+ const options = parseOptions(change["options"], ["overwrite", "ignoreIfExists"], changeIndex);
2375
+ target.operations.push({
2376
+ kind: "rename",
2377
+ changeIndex,
2378
+ oldPath: oldPath.path,
2379
+ newPath: newPath.path,
2380
+ reportedOldPath: oldPath.requestedPath,
2381
+ reportedNewPath: newPath.requestedPath,
2382
+ overwrite: options["overwrite"] ?? false,
2383
+ ignoreIfExists: options["ignoreIfExists"] ?? false,
2384
+ followedSymbolicLink: oldPath.followedSymbolicLink || newPath.followedSymbolicLink
2385
+ });
2386
+ }
2387
+ function failureResult(failures) {
2388
+ const sorted = [...failures].sort((left, right) => left.changeIndex - right.changeIndex);
2389
+ const first = sorted[0];
2390
+ return {
2391
+ success: false,
2392
+ filesModified: [],
2393
+ totalEdits: 0,
2394
+ errors: sorted.map((failure) => `change ${failure.changeIndex}: ${failure.message}`),
2395
+ ...first ? { failedChange: first.changeIndex } : {}
2396
+ };
2397
+ }
2398
+ function parseWorkspaceEdit(edit, workspaceRoot) {
2399
+ if (!isRecord3(edit))
2400
+ return { operations: [], failures: [{ changeIndex: 0, message: "No edit provided" }] };
2401
+ if (edit["changeAnnotations"] !== undefined) {
2402
+ return { operations: [], failures: [{ changeIndex: 0, message: "change annotations are unsupported" }] };
2403
+ }
2404
+ const hasChanges = edit["changes"] !== undefined;
2405
+ const hasDocumentChanges = edit["documentChanges"] !== undefined;
2406
+ if (hasChanges && hasDocumentChanges) {
2407
+ return {
2408
+ operations: [],
2409
+ failures: [{ changeIndex: 0, message: "changes and documentChanges cannot be combined" }]
2410
+ };
2411
+ }
2412
+ const target = { operations: [], failures: [] };
2413
+ if (hasChanges)
2414
+ return parseChanges(edit["changes"], workspaceRoot, target);
2415
+ return parseDocumentChanges(edit["documentChanges"], workspaceRoot, target);
2416
+ }
2417
+ function parseChanges(value, workspaceRoot, target) {
2418
+ if (!isRecord3(value))
2419
+ return { ...target, failures: [{ changeIndex: 0, message: "changes must be an object" }] };
2420
+ const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
2421
+ for (const [changeIndex, [uri, rawEdits]] of entries.entries()) {
2422
+ const resolvedPath = uriToCanonicalWorkspacePath(uri, workspaceRoot);
2423
+ if (!resolvedPath.success) {
2424
+ target.failures.push({ changeIndex, message: resolvedPath.error });
2425
+ continue;
2426
+ }
2427
+ try {
2428
+ target.operations.push({
2429
+ kind: "text",
2430
+ changeIndex,
2431
+ path: resolvedPath.path,
2432
+ reportedPath: resolvedPath.requestedPath,
2433
+ edits: parseTextEdits(rawEdits, changeIndex),
2434
+ version: null
2435
+ });
2436
+ } catch (error) {
2437
+ if (error instanceof WorkspaceEditValidationError) {
2438
+ target.failures.push({ changeIndex, message: error.detail });
2439
+ continue;
2440
+ }
2441
+ throw error;
2442
+ }
2443
+ }
2444
+ return target;
2445
+ }
2446
+ function parseDocumentChanges(value, workspaceRoot, target) {
2447
+ if (value === undefined)
2448
+ return target;
2449
+ if (!Array.isArray(value)) {
2450
+ return { ...target, failures: [{ changeIndex: 0, message: "documentChanges must be an array" }] };
2451
+ }
2452
+ for (const [changeIndex, change] of value.entries()) {
2453
+ try {
2454
+ parseDocumentChange({ change, changeIndex, workspaceRoot, target });
2455
+ } catch (error) {
2456
+ if (error instanceof WorkspaceEditValidationError) {
2457
+ target.failures.push({ changeIndex, message: error.detail });
2458
+ continue;
2459
+ }
2460
+ throw error;
2461
+ }
2462
+ }
2463
+ return target;
2464
+ }
2465
+ function parseDocumentChange(input) {
2466
+ const { change, changeIndex, workspaceRoot, target } = input;
2467
+ if (!isRecord3(change))
2468
+ throw new WorkspaceEditValidationError(changeIndex, "document change must be an object");
2469
+ if ("annotationId" in change) {
2470
+ throw new WorkspaceEditValidationError(changeIndex, "annotated resource operations are unsupported");
2471
+ }
2472
+ if (typeof change["kind"] === "string") {
2473
+ parseResourceChange({ change, changeIndex, workspaceRoot, target });
2474
+ return;
2475
+ }
2476
+ const identifier = change["textDocument"];
2477
+ if (!isRecord3(identifier) || typeof identifier["uri"] !== "string") {
2478
+ throw new WorkspaceEditValidationError(changeIndex, "textDocument.uri is required");
2479
+ }
2480
+ const version = identifier["version"];
2481
+ if (version !== null && (!Number.isInteger(version) || typeof version !== "number" || version < 0)) {
2482
+ throw new WorkspaceEditValidationError(changeIndex, "document version must be null or a non-negative integer");
2483
+ }
2484
+ const resolvedPath = uriToCanonicalWorkspacePath(identifier["uri"], workspaceRoot);
2485
+ if (!resolvedPath.success) {
2486
+ target.failures.push({ changeIndex, message: resolvedPath.error });
2487
+ return;
2488
+ }
2489
+ target.operations.push({
2490
+ kind: "text",
2491
+ changeIndex,
2492
+ path: resolvedPath.path,
2493
+ reportedPath: resolvedPath.requestedPath,
2494
+ edits: parseTextEdits(change["edits"], changeIndex),
2495
+ version
2496
+ });
2497
+ }
2498
+ function comparePosition(left, right) {
2499
+ return left.line === right.line ? left.character - right.character : left.line - right.line;
2500
+ }
2501
+ function positionsEqual(left, right) {
2502
+ return left.line === right.line && left.character === right.character;
2503
+ }
2504
+ function rangesEqual(left, right) {
2505
+ return positionsEqual(left.start, right.start) && positionsEqual(left.end, right.end);
2506
+ }
2507
+ function isEmptyRange(range) {
2508
+ return positionsEqual(range.start, range.end);
2509
+ }
2510
+ function formatRange(range) {
2511
+ return `${range.start.line + 1}:${range.start.character + 1}-${range.end.line + 1}:${range.end.character + 1}`;
2512
+ }
2513
+ function validatePosition(position, label, context) {
2514
+ const { lines, changeIndex } = context;
2515
+ if (!Number.isInteger(position.line) || !Number.isInteger(position.character)) {
2516
+ throw new WorkspaceEditValidationError(changeIndex, `${label} position must use integer line and character`);
2517
+ }
2518
+ if (position.line < 0 || position.character < 0) {
2519
+ throw new WorkspaceEditValidationError(changeIndex, `${label} position cannot be negative`);
2520
+ }
2521
+ const line = lines[position.line];
2522
+ if (line === undefined) {
2523
+ throw new WorkspaceEditValidationError(changeIndex, `${label} line ${position.line} is outside the document`);
2524
+ }
2525
+ if (position.character > line.length) {
2526
+ throw new WorkspaceEditValidationError(changeIndex, `${label} character ${position.character} is outside line ${position.line}`);
2527
+ }
2528
+ }
2529
+ function validateRange(range, lines, changeIndex) {
2530
+ const context = { lines, changeIndex };
2531
+ validatePosition(range.start, "start", context);
2532
+ validatePosition(range.end, "end", context);
2533
+ if (comparePosition(range.start, range.end) > 0) {
2534
+ throw new WorkspaceEditValidationError(changeIndex, `range ${formatRange(range)} ends before it starts`);
2535
+ }
2536
+ }
2537
+ function sortAndDeduplicate(edits) {
2538
+ const sorted = edits.map((edit, index) => ({ edit, index })).sort((left, right) => {
2539
+ const positionOrder = comparePosition(right.edit.range.start, left.edit.range.start);
2540
+ return positionOrder === 0 ? right.index - left.index : positionOrder;
2541
+ });
2542
+ const unique = [];
2543
+ for (const entry of sorted) {
2544
+ const previous = unique.at(-1);
2545
+ if (previous !== undefined && !isEmptyRange(entry.edit.range) && rangesEqual(previous.range, entry.edit.range) && previous.newText === entry.edit.newText) {
2546
+ continue;
2547
+ }
2548
+ unique.push(entry.edit);
2549
+ }
2550
+ return unique;
2551
+ }
2552
+ function validateNoOverlap(edits, changeIndex) {
2553
+ for (let index = 0;index < edits.length - 1; index += 1) {
2554
+ const later = edits[index];
2555
+ const earlier = edits[index + 1];
2556
+ if (later === undefined || earlier === undefined)
2557
+ continue;
2558
+ if (comparePosition(earlier.range.end, later.range.start) > 0) {
2559
+ throw new WorkspaceEditValidationError(changeIndex, `overlapping edits ${formatRange(earlier.range)} and ${formatRange(later.range)}`);
2560
+ }
2561
+ }
2562
+ }
2563
+ function applyNormalizedTextEdits(content, edits) {
2564
+ const lines = content.split(`
2565
+ `);
2566
+ for (const edit of edits) {
2567
+ const { start, end } = edit.range;
2568
+ const startLine = lines[start.line];
2569
+ const endLine = lines[end.line];
2570
+ if (startLine === undefined || endLine === undefined)
2571
+ continue;
2572
+ const replacement = startLine.slice(0, start.character) + edit.newText + endLine.slice(end.character);
2573
+ lines.splice(start.line, end.line - start.line + 1, ...replacement.split(`
2574
+ `));
2575
+ }
2576
+ return lines.join(`
2577
+ `);
2578
+ }
2579
+ function normalizeTextEdits(content, edits, changeIndex) {
2580
+ const lines = content.split(`
2581
+ `);
2582
+ for (const edit of edits) {
2583
+ validateRange(edit.range, lines, changeIndex);
2584
+ }
2585
+ const normalized = sortAndDeduplicate(edits);
2586
+ validateNoOverlap(normalized, changeIndex);
2587
+ return { edits: normalized, text: applyNormalizedTextEdits(content, normalized) };
2588
+ }
2589
+ function isSameOrDescendant2(candidate, parent) {
2590
+ const relativePath = relative4(parent, candidate);
2591
+ return relativePath === "" || !relativePath.startsWith("..") && relativePath !== "..";
2592
+ }
2593
+ function removeVirtualSubtree(virtual, path2) {
2594
+ for (const candidate of [...virtual.keys()]) {
2595
+ if (isSameOrDescendant2(candidate, path2))
2596
+ virtual.delete(candidate);
2597
+ }
2598
+ virtual.set(path2, { kind: "missing" });
2599
+ }
2600
+ function moveVirtualSubtree(virtual, oldPath, newPath) {
2601
+ const moved = [...virtual.entries()].filter(([candidate]) => isSameOrDescendant2(candidate, oldPath));
2602
+ removeVirtualSubtree(virtual, oldPath);
2603
+ removeVirtualSubtree(virtual, newPath);
2604
+ for (const [candidate, entry] of moved) {
2605
+ const suffix = relative4(oldPath, candidate);
2606
+ virtual.set(suffix === "" ? newPath : resolve4(newPath, suffix), entry);
2607
+ }
2608
+ }
2609
+ function virtualDirectoryHasChildren(virtual, path2) {
2610
+ for (const [candidate, entry] of virtual) {
2611
+ if (candidate !== path2 && entry.kind !== "missing" && isSameOrDescendant2(candidate, path2))
2612
+ return true;
2613
+ }
2614
+ return false;
2615
+ }
2616
+ function requireVirtualParent(virtual, path2, changeIndex) {
2617
+ if (virtual.get(dirname5(path2))?.kind !== "directory") {
2618
+ throw new WorkspaceEditValidationError(changeIndex, `parent directory does not exist for ${path2}`);
2619
+ }
2620
+ }
2621
+ function simulateOperations(parsed, snapshots) {
2622
+ const virtual = new Map(snapshots);
2623
+ const planned = [];
2624
+ const failures = [];
2625
+ for (const operation of parsed) {
2626
+ try {
2627
+ planned.push(simulateOperation(operation, virtual));
2628
+ } catch (error) {
2629
+ if (error instanceof WorkspaceEditValidationError) {
2630
+ failures.push({ changeIndex: operation.changeIndex, message: error.detail });
2631
+ continue;
2632
+ }
2633
+ throw error;
2634
+ }
2635
+ }
2636
+ return { operations: planned, failures };
2637
+ }
2638
+ function simulateOperation(operation, virtual) {
2639
+ switch (operation.kind) {
2640
+ case "text":
2641
+ return simulateText(operation, virtual);
2642
+ case "create":
2643
+ return simulateCreate(operation, virtual);
2644
+ case "rename":
2645
+ return simulateRename(operation, virtual);
2646
+ case "delete":
2647
+ return simulateDelete(operation, virtual);
2648
+ }
2649
+ }
2650
+ function rejectSymbolicLink(operation) {
2651
+ if (operation.followedSymbolicLink) {
2652
+ throw new WorkspaceEditValidationError(operation.changeIndex, "resource operations through symbolic links are unsupported");
2653
+ }
2654
+ }
2655
+ function simulateText(operation, virtual) {
2656
+ const entry = virtual.get(operation.path);
2657
+ if (entry?.kind !== "file")
2658
+ throw new WorkspaceEditValidationError(operation.changeIndex, `${operation.path} is not a file`);
2659
+ const normalized = normalizeTextEdits(entry.content, operation.edits, operation.changeIndex);
2660
+ virtual.set(operation.path, { kind: "file", content: normalized.text });
2661
+ return {
2662
+ kind: "text",
2663
+ changeIndex: operation.changeIndex,
2664
+ path: operation.path,
2665
+ beforeText: entry.content,
2666
+ afterText: normalized.text,
2667
+ editCount: normalized.edits.length,
2668
+ documentVersion: operation.version
2669
+ };
2670
+ }
2671
+ function simulateCreate(operation, virtual) {
2672
+ rejectSymbolicLink(operation);
2673
+ requireVirtualParent(virtual, operation.path, operation.changeIndex);
2674
+ const target = virtual.get(operation.path) ?? { kind: "missing" };
2675
+ if (target.kind !== "missing") {
2676
+ if (operation.overwrite && target.kind === "file") {
2677
+ virtual.set(operation.path, { kind: "file", content: "" });
2678
+ return { kind: "create", changeIndex: operation.changeIndex, path: operation.path, replaced: true };
2679
+ }
2680
+ if (operation.ignoreIfExists)
2681
+ return { kind: "noop", changeIndex: operation.changeIndex };
2682
+ throw new WorkspaceEditValidationError(operation.changeIndex, `create target already exists: ${operation.path}`);
2683
+ }
2684
+ virtual.set(operation.path, { kind: "file", content: "" });
2685
+ return { kind: "create", changeIndex: operation.changeIndex, path: operation.path, replaced: false };
2686
+ }
2687
+ function simulateRename(operation, virtual) {
2688
+ rejectSymbolicLink(operation);
2689
+ const source = virtual.get(operation.oldPath) ?? { kind: "missing" };
2690
+ if (source.kind === "missing") {
2691
+ throw new WorkspaceEditValidationError(operation.changeIndex, `rename source does not exist: ${operation.oldPath}`);
2692
+ }
2693
+ if (operation.oldPath === operation.newPath)
2694
+ return { kind: "noop", changeIndex: operation.changeIndex };
2695
+ if (isSameOrDescendant2(operation.newPath, operation.oldPath)) {
2696
+ throw new WorkspaceEditValidationError(operation.changeIndex, "cannot rename a path into its own subtree");
2697
+ }
2698
+ requireVirtualParent(virtual, operation.newPath, operation.changeIndex);
2699
+ const destination = virtual.get(operation.newPath) ?? { kind: "missing" };
2700
+ if (destination.kind !== "missing" && !operation.overwrite) {
2701
+ if (operation.ignoreIfExists)
2702
+ return { kind: "noop", changeIndex: operation.changeIndex };
2703
+ throw new WorkspaceEditValidationError(operation.changeIndex, `rename target already exists: ${operation.newPath}`);
2704
+ }
2705
+ moveVirtualSubtree(virtual, operation.oldPath, operation.newPath);
2706
+ return {
2707
+ kind: "rename",
2708
+ changeIndex: operation.changeIndex,
2709
+ oldPath: operation.oldPath,
2710
+ newPath: operation.newPath,
2711
+ sourceKind: source.kind,
2712
+ replaceDestination: destination.kind !== "missing"
2713
+ };
2714
+ }
2715
+ function simulateDelete(operation, virtual) {
2716
+ rejectSymbolicLink(operation);
2717
+ const target = virtual.get(operation.path) ?? { kind: "missing" };
2718
+ if (target.kind === "missing") {
2719
+ if (operation.ignoreIfNotExists)
2720
+ return { kind: "noop", changeIndex: operation.changeIndex };
2721
+ throw new WorkspaceEditValidationError(operation.changeIndex, `delete target does not exist: ${operation.path}`);
2722
+ }
2723
+ if (target.kind === "directory" && !operation.recursive && virtualDirectoryHasChildren(virtual, operation.path)) {
2724
+ throw new WorkspaceEditValidationError(operation.changeIndex, `directory is not empty: ${operation.path}`);
2725
+ }
2726
+ removeVirtualSubtree(virtual, operation.path);
2727
+ return {
2728
+ kind: "delete",
2729
+ changeIndex: operation.changeIndex,
2730
+ path: operation.path,
2731
+ targetKind: target.kind,
2732
+ recursive: operation.recursive
2733
+ };
2734
+ }
2735
+
2736
+ class WorkspaceSnapshotBuilder {
2737
+ workspaceRoot;
2738
+ snapshots = new Map;
2739
+ constructor(workspaceRoot) {
2740
+ this.workspaceRoot = workspaceRoot;
2741
+ }
2742
+ build(operations) {
2743
+ this.add(this.workspaceRoot, false);
2744
+ for (const operation of operations) {
2745
+ switch (operation.kind) {
2746
+ case "rename":
2747
+ this.add(operation.oldPath, true);
2748
+ this.add(operation.newPath, true);
2749
+ break;
2750
+ case "delete":
2751
+ this.add(operation.path, true);
2752
+ break;
2753
+ case "text":
2754
+ case "create":
2755
+ this.add(operation.path, false);
2756
+ break;
2757
+ }
2758
+ }
2759
+ return this.snapshots;
2760
+ }
2761
+ add(path2, includeChildren) {
2762
+ let candidate = path2;
2763
+ while (true) {
2764
+ const existing = this.snapshots.get(candidate);
2765
+ if (existing === undefined || includeChildren && existing.kind === "directory" && existing.children === undefined) {
2766
+ this.snapshots.set(candidate, snapshotPath(candidate, includeChildren && candidate === path2));
2767
+ }
2768
+ if (candidate === this.workspaceRoot)
2769
+ break;
2770
+ candidate = dirname6(candidate);
2771
+ }
2772
+ if (!includeChildren || !existsSync5(path2) || !lstatSync4(path2).isDirectory())
2773
+ return;
2774
+ for (const child of readdirSync2(path2))
2775
+ this.add(resolve5(path2, child), true);
2776
+ }
2777
+ }
2778
+ function snapshotOperations(operations, workspaceRoot) {
2779
+ return new WorkspaceSnapshotBuilder(workspaceRoot).build(operations);
2780
+ }
2781
+
2782
+ class PlanPathIndex {
2783
+ firstChangeByPath = new Map;
2784
+ reportedPathByCanonical = new Map;
2785
+ build(operations) {
2786
+ for (const operation of operations) {
2787
+ switch (operation.kind) {
2788
+ case "rename":
2789
+ this.add(operation.oldPath, operation.reportedOldPath, operation.changeIndex);
2790
+ this.add(operation.newPath, operation.reportedNewPath, operation.changeIndex);
2791
+ break;
2792
+ case "text":
2793
+ case "create":
2794
+ case "delete":
2795
+ this.add(operation.path, operation.reportedPath, operation.changeIndex);
2796
+ break;
2797
+ }
2798
+ }
2799
+ }
2800
+ add(path2, reportedPath2, changeIndex) {
2801
+ if (!this.firstChangeByPath.has(path2))
2802
+ this.firstChangeByPath.set(path2, changeIndex);
2803
+ if (!this.reportedPathByCanonical.has(path2))
2804
+ this.reportedPathByCanonical.set(path2, reportedPath2);
2805
+ }
2806
+ }
2807
+ function fingerprintWorkspaceEdit(edit, workspaceRoot) {
2808
+ const root = canonicalWorkspaceRoot(workspaceRoot);
2809
+ if (!root.success)
2810
+ return { success: false, result: failureResult([{ changeIndex: 0, message: root.error }]) };
2811
+ const parsed = parseWorkspaceEdit(edit, root.path);
2812
+ if (parsed.failures.length > 0)
2813
+ return { success: false, result: failureResult(parsed.failures) };
2814
+ return { success: true, fingerprint: canonicalFingerprint(parsed.operations) };
2815
+ }
2816
+ function planWorkspaceEdit(edit, workspaceRoot) {
2817
+ const root = canonicalWorkspaceRoot(workspaceRoot);
2818
+ if (!root.success)
2819
+ return { success: false, result: failureResult([{ changeIndex: 0, message: root.error }]) };
2820
+ const parsed = parseWorkspaceEdit(edit, root.path);
2821
+ if (parsed.failures.length > 0)
2822
+ return { success: false, result: failureResult(parsed.failures) };
2823
+ let snapshots;
2824
+ try {
2825
+ snapshots = snapshotOperations(parsed.operations, root.path);
2826
+ } catch (error) {
2827
+ return {
2828
+ success: false,
2829
+ result: failureResult([{ changeIndex: 0, message: error instanceof Error ? error.message : String(error) }])
2830
+ };
2831
+ }
2832
+ const simulated = simulateOperations(parsed.operations, snapshots);
2833
+ if (simulated.failures.length > 0)
2834
+ return { success: false, result: failureResult(simulated.failures) };
2835
+ const paths = new PlanPathIndex;
2836
+ paths.build(parsed.operations);
2837
+ const plan = {
2838
+ workspaceRoot: root.path,
2839
+ operations: simulated.operations,
2840
+ snapshots,
2841
+ firstChangeByPath: paths.firstChangeByPath,
2842
+ reportedPathByCanonical: paths.reportedPathByCanonical,
2843
+ fingerprint: canonicalFingerprint(parsed.operations)
2844
+ };
2845
+ return { success: true, plan };
2846
+ }
2847
+ function failure(message, failedChange, base) {
2848
+ return {
2849
+ success: false,
2850
+ filesModified: base?.filesModified ?? [],
2851
+ totalEdits: base?.totalEdits ?? 0,
2852
+ errors: [message],
2853
+ ...failedChange === undefined ? {} : { failedChange },
2854
+ ...base?.lateAbort ? { lateAbort: true } : {}
2855
+ };
2856
+ }
2857
+ function responseFor(result) {
2858
+ if (result.success)
2859
+ return { applied: true };
2860
+ return {
2861
+ applied: false,
2862
+ failureReason: result.errors[0] ?? "workspace edit failed",
2863
+ ...result.failedChange === undefined ? {} : { failedChange: result.failedChange }
2864
+ };
2865
+ }
2866
+ function isRecord4(value) {
2867
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2868
+ }
2869
+
2870
+ class WorkspaceMutationController {
2871
+ workspaceRoot;
2872
+ documents;
2873
+ activeLease = null;
2874
+ nextLeaseId = 1;
2875
+ io;
2876
+ constructor(workspaceRoot, documents) {
2877
+ this.workspaceRoot = workspaceRoot;
2878
+ this.documents = documents;
2879
+ }
2880
+ setIo(io) {
2881
+ this.io = io;
2882
+ }
2883
+ acquire(signal) {
2884
+ if (this.activeLease)
2885
+ return { success: false, result: failure("workspace mutation is already in progress") };
2886
+ if (signal?.aborted)
2887
+ return { success: false, result: failure("cancelled before mutating request") };
2888
+ const lease = {
2889
+ id: this.nextLeaseId,
2890
+ phase: "idle",
2891
+ ...signal === undefined ? {} : { signal }
2892
+ };
2893
+ this.nextLeaseId += 1;
2894
+ this.activeLease = lease;
2895
+ return { success: true, lease };
2896
+ }
2897
+ release(lease) {
2898
+ if (this.activeLease?.id !== lease.id)
2899
+ return;
2900
+ this.activeLease.phase = "sealed";
2901
+ this.activeLease = null;
2902
+ }
2903
+ isBeforeCommit(lease) {
2904
+ return this.activeLease?.id === lease.id && this.activeLease.phase === "idle";
2905
+ }
2906
+ async handleApplyEdit(params) {
2907
+ const lease = this.activeLease;
2908
+ if (!lease)
2909
+ return { applied: false, failureReason: "workspace/applyEdit requires an active workspace mutation" };
2910
+ if (lease.phase !== "idle") {
2911
+ return {
2912
+ applied: false,
2913
+ failureReason: workspaceApplyEditConcurrentFailureReason(lease.phase === "applying" ? "applying" : "settled")
2914
+ };
2915
+ }
2916
+ lease.phase = "applying";
2917
+ lease.applyCompletion = new Promise((resolve62) => {
2918
+ lease.resolveApply = resolve62;
2919
+ });
2920
+ const edit = isRecord4(params) ? params["edit"] : undefined;
2921
+ const record2 = edit === undefined ? { fingerprint: null, result: failure("workspace/applyEdit params.edit is required", 0) } : await this.applyEdit(edit, lease);
2922
+ lease.serverApply = record2;
2923
+ lease.phase = "settled";
2924
+ lease.resolveApply?.();
2925
+ return responseFor(record2.result);
2926
+ }
2927
+ async reconcileRename(leaseToken, edit) {
2928
+ const lease = this.requireActiveLease(leaseToken);
2929
+ if (!lease)
2930
+ return { edit, apply: failure("workspace mutation lease ended before rename reconciliation") };
2931
+ if (lease.phase === "applying")
2932
+ await lease.applyCompletion;
2933
+ if (lease.serverApply)
2934
+ return this.reconcileServerApply(lease.serverApply, edit);
2935
+ lease.phase = "sealed";
2936
+ if (!edit)
2937
+ return { edit, apply: failure("No edit provided") };
2938
+ const applied = await this.applyEdit(edit, lease);
2939
+ return { edit, apply: applied.result };
2940
+ }
2941
+ reconcileServerApply(record2, edit) {
2942
+ if (!edit)
2943
+ return { edit, apply: record2.result };
2944
+ const fingerprint = fingerprintWorkspaceEdit(edit, this.workspaceRoot);
2945
+ if (fingerprint.success && record2.fingerprint !== null && fingerprint.fingerprint === record2.fingerprint) {
2946
+ return { edit, apply: record2.result };
2947
+ }
2948
+ return {
2949
+ edit,
2950
+ apply: failure("rename result conflicts with server-applied workspace edit", 0, record2.result)
2951
+ };
2952
+ }
2953
+ async applyEdit(edit, lease) {
2954
+ const planned = planWorkspaceEdit(edit, this.workspaceRoot);
2955
+ if (!planned.success)
2956
+ return { fingerprint: null, result: planned.result };
2957
+ const versionFailure = this.documents.validateVersions(planned.plan.operations);
2958
+ if (versionFailure) {
2959
+ return {
2960
+ fingerprint: planned.plan.fingerprint,
2961
+ result: failure(versionFailure.message, versionFailure.changeIndex)
2962
+ };
2963
+ }
2964
+ const commit = commitWorkspaceEditPlan(planned.plan, {
2965
+ ...lease.signal === undefined ? {} : { signal: lease.signal },
2966
+ ...this.io === undefined ? {} : { io: this.io }
2967
+ });
2968
+ let result = commit.result;
2969
+ if (commit.delta.operations.length > 0) {
2970
+ try {
2971
+ await this.documents.synchronize(commit.delta);
2972
+ } catch (error) {
2973
+ const message = error instanceof Error ? error.message : String(error);
2974
+ result = failure(`document synchronization failed after filesystem commit: ${message}`, undefined, result);
2975
+ }
2976
+ }
2977
+ if (lease.signal?.aborted && !result.lateAbort)
2978
+ result = { ...result, lateAbort: true };
2979
+ return { fingerprint: planned.plan.fingerprint, result };
2980
+ }
2981
+ requireActiveLease(lease) {
2982
+ return this.activeLease?.id === lease.id ? this.activeLease : null;
2983
+ }
2984
+ }
2985
+ var DIAGNOSTICS_FRESHNESS_TIMEOUT_MS = 3000;
2986
+ var VERSIONLESS_PUBLISH_QUIESCENCE_MS = 250;
2987
+
2988
+ class LspClient extends LspClientConnection {
2989
+ diagnosticPullErrors = [];
2990
+ documents;
2991
+ workspaceMutations;
2992
+ diagnosticsFreshnessTimeoutMs;
2993
+ constructor(root, server2, options = {}) {
2994
+ super(root, server2, options);
2995
+ this.diagnosticsFreshnessTimeoutMs = options.diagnosticsFreshnessTimeoutMs ?? DIAGNOSTICS_FRESHNESS_TIMEOUT_MS;
2996
+ this.documents = new WorkspaceDocumentState((method, params) => this.sendNotification(method, params), (uri) => this.diagnosticsStore.delete(uri), {
2997
+ versionlessPublishQuiescenceMs: options.versionlessPublishQuiescenceMs ?? VERSIONLESS_PUBLISH_QUIESCENCE_MS
1056
2998
  });
2999
+ this.workspaceMutations = new WorkspaceMutationController(root, this.documents);
3000
+ this.setWorkspaceApplyEditHandler((params) => this.workspaceMutations.handleApplyEdit(params));
1057
3001
  }
1058
- async definition(filePath, line, character) {
1059
- const absPath = resolve(contextCwd(), filePath);
3002
+ getDiagnosticPullErrors() {
3003
+ return this.diagnosticPullErrors;
3004
+ }
3005
+ async openFile(filePath) {
3006
+ const absPath = this.resolveWorkspacePath(filePath);
3007
+ await this.documents.openFile(absPath);
3008
+ }
3009
+ getOpenDocumentVersion(filePath) {
3010
+ return this.documents.getVersion(this.resolveWorkspacePath(filePath));
3011
+ }
3012
+ getStoredDiagnostics(uri) {
3013
+ return [...this.documents.getStoredDiagnostics(uri)];
3014
+ }
3015
+ setWorkspaceEditIo(io) {
3016
+ this.workspaceMutations.setIo(io);
3017
+ }
3018
+ handlePublishDiagnostics(params) {
3019
+ super.handlePublishDiagnostics(params);
3020
+ this.documents.recordPublishedDiagnostics(params);
3021
+ }
3022
+ async definition(filePath, line, character, signal) {
3023
+ const absPath = this.resolveWorkspacePath(filePath);
1060
3024
  await this.openFile(absPath);
3025
+ const options = signal === undefined ? {} : { signal };
1061
3026
  return this.sendRequest("textDocument/definition", {
1062
- textDocument: { uri: pathToFileURL2(absPath).href },
3027
+ textDocument: { uri: pathToFileURL3(absPath).href },
1063
3028
  position: { line: line - 1, character }
1064
- });
3029
+ }, options);
1065
3030
  }
1066
- async references(filePath, line, character, includeDeclaration = true) {
1067
- const absPath = resolve(contextCwd(), filePath);
3031
+ async references(filePath, line, character, includeDeclaration = true, signal) {
3032
+ const absPath = this.resolveWorkspacePath(filePath);
1068
3033
  await this.openFile(absPath);
3034
+ const options = signal === undefined ? {} : { signal };
1069
3035
  return this.sendRequest("textDocument/references", {
1070
- textDocument: { uri: pathToFileURL2(absPath).href },
3036
+ textDocument: { uri: pathToFileURL3(absPath).href },
1071
3037
  position: { line: line - 1, character },
1072
3038
  context: { includeDeclaration }
1073
- });
3039
+ }, options);
1074
3040
  }
1075
- async documentSymbols(filePath) {
1076
- const absPath = resolve(contextCwd(), filePath);
3041
+ async documentSymbols(filePath, signal) {
3042
+ const absPath = this.resolveWorkspacePath(filePath);
1077
3043
  await this.openFile(absPath);
3044
+ const options = signal === undefined ? {} : { signal };
1078
3045
  return this.sendRequest("textDocument/documentSymbol", {
1079
- textDocument: { uri: pathToFileURL2(absPath).href }
1080
- });
3046
+ textDocument: { uri: pathToFileURL3(absPath).href }
3047
+ }, options);
1081
3048
  }
1082
- async workspaceSymbols(query) {
1083
- return this.sendRequest("workspace/symbol", { query });
3049
+ async workspaceSymbols(query, signal) {
3050
+ const options = signal === undefined ? {} : { signal };
3051
+ return this.sendRequest("workspace/symbol", { query }, options);
1084
3052
  }
1085
3053
  isUnsupportedDiagnosticPullError(error) {
1086
3054
  if (!(error instanceof Error))
@@ -1090,42 +3058,173 @@ class LspClient extends LspClientConnection {
1090
3058
  return true;
1091
3059
  return /unsupported|not supported|method not found|unknown request/i.test(error.message);
1092
3060
  }
1093
- async diagnostics(filePath) {
1094
- const absPath = resolve(contextCwd(), filePath);
1095
- const uri = pathToFileURL2(absPath).href;
1096
- await this.openFile(absPath);
1097
- await new Promise((r) => setTimeout(r, POST_DIAGNOSTICS_WAIT_MS));
1098
- try {
1099
- const result = await this.sendRequest("textDocument/diagnostic", {
1100
- textDocument: { uri }
1101
- });
1102
- if (result.items) {
1103
- return { items: result.items };
3061
+ freshnessTimeout(absPath) {
3062
+ return {
3063
+ items: [],
3064
+ transientError: {
3065
+ kind: "freshness_timeout",
3066
+ message: `Timed out waiting for fresh diagnostics for ${absPath} within ${this.diagnosticsFreshnessTimeoutMs}ms.`
1104
3067
  }
1105
- } catch (error) {
1106
- if (!this.isUnsupportedDiagnosticPullError(error)) {
1107
- this.diagnosticPullErrors.push(error instanceof Error ? error : new Error(String(error)));
3068
+ };
3069
+ }
3070
+ parseDiagnosticPullReport(value) {
3071
+ if (value.kind === "unchanged") {
3072
+ return {
3073
+ type: "unchanged",
3074
+ ...value.resultId === undefined ? {} : { resultId: value.resultId }
3075
+ };
3076
+ }
3077
+ return {
3078
+ type: "full",
3079
+ diagnostics: value.items ?? [],
3080
+ ...value.resultId === undefined ? {} : { resultId: value.resultId }
3081
+ };
3082
+ }
3083
+ async diagnostics(filePath, signal) {
3084
+ signal?.throwIfAborted();
3085
+ const absPath = this.resolveWorkspacePath(filePath);
3086
+ const uri = pathToFileURL3(absPath).href;
3087
+ await this.openFile(absPath);
3088
+ const deadlineAt = Date.now() + this.diagnosticsFreshnessTimeoutMs;
3089
+ for (;; ) {
3090
+ signal?.throwIfAborted();
3091
+ const snapshot = this.documents.captureDiagnosticSnapshot(absPath);
3092
+ if (!snapshot)
3093
+ return this.freshnessTimeout(absPath);
3094
+ const push = this.documents.resolvePushDiagnostics(snapshot);
3095
+ if (push.status === "ready")
3096
+ return { items: [...push.diagnostics] };
3097
+ let pushFallbackOnly = !this.isDiagnosticPullSupported();
3098
+ if (!pushFallbackOnly) {
3099
+ const cached = this.documents.getPullCache(snapshot);
3100
+ try {
3101
+ const remainingMs2 = deadlineAt - Date.now();
3102
+ if (remainingMs2 <= 0)
3103
+ return this.freshnessTimeout(absPath);
3104
+ const result = await this.sendRequest("textDocument/diagnostic", {
3105
+ textDocument: { uri },
3106
+ ...cached?.resultId === undefined ? {} : { previousResultId: cached.resultId }
3107
+ }, { timeoutMs: remainingMs2, ...signal === undefined ? {} : { signal } });
3108
+ if (!this.documents.isCurrentSnapshot(snapshot))
3109
+ continue;
3110
+ const report = this.parseDiagnosticPullReport(result);
3111
+ if (report.type === "full") {
3112
+ this.documents.recordPullDiagnostics(snapshot, {
3113
+ kind: "full",
3114
+ diagnostics: report.diagnostics,
3115
+ ...report.resultId === undefined ? {} : { resultId: report.resultId }
3116
+ });
3117
+ return { items: [...report.diagnostics] };
3118
+ }
3119
+ if (cached !== null && cached.documentVersion === snapshot.version && cached.resultId === report.resultId) {
3120
+ return { items: [...cached.diagnostics] };
3121
+ }
3122
+ } catch (error) {
3123
+ if (this.isUnsupportedDiagnosticPullError(error)) {
3124
+ this.setDiagnosticPullSupported(false);
3125
+ pushFallbackOnly = true;
3126
+ } else if (error instanceof LspRequestTimeoutError) {
3127
+ pushFallbackOnly = true;
3128
+ } else {
3129
+ this.diagnosticPullErrors.push(error instanceof Error ? error : new Error(String(error)));
3130
+ throw error;
3131
+ }
3132
+ }
1108
3133
  }
3134
+ if (!pushFallbackOnly)
3135
+ continue;
3136
+ const remainingMs = deadlineAt - Date.now();
3137
+ if (remainingMs <= 0)
3138
+ return this.freshnessTimeout(absPath);
3139
+ const waitMs = push.status === "wait" ? Math.min(push.waitMs, remainingMs) : remainingMs;
3140
+ await waitForDiagnosticsActivity(this.documents.waitForDiagnosticsActivity(snapshot, waitMs), signal);
1109
3141
  }
1110
- return { items: this.getStoredDiagnostics(uri) };
1111
3142
  }
1112
- async prepareRename(filePath, line, character) {
1113
- const absPath = resolve(contextCwd(), filePath);
3143
+ async prepareRename(filePath, line, character, signal) {
3144
+ const absPath = this.resolveWorkspacePath(filePath);
1114
3145
  await this.openFile(absPath);
3146
+ const options = signal === undefined ? {} : { signal };
1115
3147
  return this.sendRequest("textDocument/prepareRename", {
1116
- textDocument: { uri: pathToFileURL2(absPath).href },
3148
+ textDocument: { uri: pathToFileURL3(absPath).href },
1117
3149
  position: { line: line - 1, character }
1118
- });
3150
+ }, options);
3151
+ }
3152
+ async rename(filePath, line, character, newName, signal) {
3153
+ const absPath = this.resolveWorkspacePath(filePath);
3154
+ await this.openFile(absPath);
3155
+ const acquired = this.workspaceMutations.acquire(signal);
3156
+ if (!acquired.success)
3157
+ return { edit: null, apply: acquired.result };
3158
+ const preCommitSignal = createPreCommitAbortSignal(signal, () => this.workspaceMutations.isBeforeCommit(acquired.lease));
3159
+ try {
3160
+ const renameParams = {
3161
+ textDocument: { uri: pathToFileURL3(absPath).href },
3162
+ position: { line: line - 1, character },
3163
+ newName
3164
+ };
3165
+ const edit = preCommitSignal === undefined ? await this.sendRequest("textDocument/rename", renameParams) : await this.sendRequest("textDocument/rename", renameParams, {
3166
+ signal: preCommitSignal.signal
3167
+ });
3168
+ return await this.workspaceMutations.reconcileRename(acquired.lease, edit);
3169
+ } finally {
3170
+ preCommitSignal?.dispose();
3171
+ this.workspaceMutations.release(acquired.lease);
3172
+ }
3173
+ }
3174
+ resolveWorkspacePath(filePath) {
3175
+ return resolve6(this.root, filePath);
1119
3176
  }
1120
- async rename(filePath, line, character, newName) {
1121
- const absPath = resolve(contextCwd(), filePath);
1122
- await this.openFile(absPath);
1123
- return this.sendRequest("textDocument/rename", {
1124
- textDocument: { uri: pathToFileURL2(absPath).href },
1125
- position: { line: line - 1, character },
1126
- newName
3177
+ }
3178
+ function waitForDiagnosticsActivity(wait, signal) {
3179
+ if (!signal)
3180
+ return wait;
3181
+ if (signal.aborted)
3182
+ return Promise.reject(abortError3(signal));
3183
+ return new Promise((resolve72, reject) => {
3184
+ const onAbort = () => {
3185
+ signal.removeEventListener("abort", onAbort);
3186
+ reject(abortError3(signal));
3187
+ };
3188
+ signal.addEventListener("abort", onAbort, { once: true });
3189
+ wait.then(() => {
3190
+ signal.removeEventListener("abort", onAbort);
3191
+ resolve72();
3192
+ }, (error) => {
3193
+ signal.removeEventListener("abort", onAbort);
3194
+ reject(error);
1127
3195
  });
1128
- }
3196
+ });
3197
+ }
3198
+ function createPreCommitAbortSignal(source, isBeforeCommit) {
3199
+ if (!source)
3200
+ return;
3201
+ const controller = new AbortController;
3202
+ const onAbort = () => {
3203
+ if (isBeforeCommit() && !controller.signal.aborted)
3204
+ controller.abort(preCommitAbortReason(source));
3205
+ };
3206
+ if (source.aborted)
3207
+ onAbort();
3208
+ else
3209
+ source.addEventListener("abort", onAbort, { once: true });
3210
+ return {
3211
+ signal: controller.signal,
3212
+ dispose: () => source.removeEventListener("abort", onAbort)
3213
+ };
3214
+ }
3215
+ function preCommitAbortReason(source) {
3216
+ const reason = source.reason;
3217
+ if (reason instanceof Error && reason.name !== "AbortError")
3218
+ return reason;
3219
+ return new Error("LSP request cancelled before workspace edit commit");
3220
+ }
3221
+ function abortError3(signal) {
3222
+ const reason = signal.reason;
3223
+ if (reason instanceof Error)
3224
+ return reason;
3225
+ const error = new Error(typeof reason === "string" ? reason : "operation cancelled");
3226
+ error.name = "AbortError";
3227
+ return error;
1129
3228
  }
1130
3229
  function installProcessSignalCleanup(cleanup) {
1131
3230
  const signals = process.platform === "win32" ? ["SIGINT", "SIGTERM", "SIGBREAK"] : ["SIGINT", "SIGTERM"];
@@ -1150,10 +3249,10 @@ async function stopClientBestEffort(client) {
1150
3249
  reportBestEffortCleanupError("client stop", error);
1151
3250
  }
1152
3251
  }
1153
- function awaitWithSignal(promise, signal) {
3252
+ function awaitWithSignal2(promise, signal) {
1154
3253
  if (!signal)
1155
3254
  return promise;
1156
- return new Promise((resolve22, reject) => {
3255
+ return new Promise((resolve72, reject) => {
1157
3256
  let settled = false;
1158
3257
  const onAbort = () => {
1159
3258
  if (settled)
@@ -1171,7 +3270,7 @@ function awaitWithSignal(promise, signal) {
1171
3270
  return;
1172
3271
  settled = true;
1173
3272
  signal.removeEventListener("abort", onAbort);
1174
- resolve22(value);
3273
+ resolve72(value);
1175
3274
  }, (err) => {
1176
3275
  if (settled)
1177
3276
  return;
@@ -1196,7 +3295,7 @@ class LspManager {
1196
3295
  this.idleTimeoutMs = options.idleTimeoutMs ?? IDLE_TIMEOUT_MS;
1197
3296
  this.initTimeoutMs = options.initTimeoutMs ?? INIT_TIMEOUT_MS;
1198
3297
  this.reaperIntervalMs = options.reaperIntervalMs ?? REAPER_INTERVAL_MS;
1199
- this.clientFactory = options.clientFactory ?? ((root, server) => new LspClient(root, server));
3298
+ this.clientFactory = options.clientFactory ?? ((root, server2) => new LspClient(root, server2));
1200
3299
  this.now = options.now ?? (() => Date.now());
1201
3300
  this.startReaper();
1202
3301
  this.signalDisposer = installProcessSignalCleanup(() => this.stopAll());
@@ -1234,12 +3333,12 @@ class LspManager {
1234
3333
  await stopClientBestEffort(managed.client);
1235
3334
  }
1236
3335
  }
1237
- async getClient(root, server, signal) {
3336
+ async getClient(root, server2, signal) {
1238
3337
  if (this.disposed) {
1239
3338
  throw new Error("LspManager has been disposed");
1240
3339
  }
1241
3340
  signal?.throwIfAborted();
1242
- const key = this.getKey(root, server.id);
3341
+ const key = this.getKey(root, server2.id);
1243
3342
  let managed = this.clients.get(key);
1244
3343
  if (managed) {
1245
3344
  const t = this.now();
@@ -1253,7 +3352,7 @@ class LspManager {
1253
3352
  if (managed.initPromise) {
1254
3353
  managed.pendingWaiters++;
1255
3354
  try {
1256
- await awaitWithSignal(managed.initPromise, signal);
3355
+ await awaitWithSignal2(managed.initPromise, signal);
1257
3356
  } catch (err) {
1258
3357
  managed.pendingWaiters--;
1259
3358
  await this.tryDeleteIfOrphaned(key, managed);
@@ -1268,13 +3367,13 @@ class LspManager {
1268
3367
  if (!managed.client.isAlive()) {
1269
3368
  await stopClientBestEffort(managed.client);
1270
3369
  this.clients.delete(key);
1271
- return this.getClient(root, server, signal);
3370
+ return this.getClient(root, server2, signal);
1272
3371
  }
1273
3372
  managed.refCount++;
1274
3373
  managed.lastUsedAt = this.now();
1275
3374
  return managed.client;
1276
3375
  }
1277
- const client = this.clientFactory(root, server);
3376
+ const client = this.clientFactory(root, server2);
1278
3377
  const initStartedAt = this.now();
1279
3378
  const initPromise = (async () => {
1280
3379
  await client.start();
@@ -1291,7 +3390,7 @@ class LspManager {
1291
3390
  };
1292
3391
  this.clients.set(key, newManaged);
1293
3392
  try {
1294
- await awaitWithSignal(initPromise, signal);
3393
+ await awaitWithSignal2(initPromise, signal);
1295
3394
  } catch (err) {
1296
3395
  newManaged.pendingWaiters--;
1297
3396
  if (this.clients.get(key) === newManaged) {
@@ -1330,13 +3429,13 @@ class LspManager {
1330
3429
  this.clients.delete(key);
1331
3430
  stopClientBestEffort(managed.client);
1332
3431
  }
1333
- warmupClient(root, server) {
3432
+ warmupClient(root, server2) {
1334
3433
  if (this.disposed)
1335
3434
  return;
1336
- const key = this.getKey(root, server.id);
3435
+ const key = this.getKey(root, server2.id);
1337
3436
  if (this.clients.has(key))
1338
3437
  return;
1339
- const client = this.clientFactory(root, server);
3438
+ const client = this.clientFactory(root, server2);
1340
3439
  const initStartedAt = this.now();
1341
3440
  const initPromise = (async () => {
1342
3441
  await client.start();
@@ -1416,230 +3515,15 @@ function getLspManager() {
1416
3515
  }
1417
3516
  return _defaultInstance;
1418
3517
  }
1419
- async function disposeDefaultLspManager() {
1420
- if (_defaultInstance) {
1421
- const m = _defaultInstance;
1422
- _defaultInstance = null;
1423
- await m.stopAll();
1424
- }
1425
- }
1426
- function isPlainRecord(value) {
1427
- return typeof value === "object" && value !== null && !Array.isArray(value);
1428
- }
1429
- function isProcessAlive(pid) {
1430
- if (!Number.isInteger(pid) || pid <= 0)
1431
- return false;
1432
- try {
1433
- process.kill(pid, 0);
1434
- return true;
1435
- } catch (error) {
1436
- return error.code === "EPERM";
1437
- }
1438
- }
1439
- function readLockPid(lockPath) {
1440
- try {
1441
- const pid = Number.parseInt(readFileSync2(lockPath, "utf8").trim(), 10);
1442
- return Number.isInteger(pid) ? pid : null;
1443
- } catch {
1444
- return null;
1445
- }
1446
- }
1447
- function tryAcquireLock(lockPath, ownerPid = process.pid) {
1448
- mkdirSync(dirname(lockPath), { recursive: true });
1449
- for (let attempt = 0;attempt < 2; attempt += 1) {
1450
- const handle = writeLockFile(lockPath, ownerPid);
1451
- if (handle)
1452
- return handle;
1453
- if (!reapStaleLock(lockPath))
1454
- return null;
1455
- }
1456
- return null;
1457
- }
1458
- function writeLockFile(lockPath, ownerPid) {
1459
- try {
1460
- const fd = openSync(lockPath, "wx");
1461
- writeSync(fd, `${ownerPid}
1462
- `);
1463
- closeSync(fd);
1464
- return { release: () => unlinkQuietly(lockPath) };
1465
- } catch (error) {
1466
- if (error.code === "EEXIST")
1467
- return null;
1468
- throw error;
1469
- }
1470
- }
1471
- function reapStaleLock(lockPath) {
1472
- const pid = readLockPid(lockPath);
1473
- if (pid !== null && isProcessAlive(pid))
1474
- return false;
1475
- unlinkQuietly(lockPath);
1476
- return true;
1477
- }
1478
- function unlinkQuietly(path) {
1479
- try {
1480
- unlinkSync(path);
1481
- } catch (error) {}
1482
- }
1483
- var PROBE_TIMEOUT_MS = 500;
1484
- var DEFAULT_READY_TIMEOUT_MS = 5000;
1485
- var DEFAULT_POLL_INTERVAL_MS = 100;
1486
- var CODEX_LSP_DAEMON_CLI_ENV = "CODEX_LSP_DAEMON_CLI";
1487
-
1488
- class DaemonUnreachableError extends Error {
1489
- constructor(socketPath) {
1490
- super(`LSP daemon did not become reachable at ${socketPath}`);
1491
- this.name = "DaemonUnreachableError";
1492
- }
1493
- }
1494
- async function ensureDaemonRunning(paths, deps = defaultEnsureDaemonDeps(), options = {}) {
1495
- const readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
1496
- const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
1497
- if (await deps.probe(paths.socket))
1498
- return;
1499
- const lock = deps.acquireLock(paths.lock);
1500
- if (!lock) {
1501
- await waitUntilReachable(paths.socket, deps, readyTimeoutMs, pollIntervalMs);
1502
- return;
1503
- }
1504
- try {
1505
- if (await deps.probe(paths.socket))
1506
- return;
1507
- deps.cleanupStaleSocket(paths.socket);
1508
- deps.spawnDaemon(paths);
1509
- await waitUntilReachable(paths.socket, deps, readyTimeoutMs, pollIntervalMs);
1510
- } finally {
1511
- lock.release();
1512
- }
1513
- }
1514
- async function waitUntilReachable(socketPath, deps, readyTimeoutMs, pollIntervalMs) {
1515
- const deadline = deps.now() + readyTimeoutMs;
1516
- for (;; ) {
1517
- if (await deps.probe(socketPath))
1518
- return;
1519
- if (deps.now() >= deadline)
1520
- throw new DaemonUnreachableError(socketPath);
1521
- await deps.sleep(pollIntervalMs);
1522
- }
1523
- }
1524
- function probeSocket(socketPath, timeoutMs = PROBE_TIMEOUT_MS) {
1525
- return new Promise((resolve22) => {
1526
- const socket = connect(socketPath);
1527
- const finish = (ok) => {
1528
- socket.destroy();
1529
- resolve22(ok);
1530
- };
1531
- const timer = setTimeout(() => finish(false), timeoutMs);
1532
- timer.unref?.();
1533
- socket.once("connect", () => {
1534
- clearTimeout(timer);
1535
- finish(true);
1536
- });
1537
- socket.once("error", () => {
1538
- clearTimeout(timer);
1539
- finish(false);
1540
- });
1541
- });
1542
- }
1543
- function spawnDaemonProcess(paths) {
1544
- mkdirSync2(dirname2(paths.log), { recursive: true });
1545
- const logFd = openSync2(paths.log, "a");
1546
- try {
1547
- const cliPath = resolveDaemonCliPath();
1548
- const child = spawn2(execPath, [cliPath, "daemon"], {
1549
- detached: true,
1550
- stdio: ["ignore", logFd, logFd]
1551
- });
1552
- child.unref();
1553
- } finally {
1554
- closeSync2(logFd);
1555
- }
1556
- }
1557
- function resolveDaemonCliPath(env = process.env) {
1558
- const override = env[CODEX_LSP_DAEMON_CLI_ENV]?.trim();
1559
- if (override)
1560
- return override;
1561
- return fileURLToPath(new URL("./cli.js", import.meta.url));
1562
- }
1563
- function defaultEnsureDaemonDeps() {
1564
- return {
1565
- probe: (socketPath) => probeSocket(socketPath),
1566
- acquireLock: (lockPath) => tryAcquireLock(lockPath),
1567
- cleanupStaleSocket: (socketPath) => {
1568
- if (existsSync2(socketPath))
1569
- unlinkQuietly(socketPath);
1570
- },
1571
- spawnDaemon: (paths) => spawnDaemonProcess(paths),
1572
- sleep: (ms) => new Promise((resolve22) => {
1573
- setTimeout(resolve22, ms);
1574
- }),
1575
- now: () => Date.now()
1576
- };
1577
- }
1578
- var requireFromHere = createRequire(import.meta.url);
1579
- var MAX_SOCKET_PATH_LENGTH = 100;
1580
- var CODEX_LSP_DAEMON_VERSION_ENV = "CODEX_LSP_DAEMON_VERSION";
1581
- function resolveDaemonVersion(requireFn = requireFromHere) {
1582
- for (const candidate of ["./package.json", "../package.json"]) {
1583
- try {
1584
- const pkg = requireFn(candidate);
1585
- if (typeof pkg.version === "string" && pkg.version.length > 0)
1586
- return pkg.version;
1587
- } catch {}
1588
- }
1589
- return "0";
1590
- }
1591
- function daemonBaseDir(env = process.env) {
1592
- const explicit = env["CODEX_LSP_DAEMON_DIR"]?.trim();
1593
- if (explicit)
1594
- return explicit;
1595
- const pluginData = env["PLUGIN_DATA"]?.trim();
1596
- if (pluginData)
1597
- return join2(pluginData, "daemon");
1598
- const codexHome = env["CODEX_HOME"]?.trim();
1599
- const home = codexHome && codexHome.length > 0 ? codexHome : join2(homedir(), ".codex");
1600
- return join2(home, "codex-lsp", "daemon");
1601
- }
1602
- function daemonPaths(env = process.env, version = resolveDaemonVersionFromEnv(env) ?? resolveDaemonVersion()) {
1603
- const dir = join2(daemonBaseDir(env), `v${version}`);
1604
- return {
1605
- version,
1606
- dir,
1607
- socket: resolveSocketPath(dir, version),
1608
- lock: join2(dir, "daemon.lock"),
1609
- pid: join2(dir, "daemon.pid"),
1610
- log: join2(dir, "daemon.log")
1611
- };
1612
- }
1613
- function resolveDaemonVersionFromEnv(env = process.env) {
1614
- const version = env[CODEX_LSP_DAEMON_VERSION_ENV]?.trim();
1615
- return version && version.length > 0 ? version : null;
1616
- }
1617
- function resolveSocketPath(dir, version) {
1618
- const digest = createHash("sha256").update(dir).digest("hex").slice(0, 16);
1619
- if (process.platform === "win32") {
1620
- return `\\\\.\\pipe\\omo-lsp-${version}-${digest}`;
1621
- }
1622
- const natural = join2(dir, "daemon.sock");
1623
- if (natural.length < MAX_SOCKET_PATH_LENGTH)
1624
- return natural;
1625
- return join2(tmpdir(), `omo-lsp-${version}-${digest}.sock`);
1626
- }
1627
- var HEADER_SEPARATOR2 = Buffer.from(`\r
1628
- \r
1629
- `);
1630
- var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60000;
1631
3518
  function getInstallDecisionsPath() {
1632
- const override = contextEnv("LSP_TOOLS_MCP_INSTALL_DECISIONS");
1633
- if (!override)
1634
- return join3(homedir2(), ".codex", "lsp-install-decisions.json");
1635
- return isAbsolute(override) ? override : join3(homedir2(), override);
3519
+ return lspRequestContext().installDecisionsPath;
1636
3520
  }
1637
3521
  function loadInstallDecisions() {
1638
- const path = getInstallDecisionsPath();
1639
- if (!existsSync3(path))
3522
+ const path2 = getInstallDecisionsPath();
3523
+ if (!existsSync6(path2))
1640
3524
  return {};
1641
3525
  try {
1642
- const parsed = JSON.parse(readFileSync3(path, "utf8"));
3526
+ const parsed = JSON.parse(readFileSync4(path2, "utf8"));
1643
3527
  return isInstallDecisions(parsed) ? parsed : {};
1644
3528
  } catch {
1645
3529
  return {};
@@ -1657,22 +3541,22 @@ function isInstallDecision(value) {
1657
3541
  return value === "declined" || value === "allowed";
1658
3542
  }
1659
3543
  function writeInstallDecisions(decisions) {
1660
- const path = getInstallDecisionsPath();
1661
- mkdirSync3(dirname3(path), { recursive: true });
1662
- const tmpPath = `${path}.tmp`;
1663
- writeFileSync(tmpPath, `${JSON.stringify(decisions, null, 2)}
3544
+ const path2 = getInstallDecisionsPath();
3545
+ mkdirSync3(dirname7(path2), { recursive: true });
3546
+ const tmpPath = `${path2}.tmp`;
3547
+ writeFileSync2(tmpPath, `${JSON.stringify(decisions, null, 2)}
1664
3548
  `, "utf8");
1665
- renameSync(tmpPath, path);
3549
+ renameSync2(tmpPath, path2);
1666
3550
  }
1667
3551
  function isInstallDecisions(value) {
1668
- return isRecord2(value) && Object.values(value).every(isInstallDecisionRecord);
3552
+ return isRecord5(value) && Object.values(value).every(isInstallDecisionRecord);
1669
3553
  }
1670
3554
  function isInstallDecisionRecord(value) {
1671
- if (!isRecord2(value))
3555
+ if (!isRecord5(value))
1672
3556
  return false;
1673
3557
  return isInstallDecision(value["decision"]) && typeof value["decidedAt"] === "string";
1674
3558
  }
1675
- function isRecord2(value) {
3559
+ function isRecord5(value) {
1676
3560
  return typeof value === "object" && value !== null && !Array.isArray(value);
1677
3561
  }
1678
3562
  var LSP_INSTALL_HINTS = {
@@ -1820,27 +3704,17 @@ var BUILTIN_SERVERS = {
1820
3704
  extensions: [".razor", ".cshtml"]
1821
3705
  }
1822
3706
  };
1823
- function resolveProjectConfigPath(path) {
1824
- return isAbsolute2(path) ? path : join4(contextCwd(), path);
1825
- }
1826
3707
  function getProjectConfigPaths() {
1827
- const projectOverride = contextEnv("LSP_TOOLS_MCP_PROJECT_CONFIG");
1828
- if (projectOverride) {
1829
- return projectOverride.split(delimiter2).filter(Boolean).map(resolveProjectConfigPath);
1830
- }
1831
- return [join4(contextCwd(), ".codex", "lsp-client.json")];
3708
+ return lspRequestContext().projectConfigPaths;
1832
3709
  }
1833
3710
  function getUserConfigPath() {
1834
- const userOverride = contextEnv("LSP_TOOLS_MCP_USER_CONFIG");
1835
- if (!userOverride)
1836
- return join4(homedir3(), ".codex", "lsp-client.json");
1837
- return isAbsolute2(userOverride) ? userOverride : join4(homedir3(), userOverride);
3711
+ return lspRequestContext().userConfigPath;
1838
3712
  }
1839
- function loadJsonFile(path) {
1840
- if (!existsSync4(path))
3713
+ function loadJsonFile(path2) {
3714
+ if (!existsSync7(path2))
1841
3715
  return null;
1842
3716
  try {
1843
- const parsed = JSON.parse(readFileSync4(path, "utf-8"));
3717
+ const parsed = JSON.parse(readFileSync5(path2, "utf-8"));
1844
3718
  return isConfigJson(parsed) ? parsed : null;
1845
3719
  } catch {
1846
3720
  return null;
@@ -1857,8 +3731,8 @@ function loadAllConfigs() {
1857
3731
  return configs;
1858
3732
  }
1859
3733
  function loadFirstJsonFile(paths) {
1860
- for (const path of paths) {
1861
- const config = loadJsonFile(path);
3734
+ for (const path2 of paths) {
3735
+ const config = loadJsonFile(path2);
1862
3736
  if (config)
1863
3737
  return config;
1864
3738
  }
@@ -1979,16 +3853,16 @@ function applyOptionalServerFields(server2, entry) {
1979
3853
  }
1980
3854
  }
1981
3855
  function isConfigJson(value) {
1982
- if (!isRecord3(value))
3856
+ if (!isRecord6(value))
1983
3857
  return false;
1984
3858
  const lsp = value["lsp"];
1985
- return lsp === undefined || isRecord3(lsp);
3859
+ return lsp === undefined || isRecord6(lsp);
1986
3860
  }
1987
3861
  function parseLspEntry(value) {
1988
3862
  return isLspEntry(value) ? value : null;
1989
3863
  }
1990
3864
  function isLspEntry(value) {
1991
- if (!isRecord3(value))
3865
+ if (!isRecord6(value))
1992
3866
  return false;
1993
3867
  const disabled = value["disabled"];
1994
3868
  const command = value["command"];
@@ -1996,15 +3870,15 @@ function isLspEntry(value) {
1996
3870
  const priority = value["priority"];
1997
3871
  const env = value["env"];
1998
3872
  const initialization = value["initialization"];
1999
- return (disabled === undefined || typeof disabled === "boolean") && (command === undefined || isStringArray(command)) && (extensions === undefined || isStringArray(extensions)) && (priority === undefined || typeof priority === "number") && (env === undefined || isStringRecord(env)) && (initialization === undefined || isRecord3(initialization));
3873
+ return (disabled === undefined || typeof disabled === "boolean") && (command === undefined || isStringArray(command)) && (extensions === undefined || isStringArray(extensions)) && (priority === undefined || typeof priority === "number") && (env === undefined || isStringRecord(env)) && (initialization === undefined || isRecord6(initialization));
2000
3874
  }
2001
3875
  function isStringArray(value) {
2002
3876
  return Array.isArray(value) && value.every((item) => typeof item === "string");
2003
3877
  }
2004
3878
  function isStringRecord(value) {
2005
- return isRecord3(value) && Object.values(value).every((item) => typeof item === "string");
3879
+ return isRecord6(value) && Object.values(value).every((item) => typeof item === "string");
2006
3880
  }
2007
- function isRecord3(value) {
3881
+ function isRecord6(value) {
2008
3882
  return typeof value === "object" && value !== null && !Array.isArray(value);
2009
3883
  }
2010
3884
  function getDisabledServerIds() {
@@ -2030,7 +3904,7 @@ function isServerInstalled(command, _workingDirectory) {
2030
3904
  if (!cmd)
2031
3905
  return false;
2032
3906
  if (cmd.includes("/") || cmd.includes("\\")) {
2033
- if (existsSync5(cmd))
3907
+ if (existsSync8(cmd))
2034
3908
  return true;
2035
3909
  }
2036
3910
  const isWindows = process.platform === "win32";
@@ -2051,7 +3925,7 @@ function isServerInstalled(command, _workingDirectory) {
2051
3925
  const paths = pathEnv.split(delimiter3);
2052
3926
  for (const p of paths) {
2053
3927
  for (const suffix of exts) {
2054
- if (existsSync5(join5(p, cmd + suffix))) {
3928
+ if (existsSync8(join3(p, cmd + suffix))) {
2055
3929
  return true;
2056
3930
  }
2057
3931
  }
@@ -2146,39 +4020,50 @@ function getAllServers() {
2146
4020
  var WORKSPACE_MARKERS = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
2147
4021
  function isDirectoryPath(filePath) {
2148
4022
  try {
2149
- return statSync2(filePath).isDirectory();
4023
+ return statSync4(filePath).isDirectory();
2150
4024
  } catch {
2151
4025
  return false;
2152
4026
  }
2153
4027
  }
2154
4028
  function findWorkspaceRoot(filePath) {
2155
- const abs = resolve2(contextCwd(), filePath);
4029
+ const abs = resolvePathInsideContext(filePath);
2156
4030
  let dir = abs;
2157
4031
  if (!isDirectoryPath(dir)) {
2158
- dir = dirname4(dir);
4032
+ dir = dirname8(dir);
2159
4033
  }
2160
4034
  let prevDir = "";
2161
4035
  while (dir !== prevDir) {
2162
4036
  for (const marker of WORKSPACE_MARKERS) {
2163
- if (existsSync6(join6(dir, marker))) {
4037
+ if (existsSync9(join4(dir, marker))) {
2164
4038
  return dir;
2165
4039
  }
2166
4040
  }
2167
4041
  prevDir = dir;
2168
- dir = dirname4(dir);
4042
+ dir = dirname8(dir);
2169
4043
  }
2170
- return dirname4(abs);
4044
+ return dirname8(abs);
4045
+ }
4046
+ function resolvePathInsideContext(filePath) {
4047
+ const cwd = contextCwd();
4048
+ const abs = resolve7(cwd, filePath);
4049
+ const canonical = canonicalizeExistingOrNearestAncestor(abs);
4050
+ if (!isPathInside(cwd, canonical)) {
4051
+ throw new LspInvalidPathError(`LSP file path must be inside request cwd: ${filePath}`);
4052
+ }
4053
+ return canonical;
2171
4054
  }
2172
4055
  function formatServerLookupError(result) {
2173
4056
  if (result.status === "not_installed") {
2174
4057
  return formatNotInstalled(result);
2175
4058
  }
4059
+ const context = lspRequestContext();
4060
+ const firstProjectConfigPath = context.projectConfigPaths[0] ?? "<project lsp config>";
2176
4061
  return [
2177
4062
  `No LSP server configured for extension: ${result.extension}`,
2178
4063
  "",
2179
4064
  `Available servers: ${result.availableServers.slice(0, 10).join(", ")}${result.availableServers.length > 10 ? "..." : ""}`,
2180
4065
  "",
2181
- "Configure a custom server in '.codex/lsp-client.json':",
4066
+ `Configure a custom server in '${firstProjectConfigPath}' or '${context.userConfigPath}':`,
2182
4067
  " {",
2183
4068
  ' "lsp": {',
2184
4069
  ' "my-server": {',
@@ -2194,6 +4079,7 @@ function formatNotInstalled(result) {
2194
4079
  const { server: server2, installHint } = result;
2195
4080
  const extensions = server2.extensions.join(", ");
2196
4081
  const decision = loadInstallDecision(server2.id)?.decision;
4082
+ const context = lspRequestContext();
2197
4083
  if (decision === "declined") {
2198
4084
  return `LSP server '${server2.id}' (${extensions}) is NOT INSTALLED; user previously declined installation — proceed without LSP.`;
2199
4085
  }
@@ -2209,6 +4095,17 @@ function formatNotInstalled(result) {
2209
4095
  "The user has pre-authorized LSP installation. Run the install command, then retry this tool:",
2210
4096
  ` ${installHint}`
2211
4097
  ].join(`
4098
+ `);
4099
+ }
4100
+ if (!context.capabilities.installDecisionTool) {
4101
+ return [
4102
+ ...header,
4103
+ "To install, run:",
4104
+ ` ${installHint}`,
4105
+ "",
4106
+ "ACTION REQUIRED — ASK THE USER whether to install this LSP server.",
4107
+ "Install-decision recording is unavailable in this harness; proceed without LSP if the user declines."
4108
+ ].join(`
2212
4109
  `);
2213
4110
  }
2214
4111
  return [
@@ -2220,6 +4117,7 @@ function formatNotInstalled(result) {
2220
4117
  "- If the user agrees: run the install command above, then retry this tool.",
2221
4118
  "- If the user declines, OR has NOT explicitly asked for LSP installation:",
2222
4119
  ` call lsp_install_decision { server_id: "${server2.id}", decision: "declined" },`,
4120
+ ` which writes to ${context.installDecisionsPath},`,
2223
4121
  " then ignore this message and proceed WITHOUT LSP."
2224
4122
  ].join(`
2225
4123
  `);
@@ -2233,14 +4131,14 @@ var READ_ONLY_RETRY_TOOLS = new Set([
2233
4131
  "prepareRename"
2234
4132
  ]);
2235
4133
  async function withLspClient(filePath, fn, toolName, options = {}) {
2236
- const absPath = resolve2(contextCwd(), filePath);
4134
+ const absPath = resolvePathInsideContext(filePath);
2237
4135
  if (isDirectoryPath(absPath)) {
2238
4136
  throw new LspInvalidPathError("Directory paths are not supported by this LSP tool. " + "Use lsp.diagnostics with a directory path for directory diagnostics.");
2239
4137
  }
2240
4138
  const ext = effectiveExtension(absPath);
2241
4139
  const result = findServerForExtension(ext);
2242
4140
  if (result.status !== "found") {
2243
- throw new LspServerLookupError(formatServerLookupError(result));
4141
+ throw new LspServerLookupError(formatServerLookupError(result), result);
2244
4142
  }
2245
4143
  const server2 = result.server;
2246
4144
  const root = findWorkspaceRoot(absPath);
@@ -2273,7 +4171,7 @@ var DIAGNOSTIC_SEVERITY_FILTERS = {
2273
4171
  hint: 4
2274
4172
  };
2275
4173
  function uriToPath(uri) {
2276
- return fileURLToPath2(uri);
4174
+ return fileURLToPath3(uri);
2277
4175
  }
2278
4176
  function formatLocation(loc) {
2279
4177
  if ("targetUri" in loc) {
@@ -2359,6 +4257,9 @@ function formatApplyResult(result) {
2359
4257
  for (const file of result.filesModified) {
2360
4258
  lines.push(` - ${file}`);
2361
4259
  }
4260
+ if (result.lateAbort) {
4261
+ lines.push("Cancellation arrived after the filesystem commit began; the committed edit completed.");
4262
+ }
2362
4263
  } else {
2363
4264
  lines.push("Failed to apply some changes:");
2364
4265
  for (const err of result.errors) {
@@ -2372,6 +4273,7 @@ function formatApplyResult(result) {
2372
4273
  `);
2373
4274
  }
2374
4275
  var SKIP_DIRECTORIES = new Set(["node_modules", ".git", "dist", "build", ".next", "out"]);
4276
+ var DIRECTORY_DIAGNOSTICS_MAX_CONCURRENCY = 4;
2375
4277
  function collectFilesWithExtension(dir, extension, maxFiles) {
2376
4278
  const files = [];
2377
4279
  function walk(currentDir) {
@@ -2379,17 +4281,17 @@ function collectFilesWithExtension(dir, extension, maxFiles) {
2379
4281
  return;
2380
4282
  let entries = [];
2381
4283
  try {
2382
- entries = readdirSync(currentDir);
4284
+ entries = readdirSync3(currentDir);
2383
4285
  } catch {
2384
4286
  return;
2385
4287
  }
2386
4288
  for (const entry of entries) {
2387
4289
  if (files.length >= maxFiles)
2388
4290
  return;
2389
- const fullPath = join7(currentDir, entry);
4291
+ const fullPath = join5(currentDir, entry);
2390
4292
  let stat;
2391
4293
  try {
2392
- stat = lstatSync(fullPath);
4294
+ stat = lstatSync5(fullPath);
2393
4295
  } catch {
2394
4296
  continue;
2395
4297
  }
@@ -2407,52 +4309,65 @@ function collectFilesWithExtension(dir, extension, maxFiles) {
2407
4309
  walk(dir);
2408
4310
  return files;
2409
4311
  }
2410
- async function aggregateDiagnosticsForDirectory(directory, extension, severity, maxFiles = DEFAULT_MAX_DIRECTORY_FILES) {
4312
+ async function aggregateDiagnosticsForDirectory(directory, extension, severity, maxFiles = DEFAULT_MAX_DIRECTORY_FILES, options = {}) {
2411
4313
  if (!extension.startsWith(".")) {
2412
4314
  throw new LspInvalidPathError(`Extension must start with a dot (e.g., ".ts", not "${extension}"). Use ".${extension}" instead.`);
2413
4315
  }
2414
- const absDir = resolve3(contextCwd(), directory);
2415
- if (!existsSync7(absDir)) {
4316
+ const absDir = resolve8(options.workspaceRoot ?? contextCwd(), directory);
4317
+ if (!existsSync10(absDir)) {
2416
4318
  throw new LspInvalidPathError(`Directory does not exist: ${absDir}`);
2417
4319
  }
2418
- const serverResult = findServerForExtension(extension);
4320
+ const serverResult = options.server === undefined ? findServerForExtension(extension) : { status: "found", server: options.server };
2419
4321
  if (serverResult.status !== "found") {
2420
4322
  throw new LspServerLookupError(formatServerLookupError(serverResult));
2421
4323
  }
2422
4324
  const server2 = serverResult.server;
2423
- const allFiles = collectFilesWithExtension(absDir, extension, maxFiles + 1);
4325
+ const allFiles = (options.listFiles ?? collectFilesWithExtension)(absDir, extension, maxFiles + 1);
2424
4326
  const wasCapped = allFiles.length > maxFiles;
2425
4327
  const filesToProcess = allFiles.slice(0, maxFiles);
2426
4328
  if (filesToProcess.length === 0) {
2427
- return [
4329
+ const output = [
2428
4330
  `Directory: ${absDir}`,
2429
4331
  `Extension: ${extension}`,
2430
4332
  "Files scanned: 0",
2431
4333
  `No files found with extension "${extension}".`
2432
4334
  ].join(`
2433
4335
  `);
4336
+ return { output, totalDiagnostics: 0, fileFailures: [] };
2434
4337
  }
2435
- const root = findWorkspaceRoot(absDir);
2436
- const manager = getLspManager();
4338
+ const root = options.workspaceRoot ?? findWorkspaceRoot(absDir);
4339
+ const manager = options.manager ?? getLspManager();
2437
4340
  const allDiagnostics = [];
2438
4341
  const fileErrors = [];
2439
- const client = await manager.getClient(root, server2);
4342
+ const maxConcurrency = Math.max(1, options.maxConcurrency ?? DIRECTORY_DIAGNOSTICS_MAX_CONCURRENCY);
4343
+ options.signal?.throwIfAborted();
4344
+ const client = await manager.getClient(root, server2, options.signal);
2440
4345
  try {
2441
- for (const file of filesToProcess) {
2442
- try {
2443
- const result = await client.diagnostics(file);
2444
- const filtered = filterDiagnosticsBySeverity(result.items, severity);
2445
- allDiagnostics.push(...filtered.map((diagnostic) => ({
2446
- filePath: file,
2447
- diagnostic
2448
- })));
2449
- } catch (e) {
2450
- fileErrors.push({
2451
- file,
2452
- error: e instanceof Error ? e.message : String(e)
2453
- });
4346
+ let nextIndex = 0;
4347
+ const workers = Array.from({ length: Math.min(maxConcurrency, filesToProcess.length) }, async () => {
4348
+ for (;; ) {
4349
+ if (options.signal?.aborted)
4350
+ return;
4351
+ const file = filesToProcess[nextIndex];
4352
+ nextIndex += 1;
4353
+ if (file === undefined)
4354
+ return;
4355
+ try {
4356
+ const result = await client.diagnostics(file, options.signal);
4357
+ const filtered = filterDiagnosticsBySeverity(result.items, severity);
4358
+ allDiagnostics.push(...filtered.map((diagnostic) => ({
4359
+ filePath: file,
4360
+ diagnostic
4361
+ })));
4362
+ } catch (e) {
4363
+ fileErrors.push({
4364
+ file,
4365
+ error: e instanceof Error ? e.message : String(e)
4366
+ });
4367
+ }
2454
4368
  }
2455
- }
4369
+ });
4370
+ await Promise.all(workers);
2456
4371
  } finally {
2457
4372
  manager.releaseClient(root, server2.id);
2458
4373
  }
@@ -2480,8 +4395,8 @@ async function aggregateDiagnosticsForDirectory(directory, extension, severity,
2480
4395
  lines.push("", `... (${allDiagnostics.length - DEFAULT_MAX_DIAGNOSTICS} more diagnostics not shown)`);
2481
4396
  }
2482
4397
  }
2483
- return lines.join(`
2484
- `);
4398
+ return { output: lines.join(`
4399
+ `), totalDiagnostics: allDiagnostics.length, fileFailures: fileErrors };
2485
4400
  }
2486
4401
  var SKIP_DIRECTORIES2 = new Set(["node_modules", ".git", "dist", "build", ".next", "out"]);
2487
4402
  var MAX_SCAN_ENTRIES = 500;
@@ -2493,17 +4408,17 @@ function inferExtensionFromDirectory(directory) {
2493
4408
  return;
2494
4409
  let entries;
2495
4410
  try {
2496
- entries = readdirSync2(dir);
4411
+ entries = readdirSync4(dir);
2497
4412
  } catch {
2498
4413
  return;
2499
4414
  }
2500
4415
  for (const entry of entries) {
2501
4416
  if (scanned >= MAX_SCAN_ENTRIES)
2502
4417
  return;
2503
- const fullPath = join8(dir, entry);
4418
+ const fullPath = join6(dir, entry);
2504
4419
  let stat;
2505
4420
  try {
2506
- stat = lstatSync2(fullPath);
4421
+ stat = lstatSync6(fullPath);
2507
4422
  } catch {
2508
4423
  continue;
2509
4424
  }
@@ -2574,10 +4489,45 @@ function missingDependencyResult(error, details) {
2574
4489
  details: {
2575
4490
  ...details,
2576
4491
  error: message,
2577
- errorKind: "missing_dependency"
4492
+ errorKind: "missing_dependency",
4493
+ ...availabilityDetails(error)
2578
4494
  }
2579
4495
  };
2580
4496
  }
4497
+ function availabilityDetails(error) {
4498
+ const availability = missingDependencyAvailability(error);
4499
+ return availability === null ? {} : { availability };
4500
+ }
4501
+ function missingDependencyAvailability(error) {
4502
+ if (!(error instanceof LspServerLookupError) || error.lookup === undefined)
4503
+ return null;
4504
+ const context = lspRequestContext();
4505
+ switch (error.lookup.status) {
4506
+ case "not_configured":
4507
+ return {
4508
+ kind: "not_configured",
4509
+ extension: error.lookup.extension,
4510
+ availableServers: [...error.lookup.availableServers],
4511
+ projectConfigPaths: [...context.projectConfigPaths],
4512
+ userConfigPath: context.userConfigPath,
4513
+ installDecisionTool: context.capabilities.installDecisionTool
4514
+ };
4515
+ case "not_installed":
4516
+ return {
4517
+ kind: "not_installed",
4518
+ serverId: error.lookup.server.id,
4519
+ command: [...error.lookup.server.command],
4520
+ extensions: [...error.lookup.server.extensions],
4521
+ installHint: error.lookup.installHint,
4522
+ installDecisionTool: context.capabilities.installDecisionTool,
4523
+ installDecisionsPath: context.installDecisionsPath
4524
+ };
4525
+ default: {
4526
+ const exhaustive = error.lookup;
4527
+ return exhaustive;
4528
+ }
4529
+ }
4530
+ }
2581
4531
  function requireString(params, key) {
2582
4532
  const value = params[key];
2583
4533
  if (typeof value !== "string" || value.length === 0) {
@@ -2628,7 +4578,7 @@ async function executeLspDiagnostics(params, signal) {
2628
4578
  const filePath = requireString(params, "filePath");
2629
4579
  const severity = severityFilter(params);
2630
4580
  try {
2631
- const absPath = resolve4(contextCwd(), filePath);
4581
+ const absPath = resolvePathInsideContext(filePath);
2632
4582
  if (isDirectoryPath(absPath)) {
2633
4583
  const extension = inferExtensionFromDirectory(absPath);
2634
4584
  if (!extension) {
@@ -2645,18 +4595,33 @@ async function executeLspDiagnostics(params, signal) {
2645
4595
  };
2646
4596
  return text(message, details3);
2647
4597
  }
2648
- const output2 = await aggregateDiagnosticsForDirectory(absPath, extension, severity);
4598
+ const output2 = await aggregateDiagnosticsForDirectory(absPath, extension, severity, undefined, signal === undefined ? {} : { signal });
2649
4599
  const details2 = {
2650
4600
  filePath,
2651
4601
  severity,
2652
4602
  mode: "directory",
2653
4603
  diagnostics: [],
4604
+ totalDiagnostics: output2.totalDiagnostics,
4605
+ truncated: false,
4606
+ fileFailures: [...output2.fileFailures]
4607
+ };
4608
+ return text(output2.output, details2);
4609
+ }
4610
+ const result = await withLspClient(filePath, async (client) => client.diagnostics(filePath, signal), "diagnostics", clientOptions(signal));
4611
+ if (result.transientError) {
4612
+ const message = result.transientError.message;
4613
+ const details2 = {
4614
+ filePath,
4615
+ severity,
4616
+ mode: "file",
4617
+ diagnostics: [],
2654
4618
  totalDiagnostics: 0,
2655
- truncated: false
4619
+ truncated: false,
4620
+ error: message,
4621
+ errorKind: result.transientError.kind
2656
4622
  };
2657
- return text(output2, details2);
4623
+ return text(message, details2, true);
2658
4624
  }
2659
- const result = await withLspClient(filePath, async (client) => client.diagnostics(filePath), "diagnostics", clientOptions(signal));
2660
4625
  const diagnostics = filterDiagnosticsBySeverity(asDiagnosticArray(result), severity);
2661
4626
  const total = diagnostics.length;
2662
4627
  const truncated = total > DEFAULT_MAX_DIAGNOSTICS;
@@ -2714,7 +4679,7 @@ async function executeLspGotoDefinition(params, signal) {
2714
4679
  const line = requireNumber(params, "line");
2715
4680
  const character = requireNumber(params, "character");
2716
4681
  try {
2717
- const result = await withLspClient(filePath, async (client) => client.definition(filePath, line, character), "definition", clientOptions(signal));
4682
+ const result = await withLspClient(filePath, async (client) => client.definition(filePath, line, character, signal), "definition", clientOptions(signal));
2718
4683
  const locations = !result ? [] : Array.isArray(result) ? result : [result];
2719
4684
  const details = { filePath, line, character, locations };
2720
4685
  if (locations.length === 0)
@@ -2739,7 +4704,7 @@ async function executeLspFindReferences(params, signal) {
2739
4704
  const character = requireNumber(params, "character");
2740
4705
  const includeDeclaration = optionalBoolean(params, "includeDeclaration") ?? true;
2741
4706
  try {
2742
- const result = await withLspClient(filePath, async (client) => client.references(filePath, line, character, includeDeclaration), "references", clientOptions(signal));
4707
+ const result = await withLspClient(filePath, async (client) => client.references(filePath, line, character, includeDeclaration, signal), "references", clientOptions(signal));
2743
4708
  const references = Array.isArray(result) ? result : [];
2744
4709
  const total = references.length;
2745
4710
  const truncated = total > DEFAULT_MAX_REFERENCES;
@@ -2750,199 +4715,36 @@ async function executeLspFindReferences(params, signal) {
2750
4715
  character,
2751
4716
  references,
2752
4717
  totalReferences: total,
2753
- truncated
2754
- };
2755
- if (total === 0)
2756
- return text("No references found", details);
2757
- const output = [
2758
- ...truncated ? [`Found ${total} references (showing first ${DEFAULT_MAX_REFERENCES}):`] : [],
2759
- ...limited.map(formatLocation)
2760
- ].join(`
2761
- `);
2762
- return text(output, details);
2763
- } catch (error) {
2764
- const missingDependency = missingDependencyResult(error, {
2765
- filePath,
2766
- line,
2767
- character,
2768
- references: [],
2769
- totalReferences: 0,
2770
- truncated: false
2771
- });
2772
- if (missingDependency)
2773
- return missingDependency;
2774
- throw error;
2775
- }
2776
- }
2777
- function errorMessage2(error) {
2778
- return error instanceof Error ? error.message : String(error);
2779
- }
2780
- function isPathInsideWorkspace(filePath, workspaceRoot) {
2781
- const relativePath = relative(workspaceRoot, filePath);
2782
- return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute3(relativePath);
2783
- }
2784
- function realpathForValidation(filePath) {
2785
- if (existsSync8(filePath))
2786
- return realpathSync(filePath);
2787
- const parent = dirname5(filePath);
2788
- return resolve5(realpathSync(parent), relative(parent, filePath));
2789
- }
2790
- function uriToWorkspacePath(uri, workspaceRoot) {
2791
- let filePath;
2792
- try {
2793
- filePath = fileURLToPath3(uri);
2794
- } catch (error) {
2795
- return { success: false, error: `non-file URI ${uri}: ${errorMessage2(error)}` };
2796
- }
2797
- let validatedPath;
2798
- try {
2799
- validatedPath = realpathForValidation(filePath);
2800
- } catch (error) {
2801
- return { success: false, error: `${filePath}: ${errorMessage2(error)}` };
2802
- }
2803
- if (!isPathInsideWorkspace(validatedPath, workspaceRoot)) {
2804
- return { success: false, error: `${filePath}: outside workspace ${workspaceRoot}` };
2805
- }
2806
- return { success: true, path: filePath };
2807
- }
2808
- function applyTextEditsToFile(filePath, edits) {
2809
- try {
2810
- const content = readFileSync5(filePath, "utf-8");
2811
- const lines = content.split(`
2812
- `);
2813
- const sortedEdits = [...edits].sort((a, b) => {
2814
- if (b.range.start.line !== a.range.start.line) {
2815
- return b.range.start.line - a.range.start.line;
2816
- }
2817
- return b.range.start.character - a.range.start.character;
2818
- });
2819
- for (const edit of sortedEdits) {
2820
- const startLine = edit.range.start.line;
2821
- const startChar = edit.range.start.character;
2822
- const endLine = edit.range.end.line;
2823
- const endChar = edit.range.end.character;
2824
- if (startLine === endLine) {
2825
- const line = lines[startLine] ?? "";
2826
- lines[startLine] = line.substring(0, startChar) + edit.newText + line.substring(endChar);
2827
- } else {
2828
- const firstLine = lines[startLine] ?? "";
2829
- const lastLine = lines[endLine] ?? "";
2830
- const newContent = firstLine.substring(0, startChar) + edit.newText + lastLine.substring(endChar);
2831
- lines.splice(startLine, endLine - startLine + 1, ...newContent.split(`
2832
- `));
2833
- }
2834
- }
2835
- writeFileSync2(filePath, lines.join(`
2836
- `), "utf-8");
2837
- return { success: true, editCount: edits.length };
2838
- } catch (err) {
2839
- return {
2840
- success: false,
2841
- editCount: 0,
2842
- error: err instanceof Error ? err.message : String(err)
2843
- };
2844
- }
2845
- }
2846
- function applyWorkspaceEdit(edit, options = {}) {
2847
- if (!edit) {
2848
- return { success: false, filesModified: [], totalEdits: 0, errors: ["No edit provided"] };
2849
- }
2850
- const result = { success: true, filesModified: [], totalEdits: 0, errors: [] };
2851
- const workspaceRoot = realpathSync(options.workspaceRoot ?? contextCwd());
2852
- if (edit.changes) {
2853
- for (const [uri, edits] of Object.entries(edit.changes)) {
2854
- const validatedPath = uriToWorkspacePath(uri, workspaceRoot);
2855
- if (!validatedPath.success) {
2856
- result.success = false;
2857
- result.errors.push(validatedPath.error);
2858
- continue;
2859
- }
2860
- const applyResult = applyTextEditsToFile(validatedPath.path, edits);
2861
- if (applyResult.success) {
2862
- result.filesModified.push(validatedPath.path);
2863
- result.totalEdits += applyResult.editCount;
2864
- } else {
2865
- result.success = false;
2866
- result.errors.push(`${validatedPath.path}: ${applyResult.error}`);
2867
- }
2868
- }
2869
- }
2870
- if (edit.documentChanges) {
2871
- for (const change of edit.documentChanges) {
2872
- if (!("kind" in change)) {
2873
- const validatedPath = uriToWorkspacePath(change.textDocument.uri, workspaceRoot);
2874
- if (!validatedPath.success) {
2875
- result.success = false;
2876
- result.errors.push(validatedPath.error);
2877
- continue;
2878
- }
2879
- const applyResult = applyTextEditsToFile(validatedPath.path, change.edits);
2880
- if (applyResult.success) {
2881
- result.filesModified.push(validatedPath.path);
2882
- result.totalEdits += applyResult.editCount;
2883
- } else {
2884
- result.success = false;
2885
- result.errors.push(`${validatedPath.path}: ${applyResult.error}`);
2886
- }
2887
- continue;
2888
- }
2889
- if (change.kind === "create") {
2890
- try {
2891
- const validatedPath = uriToWorkspacePath(change.uri, workspaceRoot);
2892
- if (!validatedPath.success) {
2893
- result.success = false;
2894
- result.errors.push(`Create ${change.uri}: ${validatedPath.error}`);
2895
- continue;
2896
- }
2897
- writeFileSync2(validatedPath.path, "", "utf-8");
2898
- result.filesModified.push(validatedPath.path);
2899
- } catch (err) {
2900
- result.success = false;
2901
- result.errors.push(`Create ${change.uri}: ${String(err)}`);
2902
- }
2903
- } else if (change.kind === "rename") {
2904
- try {
2905
- const oldPath = uriToWorkspacePath(change.oldUri, workspaceRoot);
2906
- const newPath = uriToWorkspacePath(change.newUri, workspaceRoot);
2907
- if (!oldPath.success || !newPath.success) {
2908
- const error = oldPath.success ? newPath.success ? "invalid URI" : newPath.error : oldPath.error;
2909
- result.success = false;
2910
- result.errors.push(`Rename ${change.oldUri}: ${error}`);
2911
- continue;
2912
- }
2913
- const content = readFileSync5(oldPath.path, "utf-8");
2914
- writeFileSync2(newPath.path, content, "utf-8");
2915
- unlinkSync2(oldPath.path);
2916
- result.filesModified.push(newPath.path);
2917
- } catch (err) {
2918
- result.success = false;
2919
- result.errors.push(`Rename ${change.oldUri}: ${String(err)}`);
2920
- }
2921
- } else if (change.kind === "delete") {
2922
- try {
2923
- const validatedPath = uriToWorkspacePath(change.uri, workspaceRoot);
2924
- if (!validatedPath.success) {
2925
- result.success = false;
2926
- result.errors.push(`Delete ${change.uri}: ${validatedPath.error}`);
2927
- continue;
2928
- }
2929
- unlinkSync2(validatedPath.path);
2930
- result.filesModified.push(validatedPath.path);
2931
- } catch (err) {
2932
- result.success = false;
2933
- result.errors.push(`Delete ${change.uri}: ${String(err)}`);
2934
- }
2935
- }
2936
- }
4718
+ truncated
4719
+ };
4720
+ if (total === 0)
4721
+ return text("No references found", details);
4722
+ const output = [
4723
+ ...truncated ? [`Found ${total} references (showing first ${DEFAULT_MAX_REFERENCES}):`] : [],
4724
+ ...limited.map(formatLocation)
4725
+ ].join(`
4726
+ `);
4727
+ return text(output, details);
4728
+ } catch (error) {
4729
+ const missingDependency = missingDependencyResult(error, {
4730
+ filePath,
4731
+ line,
4732
+ character,
4733
+ references: [],
4734
+ totalReferences: 0,
4735
+ truncated: false
4736
+ });
4737
+ if (missingDependency)
4738
+ return missingDependency;
4739
+ throw error;
2937
4740
  }
2938
- return result;
2939
4741
  }
2940
4742
  async function executeLspPrepareRename(params, signal) {
2941
4743
  const filePath = requireString(params, "filePath");
2942
4744
  const line = requireNumber(params, "line");
2943
4745
  const character = requireNumber(params, "character");
2944
4746
  try {
2945
- const result = await withLspClient(filePath, async (client) => client.prepareRename(filePath, line, character), "prepareRename", clientOptions(signal));
4747
+ const result = await withLspClient(filePath, async (client) => client.prepareRename(filePath, line, character, signal), "prepareRename", clientOptions(signal));
2946
4748
  const details = { filePath, line, character, result };
2947
4749
  return text(formatPrepareRenameResult(result), details);
2948
4750
  } catch (error) {
@@ -2963,13 +4765,9 @@ async function executeLspRename(params, signal) {
2963
4765
  const character = requireNumber(params, "character");
2964
4766
  const newName = requireString(params, "newName");
2965
4767
  try {
2966
- const edit = await withLspClient(filePath, async (client, workspaceRoot) => ({
2967
- edit: await client.rename(filePath, line, character, newName),
2968
- workspaceRoot
2969
- }), "rename", clientOptions(signal));
2970
- const apply = applyWorkspaceEdit(edit.edit, { workspaceRoot: edit.workspaceRoot });
2971
- const details = { filePath, line, character, newName, apply, edit: edit.edit };
2972
- return text(formatApplyResult(apply), details, !apply.success);
4768
+ const result = await withLspClient(filePath, async (client) => client.rename(filePath, line, character, newName, signal), "rename", clientOptions(signal));
4769
+ const details = { filePath, line, character, newName, apply: result.apply, edit: result.edit };
4770
+ return text(formatApplyResult(result.apply), details, !result.apply.success);
2973
4771
  } catch (error) {
2974
4772
  const missingDependency = missingDependencyResult(error, {
2975
4773
  filePath,
@@ -3038,10 +4836,10 @@ async function executeLspSymbols(params, signal) {
3038
4836
  errorKind: "missing_query"
3039
4837
  });
3040
4838
  }
3041
- const symbols2 = await withLspClient(filePath, async (client) => client.workspaceSymbols(query), "workspaceSymbols", clientOptions(signal));
4839
+ const symbols2 = await withLspClient(filePath, async (client) => client.workspaceSymbols(query, signal), "workspaceSymbols", clientOptions(signal));
3042
4840
  return formatSymbolsResult(filePath, scope, symbols2, limit, query);
3043
4841
  }
3044
- const symbols = await withLspClient(filePath, async (client) => client.documentSymbols(filePath), "documentSymbols", clientOptions(signal));
4842
+ const symbols = await withLspClient(filePath, async (client) => client.documentSymbols(filePath, signal), "documentSymbols", clientOptions(signal));
3045
4843
  return formatSymbolsResult(filePath, scope, symbols, limit);
3046
4844
  } catch (error) {
3047
4845
  const query = optionalString(params, "query");
@@ -3195,35 +4993,8 @@ var LSP_MCP_TOOLS = [
3195
4993
  }
3196
4994
  ];
3197
4995
  var CONTEXT_KEY = "_context";
3198
- function encodeJsonLine(message) {
3199
- return `${JSON.stringify(message)}
3200
- `;
3201
- }
3202
- function createLineDecoder(onMessage, onParseError) {
3203
- let buffer = "";
3204
- return {
3205
- push(chunk) {
3206
- buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
3207
- let index = buffer.indexOf(`
3208
- `);
3209
- while (index !== -1) {
3210
- const raw = buffer.slice(0, index).trim();
3211
- buffer = buffer.slice(index + 1);
3212
- if (raw.length > 0) {
3213
- try {
3214
- onMessage(JSON.parse(raw));
3215
- } catch (error) {
3216
- onParseError?.(raw, error);
3217
- }
3218
- }
3219
- index = buffer.indexOf(`
3220
- `);
3221
- }
3222
- }
3223
- };
3224
- }
3225
4996
  var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
3226
- var REQUEST_ID = 1;
4997
+ var nextProxyRequestId = 1;
3227
4998
 
3228
4999
  class DaemonRequestError extends Error {
3229
5000
  requestWritten;
@@ -3233,46 +5004,82 @@ class DaemonRequestError extends Error {
3233
5004
  this.requestWritten = requestWritten;
3234
5005
  }
3235
5006
  }
3236
- async function callToolViaDaemon(name, args, options = {}) {
5007
+
5008
+ class DaemonAuthenticationRejectedError extends DaemonRequestError {
5009
+ constructor() {
5010
+ super("daemon authentication failed before dispatch", true);
5011
+ this.name = "DaemonAuthenticationRejectedError";
5012
+ }
5013
+ }
5014
+
5015
+ class DaemonRequestCancelledError extends DaemonRequestError {
5016
+ constructor(requestWritten) {
5017
+ super("daemon request cancelled", requestWritten);
5018
+ this.name = "DaemonRequestCancelledError";
5019
+ }
5020
+ }
5021
+ async function callToolViaDaemon(name, args, options) {
5022
+ const context = requireContext(options.context);
3237
5023
  const paths = options.paths ?? daemonPaths();
3238
- const ensure = options.ensure ?? ensureDaemonRunning;
5024
+ const ensure = options.ensure ?? ((ensurePaths, signal) => ensureDaemonRunning(ensurePaths, undefined, signal === undefined ? {} : { signal }));
3239
5025
  const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
3240
- const requestArgs = withContext(args, options.context);
5026
+ const requestArgs = withContext(args, context);
3241
5027
  let lastError;
3242
- for (let attempt = 0;attempt < 2; attempt += 1) {
5028
+ let authRefreshUsed = false;
5029
+ for (let attempt = 0;attempt < 3; attempt += 1) {
3243
5030
  try {
3244
- await ensure(paths);
3245
- return await sendToolCall(paths.socket, name, requestArgs, timeoutMs);
5031
+ await ensureDaemonAvailable(paths, ensure, options.signal);
5032
+ const token = readAuthToken(paths);
5033
+ if (!token)
5034
+ throw new DaemonRequestError("daemon auth token missing", false);
5035
+ const sendOptions = options.signal === undefined ? { timeoutMs } : { timeoutMs, signal: options.signal };
5036
+ return await sendToolCall(paths, token, name, requestArgs, sendOptions);
3246
5037
  } catch (error) {
3247
5038
  lastError = error;
3248
- if (error instanceof DaemonRequestError && error.requestWritten)
5039
+ if (error instanceof DaemonAuthenticationRejectedError && !authRefreshUsed) {
5040
+ authRefreshUsed = true;
5041
+ continue;
5042
+ }
5043
+ if (error instanceof DaemonRequestCancelledError)
5044
+ break;
5045
+ if (error instanceof DaemonRequestError && (error.requestWritten || !isRetryableTool(name)))
3249
5046
  break;
3250
5047
  }
3251
5048
  }
3252
5049
  return daemonUnreachableResult(paths, lastError);
3253
5050
  }
3254
- function callDiagnosticsViaDaemon(filePath, options = {}) {
5051
+ function callDiagnosticsViaDaemon(filePath, options) {
3255
5052
  return callToolViaDaemon("diagnostics", { filePath, severity: "error" }, options);
3256
5053
  }
3257
- var FORWARDED_ENV_KEYS = [
3258
- "LSP_TOOLS_MCP_PROJECT_CONFIG",
3259
- "LSP_TOOLS_MCP_USER_CONFIG",
3260
- "LSP_TOOLS_MCP_INSTALL_DECISIONS"
3261
- ];
3262
- function currentRequestContext(env = process.env) {
3263
- const forwarded = {};
3264
- for (const key of FORWARDED_ENV_KEYS) {
3265
- const value = env[key];
3266
- if (value !== undefined)
3267
- forwarded[key] = value;
3268
- }
3269
- return { cwd: process.cwd(), env: forwarded };
5054
+ function requireContext(context) {
5055
+ if (!context)
5056
+ throw new DaemonRequestError("daemon tool context is required", false);
5057
+ return parseLspRequestContext(context);
3270
5058
  }
3271
5059
  function withContext(args, context) {
3272
- if (!context || context.cwd === undefined && context.env === undefined)
3273
- return args;
3274
5060
  return { ...args, [CONTEXT_KEY]: context };
3275
5061
  }
5062
+ function ensureDaemonAvailable(paths, ensure, signal) {
5063
+ if (!signal)
5064
+ return ensure(paths);
5065
+ return new Promise((resolve9, reject) => {
5066
+ let settled = false;
5067
+ const finish = (run) => {
5068
+ if (settled)
5069
+ return;
5070
+ settled = true;
5071
+ signal.removeEventListener("abort", onAbort);
5072
+ run();
5073
+ };
5074
+ const onAbort = () => finish(() => reject(new DaemonRequestCancelledError(false)));
5075
+ if (signal.aborted) {
5076
+ onAbort();
5077
+ return;
5078
+ }
5079
+ signal.addEventListener("abort", onAbort, { once: true });
5080
+ Promise.resolve().then(() => ensure(paths, signal)).then(() => finish(() => resolve9()), (error) => finish(() => reject(signal.aborted ? new DaemonRequestCancelledError(false) : error)));
5081
+ });
5082
+ }
3276
5083
  function daemonUnreachableResult(paths, error) {
3277
5084
  const text2 = [
3278
5085
  `LSP daemon unreachable: ${errorText(error)}.`,
@@ -3284,39 +5091,84 @@ function daemonUnreachableResult(paths, error) {
3284
5091
  `);
3285
5092
  return { content: [{ type: "text", text: text2 }], isError: true };
3286
5093
  }
3287
- function sendToolCall(socketPath, name, args, timeoutMs) {
3288
- return new Promise((resolve6, reject) => {
3289
- const socket = connect2(socketPath);
5094
+ function sendToolCall(paths, token, name, args, options) {
5095
+ return new Promise((resolve9, reject) => {
5096
+ const socket = connect(paths.socket);
5097
+ const requestId = allocateProxyRequestId();
3290
5098
  let settled = false;
3291
5099
  let requestWritten = false;
5100
+ let cancelAfterWrite = false;
5101
+ const cancelPayload = () => encodeJsonLine({
5102
+ jsonrpc: "2.0",
5103
+ method: "$/cancelRequest",
5104
+ params: { _omo: authEnvelope(token), id: requestId }
5105
+ });
3292
5106
  const finish = (run) => {
3293
5107
  if (settled)
3294
5108
  return;
3295
5109
  settled = true;
3296
5110
  clearTimeout(timer);
3297
- socket.destroy();
5111
+ options.signal?.removeEventListener("abort", onAbort);
5112
+ destroyAfterCancel();
3298
5113
  run();
3299
5114
  };
3300
- const timer = setTimeout(() => finish(() => reject(new DaemonRequestError("daemon request timed out", requestWritten))), timeoutMs);
5115
+ const sendCancel = () => {
5116
+ if (!requestWritten) {
5117
+ cancelAfterWrite = true;
5118
+ return;
5119
+ }
5120
+ if (!socket.writable)
5121
+ return;
5122
+ socket.write(cancelPayload());
5123
+ };
5124
+ const destroyAfterCancel = () => {
5125
+ socket.destroy();
5126
+ };
5127
+ const onAbort = () => {
5128
+ sendCancel();
5129
+ finish(() => reject(new DaemonRequestCancelledError(requestWritten)));
5130
+ };
5131
+ const timer = setTimeout(() => {
5132
+ sendCancel();
5133
+ finish(() => reject(new DaemonRequestError("daemon request timed out", requestWritten)));
5134
+ }, options.timeoutMs);
3301
5135
  timer.unref();
5136
+ if (options.signal?.aborted) {
5137
+ onAbort();
5138
+ return;
5139
+ }
5140
+ options.signal?.addEventListener("abort", onAbort, { once: true });
3302
5141
  const decoder = createLineDecoder((message) => {
3303
- const result = toToolResult(message);
5142
+ if (isAuthErrorResponse(message)) {
5143
+ finish(() => reject(new DaemonAuthenticationRejectedError));
5144
+ return;
5145
+ }
5146
+ const result = toToolResult(message, requestId);
3304
5147
  if (result)
3305
- finish(() => resolve6(result));
5148
+ finish(() => resolve9(result));
3306
5149
  else
3307
5150
  finish(() => reject(new DaemonRequestError("invalid daemon response", requestWritten)));
3308
5151
  });
3309
5152
  socket.once("connect", () => {
3310
- requestWritten = true;
3311
- socket.write(encodeJsonLine({ jsonrpc: "2.0", id: REQUEST_ID, method: "tools/call", params: { name, arguments: args } }));
5153
+ const payload = encodeJsonLine({
5154
+ jsonrpc: "2.0",
5155
+ id: requestId,
5156
+ method: "tools/call",
5157
+ params: { _omo: authEnvelope(token), name, arguments: args }
5158
+ });
5159
+ socket.write(payload, () => {
5160
+ requestWritten = true;
5161
+ if (cancelAfterWrite && socket.writable)
5162
+ socket.write(cancelPayload());
5163
+ });
3312
5164
  });
3313
5165
  socket.on("data", (chunk) => decoder.push(chunk));
3314
5166
  socket.once("error", (error) => finish(() => reject(new DaemonRequestError(error.message, requestWritten))));
3315
5167
  socket.once("close", () => finish(() => reject(new DaemonRequestError("daemon connection closed", requestWritten))));
3316
5168
  });
3317
5169
  }
3318
- function toToolResult(message) {
3319
- if (!isPlainRecord(message) || message["id"] !== REQUEST_ID)
5170
+ function toToolResult(message, requestId) {
5171
+ if (!isPlainRecord(message) || message["id"] !== requestId)
3320
5172
  return null;
3321
5173
  const result = message["result"];
3322
5174
  if (!isPlainRecord(result) || !Array.isArray(result["content"]))
@@ -3327,167 +5179,436 @@ function toToolResult(message) {
3327
5179
  details: result["details"]
3328
5180
  };
3329
5181
  }
5182
+ function allocateProxyRequestId() {
5183
+ const id = nextProxyRequestId;
5184
+ nextProxyRequestId += 1;
5185
+ if (nextProxyRequestId > Number.MAX_SAFE_INTEGER)
5186
+ nextProxyRequestId = 1;
5187
+ return id;
5188
+ }
5189
+ function isRetryableTool(name) {
5190
+ return name !== "rename" && name !== "lsp_rename";
5191
+ }
3330
5192
  function errorText(error) {
3331
5193
  return error instanceof Error ? error.message : String(error);
3332
5194
  }
5195
+ var OMO_LSP_DAEMON_CLI2 = "OMO_LSP_DAEMON_CLI";
5196
+ var OMO_LSP_DAEMON_VERSION2 = "OMO_LSP_DAEMON_VERSION";
5197
+ var DAEMON_VERSION_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
5198
+
5199
+ class InvalidRuntimeOverrideError2 extends Error {
5200
+ code = "invalid_runtime_override";
5201
+ reason;
5202
+ constructor(reason, message) {
5203
+ super(message);
5204
+ this.name = "InvalidRuntimeOverrideError";
5205
+ this.reason = reason;
5206
+ }
5207
+ }
5208
+ function validateDaemonVersion2(version) {
5209
+ if (!DAEMON_VERSION_PATTERN2.test(version)) {
5210
+ throw new Error("LSP daemon version must match [A-Za-z0-9][A-Za-z0-9._+-]{0,127}");
5211
+ }
5212
+ return version;
5213
+ }
5214
+ function resolveDaemonRuntime2(env, defaults) {
5215
+ const cliOverride = env[OMO_LSP_DAEMON_CLI2];
5216
+ const versionOverride = env[OMO_LSP_DAEMON_VERSION2];
5217
+ const hasCliOverride = cliOverride !== undefined;
5218
+ const hasVersionOverride = versionOverride !== undefined;
5219
+ if (hasCliOverride !== hasVersionOverride) {
5220
+ throw new InvalidRuntimeOverrideError2("paired_values_required", `${OMO_LSP_DAEMON_CLI2} and ${OMO_LSP_DAEMON_VERSION2} must be set together`);
5221
+ }
5222
+ if (!hasCliOverride || !hasVersionOverride) {
5223
+ if (!isAbsolute4(defaults.cliPath)) {
5224
+ throw new InvalidRuntimeOverrideError2("packaged_cli_must_be_absolute", "Packaged LSP daemon CLI path must be absolute");
5225
+ }
5226
+ return { cliPath: defaults.cliPath, version: validateDaemonVersion2(defaults.version) };
5227
+ }
5228
+ if (!isAbsolute4(cliOverride)) {
5229
+ throw new InvalidRuntimeOverrideError2("cli_must_be_absolute", `${OMO_LSP_DAEMON_CLI2} must be an absolute path to an existing regular file`);
5230
+ }
5231
+ const stat = statSync5(cliOverride, { throwIfNoEntry: false });
5232
+ if (!stat) {
5233
+ throw new InvalidRuntimeOverrideError2("cli_not_found", `${OMO_LSP_DAEMON_CLI2} points to a missing file`);
5234
+ }
5235
+ if (!stat.isFile()) {
5236
+ throw new InvalidRuntimeOverrideError2("cli_not_file", `${OMO_LSP_DAEMON_CLI2} must point to a regular file`);
5237
+ }
5238
+ return { cliPath: cliOverride, version: validateDaemonVersion2(versionOverride) };
5239
+ }
5240
+ function callDiagnosticsViaDaemon2(filePath, options) {
5241
+ return callDiagnosticsViaDaemon(filePath, options);
5242
+ }
5243
+
5244
+ // ../../../../lsp-core/src/post-edit/orchestration.ts
5245
+ import { extname as extname2 } from "node:path";
5246
+ var DEFAULT_MAX_CONCURRENCY = 4;
5247
+ var CLEAN_DIAGNOSTICS_TEXT = "No diagnostics found";
5248
+ function createPostEditNotConfiguredCache() {
5249
+ return { notConfiguredExtensions: new Set };
5250
+ }
5251
+ async function collectPostEditDiagnostics(input) {
5252
+ const cache = input.cache === undefined ? createPostEditNotConfiguredCache() : input.cache;
5253
+ const filePaths = firstSeenDiagnosticTargets(input.filePaths, cache);
5254
+ const results = await runBoundedDiagnostics(filePaths, input.runDiagnostics, input.maxConcurrency);
5255
+ const blocks = [];
5256
+ const observations = [];
5257
+ for (const result of results) {
5258
+ const classification = classifyDiagnostics(result.outcome);
5259
+ switch (classification.kind) {
5260
+ case "clean":
5261
+ observations.push({ filePath: result.filePath, kind: "clean" });
5262
+ break;
5263
+ case "not_configured":
5264
+ cache.notConfiguredExtensions.add(classification.extension);
5265
+ observations.push({ filePath: result.filePath, kind: "not_configured" });
5266
+ break;
5267
+ case "block":
5268
+ blocks.push({ filePath: result.filePath, diagnostics: classification.diagnostics });
5269
+ observations.push({ filePath: result.filePath, kind: "block" });
5270
+ break;
5271
+ default: {
5272
+ const exhaustive = classification;
5273
+ return exhaustive;
5274
+ }
5275
+ }
5276
+ }
5277
+ return { blocks, observations };
5278
+ }
5279
+ function firstSeenDiagnosticTargets(filePaths, cache) {
5280
+ const seen = new Set;
5281
+ const targets = [];
5282
+ for (const filePath of filePaths) {
5283
+ if (filePath.length === 0 || seen.has(filePath))
5284
+ continue;
5285
+ seen.add(filePath);
5286
+ const extension = extensionKey(filePath);
5287
+ if (extension !== undefined && cache.notConfiguredExtensions.has(extension))
5288
+ continue;
5289
+ targets.push({ index: targets.length, filePath });
5290
+ }
5291
+ return targets;
5292
+ }
5293
+ async function runBoundedDiagnostics(filePaths, runDiagnostics, maxConcurrency) {
5294
+ const results = [];
5295
+ const workerCount = Math.min(Math.max(1, maxConcurrency ?? DEFAULT_MAX_CONCURRENCY), filePaths.length);
5296
+ let nextIndex = 0;
5297
+ async function worker() {
5298
+ for (;; ) {
5299
+ const target = filePaths[nextIndex];
5300
+ nextIndex += 1;
5301
+ if (target === undefined)
5302
+ return;
5303
+ results[target.index] = {
5304
+ index: target.index,
5305
+ filePath: target.filePath,
5306
+ outcome: await collectFileDiagnostics(target.filePath, runDiagnostics)
5307
+ };
5308
+ }
5309
+ }
5310
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
5311
+ return results.filter((result) => result !== undefined);
5312
+ }
5313
+ async function collectFileDiagnostics(filePath, runDiagnostics) {
5314
+ try {
5315
+ return normalizeDiagnosticsOutcome(await runDiagnostics(filePath));
5316
+ } catch (error) {
5317
+ if (error instanceof Error)
5318
+ return formatDiagnosticsError(error);
5319
+ return normalizeDiagnosticsText(String(error));
5320
+ }
5321
+ }
5322
+ function normalizeDiagnosticsOutcome(outcome) {
5323
+ if (typeof outcome === "string")
5324
+ return normalizeDiagnosticsText(outcome);
5325
+ return outcome;
5326
+ }
5327
+ function normalizeDiagnosticsText(text2) {
5328
+ return text2.trim();
5329
+ }
5330
+ function formatDiagnosticsError(error) {
5331
+ const message = normalizeDiagnosticsText(error.message);
5332
+ return message.length > 0 ? message : normalizeDiagnosticsText(String(error));
5333
+ }
5334
+ function classifyDiagnostics(outcome) {
5335
+ if (typeof outcome !== "string") {
5336
+ return { kind: "not_configured", extension: outcome.extension };
5337
+ }
5338
+ const diagnostics = outcome;
5339
+ if (diagnostics.length === 0 || diagnostics === CLEAN_DIAGNOSTICS_TEXT)
5340
+ return { kind: "clean" };
5341
+ return { kind: "block", diagnostics };
5342
+ }
5343
+ function extensionKey(filePath) {
5344
+ const extension = extname2(filePath).toLowerCase();
5345
+ return extension.length === 0 ? undefined : extension;
5346
+ }
5347
+ // ../../../../lsp-core/src/request-context.ts
5348
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
5349
+ import { existsSync as existsSync11, realpathSync as realpathSync4, statSync as statSync6 } from "node:fs";
5350
+ import { basename as basename3, delimiter as delimiter4, dirname as dirname2, isAbsolute as isAbsolute5, join as join7, relative as relative5, resolve as resolve9 } from "node:path";
3333
5351
 
3334
- // components/lsp/src/codex-hook.ts
3335
- import { readFileSync as readFileSync9 } from "node:fs";
5352
+ class LspRequestContextParseError2 extends Error {
5353
+ code;
5354
+ name = "LspRequestContextParseError";
5355
+ constructor(code, message) {
5356
+ super(message);
5357
+ this.code = code;
5358
+ }
5359
+ }
5360
+ var storage2 = new AsyncLocalStorage2;
5361
+ var CONTEXT_FIELDS2 = new Set(["cwd", "projectConfigPaths", "userConfigPath", "installDecisionsPath", "capabilities"]);
5362
+ var CAPABILITY_FIELDS2 = new Set(["installDecisionTool"]);
5363
+ function parseLspRequestContext2(value) {
5364
+ if (!isRecord7(value)) {
5365
+ throw new LspRequestContextParseError2("invalid_context", "LSP request context must be an object.");
5366
+ }
5367
+ rejectUnknownFields2(value, CONTEXT_FIELDS2, "context");
5368
+ const cwd = stringField2(value, "cwd");
5369
+ const projectConfigPaths = stringArrayField2(value, "projectConfigPaths");
5370
+ const userConfigPath = stringField2(value, "userConfigPath");
5371
+ const installDecisionsPath = stringField2(value, "installDecisionsPath");
5372
+ const capabilities = capabilitiesField2(value["capabilities"]);
5373
+ const canonical = canonicalCwd2(cwd);
5374
+ for (const path2 of projectConfigPaths) {
5375
+ requireAbsolutePath2(path2, "projectConfigPaths");
5376
+ const projectPath = canonicalizeExistingOrNearestAncestor2(path2);
5377
+ if (!isPathInside2(canonical, projectPath)) {
5378
+ throw new LspRequestContextParseError2("project_config_outside_cwd", `Project LSP config path must be inside cwd: ${path2}`);
5379
+ }
5380
+ }
5381
+ requireAbsolutePath2(userConfigPath, "userConfigPath");
5382
+ requireAbsolutePath2(installDecisionsPath, "installDecisionsPath");
5383
+ return {
5384
+ cwd: canonical,
5385
+ projectConfigPaths: projectConfigPaths.map((path2) => canonicalizeExistingOrNearestAncestor2(path2)),
5386
+ userConfigPath,
5387
+ installDecisionsPath,
5388
+ capabilities
5389
+ };
5390
+ }
5391
+ function canonicalCwd2(cwd) {
5392
+ const resolved = resolve9(cwd);
5393
+ if (!existsSync11(resolved) || !statSync6(resolved).isDirectory()) {
5394
+ throw new LspRequestContextParseError2("invalid_cwd", `LSP request cwd must be an existing directory: ${cwd}`);
5395
+ }
5396
+ return realpathSync4(resolved);
5397
+ }
5398
+ function canonicalizeExistingOrNearestAncestor2(path2) {
5399
+ let current = resolve9(path2);
5400
+ const suffix = [];
5401
+ while (true) {
5402
+ try {
5403
+ const existing = realpathSync4(current);
5404
+ return suffix.length === 0 ? existing : join7(existing, ...suffix);
5405
+ } catch (error) {
5406
+ if (!isMissingPathError2(error))
5407
+ throw error;
5408
+ const parent = dirname2(current);
5409
+ if (parent === current)
5410
+ throw error;
5411
+ suffix.unshift(basename3(current));
5412
+ current = parent;
5413
+ }
5414
+ }
5415
+ }
5416
+ function capabilitiesField2(value) {
5417
+ if (!isRecord7(value)) {
5418
+ throw new LspRequestContextParseError2("invalid_capabilities", "LSP request capabilities must be an object.");
5419
+ }
5420
+ rejectUnknownFields2(value, CAPABILITY_FIELDS2, "capabilities");
5421
+ const installDecisionTool = value["installDecisionTool"];
5422
+ if (typeof installDecisionTool !== "boolean") {
5423
+ throw new LspRequestContextParseError2("invalid_install_decision_capability", "LSP request capabilities.installDecisionTool must be a boolean.");
5424
+ }
5425
+ return { installDecisionTool };
5426
+ }
5427
+ function stringField2(value, field) {
5428
+ const fieldValue = value[field];
5429
+ if (typeof fieldValue !== "string" || fieldValue.length === 0) {
5430
+ throw new LspRequestContextParseError2("invalid_field", `LSP request context.${field} must be a non-empty string.`);
5431
+ }
5432
+ return fieldValue;
5433
+ }
5434
+ function stringArrayField2(value, field) {
5435
+ const fieldValue = value[field];
5436
+ if (!Array.isArray(fieldValue) || !fieldValue.every((item) => typeof item === "string" && item.length > 0)) {
5437
+ throw new LspRequestContextParseError2("invalid_field", `LSP request context.${field} must be a non-empty string array.`);
5438
+ }
5439
+ return fieldValue;
5440
+ }
5441
+ function requireAbsolutePath2(path2, field) {
5442
+ if (!isAbsolute5(path2)) {
5443
+ throw new LspRequestContextParseError2("relative_path", `LSP request context.${field} must be absolute: ${path2}`);
5444
+ }
5445
+ }
5446
+ function isPathInside2(parent, child) {
5447
+ const childPath = resolve9(child);
5448
+ const relativePath = relative5(parent, childPath);
5449
+ return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute5(relativePath);
5450
+ }
5451
+ function isMissingPathError2(error) {
5452
+ const code = errorCode2(error);
5453
+ return code === "ENOENT" || code === "ENOTDIR";
5454
+ }
5455
+ function rejectUnknownFields2(value, allowed, scope) {
5456
+ const unknown = Object.keys(value).filter((key) => !allowed.has(key));
5457
+ if (unknown.length > 0) {
5458
+ throw new LspRequestContextParseError2("unknown_field", `Unknown LSP request ${scope} field: ${unknown.join(", ")}`);
5459
+ }
5460
+ }
5461
+ function isRecord7(value) {
5462
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5463
+ }
5464
+ function errorCode2(error) {
5465
+ if (!error || typeof error !== "object" || !("code" in error))
5466
+ return;
5467
+ const code = Reflect.get(error, "code");
5468
+ return typeof code === "string" ? code : undefined;
5469
+ }
3336
5470
 
3337
- // components/lsp/src/daemon-cli-path.ts
3338
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
5471
+ // src/daemon-cli-path.ts
5472
+ import { existsSync as existsSync13, readFileSync as readFileSync6 } from "node:fs";
3339
5473
  import { createRequire as createRequire2 } from "node:module";
3340
- import { dirname as dirname6, join as join9 } from "node:path";
5474
+ import { dirname as dirname9, join as join9 } from "node:path";
3341
5475
  import { fileURLToPath as fileURLToPath4 } from "node:url";
3342
5476
  var requireFromHere2 = createRequire2(import.meta.url);
3343
- var PACKAGE_LSP_DAEMON_CLI = "@code-yeongyu/lsp-daemon/dist/cli.js";
3344
- var CODEX_LSP_DAEMON_CLI_ENV2 = "CODEX_LSP_DAEMON_CLI";
3345
- var CODEX_LSP_DAEMON_VERSION_ENV2 = "CODEX_LSP_DAEMON_VERSION";
5477
+ var PACKAGE_LSP_DAEMON_CLI = "@code-yeongyu/lsp-daemon/cli";
3346
5478
  function ensureLspDaemonCliEnv(env = process.env) {
3347
- const configuredCli = env[CODEX_LSP_DAEMON_CLI_ENV2]?.trim();
3348
- const resolution = configuredCli ? resolveConfiguredLspDaemonCli(configuredCli) : resolveLspDaemonCli();
3349
- if (!configuredCli)
3350
- env[CODEX_LSP_DAEMON_CLI_ENV2] = resolution.cliPath;
3351
- if (!env[CODEX_LSP_DAEMON_VERSION_ENV2]?.trim() && resolution.version !== null) {
3352
- env[CODEX_LSP_DAEMON_VERSION_ENV2] = resolution.version;
3353
- }
3354
- }
3355
- function resolveLspDaemonCliPath() {
3356
- return resolveLspDaemonCli().cliPath;
5479
+ const runtime = resolveDaemonRuntime2(env, resolveLspDaemonCli());
5480
+ env[OMO_LSP_DAEMON_CLI2] = runtime.cliPath;
5481
+ env[OMO_LSP_DAEMON_VERSION2] = runtime.version;
5482
+ }
5483
+ function resolveLspDaemonCliPath(env = process.env) {
5484
+ const runtime = resolveDaemonRuntime2(env, resolveLspDaemonCli());
5485
+ if (env[OMO_LSP_DAEMON_CLI2] === undefined)
5486
+ env[OMO_LSP_DAEMON_CLI2] = runtime.cliPath;
5487
+ if (env[OMO_LSP_DAEMON_VERSION2] === undefined)
5488
+ env[OMO_LSP_DAEMON_VERSION2] = runtime.version;
5489
+ return runtime.cliPath;
3357
5490
  }
3358
5491
  function resolveLspDaemonCli() {
3359
5492
  const packageCli = resolvePackageLspDaemonCliPath();
3360
5493
  if (packageCli !== null)
3361
5494
  return resolveConfiguredLspDaemonCli(packageCli);
3362
5495
  const bundledCli = fileURLToPath4(new URL("../../lsp-daemon/dist/cli.js", import.meta.url));
3363
- if (existsSync9(bundledCli))
5496
+ if (existsSync13(bundledCli))
3364
5497
  return resolveConfiguredLspDaemonCli(bundledCli);
3365
5498
  return resolveConfiguredLspDaemonCli(bundledCli);
3366
5499
  }
3367
5500
  function resolvePackageLspDaemonCliPath() {
3368
5501
  try {
3369
5502
  return requireFromHere2.resolve(PACKAGE_LSP_DAEMON_CLI);
3370
- } catch {
5503
+ } catch (error) {
5504
+ if (!(error instanceof Error))
5505
+ throw error;
3371
5506
  return null;
3372
5507
  }
3373
5508
  }
3374
5509
  function resolveConfiguredLspDaemonCli(cliPath) {
5510
+ const version = readDaemonPackageVersion(cliPath);
5511
+ if (version === null) {
5512
+ throw new Error(`Unable to determine packaged LSP daemon version beside ${cliPath}`);
5513
+ }
3375
5514
  return {
3376
5515
  cliPath,
3377
- version: readDaemonPackageVersion(cliPath)
5516
+ version
3378
5517
  };
3379
5518
  }
3380
5519
  function readDaemonPackageVersion(cliPath) {
3381
5520
  try {
3382
- const parsed = JSON.parse(readFileSync6(join9(dirname6(cliPath), "package.json"), "utf8"));
3383
- if (isRecord4(parsed) && typeof parsed["version"] === "string" && parsed["version"].length > 0) {
5521
+ const parsed = JSON.parse(readFileSync6(join9(dirname9(cliPath), "package.json"), "utf8"));
5522
+ if (isRecord9(parsed) && typeof parsed["version"] === "string" && parsed["version"].length > 0) {
3384
5523
  return parsed["version"];
3385
5524
  }
3386
- } catch {}
5525
+ } catch (error) {
5526
+ if (!(error instanceof Error))
5527
+ throw error;
5528
+ }
3387
5529
  return null;
3388
5530
  }
3389
- function isRecord4(value) {
5531
+ function isRecord9(value) {
3390
5532
  return typeof value === "object" && value !== null && !Array.isArray(value);
3391
5533
  }
3392
5534
 
3393
- // components/lsp/src/lsp-session-state.ts
5535
+ // src/lsp-session-state.ts
3394
5536
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "node:fs";
3395
- import { homedir as homedir4 } from "node:os";
3396
- import { dirname as dirname8, extname as extname2, join as join11 } from "node:path";
5537
+ import { homedir } from "node:os";
5538
+ import { dirname as dirname11, join as join10 } from "node:path";
3397
5539
  function sessionIdFrom(input) {
3398
5540
  return typeof input.session_id === "string" && input.session_id.length > 0 ? input.session_id : undefined;
3399
5541
  }
3400
- function shouldSkipUnavailableLspDiagnostics(filePath, sessionId) {
5542
+ function readLspPostEditCache(sessionId) {
3401
5543
  if (sessionId === undefined)
3402
- return false;
3403
- const state = readSessionState(sessionStatePath(sessionId));
3404
- const extension = extensionKey(filePath);
3405
- return extension !== undefined && state.postCompactProbePending !== true && state.unavailableExtensions.includes(extension);
3406
- }
3407
- function recordLspDiagnosticsObservations(sessionId, observations) {
3408
- if (sessionId === undefined || observations.length === 0)
3409
- return;
5544
+ return { notConfiguredExtensions: new Set };
3410
5545
  const state = readSessionState(sessionStatePath(sessionId));
3411
- const unavailableExtensions = new Set(state.unavailableExtensions);
3412
- for (const observation of observations) {
3413
- const extension = extensionKey(observation.filePath);
3414
- if (extension === undefined)
3415
- continue;
3416
- if (observation.unavailable) {
3417
- unavailableExtensions.add(extension);
3418
- } else {
3419
- unavailableExtensions.delete(extension);
3420
- }
3421
- }
3422
- writeSessionState(sessionStatePath(sessionId), { unavailableExtensions: [...unavailableExtensions].sort() });
5546
+ return { notConfiguredExtensions: new Set(state.notConfiguredExtensions) };
3423
5547
  }
3424
- function markLspSessionCompacted(sessionId) {
5548
+ function writeLspPostEditCache(sessionId, cache) {
3425
5549
  if (sessionId === undefined)
3426
5550
  return;
3427
- const state = readSessionState(sessionStatePath(sessionId));
3428
- if (state.unavailableExtensions.length === 0)
3429
- return;
3430
5551
  writeSessionState(sessionStatePath(sessionId), {
3431
- unavailableExtensions: state.unavailableExtensions,
3432
- postCompactProbePending: true
5552
+ notConfiguredExtensions: [...cache.notConfiguredExtensions].sort()
3433
5553
  });
3434
5554
  }
3435
- function isUnavailableLspDiagnostics(diagnostics) {
3436
- const normalized = diagnostics.trim();
3437
- return normalized.includes("LSP request timeout (method: initialize)") || normalized.includes("LSP server is still initializing") || normalized.includes("NOT INSTALLED") || normalized.includes("Command not found:");
5555
+ function markLspSessionCompacted(sessionId) {
5556
+ if (sessionId === undefined)
5557
+ return;
5558
+ writeSessionState(sessionStatePath(sessionId), emptyState());
3438
5559
  }
3439
5560
  function isLspDaemonUnreachableDiagnostics(diagnostics) {
3440
5561
  return diagnostics.includes("LSP daemon unreachable");
3441
5562
  }
3442
5563
  function sessionStatePath(sessionId) {
3443
- const root = process.env["PLUGIN_DATA"] ?? join11(homedir4(), ".codex", "codex-lsp");
3444
- return join11(root, "sessions", `${safePathSegment(sessionId)}.json`);
5564
+ const root = process.env["PLUGIN_DATA"] ?? join10(homedir(), ".codex", "codex-lsp");
5565
+ return join10(root, "sessions", `${safePathSegment(sessionId)}.json`);
3445
5566
  }
3446
- function readSessionState(path) {
5567
+ function readSessionState(path2) {
3447
5568
  try {
3448
- const parsed = JSON.parse(readFileSync8(path, "utf8"));
3449
- if (isLspSessionState(parsed))
3450
- return parsed;
5569
+ const parsed = JSON.parse(readFileSync8(path2, "utf8"));
5570
+ if (isRecord10(parsed) && isLspSessionState(parsed))
5571
+ return normalizeSessionState(parsed);
3451
5572
  return emptyState();
3452
5573
  } catch (error) {
3453
- if (error instanceof SyntaxError || isRecord6(error) && error["code"] === "ENOENT")
5574
+ if (error instanceof SyntaxError || isRecord10(error) && error["code"] === "ENOENT")
3454
5575
  return emptyState();
3455
5576
  throw error;
3456
5577
  }
3457
5578
  }
3458
- function writeSessionState(path, state) {
3459
- mkdirSync4(dirname8(path), { recursive: true });
3460
- writeFileSync3(path, `${JSON.stringify(state)}
5579
+ function writeSessionState(path2, state) {
5580
+ mkdirSync4(dirname11(path2), { recursive: true });
5581
+ writeFileSync3(path2, `${JSON.stringify(state)}
3461
5582
  `);
3462
5583
  }
3463
5584
  function emptyState() {
3464
- return { unavailableExtensions: [] };
3465
- }
3466
- function extensionKey(filePath) {
3467
- const extension = extname2(filePath).toLowerCase();
3468
- return extension.length === 0 ? undefined : extension;
5585
+ return { notConfiguredExtensions: [] };
3469
5586
  }
3470
5587
  function safePathSegment(value) {
3471
5588
  return value.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 120) || "unknown-session";
3472
5589
  }
3473
5590
  function isLspSessionState(value) {
3474
- if (!isRecord6(value) || !Array.isArray(value["unavailableExtensions"]))
3475
- return false;
3476
- const postCompactProbePending = value["postCompactProbePending"];
3477
- return value["unavailableExtensions"].every((item) => typeof item === "string") && (postCompactProbePending === undefined || typeof postCompactProbePending === "boolean");
5591
+ const notConfiguredExtensions = value["notConfiguredExtensions"] ?? value["unavailableExtensions"];
5592
+ return Array.isArray(notConfiguredExtensions) && notConfiguredExtensions.every((item) => typeof item === "string");
3478
5593
  }
3479
- function isRecord6(value) {
5594
+ function normalizeSessionState(value) {
5595
+ const notConfiguredExtensions = value["notConfiguredExtensions"] ?? value["unavailableExtensions"];
5596
+ return {
5597
+ notConfiguredExtensions: Array.isArray(notConfiguredExtensions) ? notConfiguredExtensions.filter((item) => typeof item === "string").sort() : []
5598
+ };
5599
+ }
5600
+ function isRecord10(value) {
3480
5601
  return typeof value === "object" && value !== null && !Array.isArray(value);
3481
5602
  }
3482
5603
 
3483
- // components/lsp/src/mutated-file-paths.ts
5604
+ // src/mutated-file-paths.ts
3484
5605
  var MUTATION_TOOL_NAMES = new Set(["apply_patch", "write", "edit", "multiedit", "multi_edit"]);
3485
5606
  function extractMutatedFilePaths(input) {
3486
5607
  if (!isMutationTool(input.tool_name))
3487
5608
  return [];
3488
5609
  if (isFailedToolResponse(input.tool_response))
3489
5610
  return [];
3490
- const toolInput = isRecord7(input.tool_input) ? input.tool_input : {};
5611
+ const toolInput = isRecord11(input.tool_input) ? input.tool_input : {};
3491
5612
  const paths = new Set;
3492
5613
  addStringValue(paths, toolInput["path"]);
3493
5614
  addStringValue(paths, toolInput["filePath"]);
@@ -3506,7 +5627,7 @@ function isMutationTool(value) {
3506
5627
  return MUTATION_TOOL_NAMES.has(value.toLowerCase());
3507
5628
  }
3508
5629
  function isFailedToolResponse(value) {
3509
- if (!isRecord7(value))
5630
+ if (!isRecord11(value))
3510
5631
  return false;
3511
5632
  return value["isError"] === true || value["is_error"] === true || value["error"] === true || value["status"] === "error";
3512
5633
  }
@@ -3532,9 +5653,9 @@ function addPatchInput(paths, value) {
3532
5653
  return;
3533
5654
  for (const line of value.split(`
3534
5655
  `)) {
3535
- const path = extractPatchHeaderPath(line);
3536
- if (path !== undefined)
3537
- paths.add(path);
5656
+ const path2 = extractPatchHeaderPath(line);
5657
+ if (path2 !== undefined)
5658
+ paths.add(path2);
3538
5659
  }
3539
5660
  }
3540
5661
  function extractPatchHeaderPath(line) {
@@ -3549,7 +5670,7 @@ function addPatchFiles(paths, value) {
3549
5670
  if (!Array.isArray(value))
3550
5671
  return;
3551
5672
  for (const item of value) {
3552
- if (!isRecord7(item))
5673
+ if (!isRecord11(item))
3553
5674
  continue;
3554
5675
  addStringValue(paths, item["path"]);
3555
5676
  addStringValue(paths, item["filePath"]);
@@ -3558,18 +5679,15 @@ function addPatchFiles(paths, value) {
3558
5679
  addStringValue(paths, item["move_path"]);
3559
5680
  }
3560
5681
  }
3561
- function isRecord7(value) {
5682
+ function isRecord11(value) {
3562
5683
  return typeof value === "object" && value !== null && !Array.isArray(value);
3563
5684
  }
3564
5685
 
3565
- // components/lsp/src/codex-hook.ts
3566
- var CLEAN_DIAGNOSTICS_TEXT = "No diagnostics found";
3567
- var UNSUPPORTED_EXTENSION_TEXT = "No LSP server configured for extension:";
5686
+ // src/codex-hook.ts
3568
5687
  var DIAGNOSTIC_START_PATTERN = /(?:error|warning|information|hint)\[[^\]\r\n]+\] \(\d+\) at \d+:\d+:/g;
3569
5688
  var DIAGNOSTIC_CHUNK_PATTERN = /^(?:error|warning|information|hint)\[[^\]\r\n]+\] \(\d+\) at \d+:\d+:/;
3570
5689
  var DEFAULT_MAX_HOOK_FEEDBACK_CHARS = 8000;
3571
5690
  var CONTEXT_PRESSURE_MAX_HOOK_FEEDBACK_CHARS = 1200;
3572
- var MAX_CONCURRENT_DIAGNOSTICS = 4;
3573
5691
  var CONTEXT_PRESSURE_MARKERS = [
3574
5692
  "context compacted",
3575
5693
  "context_length_exceeded",
@@ -3581,29 +5699,47 @@ var CONTEXT_PRESSURE_MARKERS = [
3581
5699
  ];
3582
5700
  async function runLspDiagnosticsText(filePath) {
3583
5701
  ensureLspDaemonCliEnv();
3584
- const result = await callDiagnosticsViaDaemon(filePath, { context: currentRequestContext() });
5702
+ const result = await callDiagnosticsViaDaemon2(filePath, { context: codexLspRequestContext() });
5703
+ return postEditOutcomeFromDaemonResult(result);
5704
+ }
5705
+ function postEditOutcomeFromDaemonResult(result) {
5706
+ const availability = notConfiguredAvailability(result.details);
5707
+ if (availability !== undefined)
5708
+ return { kind: "not_configured", extension: availability.extension };
3585
5709
  return result.content.map((block) => block.text).join(`
3586
5710
  `);
3587
5711
  }
5712
+ function notConfiguredAvailability(details) {
5713
+ if (!isRecord12(details))
5714
+ return;
5715
+ const availability = details["availability"];
5716
+ if (!isRecord12(availability))
5717
+ return;
5718
+ if (availability["kind"] !== "not_configured")
5719
+ return;
5720
+ const extension = availability["extension"];
5721
+ return typeof extension === "string" && extension.length > 0 ? { extension } : undefined;
5722
+ }
5723
+ function codexLspRequestContext(env = process.env, cwd = process.cwd()) {
5724
+ const canonicalCwd3 = realpathSync6(resolve11(cwd));
5725
+ const codexHome = resolve11(env["CODEX_HOME"]?.trim() || join11(homedir3(), ".codex"));
5726
+ return parseLspRequestContext2({
5727
+ cwd: canonicalCwd3,
5728
+ projectConfigPaths: [join11(canonicalCwd3, ".codex", "lsp-client.json")],
5729
+ userConfigPath: join11(codexHome, "lsp-client.json"),
5730
+ installDecisionsPath: join11(codexHome, "lsp-install-decisions.json"),
5731
+ capabilities: { installDecisionTool: true }
5732
+ });
5733
+ }
3588
5734
  async function runLspPostToolUseHook(input, runDiagnostics = runLspDiagnosticsText) {
3589
5735
  const sessionId = sessionIdFrom(input);
3590
- const filePaths = extractMutatedFilePaths(input).filter((filePath) => !shouldSkipUnavailableLspDiagnostics(filePath, sessionId));
5736
+ const filePaths = extractMutatedFilePaths(input);
3591
5737
  if (filePaths.length === 0)
3592
5738
  return "";
3593
- const blocks = [];
3594
- const observations = [];
3595
- for (const { filePath, diagnostics } of await collectDiagnostics(filePaths, runDiagnostics)) {
3596
- if (isLspDaemonUnreachableDiagnostics(diagnostics))
3597
- continue;
3598
- const unavailable = isUnavailableLspDiagnostics(diagnostics);
3599
- observations.push({ filePath, unavailable });
3600
- if (isCleanDiagnostics(diagnostics))
3601
- continue;
3602
- if (unavailable)
3603
- continue;
3604
- blocks.push({ filePath, diagnostics });
3605
- }
3606
- recordLspDiagnosticsObservations(sessionId, observations);
5739
+ const cache = readLspPostEditCache(sessionId);
5740
+ const result = await collectPostEditDiagnostics({ filePaths, runDiagnostics, cache });
5741
+ writeLspPostEditCache(sessionId, cache);
5742
+ const blocks = result.blocks.filter(({ diagnostics }) => !isLspDaemonUnreachableDiagnostics(diagnostics));
3607
5743
  if (blocks.length === 0)
3608
5744
  return "";
3609
5745
  const rawReason = blocks.map(formatDiagnosticBlock).join(`
@@ -3625,38 +5761,6 @@ async function runLspPostCompactHook(input) {
3625
5761
  markLspSessionCompacted(sessionIdFrom(input));
3626
5762
  return "";
3627
5763
  }
3628
- async function collectDiagnostics(filePaths, runDiagnostics) {
3629
- const results = [];
3630
- let nextIndex = 0;
3631
- const workerCount = Math.min(MAX_CONCURRENT_DIAGNOSTICS, filePaths.length);
3632
- async function worker() {
3633
- for (;; ) {
3634
- const index = nextIndex;
3635
- nextIndex += 1;
3636
- const filePath = filePaths[index];
3637
- if (filePath === undefined)
3638
- return;
3639
- results[index] = { filePath, diagnostics: await collectFileDiagnostics(filePath, runDiagnostics) };
3640
- }
3641
- }
3642
- await Promise.all(Array.from({ length: workerCount }, () => worker()));
3643
- return results;
3644
- }
3645
- async function collectFileDiagnostics(filePath, runDiagnostics) {
3646
- try {
3647
- return (await runDiagnostics(filePath)).trim();
3648
- } catch (error) {
3649
- return formatDiagnosticsError(error);
3650
- }
3651
- }
3652
- function formatDiagnosticsError(error) {
3653
- if (error instanceof Error) {
3654
- const message = error.message.trim();
3655
- if (message.length > 0)
3656
- return message;
3657
- }
3658
- return String(error).trim();
3659
- }
3660
5764
  function formatDiagnosticBlock({ filePath, diagnostics }) {
3661
5765
  return `LSP diagnostics after editing ${filePath}:
3662
5766
 
@@ -3739,14 +5843,11 @@ function limitHookText(text2, maxChars) {
3739
5843
  const head = text2.slice(0, maxChars - marker.length).replace(/[ \t\r\n]+$/, "");
3740
5844
  return `${head}${marker}`;
3741
5845
  }
3742
- function isCleanDiagnostics(diagnostics) {
3743
- return diagnostics.length === 0 || diagnostics === CLEAN_DIAGNOSTICS_TEXT || diagnostics.startsWith(UNSUPPORTED_EXTENSION_TEXT);
3744
- }
3745
- function isRecord8(value) {
5846
+ function isRecord12(value) {
3746
5847
  return typeof value === "object" && value !== null && !Array.isArray(value);
3747
5848
  }
3748
5849
 
3749
- // components/lsp/src/codex-hook-cli.ts
5850
+ // src/codex-hook-cli.ts
3750
5851
  async function runPostToolUseHookCli(stdin = processStdin) {
3751
5852
  await runHookCli((input) => runLspPostToolUseHook(input), stdin);
3752
5853
  }
@@ -3759,16 +5860,12 @@ async function runHookCli(runHook, stdin) {
3759
5860
  if (!raw.trim())
3760
5861
  return;
3761
5862
  const parsed = JSON.parse(raw);
3762
- const input = isRecord8(parsed) ? parsed : {};
5863
+ const input = isRecord12(parsed) ? parsed : {};
3763
5864
  const output = await runHook(input);
3764
5865
  if (output)
3765
5866
  process.stdout.write(output);
3766
5867
  } catch {
3767
5868
  return;
3768
- } finally {
3769
- await disposeDefaultLspManager().catch(() => {
3770
- return;
3771
- });
3772
5869
  }
3773
5870
  }
3774
5871
  async function readStdin(stdin) {
@@ -3780,7 +5877,7 @@ async function readStdin(stdin) {
3780
5877
  return raw;
3781
5878
  }
3782
5879
 
3783
- // components/lsp/src/cli.ts
5880
+ // src/cli.ts
3784
5881
  async function main() {
3785
5882
  const [command = "mcp", subcommand = ""] = argv.slice(2);
3786
5883
  if (command === "hook" && subcommand === "post-tool-use") {
@@ -3807,14 +5904,14 @@ main().catch((error) => {
3807
5904
  async function runPackageLspMcpCli() {
3808
5905
  const cliPath = resolveLspDaemonCliPath();
3809
5906
  const child = spawn3(execPath2, [cliPath, "mcp"], { stdio: "inherit" });
3810
- await new Promise((resolve6, reject) => {
5907
+ await new Promise((resolve10, reject) => {
3811
5908
  child.once("error", reject);
3812
5909
  child.once("exit", (code, signal) => {
3813
5910
  if (code !== null && code !== 0)
3814
5911
  process.exitCode = code;
3815
5912
  if (code === null && signal !== null)
3816
5913
  process.exitCode = 1;
3817
- resolve6();
5914
+ resolve10();
3818
5915
  });
3819
5916
  });
3820
5917
  }