whale-code 6.4.0

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 (319) hide show
  1. package/README.md +95 -0
  2. package/bin/swag-agent.js +9 -0
  3. package/bin/swagmanager-mcp.js +321 -0
  4. package/dist/cli/app.d.ts +26 -0
  5. package/dist/cli/app.js +64 -0
  6. package/dist/cli/chat/AgentSelector.d.ts +14 -0
  7. package/dist/cli/chat/AgentSelector.js +14 -0
  8. package/dist/cli/chat/ChatApp.d.ts +9 -0
  9. package/dist/cli/chat/ChatApp.js +267 -0
  10. package/dist/cli/chat/ChatInput.d.ts +39 -0
  11. package/dist/cli/chat/ChatInput.js +509 -0
  12. package/dist/cli/chat/MarkdownText.d.ts +10 -0
  13. package/dist/cli/chat/MarkdownText.js +20 -0
  14. package/dist/cli/chat/MessageList.d.ts +37 -0
  15. package/dist/cli/chat/MessageList.js +80 -0
  16. package/dist/cli/chat/ModelSelector.d.ts +20 -0
  17. package/dist/cli/chat/ModelSelector.js +73 -0
  18. package/dist/cli/chat/RewindViewer.d.ts +26 -0
  19. package/dist/cli/chat/RewindViewer.js +185 -0
  20. package/dist/cli/chat/StoreSelector.d.ts +14 -0
  21. package/dist/cli/chat/StoreSelector.js +24 -0
  22. package/dist/cli/chat/StreamingText.d.ts +12 -0
  23. package/dist/cli/chat/StreamingText.js +12 -0
  24. package/dist/cli/chat/SubagentPanel.d.ts +45 -0
  25. package/dist/cli/chat/SubagentPanel.js +110 -0
  26. package/dist/cli/chat/TeamPanel.d.ts +21 -0
  27. package/dist/cli/chat/TeamPanel.js +42 -0
  28. package/dist/cli/chat/ToolIndicator.d.ts +25 -0
  29. package/dist/cli/chat/ToolIndicator.js +436 -0
  30. package/dist/cli/chat/hooks/useAgentLoop.d.ts +39 -0
  31. package/dist/cli/chat/hooks/useAgentLoop.js +382 -0
  32. package/dist/cli/chat/hooks/useSlashCommands.d.ts +37 -0
  33. package/dist/cli/chat/hooks/useSlashCommands.js +387 -0
  34. package/dist/cli/commands/config-cmd.d.ts +10 -0
  35. package/dist/cli/commands/config-cmd.js +99 -0
  36. package/dist/cli/commands/doctor.d.ts +14 -0
  37. package/dist/cli/commands/doctor.js +172 -0
  38. package/dist/cli/commands/init.d.ts +16 -0
  39. package/dist/cli/commands/init.js +278 -0
  40. package/dist/cli/commands/mcp.d.ts +12 -0
  41. package/dist/cli/commands/mcp.js +162 -0
  42. package/dist/cli/login/LoginApp.d.ts +7 -0
  43. package/dist/cli/login/LoginApp.js +157 -0
  44. package/dist/cli/print-mode.d.ts +31 -0
  45. package/dist/cli/print-mode.js +202 -0
  46. package/dist/cli/serve-mode.d.ts +37 -0
  47. package/dist/cli/serve-mode.js +636 -0
  48. package/dist/cli/services/agent-definitions.d.ts +25 -0
  49. package/dist/cli/services/agent-definitions.js +91 -0
  50. package/dist/cli/services/agent-events.d.ts +178 -0
  51. package/dist/cli/services/agent-events.js +175 -0
  52. package/dist/cli/services/agent-loop.d.ts +90 -0
  53. package/dist/cli/services/agent-loop.js +762 -0
  54. package/dist/cli/services/agent-worker-base.d.ts +97 -0
  55. package/dist/cli/services/agent-worker-base.js +220 -0
  56. package/dist/cli/services/auth-service.d.ts +30 -0
  57. package/dist/cli/services/auth-service.js +160 -0
  58. package/dist/cli/services/background-processes.d.ts +126 -0
  59. package/dist/cli/services/background-processes.js +318 -0
  60. package/dist/cli/services/browser-auth.d.ts +24 -0
  61. package/dist/cli/services/browser-auth.js +180 -0
  62. package/dist/cli/services/claude-md-loader.d.ts +16 -0
  63. package/dist/cli/services/claude-md-loader.js +58 -0
  64. package/dist/cli/services/config-store.d.ts +47 -0
  65. package/dist/cli/services/config-store.js +79 -0
  66. package/dist/cli/services/debug-log.d.ts +10 -0
  67. package/dist/cli/services/debug-log.js +52 -0
  68. package/dist/cli/services/error-logger.d.ts +58 -0
  69. package/dist/cli/services/error-logger.js +269 -0
  70. package/dist/cli/services/file-history.d.ts +21 -0
  71. package/dist/cli/services/file-history.js +83 -0
  72. package/dist/cli/services/format-server-response.d.ts +16 -0
  73. package/dist/cli/services/format-server-response.js +440 -0
  74. package/dist/cli/services/git-context.d.ts +11 -0
  75. package/dist/cli/services/git-context.js +66 -0
  76. package/dist/cli/services/hooks.d.ts +85 -0
  77. package/dist/cli/services/hooks.js +258 -0
  78. package/dist/cli/services/interactive-tools.d.ts +125 -0
  79. package/dist/cli/services/interactive-tools.js +260 -0
  80. package/dist/cli/services/keybinding-manager.d.ts +52 -0
  81. package/dist/cli/services/keybinding-manager.js +115 -0
  82. package/dist/cli/services/local-tools.d.ts +22 -0
  83. package/dist/cli/services/local-tools.js +697 -0
  84. package/dist/cli/services/lsp-manager.d.ts +18 -0
  85. package/dist/cli/services/lsp-manager.js +717 -0
  86. package/dist/cli/services/mcp-client.d.ts +48 -0
  87. package/dist/cli/services/mcp-client.js +157 -0
  88. package/dist/cli/services/memory-manager.d.ts +16 -0
  89. package/dist/cli/services/memory-manager.js +57 -0
  90. package/dist/cli/services/model-manager.d.ts +18 -0
  91. package/dist/cli/services/model-manager.js +71 -0
  92. package/dist/cli/services/model-router.d.ts +26 -0
  93. package/dist/cli/services/model-router.js +149 -0
  94. package/dist/cli/services/permission-modes.d.ts +13 -0
  95. package/dist/cli/services/permission-modes.js +43 -0
  96. package/dist/cli/services/rewind.d.ts +84 -0
  97. package/dist/cli/services/rewind.js +194 -0
  98. package/dist/cli/services/ripgrep.d.ts +28 -0
  99. package/dist/cli/services/ripgrep.js +138 -0
  100. package/dist/cli/services/sandbox.d.ts +29 -0
  101. package/dist/cli/services/sandbox.js +97 -0
  102. package/dist/cli/services/server-tools.d.ts +61 -0
  103. package/dist/cli/services/server-tools.js +543 -0
  104. package/dist/cli/services/session-persistence.d.ts +23 -0
  105. package/dist/cli/services/session-persistence.js +99 -0
  106. package/dist/cli/services/subagent-worker.d.ts +19 -0
  107. package/dist/cli/services/subagent-worker.js +41 -0
  108. package/dist/cli/services/subagent.d.ts +47 -0
  109. package/dist/cli/services/subagent.js +647 -0
  110. package/dist/cli/services/system-prompt.d.ts +7 -0
  111. package/dist/cli/services/system-prompt.js +198 -0
  112. package/dist/cli/services/team-lead.d.ts +73 -0
  113. package/dist/cli/services/team-lead.js +512 -0
  114. package/dist/cli/services/team-state.d.ts +77 -0
  115. package/dist/cli/services/team-state.js +398 -0
  116. package/dist/cli/services/teammate.d.ts +31 -0
  117. package/dist/cli/services/teammate.js +689 -0
  118. package/dist/cli/services/telemetry.d.ts +61 -0
  119. package/dist/cli/services/telemetry.js +209 -0
  120. package/dist/cli/services/tools/agent-tools.d.ts +14 -0
  121. package/dist/cli/services/tools/agent-tools.js +347 -0
  122. package/dist/cli/services/tools/file-ops.d.ts +15 -0
  123. package/dist/cli/services/tools/file-ops.js +487 -0
  124. package/dist/cli/services/tools/search-tools.d.ts +8 -0
  125. package/dist/cli/services/tools/search-tools.js +186 -0
  126. package/dist/cli/services/tools/shell-exec.d.ts +10 -0
  127. package/dist/cli/services/tools/shell-exec.js +168 -0
  128. package/dist/cli/services/tools/task-manager.d.ts +28 -0
  129. package/dist/cli/services/tools/task-manager.js +209 -0
  130. package/dist/cli/services/tools/web-tools.d.ts +11 -0
  131. package/dist/cli/services/tools/web-tools.js +395 -0
  132. package/dist/cli/setup/SetupApp.d.ts +9 -0
  133. package/dist/cli/setup/SetupApp.js +191 -0
  134. package/dist/cli/shared/MatrixIntro.d.ts +4 -0
  135. package/dist/cli/shared/MatrixIntro.js +83 -0
  136. package/dist/cli/shared/Theme.d.ts +74 -0
  137. package/dist/cli/shared/Theme.js +127 -0
  138. package/dist/cli/shared/WhaleBanner.d.ts +10 -0
  139. package/dist/cli/shared/WhaleBanner.js +12 -0
  140. package/dist/cli/shared/markdown.d.ts +21 -0
  141. package/dist/cli/shared/markdown.js +756 -0
  142. package/dist/cli/status/StatusApp.d.ts +4 -0
  143. package/dist/cli/status/StatusApp.js +105 -0
  144. package/dist/cli/stores/StoreApp.d.ts +7 -0
  145. package/dist/cli/stores/StoreApp.js +81 -0
  146. package/dist/index.d.ts +15 -0
  147. package/dist/index.js +538 -0
  148. package/dist/local-agent/connection.d.ts +48 -0
  149. package/dist/local-agent/connection.js +332 -0
  150. package/dist/local-agent/discovery.d.ts +18 -0
  151. package/dist/local-agent/discovery.js +146 -0
  152. package/dist/local-agent/executor.d.ts +34 -0
  153. package/dist/local-agent/executor.js +241 -0
  154. package/dist/local-agent/index.d.ts +14 -0
  155. package/dist/local-agent/index.js +198 -0
  156. package/dist/node/adapters/base.d.ts +35 -0
  157. package/dist/node/adapters/base.js +10 -0
  158. package/dist/node/adapters/discord.d.ts +29 -0
  159. package/dist/node/adapters/discord.js +299 -0
  160. package/dist/node/adapters/email.d.ts +23 -0
  161. package/dist/node/adapters/email.js +218 -0
  162. package/dist/node/adapters/imessage.d.ts +17 -0
  163. package/dist/node/adapters/imessage.js +118 -0
  164. package/dist/node/adapters/slack.d.ts +26 -0
  165. package/dist/node/adapters/slack.js +259 -0
  166. package/dist/node/adapters/sms.d.ts +23 -0
  167. package/dist/node/adapters/sms.js +161 -0
  168. package/dist/node/adapters/telegram.d.ts +17 -0
  169. package/dist/node/adapters/telegram.js +101 -0
  170. package/dist/node/adapters/webchat.d.ts +27 -0
  171. package/dist/node/adapters/webchat.js +160 -0
  172. package/dist/node/adapters/whatsapp.d.ts +28 -0
  173. package/dist/node/adapters/whatsapp.js +230 -0
  174. package/dist/node/cli.d.ts +2 -0
  175. package/dist/node/cli.js +325 -0
  176. package/dist/node/config.d.ts +17 -0
  177. package/dist/node/config.js +31 -0
  178. package/dist/node/runtime.d.ts +50 -0
  179. package/dist/node/runtime.js +351 -0
  180. package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +11 -0
  181. package/dist/server/handlers/__test-utils__/mock-supabase.js +393 -0
  182. package/dist/server/handlers/analytics.d.ts +17 -0
  183. package/dist/server/handlers/analytics.js +266 -0
  184. package/dist/server/handlers/api-keys.d.ts +6 -0
  185. package/dist/server/handlers/api-keys.js +221 -0
  186. package/dist/server/handlers/billing.d.ts +33 -0
  187. package/dist/server/handlers/billing.js +272 -0
  188. package/dist/server/handlers/browser.d.ts +10 -0
  189. package/dist/server/handlers/browser.js +517 -0
  190. package/dist/server/handlers/catalog.d.ts +99 -0
  191. package/dist/server/handlers/catalog.js +976 -0
  192. package/dist/server/handlers/comms.d.ts +254 -0
  193. package/dist/server/handlers/comms.js +588 -0
  194. package/dist/server/handlers/creations.d.ts +6 -0
  195. package/dist/server/handlers/creations.js +479 -0
  196. package/dist/server/handlers/crm.d.ts +89 -0
  197. package/dist/server/handlers/crm.js +538 -0
  198. package/dist/server/handlers/discovery.d.ts +6 -0
  199. package/dist/server/handlers/discovery.js +288 -0
  200. package/dist/server/handlers/embeddings.d.ts +92 -0
  201. package/dist/server/handlers/embeddings.js +197 -0
  202. package/dist/server/handlers/enrichment.d.ts +8 -0
  203. package/dist/server/handlers/enrichment.js +768 -0
  204. package/dist/server/handlers/image-gen.d.ts +6 -0
  205. package/dist/server/handlers/image-gen.js +409 -0
  206. package/dist/server/handlers/inventory.d.ts +319 -0
  207. package/dist/server/handlers/inventory.js +447 -0
  208. package/dist/server/handlers/kali.d.ts +10 -0
  209. package/dist/server/handlers/kali.js +210 -0
  210. package/dist/server/handlers/llm-providers.d.ts +6 -0
  211. package/dist/server/handlers/llm-providers.js +673 -0
  212. package/dist/server/handlers/local-agent.d.ts +6 -0
  213. package/dist/server/handlers/local-agent.js +118 -0
  214. package/dist/server/handlers/meta-ads.d.ts +111 -0
  215. package/dist/server/handlers/meta-ads.js +2279 -0
  216. package/dist/server/handlers/nodes.d.ts +33 -0
  217. package/dist/server/handlers/nodes.js +699 -0
  218. package/dist/server/handlers/operations.d.ts +138 -0
  219. package/dist/server/handlers/operations.js +131 -0
  220. package/dist/server/handlers/platform.d.ts +23 -0
  221. package/dist/server/handlers/platform.js +227 -0
  222. package/dist/server/handlers/supply-chain.d.ts +19 -0
  223. package/dist/server/handlers/supply-chain.js +327 -0
  224. package/dist/server/handlers/transcription.d.ts +17 -0
  225. package/dist/server/handlers/transcription.js +121 -0
  226. package/dist/server/handlers/video-gen.d.ts +6 -0
  227. package/dist/server/handlers/video-gen.js +466 -0
  228. package/dist/server/handlers/voice.d.ts +8 -0
  229. package/dist/server/handlers/voice.js +1146 -0
  230. package/dist/server/handlers/workflow-steps.d.ts +86 -0
  231. package/dist/server/handlers/workflow-steps.js +2349 -0
  232. package/dist/server/handlers/workflows.d.ts +7 -0
  233. package/dist/server/handlers/workflows.js +989 -0
  234. package/dist/server/index.d.ts +1 -0
  235. package/dist/server/index.js +2427 -0
  236. package/dist/server/lib/batch-client.d.ts +80 -0
  237. package/dist/server/lib/batch-client.js +467 -0
  238. package/dist/server/lib/code-worker-pool.d.ts +31 -0
  239. package/dist/server/lib/code-worker-pool.js +224 -0
  240. package/dist/server/lib/code-worker.d.ts +1 -0
  241. package/dist/server/lib/code-worker.js +188 -0
  242. package/dist/server/lib/compaction-service.d.ts +32 -0
  243. package/dist/server/lib/compaction-service.js +162 -0
  244. package/dist/server/lib/logger.d.ts +19 -0
  245. package/dist/server/lib/logger.js +46 -0
  246. package/dist/server/lib/otel.d.ts +38 -0
  247. package/dist/server/lib/otel.js +126 -0
  248. package/dist/server/lib/pg-rate-limiter.d.ts +21 -0
  249. package/dist/server/lib/pg-rate-limiter.js +86 -0
  250. package/dist/server/lib/prompt-sanitizer.d.ts +37 -0
  251. package/dist/server/lib/prompt-sanitizer.js +177 -0
  252. package/dist/server/lib/provider-capabilities.d.ts +85 -0
  253. package/dist/server/lib/provider-capabilities.js +190 -0
  254. package/dist/server/lib/provider-failover.d.ts +74 -0
  255. package/dist/server/lib/provider-failover.js +210 -0
  256. package/dist/server/lib/rate-limiter.d.ts +39 -0
  257. package/dist/server/lib/rate-limiter.js +147 -0
  258. package/dist/server/lib/server-agent-loop.d.ts +107 -0
  259. package/dist/server/lib/server-agent-loop.js +667 -0
  260. package/dist/server/lib/server-subagent.d.ts +78 -0
  261. package/dist/server/lib/server-subagent.js +203 -0
  262. package/dist/server/lib/session-checkpoint.d.ts +51 -0
  263. package/dist/server/lib/session-checkpoint.js +145 -0
  264. package/dist/server/lib/ssrf-guard.d.ts +13 -0
  265. package/dist/server/lib/ssrf-guard.js +240 -0
  266. package/dist/server/lib/supabase-client.d.ts +7 -0
  267. package/dist/server/lib/supabase-client.js +78 -0
  268. package/dist/server/lib/template-resolver.d.ts +31 -0
  269. package/dist/server/lib/template-resolver.js +215 -0
  270. package/dist/server/lib/utils.d.ts +16 -0
  271. package/dist/server/lib/utils.js +147 -0
  272. package/dist/server/local-agent-gateway.d.ts +82 -0
  273. package/dist/server/local-agent-gateway.js +426 -0
  274. package/dist/server/providers/anthropic.d.ts +20 -0
  275. package/dist/server/providers/anthropic.js +199 -0
  276. package/dist/server/providers/bedrock.d.ts +20 -0
  277. package/dist/server/providers/bedrock.js +194 -0
  278. package/dist/server/providers/gemini.d.ts +24 -0
  279. package/dist/server/providers/gemini.js +486 -0
  280. package/dist/server/providers/openai.d.ts +24 -0
  281. package/dist/server/providers/openai.js +522 -0
  282. package/dist/server/providers/registry.d.ts +32 -0
  283. package/dist/server/providers/registry.js +58 -0
  284. package/dist/server/providers/shared.d.ts +32 -0
  285. package/dist/server/providers/shared.js +124 -0
  286. package/dist/server/providers/types.d.ts +92 -0
  287. package/dist/server/providers/types.js +12 -0
  288. package/dist/server/proxy-handlers.d.ts +6 -0
  289. package/dist/server/proxy-handlers.js +89 -0
  290. package/dist/server/tool-router.d.ts +149 -0
  291. package/dist/server/tool-router.js +803 -0
  292. package/dist/server/validation.d.ts +24 -0
  293. package/dist/server/validation.js +301 -0
  294. package/dist/server/worker.d.ts +19 -0
  295. package/dist/server/worker.js +201 -0
  296. package/dist/setup.d.ts +8 -0
  297. package/dist/setup.js +181 -0
  298. package/dist/shared/agent-core.d.ts +157 -0
  299. package/dist/shared/agent-core.js +534 -0
  300. package/dist/shared/anthropic-types.d.ts +105 -0
  301. package/dist/shared/anthropic-types.js +7 -0
  302. package/dist/shared/api-client.d.ts +90 -0
  303. package/dist/shared/api-client.js +379 -0
  304. package/dist/shared/constants.d.ts +33 -0
  305. package/dist/shared/constants.js +80 -0
  306. package/dist/shared/sse-parser.d.ts +26 -0
  307. package/dist/shared/sse-parser.js +259 -0
  308. package/dist/shared/tool-dispatch.d.ts +52 -0
  309. package/dist/shared/tool-dispatch.js +191 -0
  310. package/dist/shared/types.d.ts +72 -0
  311. package/dist/shared/types.js +7 -0
  312. package/dist/updater.d.ts +25 -0
  313. package/dist/updater.js +140 -0
  314. package/dist/webchat/widget.d.ts +0 -0
  315. package/dist/webchat/widget.js +397 -0
  316. package/package.json +95 -0
  317. package/src/cli/services/builtin-skills/commit.md +19 -0
  318. package/src/cli/services/builtin-skills/review-pr.md +21 -0
  319. package/src/cli/services/builtin-skills/review.md +18 -0
@@ -0,0 +1,717 @@
1
+ /**
2
+ * LSP Manager — Language Server Protocol client for code intelligence
3
+ *
4
+ * Provides go-to-definition, find-references, hover, document/workspace symbols,
5
+ * go-to-implementation, and call hierarchy via stdio JSON-RPC to language servers.
6
+ *
7
+ * Zero external dependencies — Node.js builtins only (child_process, Buffer).
8
+ * Servers are lazy-spawned on first request and keyed by language + workspace root.
9
+ */
10
+ import { spawn, execSync } from "child_process";
11
+ import { readFileSync, existsSync, statSync } from "fs";
12
+ import { resolve, dirname, extname } from "path";
13
+ const LANG_CONFIGS = [
14
+ {
15
+ id: "typescript",
16
+ languageIds: ["typescript", "typescriptreact", "javascript", "javascriptreact"],
17
+ binaries: ["typescript-language-server"],
18
+ args: ["--stdio"],
19
+ installHint: "npm i -g typescript-language-server typescript",
20
+ },
21
+ {
22
+ id: "python",
23
+ languageIds: ["python"],
24
+ binaries: ["pyright-langserver", "pylsp", "python-language-server"],
25
+ args: ["--stdio"],
26
+ installHint: "npm i -g pyright",
27
+ },
28
+ {
29
+ id: "rust",
30
+ languageIds: ["rust"],
31
+ binaries: ["rust-analyzer"],
32
+ args: [],
33
+ installHint: "rustup component add rust-analyzer",
34
+ },
35
+ {
36
+ id: "go",
37
+ languageIds: ["go"],
38
+ binaries: ["gopls"],
39
+ args: ["serve"],
40
+ installHint: "go install golang.org/x/tools/gopls@latest",
41
+ },
42
+ {
43
+ id: "clangd",
44
+ languageIds: ["c", "cpp"],
45
+ binaries: ["clangd"],
46
+ args: [],
47
+ installHint: "brew install llvm (or apt install clangd)",
48
+ },
49
+ {
50
+ id: "java",
51
+ languageIds: ["java"],
52
+ binaries: ["jdtls"],
53
+ args: [],
54
+ installHint: "brew install jdtls",
55
+ },
56
+ ];
57
+ const EXT_TO_LANGID = {
58
+ ".ts": "typescript", ".tsx": "typescriptreact",
59
+ ".js": "javascript", ".jsx": "javascriptreact",
60
+ ".mjs": "javascript", ".cjs": "javascript",
61
+ ".py": "python", ".rs": "rust", ".go": "go",
62
+ ".c": "c", ".h": "c",
63
+ ".cpp": "cpp", ".cc": "cpp", ".cxx": "cpp", ".hpp": "cpp",
64
+ ".java": "java",
65
+ };
66
+ // Build lookup: languageId → config
67
+ const LANGID_TO_CONFIG = new Map();
68
+ for (const cfg of LANG_CONFIGS) {
69
+ for (const lid of cfg.languageIds) {
70
+ LANGID_TO_CONFIG.set(lid, cfg);
71
+ }
72
+ }
73
+ // ============================================================================
74
+ // JSON-RPC MESSAGE BUFFER
75
+ // ============================================================================
76
+ class MessageBuffer {
77
+ buffer = Buffer.alloc(0);
78
+ append(data) {
79
+ this.buffer = Buffer.concat([this.buffer, data]);
80
+ }
81
+ tryRead() {
82
+ const headerEnd = this.buffer.indexOf("\r\n\r\n");
83
+ if (headerEnd === -1)
84
+ return null;
85
+ const header = this.buffer.subarray(0, headerEnd).toString("ascii");
86
+ const match = header.match(/Content-Length:\s*(\d+)/i);
87
+ if (!match) {
88
+ this.buffer = this.buffer.subarray(headerEnd + 4);
89
+ return null;
90
+ }
91
+ const contentLength = parseInt(match[1], 10);
92
+ const bodyStart = headerEnd + 4;
93
+ if (this.buffer.length < bodyStart + contentLength)
94
+ return null;
95
+ const body = this.buffer.subarray(bodyStart, bodyStart + contentLength).toString("utf-8");
96
+ this.buffer = this.buffer.subarray(bodyStart + contentLength);
97
+ try {
98
+ return JSON.parse(body);
99
+ }
100
+ catch {
101
+ return null;
102
+ }
103
+ }
104
+ }
105
+ function encodeMessage(msg) {
106
+ const body = JSON.stringify(msg);
107
+ const header = `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n`;
108
+ return Buffer.from(header + body, "utf-8");
109
+ }
110
+ const servers = new Map();
111
+ const REQUEST_TIMEOUT = 30_000;
112
+ let cleanupRegistered = false;
113
+ // Simple content hash for change detection (fast, not crypto)
114
+ function quickHash(content) {
115
+ let h = 0;
116
+ for (let i = 0; i < content.length; i++) {
117
+ h = ((h << 5) - h + content.charCodeAt(i)) | 0;
118
+ }
119
+ return h.toString(36);
120
+ }
121
+ function findBinary(binaries) {
122
+ for (const bin of binaries) {
123
+ try {
124
+ execSync(`which ${bin} 2>/dev/null`, { encoding: "utf-8" });
125
+ return bin;
126
+ }
127
+ catch {
128
+ continue;
129
+ }
130
+ }
131
+ return null;
132
+ }
133
+ function findWorkspaceRoot(filePath) {
134
+ let dir = dirname(resolve(filePath));
135
+ const markers = [".git", "package.json", "Cargo.toml", "go.mod", "pyproject.toml", "setup.py"];
136
+ for (let i = 0; i < 20; i++) {
137
+ for (const marker of markers) {
138
+ if (existsSync(resolve(dir, marker)))
139
+ return dir;
140
+ }
141
+ const parent = dirname(dir);
142
+ if (parent === dir)
143
+ break;
144
+ dir = parent;
145
+ }
146
+ return dirname(resolve(filePath));
147
+ }
148
+ function fileUri(filePath) {
149
+ const abs = resolve(filePath);
150
+ return "file://" + abs.split("/").map(seg => encodeURIComponent(seg)).join("/");
151
+ }
152
+ function uriToPath(uri) {
153
+ if (uri.startsWith("file://"))
154
+ return decodeURIComponent(uri.slice(7));
155
+ return decodeURIComponent(uri);
156
+ }
157
+ // ============================================================================
158
+ // SERVER LIFECYCLE
159
+ // ============================================================================
160
+ function spawnServer(config, workspaceRoot) {
161
+ const binary = findBinary(config.binaries);
162
+ if (!binary) {
163
+ throw new Error(`No language server found for ${config.id}. Install: ${config.installHint}`);
164
+ }
165
+ const child = spawn(binary, config.args, {
166
+ cwd: workspaceRoot,
167
+ stdio: ["pipe", "pipe", "pipe"],
168
+ env: { ...process.env },
169
+ });
170
+ const key = `${config.id}:${workspaceRoot}`;
171
+ const server = {
172
+ process: child,
173
+ configId: config.id,
174
+ workspaceRoot,
175
+ messageBuffer: new MessageBuffer(),
176
+ nextId: 1,
177
+ pending: new Map(),
178
+ openedFiles: new Map(),
179
+ ready: false,
180
+ alive: true,
181
+ projectIndexed: false,
182
+ capabilities: null,
183
+ initPromise: Promise.resolve(),
184
+ };
185
+ child.stdout.on("data", (data) => {
186
+ server.messageBuffer.append(data);
187
+ let msg;
188
+ while ((msg = server.messageBuffer.tryRead()) !== null) {
189
+ handleMessage(server, msg);
190
+ }
191
+ });
192
+ child.stderr.on("data", () => { });
193
+ child.on("exit", () => {
194
+ server.alive = false;
195
+ servers.delete(key);
196
+ for (const [, req] of server.pending) {
197
+ clearTimeout(req.timer);
198
+ req.reject(new Error("Language server exited"));
199
+ }
200
+ server.pending.clear();
201
+ });
202
+ // Register cleanup on first server
203
+ if (!cleanupRegistered) {
204
+ cleanupRegistered = true;
205
+ process.on("exit", shutdownAll);
206
+ process.on("SIGINT", () => { shutdownAll(); process.exit(0); });
207
+ process.on("SIGTERM", () => { shutdownAll(); process.exit(0); });
208
+ }
209
+ server.initPromise = initializeServer(server);
210
+ servers.set(key, server);
211
+ return server;
212
+ }
213
+ async function getOrCreateServer(filePath) {
214
+ const ext = extname(filePath).toLowerCase();
215
+ const langId = EXT_TO_LANGID[ext];
216
+ if (!langId)
217
+ throw new Error(`No language server configured for ${ext} files`);
218
+ const config = LANGID_TO_CONFIG.get(langId);
219
+ if (!config)
220
+ throw new Error(`No language config for ${langId}`);
221
+ const workspaceRoot = findWorkspaceRoot(filePath);
222
+ const key = `${config.id}:${workspaceRoot}`;
223
+ const existing = servers.get(key);
224
+ // Auto-restart: if server died, remove and respawn
225
+ if (existing && !existing.alive) {
226
+ servers.delete(key);
227
+ }
228
+ else if (existing && existing.ready) {
229
+ return existing;
230
+ }
231
+ else if (existing) {
232
+ await existing.initPromise;
233
+ return existing;
234
+ }
235
+ const server = spawnServer(config, workspaceRoot);
236
+ await server.initPromise;
237
+ return server;
238
+ }
239
+ // ============================================================================
240
+ // MESSAGE HANDLING
241
+ // ============================================================================
242
+ function handleMessage(server, msg) {
243
+ // Response to a request we sent
244
+ if ("id" in msg && "result" in msg || "id" in msg && "error" in msg) {
245
+ if (server.pending.has(msg.id)) {
246
+ const req = server.pending.get(msg.id);
247
+ server.pending.delete(msg.id);
248
+ clearTimeout(req.timer);
249
+ if (msg.error) {
250
+ req.reject(new Error(msg.error.message || JSON.stringify(msg.error)));
251
+ }
252
+ else {
253
+ req.resolve(msg.result);
254
+ }
255
+ return;
256
+ }
257
+ }
258
+ // Server-initiated request — respond to avoid timeouts on the server side
259
+ if ("id" in msg && "method" in msg) {
260
+ const response = { jsonrpc: "2.0", id: msg.id };
261
+ switch (msg.method) {
262
+ case "window/workDoneProgress/create":
263
+ case "client/registerCapability":
264
+ case "client/unregisterCapability":
265
+ response.result = null;
266
+ break;
267
+ case "workspace/configuration":
268
+ // Return empty config for each requested scope
269
+ response.result = (msg.params?.items || []).map(() => ({}));
270
+ break;
271
+ case "window/showMessageRequest":
272
+ response.result = null; // Dismiss
273
+ break;
274
+ default:
275
+ response.result = null;
276
+ break;
277
+ }
278
+ server.process.stdin.write(encodeMessage(response));
279
+ return;
280
+ }
281
+ // Other notifications (diagnostics, etc.) silently ignored
282
+ }
283
+ function sendRequest(server, method, params) {
284
+ if (!server.alive)
285
+ return Promise.reject(new Error("Language server is not running"));
286
+ return new Promise((resolve, reject) => {
287
+ const id = server.nextId++;
288
+ const timer = setTimeout(() => {
289
+ server.pending.delete(id);
290
+ reject(new Error(`LSP request timed out: ${method}`));
291
+ }, REQUEST_TIMEOUT);
292
+ server.pending.set(id, { resolve, reject, timer });
293
+ server.process.stdin.write(encodeMessage({ jsonrpc: "2.0", id, method, params }));
294
+ });
295
+ }
296
+ function sendNotification(server, method, params) {
297
+ if (!server.alive)
298
+ return;
299
+ server.process.stdin.write(encodeMessage({ jsonrpc: "2.0", method, params }));
300
+ }
301
+ async function initializeServer(server) {
302
+ const result = await sendRequest(server, "initialize", {
303
+ processId: process.pid,
304
+ rootUri: fileUri(server.workspaceRoot),
305
+ rootPath: server.workspaceRoot,
306
+ capabilities: {
307
+ textDocument: {
308
+ synchronization: {
309
+ didOpen: true,
310
+ didClose: true,
311
+ didChange: 1, // Full content sync
312
+ willSave: false,
313
+ willSaveWaitUntil: false,
314
+ didSave: false,
315
+ },
316
+ definition: { dynamicRegistration: false },
317
+ references: { dynamicRegistration: false },
318
+ hover: { dynamicRegistration: false, contentFormat: ["markdown", "plaintext"] },
319
+ documentSymbol: { dynamicRegistration: false, hierarchicalDocumentSymbolSupport: true },
320
+ implementation: { dynamicRegistration: false },
321
+ callHierarchy: { dynamicRegistration: false },
322
+ },
323
+ workspace: {
324
+ symbol: { dynamicRegistration: false },
325
+ workspaceFolders: true,
326
+ configuration: true,
327
+ },
328
+ window: {
329
+ workDoneProgress: true,
330
+ },
331
+ },
332
+ workspaceFolders: [{ uri: fileUri(server.workspaceRoot), name: server.workspaceRoot.split("/").pop() }],
333
+ });
334
+ server.capabilities = result.capabilities;
335
+ sendNotification(server, "initialized", {});
336
+ server.ready = true;
337
+ }
338
+ // ============================================================================
339
+ // DOCUMENT SYNC
340
+ // ============================================================================
341
+ /**
342
+ * Open or re-sync a file with the language server.
343
+ * - First open: sends didOpen + probes with documentSymbol to ensure server is ready
344
+ * - Subsequent: checks mtime → if changed, sends didChange + re-probes
345
+ *
346
+ * The probe (documentSymbol request) is key: the server must fully parse the file
347
+ * to respond, so when it returns we know all symbols/types are available.
348
+ * This is more reliable than waiting for diagnostics notifications.
349
+ */
350
+ async function ensureFileOpen(server, filePath) {
351
+ const uri = fileUri(filePath);
352
+ const absPath = resolve(filePath);
353
+ let text;
354
+ let mtimeMs;
355
+ try {
356
+ text = readFileSync(absPath, "utf-8");
357
+ mtimeMs = statSync(absPath).mtimeMs;
358
+ }
359
+ catch {
360
+ throw new Error(`Cannot read file: ${filePath}`);
361
+ }
362
+ const hash = quickHash(text);
363
+ const existing = server.openedFiles.get(uri);
364
+ if (existing) {
365
+ // Already open — check if content changed
366
+ if (existing.mtimeMs === mtimeMs && existing.contentHash === hash) {
367
+ return; // No changes
368
+ }
369
+ // File changed on disk — send didChange with full content
370
+ existing.version++;
371
+ existing.contentHash = hash;
372
+ existing.mtimeMs = mtimeMs;
373
+ sendNotification(server, "textDocument/didChange", {
374
+ textDocument: { uri, version: existing.version },
375
+ contentChanges: [{ text }],
376
+ });
377
+ // Probe: force server to re-process the file before we query it
378
+ await sendRequest(server, "textDocument/documentSymbol", { textDocument: { uri } });
379
+ return;
380
+ }
381
+ // First open
382
+ const ext = extname(filePath).toLowerCase();
383
+ const langId = EXT_TO_LANGID[ext] || "plaintext";
384
+ const openedFile = { uri, version: 1, contentHash: hash, mtimeMs };
385
+ server.openedFiles.set(uri, openedFile);
386
+ sendNotification(server, "textDocument/didOpen", {
387
+ textDocument: { uri, languageId: langId, version: 1, text },
388
+ });
389
+ // Probe: documentSymbol ensures the server has parsed this file's AST.
390
+ await sendRequest(server, "textDocument/documentSymbol", { textDocument: { uri } });
391
+ // On first file open, also probe with workspace/symbol to wait for full
392
+ // project indexing (type checking, cross-file resolution). This takes ~10s
393
+ // on cold start but is essential for hover/definition to work correctly.
394
+ // Subsequent files skip this since the project is already indexed.
395
+ if (!server.projectIndexed) {
396
+ await sendRequest(server, "workspace/symbol", { query: "" });
397
+ server.projectIndexed = true;
398
+ }
399
+ }
400
+ /**
401
+ * Notify server that a file was modified externally (by edit_file, write_file, etc.).
402
+ * Call this from local-tools after any file modification.
403
+ */
404
+ export function notifyFileChanged(filePath) {
405
+ const absPath = resolve(filePath);
406
+ const uri = fileUri(absPath);
407
+ // Find any server that has this file open and invalidate it
408
+ for (const server of servers.values()) {
409
+ const opened = server.openedFiles.get(uri);
410
+ if (opened) {
411
+ // Force mtime to 0 so next ensureFileOpen detects the change
412
+ opened.mtimeMs = 0;
413
+ }
414
+ }
415
+ }
416
+ const OPERATIONS = new Set([
417
+ "goToDefinition", "findReferences", "hover",
418
+ "documentSymbol", "workspaceSymbol", "goToImplementation",
419
+ "prepareCallHierarchy", "incomingCalls", "outgoingCalls",
420
+ ]);
421
+ function toPosition(line, character) {
422
+ return { line: Math.max(0, line - 1), character: Math.max(0, character - 1) };
423
+ }
424
+ async function runOperation(operation, server, filePath, line, character, query) {
425
+ const uri = fileUri(filePath);
426
+ const pos = toPosition(line, character);
427
+ switch (operation) {
428
+ case "goToDefinition": {
429
+ const result = await sendRequest(server, "textDocument/definition", {
430
+ textDocument: { uri }, position: pos,
431
+ });
432
+ return formatLocations(result, "Definition");
433
+ }
434
+ case "findReferences": {
435
+ const result = await sendRequest(server, "textDocument/references", {
436
+ textDocument: { uri }, position: pos,
437
+ context: { includeDeclaration: true },
438
+ });
439
+ return formatLocations(result, "References");
440
+ }
441
+ case "hover": {
442
+ const result = await sendRequest(server, "textDocument/hover", {
443
+ textDocument: { uri }, position: pos,
444
+ });
445
+ return formatHover(result);
446
+ }
447
+ case "documentSymbol": {
448
+ const result = await sendRequest(server, "textDocument/documentSymbol", {
449
+ textDocument: { uri },
450
+ });
451
+ return formatDocumentSymbols(result, filePath);
452
+ }
453
+ case "workspaceSymbol": {
454
+ const result = await sendRequest(server, "workspace/symbol", {
455
+ query: query || "",
456
+ });
457
+ return formatWorkspaceSymbols(result);
458
+ }
459
+ case "goToImplementation": {
460
+ const result = await sendRequest(server, "textDocument/implementation", {
461
+ textDocument: { uri }, position: pos,
462
+ });
463
+ return formatLocations(result, "Implementations");
464
+ }
465
+ case "prepareCallHierarchy": {
466
+ const result = await sendRequest(server, "textDocument/prepareCallHierarchy", {
467
+ textDocument: { uri }, position: pos,
468
+ });
469
+ return formatCallHierarchyItems(result);
470
+ }
471
+ case "incomingCalls": {
472
+ const items = await sendRequest(server, "textDocument/prepareCallHierarchy", {
473
+ textDocument: { uri }, position: pos,
474
+ });
475
+ if (!items || (Array.isArray(items) && items.length === 0)) {
476
+ return "No call hierarchy item found at this position.";
477
+ }
478
+ const item = Array.isArray(items) ? items[0] : items;
479
+ const result = await sendRequest(server, "callHierarchy/incomingCalls", { item });
480
+ return formatIncomingCalls(result);
481
+ }
482
+ case "outgoingCalls": {
483
+ const items = await sendRequest(server, "textDocument/prepareCallHierarchy", {
484
+ textDocument: { uri }, position: pos,
485
+ });
486
+ if (!items || (Array.isArray(items) && items.length === 0)) {
487
+ return "No call hierarchy item found at this position.";
488
+ }
489
+ const item = Array.isArray(items) ? items[0] : items;
490
+ const result = await sendRequest(server, "callHierarchy/outgoingCalls", { item });
491
+ return formatOutgoingCalls(result);
492
+ }
493
+ default:
494
+ return `Unknown operation: ${operation}`;
495
+ }
496
+ }
497
+ // ============================================================================
498
+ // RESULT FORMATTERS
499
+ // ============================================================================
500
+ function normalizeLocations(result) {
501
+ if (!result)
502
+ return [];
503
+ if (Array.isArray(result)) {
504
+ return result.map(item => {
505
+ if (item.targetUri) {
506
+ return { uri: item.targetUri, range: item.targetSelectionRange || item.targetRange };
507
+ }
508
+ return item;
509
+ });
510
+ }
511
+ if (result.uri)
512
+ return [result];
513
+ if (result.targetUri) {
514
+ return [{ uri: result.targetUri, range: result.targetSelectionRange || result.targetRange }];
515
+ }
516
+ return [];
517
+ }
518
+ function formatLocations(result, label) {
519
+ const locations = normalizeLocations(result);
520
+ if (locations.length === 0)
521
+ return `No ${label.toLowerCase()} found.`;
522
+ const byFile = new Map();
523
+ for (const loc of locations) {
524
+ const path = uriToPath(loc.uri);
525
+ if (!byFile.has(path))
526
+ byFile.set(path, []);
527
+ byFile.get(path).push({
528
+ line: loc.range.start.line + 1,
529
+ char: loc.range.start.character + 1,
530
+ });
531
+ }
532
+ const lines = [`${label} (${locations.length}):`];
533
+ for (const [path, positions] of byFile) {
534
+ if (byFile.size > 1)
535
+ lines.push(` ${path}`);
536
+ for (const pos of positions) {
537
+ const prefix = byFile.size > 1 ? " " : " ";
538
+ lines.push(`${prefix}${path}:${pos.line}:${pos.char}`);
539
+ }
540
+ }
541
+ return lines.join("\n");
542
+ }
543
+ function formatHover(result) {
544
+ if (!result || !result.contents)
545
+ return "No hover information.";
546
+ const contents = result.contents;
547
+ if (typeof contents === "string")
548
+ return contents;
549
+ if (contents.kind === "markdown" || contents.kind === "plaintext") {
550
+ return contents.value || "";
551
+ }
552
+ if (contents.language && contents.value) {
553
+ return `\`\`\`${contents.language}\n${contents.value}\n\`\`\``;
554
+ }
555
+ if (Array.isArray(contents)) {
556
+ return contents
557
+ .map((c) => {
558
+ if (typeof c === "string")
559
+ return c;
560
+ if (c.language && c.value)
561
+ return `\`\`\`${c.language}\n${c.value}\n\`\`\``;
562
+ if (c.value)
563
+ return c.value;
564
+ return String(c);
565
+ })
566
+ .join("\n\n");
567
+ }
568
+ return JSON.stringify(contents, null, 2);
569
+ }
570
+ const SYMBOL_KIND_NAMES = {
571
+ 1: "File", 2: "Module", 3: "Namespace", 4: "Package", 5: "Class",
572
+ 6: "Method", 7: "Property", 8: "Field", 9: "Constructor", 10: "Enum",
573
+ 11: "Interface", 12: "Function", 13: "Variable", 14: "Constant",
574
+ 15: "String", 16: "Number", 17: "Boolean", 18: "Array", 19: "Object",
575
+ 20: "Key", 21: "Null", 22: "EnumMember", 23: "Struct", 24: "Event",
576
+ 25: "Operator", 26: "TypeParameter",
577
+ };
578
+ function symbolKindName(kind) {
579
+ return SYMBOL_KIND_NAMES[kind] || `Kind(${kind})`;
580
+ }
581
+ function formatDocumentSymbols(result, filePath) {
582
+ if (!result || !Array.isArray(result) || result.length === 0) {
583
+ return `No symbols found in ${filePath}`;
584
+ }
585
+ const lines = [`Symbols in ${filePath}:`];
586
+ function walk(symbols, indent) {
587
+ for (const sym of symbols) {
588
+ const kind = symbolKindName(sym.kind);
589
+ const line = sym.range?.start?.line != null
590
+ ? sym.range.start.line + 1
591
+ : sym.location?.range?.start?.line != null
592
+ ? sym.location.range.start.line + 1
593
+ : "?";
594
+ lines.push(`${" ".repeat(indent + 1)}${kind} ${sym.name} :${line}`);
595
+ if (sym.children?.length)
596
+ walk(sym.children, indent + 1);
597
+ }
598
+ }
599
+ walk(result, 0);
600
+ return lines.join("\n");
601
+ }
602
+ function formatWorkspaceSymbols(result) {
603
+ if (!result || !Array.isArray(result) || result.length === 0) {
604
+ return "No workspace symbols found.";
605
+ }
606
+ const lines = [`Workspace symbols (${result.length}):`];
607
+ for (const sym of result.slice(0, 100)) {
608
+ const kind = symbolKindName(sym.kind);
609
+ const path = sym.location?.uri ? uriToPath(sym.location.uri) : "?";
610
+ const line = sym.location?.range?.start?.line != null ? sym.location.range.start.line + 1 : "?";
611
+ lines.push(` ${kind} ${sym.name} ${path}:${line}`);
612
+ }
613
+ if (result.length > 100)
614
+ lines.push(` ... and ${result.length - 100} more`);
615
+ return lines.join("\n");
616
+ }
617
+ function formatCallHierarchyItems(result) {
618
+ if (!result || !Array.isArray(result) || result.length === 0) {
619
+ return "No call hierarchy item found at this position.";
620
+ }
621
+ const lines = ["Call hierarchy items:"];
622
+ for (const item of result) {
623
+ const kind = symbolKindName(item.kind);
624
+ const path = item.uri ? uriToPath(item.uri) : "?";
625
+ const line = item.range?.start?.line != null ? item.range.start.line + 1 : "?";
626
+ lines.push(` ${kind} ${item.name} ${path}:${line}`);
627
+ }
628
+ return lines.join("\n");
629
+ }
630
+ function formatIncomingCalls(result) {
631
+ if (!result || !Array.isArray(result) || result.length === 0) {
632
+ return "No incoming calls found.";
633
+ }
634
+ const lines = [`Incoming calls (${result.length}):`];
635
+ for (const call of result) {
636
+ const from = call.from;
637
+ const kind = symbolKindName(from.kind);
638
+ const path = from.uri ? uriToPath(from.uri) : "?";
639
+ const line = from.range?.start?.line != null ? from.range.start.line + 1 : "?";
640
+ lines.push(` ${kind} ${from.name} ${path}:${line}`);
641
+ }
642
+ return lines.join("\n");
643
+ }
644
+ function formatOutgoingCalls(result) {
645
+ if (!result || !Array.isArray(result) || result.length === 0) {
646
+ return "No outgoing calls found.";
647
+ }
648
+ const lines = [`Outgoing calls (${result.length}):`];
649
+ for (const call of result) {
650
+ const to = call.to;
651
+ const kind = symbolKindName(to.kind);
652
+ const path = to.uri ? uriToPath(to.uri) : "?";
653
+ const line = to.range?.start?.line != null ? to.range.start.line + 1 : "?";
654
+ lines.push(` ${kind} ${to.name} ${path}:${line}`);
655
+ }
656
+ return lines.join("\n");
657
+ }
658
+ // ============================================================================
659
+ // CLEANUP
660
+ // ============================================================================
661
+ function shutdownAll() {
662
+ for (const [key, server] of servers) {
663
+ try {
664
+ const id = server.nextId++;
665
+ server.process.stdin.write(encodeMessage({ jsonrpc: "2.0", id, method: "shutdown", params: null }));
666
+ sendNotification(server, "exit", null);
667
+ setTimeout(() => {
668
+ try {
669
+ server.process.kill("SIGTERM");
670
+ }
671
+ catch { /* dead */ }
672
+ }, 2000);
673
+ }
674
+ catch {
675
+ try {
676
+ server.process.kill("SIGTERM");
677
+ }
678
+ catch { /* dead */ }
679
+ }
680
+ servers.delete(key);
681
+ }
682
+ }
683
+ // ============================================================================
684
+ // PUBLIC API
685
+ // ============================================================================
686
+ export async function executeLSP(operation, input) {
687
+ const op = operation;
688
+ if (!OPERATIONS.has(op)) {
689
+ return {
690
+ success: false,
691
+ output: `Unknown LSP operation: ${operation}. Valid: ${[...OPERATIONS].join(", ")}`,
692
+ };
693
+ }
694
+ const filePath = input.filePath;
695
+ const line = input.line;
696
+ const character = input.character;
697
+ const query = input.query;
698
+ if (!filePath)
699
+ return { success: false, output: "filePath is required" };
700
+ if (!line || line < 1)
701
+ return { success: false, output: "line is required (1-based)" };
702
+ if (!character || character < 1)
703
+ return { success: false, output: "character is required (1-based)" };
704
+ const resolved = resolve(filePath);
705
+ if (!existsSync(resolved)) {
706
+ return { success: false, output: `File not found: ${resolved}` };
707
+ }
708
+ try {
709
+ const server = await getOrCreateServer(resolved);
710
+ await ensureFileOpen(server, resolved);
711
+ const result = await runOperation(op, server, resolved, line, character, query);
712
+ return { success: true, output: result };
713
+ }
714
+ catch (err) {
715
+ return { success: false, output: `LSP error: ${err.message || err}` };
716
+ }
717
+ }