oh-my-opencode 5.0.0-beta.21 → 5.0.0-beta.23

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 (151) hide show
  1. package/dist/cli/index.js +290 -288
  2. package/dist/cli-node/index.js +290 -288
  3. package/dist/index.js +1675 -1657
  4. package/dist/skills/debugging/SKILL.md +2 -0
  5. package/dist/skills/debugging/references/methodology/00-setup.md +12 -0
  6. package/dist/skills/debugging/references/methodology/02-investigate.md +21 -0
  7. package/dist/skills/debugging/references/runtimes/go.md +15 -1
  8. package/dist/skills/debugging/references/runtimes/native-binary.md +38 -0
  9. package/dist/skills/debugging/references/runtimes/node.md +25 -0
  10. package/dist/skills/debugging/references/runtimes/python.md +13 -0
  11. package/dist/skills/debugging/references/runtimes/rust.md +41 -0
  12. package/dist/skills/debugging/references/scripts/dap.mjs +267 -0
  13. package/dist/skills/debugging/references/scripts/dap.test.ts +86 -0
  14. package/dist/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
  15. package/dist/skills/debugging/references/tools/dap.md +103 -0
  16. package/dist/skills/debugging/references/tools/frida.md +193 -0
  17. package/dist/tools/delegate-task/types.d.ts +3 -0
  18. package/dist/tui.js +131 -147
  19. package/package.json +18 -15
  20. package/packages/lsp-core/src/index.ts +1 -0
  21. package/packages/lsp-core/src/lsp/client-wrapper.ts +25 -5
  22. package/packages/lsp-core/src/lsp/client.ts +26 -0
  23. package/packages/lsp-core/src/lsp/connection.ts +16 -0
  24. package/packages/lsp-core/src/lsp/constants.ts +1 -0
  25. package/packages/lsp-core/src/lsp/format-document.test.ts +143 -0
  26. package/packages/lsp-core/src/lsp/format-document.ts +90 -0
  27. package/packages/lsp-core/src/lsp/manager-max-clients.test.ts +151 -0
  28. package/packages/lsp-core/src/lsp/manager.ts +34 -1
  29. package/packages/lsp-core/src/lsp/server-definitions.ts +29 -0
  30. package/packages/lsp-core/src/lsp/server-installation.test.ts +376 -0
  31. package/packages/lsp-core/src/lsp/server-installation.ts +200 -27
  32. package/packages/lsp-core/src/lsp/server-resolution-local-binary.test.ts +170 -0
  33. package/packages/lsp-core/src/lsp/server-resolution.ts +18 -5
  34. package/packages/lsp-core/src/lsp/transport.ts +9 -0
  35. package/packages/lsp-core/src/lsp/types.ts +8 -0
  36. package/packages/lsp-core/src/tool-surface.test.ts +13 -1
  37. package/packages/lsp-core/src/tools/definitions.ts +14 -0
  38. package/packages/lsp-core/src/tools/format.test.ts +56 -0
  39. package/packages/lsp-core/src/tools/format.ts +56 -0
  40. package/packages/lsp-core/src/tools/index.ts +1 -0
  41. package/packages/lsp-core/src/tools/types.ts +10 -0
  42. package/packages/lsp-daemon/dist/cli.js +432 -144
  43. package/packages/lsp-daemon/dist/client.d.ts +10 -0
  44. package/packages/lsp-daemon/dist/client.js +401 -107
  45. package/packages/lsp-daemon/dist/daemon-client.d.ts +1 -0
  46. package/packages/lsp-daemon/dist/daemon-client.js +3 -0
  47. package/packages/lsp-daemon/dist/index.js +395 -107
  48. package/packages/lsp-daemon/package.json +1 -1
  49. package/packages/lsp-tools-mcp/dist/cli.js +384 -99
  50. package/packages/lsp-tools-mcp/dist/lsp/manager.js +53 -0
  51. package/packages/lsp-tools-mcp/dist/mcp.js +384 -99
  52. package/packages/lsp-tools-mcp/dist/tools.js +385 -99
  53. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  54. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +2 -2
  55. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  56. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  57. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +69 -35
  58. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +21 -2
  59. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  60. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  61. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  62. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  63. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  64. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  65. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  66. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  67. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +404 -121
  68. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  69. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  70. package/packages/omo-codex/plugin/components/rules/dist/cli.js +3 -3
  71. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  72. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  73. package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +3 -3
  74. package/packages/omo-codex/plugin/components/rules/test/post-compact-budget.test.ts +4 -4
  75. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  76. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  77. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +30 -15
  78. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +30 -15
  79. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  80. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  81. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  82. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  83. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
  84. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  85. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +5 -1
  86. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +2 -1
  87. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +11 -1
  88. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +22 -10
  89. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  90. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  91. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +5 -1
  92. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +13 -1
  93. package/packages/omo-codex/plugin/components/ulw-loop/src/cli.ts +3 -5
  94. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-entrypoint.test.ts +26 -2
  95. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  96. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  97. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  98. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  99. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  100. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  101. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  102. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  103. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  104. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  105. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  106. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  107. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  108. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  109. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  110. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  111. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  112. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  113. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
  114. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  115. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  116. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  117. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  118. package/packages/omo-codex/plugin/model-catalog.json +3 -3
  119. package/packages/omo-codex/plugin/package-lock.json +13 -13
  120. package/packages/omo-codex/plugin/package.json +1 -1
  121. package/packages/omo-codex/plugin/scripts/migrate-codex-config/catalog.mjs +3 -3
  122. package/packages/omo-codex/plugin/skills/debugging/SKILL.md +2 -0
  123. package/packages/omo-codex/plugin/skills/debugging/references/methodology/00-setup.md +12 -0
  124. package/packages/omo-codex/plugin/skills/debugging/references/methodology/02-investigate.md +21 -0
  125. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/go.md +15 -1
  126. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/native-binary.md +38 -0
  127. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/node.md +25 -0
  128. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/python.md +13 -0
  129. package/packages/omo-codex/plugin/skills/debugging/references/runtimes/rust.md +41 -0
  130. package/packages/omo-codex/plugin/skills/debugging/references/scripts/dap.mjs +267 -0
  131. package/packages/omo-codex/plugin/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
  132. package/packages/omo-codex/plugin/skills/debugging/references/tools/dap.md +103 -0
  133. package/packages/omo-codex/plugin/skills/debugging/references/tools/frida.md +193 -0
  134. package/packages/omo-codex/plugin/test/aggregate-model-catalog.test.mjs +1 -1
  135. package/packages/omo-codex/plugin/test/auto-update.test.mjs +2 -2
  136. package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +8 -8
  137. package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +2 -2
  138. package/packages/omo-codex/scripts/install-dist/install-local.mjs +38 -23
  139. package/packages/shared-skills/skills/debugging/SKILL.md +2 -0
  140. package/packages/shared-skills/skills/debugging/references/methodology/00-setup.md +12 -0
  141. package/packages/shared-skills/skills/debugging/references/methodology/02-investigate.md +21 -0
  142. package/packages/shared-skills/skills/debugging/references/runtimes/go.md +15 -1
  143. package/packages/shared-skills/skills/debugging/references/runtimes/native-binary.md +38 -0
  144. package/packages/shared-skills/skills/debugging/references/runtimes/node.md +25 -0
  145. package/packages/shared-skills/skills/debugging/references/runtimes/python.md +13 -0
  146. package/packages/shared-skills/skills/debugging/references/runtimes/rust.md +41 -0
  147. package/packages/shared-skills/skills/debugging/references/scripts/dap.mjs +267 -0
  148. package/packages/shared-skills/skills/debugging/references/scripts/dap.test.ts +86 -0
  149. package/packages/shared-skills/skills/debugging/references/scripts/fixture-adapter.mjs +59 -0
  150. package/packages/shared-skills/skills/debugging/references/tools/dap.md +103 -0
  151. package/packages/shared-skills/skills/debugging/references/tools/frida.md +193 -0
@@ -25,6 +25,7 @@ var DEFAULT_MAX_DIRECTORY_FILES = 50;
25
25
  var REQUEST_TIMEOUT_MS = 15000;
26
26
  var INIT_TIMEOUT_MS = 60000;
27
27
  var IDLE_TIMEOUT_MS = 5 * 60000;
28
+ var MAX_RESIDENT_CLIENTS = 6;
28
29
  var REAPER_INTERVAL_MS = 60000;
29
30
  var STOP_HARD_KILL_TIMEOUT_MS = 5000;
30
31
  var STOP_SIGKILL_GRACE_MS = 1000;
@@ -617,6 +618,7 @@ class LspClientTransport {
617
618
  this.diagnosticsStore = new Map;
618
619
  this.workspaceApplyEditHandler = null;
619
620
  this.diagnosticPullSupported = false;
621
+ this.documentFormattingSupported = false;
620
622
  this.requestTimeoutMs = timeouts.requestTimeoutMs ?? REQUEST_TIMEOUT_MS;
621
623
  this.initializeTimeoutMs = timeouts.initializeTimeoutMs ?? INIT_TIMEOUT_MS;
622
624
  }
@@ -638,6 +640,12 @@ class LspClientTransport {
638
640
  isDiagnosticPullSupported() {
639
641
  return this.diagnosticPullSupported;
640
642
  }
643
+ setDocumentFormattingSupported(supported) {
644
+ this.documentFormattingSupported = supported;
645
+ }
646
+ isDocumentFormattingSupported() {
647
+ return this.documentFormattingSupported;
648
+ }
641
649
  handlePublishDiagnostics(params) {
642
650
  this.diagnosticsStore.set(params.uri, [...params.diagnostics]);
643
651
  }
@@ -851,6 +859,14 @@ function supportsDiagnosticPull(capabilities) {
851
859
  return false;
852
860
  return Object.hasOwn(capabilities, "diagnosticProvider");
853
861
  }
862
+ function supportsDocumentFormatting(capabilities) {
863
+ if (capabilities === undefined)
864
+ return false;
865
+ const provider = capabilities.documentFormattingProvider;
866
+ if (provider === undefined || provider === null || provider === false)
867
+ return false;
868
+ return provider === true || typeof provider === "object";
869
+ }
854
870
 
855
871
  class LspClientConnection extends LspClientTransport {
856
872
  async initialize() {
@@ -867,6 +883,7 @@ class LspClientConnection extends LspClientTransport {
867
883
  references: {},
868
884
  documentSymbol: { hierarchicalDocumentSymbolSupport: true },
869
885
  publishDiagnostics: {},
886
+ formatting: { dynamicRegistration: false },
870
887
  rename: {
871
888
  prepareSupport: true,
872
889
  prepareSupportDefaultBehavior: 1
@@ -908,6 +925,7 @@ class LspClientConnection extends LspClientTransport {
908
925
  initializationOptions: this.server.initialization
909
926
  }, { timeoutMs: this.initializeTimeoutMs });
910
927
  this.setDiagnosticPullSupported(supportsDiagnosticPull(result?.capabilities));
928
+ this.setDocumentFormattingSupported(supportsDocumentFormatting(result?.capabilities));
911
929
  await this.sendNotification("initialized", {});
912
930
  await this.sendNotification("workspace/didChangeConfiguration", {
913
931
  settings: { json: { validate: { enable: true } } }
@@ -2502,6 +2520,18 @@ class LspClient extends LspClientConnection {
2502
2520
  textDocument: { uri: pathToFileURL3(absPath).href }
2503
2521
  }, options);
2504
2522
  }
2523
+ async formatDocument(filePath, options, signal) {
2524
+ if (!this.isDocumentFormattingSupported())
2525
+ return null;
2526
+ const absPath = this.resolveWorkspacePath(filePath);
2527
+ await this.openFile(absPath);
2528
+ const requestOptions = signal === undefined ? {} : { signal };
2529
+ const edits = await this.sendRequest("textDocument/formatting", {
2530
+ textDocument: { uri: pathToFileURL3(absPath).href },
2531
+ options
2532
+ }, requestOptions);
2533
+ return edits ?? [];
2534
+ }
2505
2535
  async workspaceSymbols(query, signal) {
2506
2536
  const options = signal === undefined ? {} : { signal };
2507
2537
  return this.sendRequest("workspace/symbol", { query }, options);
@@ -2754,6 +2784,7 @@ class LspManager {
2754
2784
  this.disposed = false;
2755
2785
  this.idleTimeoutMs = options.idleTimeoutMs ?? IDLE_TIMEOUT_MS;
2756
2786
  this.initTimeoutMs = options.initTimeoutMs ?? INIT_TIMEOUT_MS;
2787
+ this.maxResidentClients = options.maxResidentClients ?? MAX_RESIDENT_CLIENTS;
2757
2788
  this.reaperIntervalMs = options.reaperIntervalMs ?? REAPER_INTERVAL_MS;
2758
2789
  this.clientFactory = options.clientFactory ?? ((root, server) => new LspClient(root, server));
2759
2790
  this.now = options.now ?? (() => Date.now());
@@ -2787,6 +2818,26 @@ class LspManager {
2787
2818
  }
2788
2819
  }
2789
2820
  }
2821
+ evictForAdmission() {
2822
+ while (this.clients.size >= this.maxResidentClients) {
2823
+ const victim = this.leastRecentlyUsedIdleClient();
2824
+ if (!victim)
2825
+ return;
2826
+ this.clients.delete(victim.key);
2827
+ stopClientBestEffort(victim.managed.client);
2828
+ }
2829
+ }
2830
+ leastRecentlyUsedIdleClient() {
2831
+ let candidate = null;
2832
+ for (const [key, managed] of this.clients) {
2833
+ if (managed.refCount > 0 || managed.pendingWaiters > 0 || managed.isInitializing)
2834
+ continue;
2835
+ if (candidate === null || managed.lastUsedAt < candidate.managed.lastUsedAt) {
2836
+ candidate = { key, managed };
2837
+ }
2838
+ }
2839
+ return candidate;
2840
+ }
2790
2841
  async tryDeleteIfOrphaned(key, managed) {
2791
2842
  if (managed.refCount === 0 && managed.pendingWaiters === 0 && !managed.isInitializing && this.clients.get(key) === managed) {
2792
2843
  this.clients.delete(key);
@@ -2833,6 +2884,7 @@ class LspManager {
2833
2884
  managed.lastUsedAt = this.now();
2834
2885
  return managed.client;
2835
2886
  }
2887
+ this.evictForAdmission();
2836
2888
  const client = this.clientFactory(root, server);
2837
2889
  const initStartedAt = this.now();
2838
2890
  const initPromise = (async () => {
@@ -2895,6 +2947,7 @@ class LspManager {
2895
2947
  const key = this.getKey(root, server.id);
2896
2948
  if (this.clients.has(key))
2897
2949
  return;
2950
+ this.evictForAdmission();
2898
2951
  const client = this.clientFactory(root, server);
2899
2952
  const initStartedAt = this.now();
2900
2953
  const initPromise = (async () => {
@@ -3295,7 +3348,7 @@ function createIdleTimer(idleTimeoutMs, log, onTimeout) {
3295
3348
  }
3296
3349
  // ../lsp-core/src/lsp/client-wrapper.ts
3297
3350
  import { existsSync as existsSync10, realpathSync as realpathSync5, statSync as statSync3 } from "node:fs";
3298
- import { basename as basename3, dirname as dirname7, join as join5, resolve as resolve7 } from "node:path";
3351
+ import { basename as basename3, dirname as dirname8, join as join5, resolve as resolve8 } from "node:path";
3299
3352
 
3300
3353
  // ../lsp-core/src/request-context.ts
3301
3354
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -3512,57 +3565,6 @@ function nearestExistingAncestor(directory) {
3512
3565
  return current;
3513
3566
  }
3514
3567
 
3515
- // ../lsp-core/src/lsp/server-install-state.ts
3516
- import { existsSync as existsSync7, mkdirSync, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
3517
- import { dirname as dirname6 } from "node:path";
3518
- function getInstallDecisionsPath() {
3519
- return lspRequestContext().installDecisionsPath;
3520
- }
3521
- function loadInstallDecisions() {
3522
- const path = getInstallDecisionsPath();
3523
- if (!existsSync7(path))
3524
- return {};
3525
- try {
3526
- const parsed = JSON.parse(readFileSync3(path, "utf8"));
3527
- return isInstallDecisions(parsed) ? parsed : {};
3528
- } catch {
3529
- return {};
3530
- }
3531
- }
3532
- function loadInstallDecision(serverId) {
3533
- return loadInstallDecisions()[serverId];
3534
- }
3535
- function recordInstallDecision(serverId, decision, decidedAt = new Date().toISOString()) {
3536
- const decisions = loadInstallDecisions();
3537
- decisions[serverId] = { decision, decidedAt };
3538
- writeInstallDecisions(decisions);
3539
- }
3540
- function isInstallDecision(value) {
3541
- return value === "declined" || value === "allowed";
3542
- }
3543
- function writeInstallDecisions(decisions) {
3544
- const path = getInstallDecisionsPath();
3545
- mkdirSync(dirname6(path), { recursive: true });
3546
- const tmpPath = `${path}.tmp`;
3547
- writeFileSync2(tmpPath, `${JSON.stringify(decisions, null, 2)}
3548
- `, "utf8");
3549
- renameSync2(tmpPath, path);
3550
- }
3551
- function isInstallDecisions(value) {
3552
- return isRecord5(value) && Object.values(value).every(isInstallDecisionRecord);
3553
- }
3554
- function isInstallDecisionRecord(value) {
3555
- if (!isRecord5(value))
3556
- return false;
3557
- return isInstallDecision(value["decision"]) && typeof value["decidedAt"] === "string";
3558
- }
3559
- function isRecord5(value) {
3560
- return typeof value === "object" && value !== null && !Array.isArray(value);
3561
- }
3562
-
3563
- // ../lsp-core/src/lsp/config-loader.ts
3564
- import { existsSync as existsSync8, readFileSync as readFileSync4 } from "node:fs";
3565
-
3566
3568
  // ../lsp-core/src/lsp/server-definitions.ts
3567
3569
  var LSP_INSTALL_HINTS = {
3568
3570
  typescript: "npm install -g typescript-language-server typescript",
@@ -3608,6 +3610,26 @@ var LSP_INSTALL_HINTS = {
3608
3610
  julials: `julia -e 'using Pkg; Pkg.add("LanguageServer")'`,
3609
3611
  razor: "Razor runs through the Roslyn language server (cohosting). " + "Install: dotnet tool install -g roslyn-language-server --prerelease (requires v5.8.0+). See https://github.com/dotnet/razor"
3610
3612
  };
3613
+ var LSP_LOCAL_INSTALL_HINTS = {
3614
+ typescript: "bun add -d typescript-language-server typescript",
3615
+ vue: "bun add -d @vue/language-server",
3616
+ eslint: "bun add -d vscode-langservers-extracted",
3617
+ oxlint: "bun add -d oxlint",
3618
+ biome: "bun add -d @biomejs/biome",
3619
+ svelte: "bun add -d svelte-language-server",
3620
+ astro: "bun add -d @astrojs/language-server",
3621
+ bash: "bun add -d bash-language-server",
3622
+ "bash-ls": "bun add -d bash-language-server",
3623
+ "yaml-ls": "bun add -d yaml-language-server",
3624
+ php: "bun add -d intelephense",
3625
+ prisma: "bun add -d prisma",
3626
+ dockerfile: "bun add -d dockerfile-language-server-nodejs",
3627
+ pyright: "uv add --dev pyright",
3628
+ basedpyright: "uv add --dev basedpyright",
3629
+ ruff: "uv add --dev ruff",
3630
+ ty: "uv add --dev ty",
3631
+ "ruby-lsp": "bundle add ruby-lsp --group development"
3632
+ };
3611
3633
  var BUILTIN_SERVERS = {
3612
3634
  typescript: {
3613
3635
  command: ["typescript-language-server", "--stdio"],
@@ -3710,7 +3732,56 @@ var BUILTIN_SERVERS = {
3710
3732
  }
3711
3733
  };
3712
3734
 
3735
+ // ../lsp-core/src/lsp/server-install-state.ts
3736
+ import { existsSync as existsSync7, mkdirSync, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
3737
+ import { dirname as dirname6 } from "node:path";
3738
+ function getInstallDecisionsPath() {
3739
+ return lspRequestContext().installDecisionsPath;
3740
+ }
3741
+ function loadInstallDecisions() {
3742
+ const path = getInstallDecisionsPath();
3743
+ if (!existsSync7(path))
3744
+ return {};
3745
+ try {
3746
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
3747
+ return isInstallDecisions(parsed) ? parsed : {};
3748
+ } catch {
3749
+ return {};
3750
+ }
3751
+ }
3752
+ function loadInstallDecision(serverId) {
3753
+ return loadInstallDecisions()[serverId];
3754
+ }
3755
+ function recordInstallDecision(serverId, decision, decidedAt = new Date().toISOString()) {
3756
+ const decisions = loadInstallDecisions();
3757
+ decisions[serverId] = { decision, decidedAt };
3758
+ writeInstallDecisions(decisions);
3759
+ }
3760
+ function isInstallDecision(value) {
3761
+ return value === "declined" || value === "allowed";
3762
+ }
3763
+ function writeInstallDecisions(decisions) {
3764
+ const path = getInstallDecisionsPath();
3765
+ mkdirSync(dirname6(path), { recursive: true });
3766
+ const tmpPath = `${path}.tmp`;
3767
+ writeFileSync2(tmpPath, `${JSON.stringify(decisions, null, 2)}
3768
+ `, "utf8");
3769
+ renameSync2(tmpPath, path);
3770
+ }
3771
+ function isInstallDecisions(value) {
3772
+ return isRecord5(value) && Object.values(value).every(isInstallDecisionRecord);
3773
+ }
3774
+ function isInstallDecisionRecord(value) {
3775
+ if (!isRecord5(value))
3776
+ return false;
3777
+ return isInstallDecision(value["decision"]) && typeof value["decidedAt"] === "string";
3778
+ }
3779
+ function isRecord5(value) {
3780
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3781
+ }
3782
+
3713
3783
  // ../lsp-core/src/lsp/config-loader.ts
3784
+ import { existsSync as existsSync8, readFileSync as readFileSync4 } from "node:fs";
3714
3785
  function getProjectConfigPaths() {
3715
3786
  return lspRequestContext().projectConfigPaths;
3716
3787
  }
@@ -3907,53 +3978,154 @@ function getDisabledServerIds() {
3907
3978
 
3908
3979
  // ../lsp-core/src/lsp/server-installation.ts
3909
3980
  import { existsSync as existsSync9 } from "node:fs";
3910
- import { delimiter as delimiter3, join as join4 } from "node:path";
3911
- function isServerInstalled(command, _workingDirectory) {
3912
- if (command.length === 0)
3913
- return false;
3914
- const [cmd] = command;
3915
- if (!cmd)
3916
- return false;
3917
- if (cmd.includes("/") || cmd.includes("\\")) {
3918
- if (existsSync9(cmd))
3919
- return true;
3981
+ import { delimiter as delimiter3, dirname as dirname7, isAbsolute as isAbsolute3, join as join4, resolve as resolve7 } from "node:path";
3982
+ var LOCAL_BIN_RULES = [
3983
+ {
3984
+ markers: ["package.json", "bun.lock", "bun.lockb", "package-lock.json", "yarn.lock", "pnpm-lock.yaml"],
3985
+ binDirs: [join4("node_modules", ".bin")]
3986
+ },
3987
+ {
3988
+ markers: [
3989
+ "pyproject.toml",
3990
+ "ty.toml",
3991
+ "requirements.txt",
3992
+ "setup.py",
3993
+ "setup.cfg",
3994
+ "Pipfile",
3995
+ "pyrightconfig.json",
3996
+ "ruff.toml",
3997
+ ".ruff.toml"
3998
+ ],
3999
+ binDirs: [
4000
+ join4(".venv", "bin"),
4001
+ join4(".venv", "Scripts"),
4002
+ join4("venv", "bin"),
4003
+ join4("venv", "Scripts"),
4004
+ join4(".env", "bin"),
4005
+ join4(".env", "Scripts")
4006
+ ]
4007
+ },
4008
+ {
4009
+ markers: ["Gemfile", "Gemfile.lock"],
4010
+ binDirs: [join4("vendor", "bundle", "bin"), "bin"]
4011
+ },
4012
+ {
4013
+ markers: ["go.mod", "go.sum", "go.work"],
4014
+ binDirs: ["bin"]
3920
4015
  }
3921
- const isWindows = process.platform === "win32";
3922
- let exts = [""];
3923
- if (isWindows) {
3924
- const pathExt = process.env["PATHEXT"] ?? "";
3925
- if (pathExt) {
3926
- const systemExts = pathExt.split(";").filter(Boolean);
3927
- exts = [...new Set([...exts, ...systemExts, ".exe", ".cmd", ".bat", ".ps1"])];
3928
- } else {
3929
- exts = ["", ".exe", ".cmd", ".bat", ".ps1"];
4016
+ ];
4017
+ var resolutionCache = new Map;
4018
+ var probeCount = 0;
4019
+ function probe(path) {
4020
+ probeCount += 1;
4021
+ return existsSync9(path);
4022
+ }
4023
+ function executableSuffixes(platform, pathExt) {
4024
+ if (platform !== "win32")
4025
+ return [""];
4026
+ const configured = pathExt ?? process.env["PATHEXT"] ?? "";
4027
+ const systemExts = configured.split(";").filter((entry) => entry.length > 0).map((entry) => entry.toLowerCase());
4028
+ return [...new Set(["", ...systemExts, ".exe", ".cmd", ".bat", ".ps1"])];
4029
+ }
4030
+ function probeWithSuffixes(directory, command, suffixes) {
4031
+ for (const suffix of suffixes) {
4032
+ const candidate = join4(directory, command + suffix);
4033
+ if (probe(candidate))
4034
+ return candidate;
4035
+ }
4036
+ return null;
4037
+ }
4038
+ function resolveLocal(command, workingDirectory, suffixes, stopAt) {
4039
+ const boundary = stopAt === undefined ? undefined : resolve7(stopAt);
4040
+ let current = resolve7(workingDirectory);
4041
+ while (true) {
4042
+ for (const rule of LOCAL_BIN_RULES) {
4043
+ if (!rule.markers.some((marker) => probe(join4(current, marker))))
4044
+ continue;
4045
+ for (const binDir of rule.binDirs) {
4046
+ const found = probeWithSuffixes(join4(current, binDir), command, suffixes);
4047
+ if (found !== null)
4048
+ return found;
4049
+ }
3930
4050
  }
4051
+ if (probe(join4(current, ".git")))
4052
+ return null;
4053
+ if (boundary !== undefined && current === boundary)
4054
+ return null;
4055
+ const parent = dirname7(current);
4056
+ if (parent === current)
4057
+ return null;
4058
+ current = parent;
3931
4059
  }
3932
- let pathEnv = process.env["PATH"] ?? "";
3933
- if (isWindows && !pathEnv) {
4060
+ }
4061
+ function resolveFromPath(command, suffixes, platform, pathEnvOverride) {
4062
+ let pathEnv = pathEnvOverride ?? process.env["PATH"] ?? "";
4063
+ if (platform === "win32" && !pathEnv) {
3934
4064
  pathEnv = process.env["Path"] ?? "";
3935
4065
  }
3936
- const paths = pathEnv.split(delimiter3);
3937
- for (const p of paths) {
3938
- for (const suffix of exts) {
3939
- if (existsSync9(join4(p, cmd + suffix))) {
3940
- return true;
3941
- }
3942
- }
4066
+ for (const entry of pathEnv.split(delimiter3)) {
4067
+ if (entry.length === 0)
4068
+ continue;
4069
+ const found = probeWithSuffixes(entry, command, suffixes);
4070
+ if (found !== null)
4071
+ return found;
3943
4072
  }
3944
- if (cmd === "node")
3945
- return true;
3946
- return false;
4073
+ return null;
4074
+ }
4075
+ function resolveServerBinary(command, workingDirectory, options = {}) {
4076
+ if (command.length === 0)
4077
+ return null;
4078
+ const [cmd] = command;
4079
+ if (cmd === undefined || cmd.length === 0)
4080
+ return null;
4081
+ const platform = options.platform ?? process.platform;
4082
+ const suffixes = executableSuffixes(platform, options.pathExt);
4083
+ if (cmd.includes("/") || cmd.includes("\\")) {
4084
+ const explicit = isAbsolute3(cmd) ? cmd : resolve7(workingDirectory ?? process.cwd(), cmd);
4085
+ return probe(explicit) ? explicit : null;
4086
+ }
4087
+ const cacheKey = JSON.stringify([
4088
+ workingDirectory ?? "",
4089
+ cmd,
4090
+ platform,
4091
+ options.stopAt ?? "",
4092
+ options.pathExt ?? "",
4093
+ options.pathEnv ?? process.env["PATH"] ?? ""
4094
+ ]);
4095
+ const cached = resolutionCache.get(cacheKey);
4096
+ if (cached !== undefined)
4097
+ return cached;
4098
+ let resolved = null;
4099
+ if (workingDirectory !== undefined && workingDirectory.length > 0) {
4100
+ resolved = resolveLocal(cmd, workingDirectory, suffixes, options.stopAt);
4101
+ }
4102
+ resolved ??= resolveFromPath(cmd, suffixes, platform, options.pathEnv);
4103
+ if (resolved === null && cmd === "node")
4104
+ resolved = cmd;
4105
+ resolutionCache.set(cacheKey, resolved);
4106
+ return resolved;
4107
+ }
4108
+ function isServerInstalled(command, workingDirectory, options = {}) {
4109
+ if (command.length === 0)
4110
+ return false;
4111
+ return resolveServerBinary(command, workingDirectory, options) !== null;
3947
4112
  }
3948
4113
 
3949
4114
  // ../lsp-core/src/lsp/server-resolution.ts
4115
+ function withResolvedCommand(command, binaryPath) {
4116
+ return [binaryPath, ...command.slice(1)];
4117
+ }
3950
4118
  function findServerForExtension(ext) {
3951
4119
  const servers = getMergedServers();
4120
+ const workingDirectory = contextCwd();
3952
4121
  for (const server2 of servers) {
3953
- if (server2.extensions.includes(ext) && isServerInstalled(server2.command)) {
4122
+ if (!server2.extensions.includes(ext))
4123
+ continue;
4124
+ const binaryPath = resolveServerBinary(server2.command, workingDirectory);
4125
+ if (binaryPath !== null) {
3954
4126
  const resolvedServer = {
3955
4127
  id: server2.id,
3956
- command: server2.command,
4128
+ command: withResolvedCommand(server2.command, binaryPath),
3957
4129
  extensions: server2.extensions,
3958
4130
  priority: server2.priority
3959
4131
  };
@@ -4000,6 +4172,7 @@ function findServerForExtension(ext) {
4000
4172
  function getAllServers() {
4001
4173
  const servers = getMergedServers();
4002
4174
  const disabled = getDisabledServerIds();
4175
+ const workingDirectory = contextCwd();
4003
4176
  const result = [];
4004
4177
  const seen = new Set;
4005
4178
  for (const server2 of servers) {
@@ -4007,7 +4180,7 @@ function getAllServers() {
4007
4180
  continue;
4008
4181
  result.push({
4009
4182
  id: server2.id,
4010
- installed: isServerInstalled(server2.command),
4183
+ installed: isServerInstalled(server2.command, workingDirectory),
4011
4184
  extensions: server2.extensions,
4012
4185
  disabled: false,
4013
4186
  source: server2.source,
@@ -4021,7 +4194,7 @@ function getAllServers() {
4021
4194
  const builtin = BUILTIN_SERVERS[id];
4022
4195
  result.push({
4023
4196
  id,
4024
- installed: builtin ? isServerInstalled(builtin.command) : false,
4197
+ installed: builtin ? isServerInstalled(builtin.command, workingDirectory) : false,
4025
4198
  extensions: builtin?.extensions ?? [],
4026
4199
  disabled: true,
4027
4200
  source: "disabled",
@@ -4044,7 +4217,7 @@ function findWorkspaceRoot(filePath) {
4044
4217
  const abs = resolveReadablePathInsideContext(filePath);
4045
4218
  let dir = abs;
4046
4219
  if (!isDirectoryPath(dir)) {
4047
- dir = dirname7(dir);
4220
+ dir = dirname8(dir);
4048
4221
  }
4049
4222
  if (!isPathInside(cwd, abs))
4050
4223
  return findWorkspaceRootOutsideContext(dir);
@@ -4060,13 +4233,13 @@ function findWorkspaceRoot(filePath) {
4060
4233
  }
4061
4234
  if (dir === cwd)
4062
4235
  break;
4063
- dir = dirname7(dir);
4236
+ dir = dirname8(dir);
4064
4237
  }
4065
4238
  return fallbackRoot;
4066
4239
  }
4067
4240
  function resolveReadablePathInsideContext(filePath) {
4068
4241
  const cwd = contextCwd();
4069
- const abs = resolve7(cwd, filePath);
4242
+ const abs = resolve8(cwd, filePath);
4070
4243
  if (isPathInside(cwd, abs))
4071
4244
  return abs;
4072
4245
  const rebased = rebaseThroughCanonicalAncestor(abs, cwd);
@@ -4092,7 +4265,7 @@ function rebaseThroughCanonicalAncestor(path, cwd) {
4092
4265
  if (isPathInside(cwd, canonical))
4093
4266
  return suffix.length === 0 ? canonical : join5(canonical, ...suffix);
4094
4267
  }
4095
- const parent = dirname7(current);
4268
+ const parent = dirname8(current);
4096
4269
  if (parent === current)
4097
4270
  return;
4098
4271
  suffix.unshift(basename3(current));
@@ -4115,7 +4288,7 @@ function nearestExistingDirectoryInsideContext(directory, cwd) {
4115
4288
  return canonical;
4116
4289
  if (current === cwd)
4117
4290
  return;
4118
- current = dirname7(current);
4291
+ current = dirname8(current);
4119
4292
  }
4120
4293
  return;
4121
4294
  }
@@ -4142,6 +4315,19 @@ function formatServerLookupError(result) {
4142
4315
  ].join(`
4143
4316
  `);
4144
4317
  }
4318
+ function formatInstallOptions(serverId, installHint) {
4319
+ const localHint = LSP_LOCAL_INSTALL_HINTS[serverId];
4320
+ if (localHint === undefined) {
4321
+ return ["To install, run:", ` ${installHint}`];
4322
+ }
4323
+ return [
4324
+ "To install in THIS repository (preferred — no global install needed):",
4325
+ ` ${localHint}`,
4326
+ "",
4327
+ "Or install it globally:",
4328
+ ` ${installHint}`
4329
+ ];
4330
+ }
4145
4331
  function formatNotInstalled(result) {
4146
4332
  const { server: server2, installHint } = result;
4147
4333
  const extensions = server2.extensions.join(", ");
@@ -4156,19 +4342,19 @@ function formatNotInstalled(result) {
4156
4342
  `Command not found: ${server2.command[0]}`,
4157
4343
  ""
4158
4344
  ];
4345
+ const installOptions = formatInstallOptions(server2.id, installHint);
4159
4346
  if (decision === "allowed") {
4160
4347
  return [
4161
4348
  ...header,
4162
4349
  "The user has pre-authorized LSP installation. Run the install command, then retry this tool:",
4163
- ` ${installHint}`
4350
+ ...installOptions.slice(1)
4164
4351
  ].join(`
4165
4352
  `);
4166
4353
  }
4167
4354
  if (!context.capabilities.installDecisionTool) {
4168
4355
  return [
4169
4356
  ...header,
4170
- "To install, run:",
4171
- ` ${installHint}`,
4357
+ ...installOptions,
4172
4358
  "",
4173
4359
  "ACTION REQUIRED — ASK THE USER whether to install this LSP server.",
4174
4360
  "Install-decision recording is unavailable in this harness; proceed without LSP if the user declines."
@@ -4177,8 +4363,7 @@ function formatNotInstalled(result) {
4177
4363
  }
4178
4364
  return [
4179
4365
  ...header,
4180
- "To install, run:",
4181
- ` ${installHint}`,
4366
+ ...installOptions,
4182
4367
  "",
4183
4368
  "ACTION REQUIRED — ASK THE USER whether to install this LSP server.",
4184
4369
  "- If the user agrees: run the install command above, then retry this tool.",
@@ -4234,7 +4419,7 @@ async function withLspClient(filePath, fn, toolName, options = {}) {
4234
4419
 
4235
4420
  // ../lsp-core/src/lsp/directory-diagnostics.ts
4236
4421
  import { existsSync as existsSync11, lstatSync as lstatSync4, readdirSync as readdirSync3 } from "node:fs";
4237
- import { join as join6, resolve as resolve8 } from "node:path";
4422
+ import { join as join6, resolve as resolve9 } from "node:path";
4238
4423
 
4239
4424
  // ../lsp-core/src/lsp/formatters.ts
4240
4425
  import { fileURLToPath as fileURLToPath2 } from "node:url";
@@ -4389,7 +4574,7 @@ async function aggregateDiagnosticsForDirectory(directory, extension, severity,
4389
4574
  if (!extension.startsWith(".")) {
4390
4575
  throw new LspInvalidPathError(`Extension must start with a dot (e.g., ".ts", not "${extension}"). Use ".${extension}" instead.`);
4391
4576
  }
4392
- const absDir = resolve8(options.workspaceRoot ?? contextCwd(), directory);
4577
+ const absDir = resolve9(options.workspaceRoot ?? contextCwd(), directory);
4393
4578
  if (!existsSync11(absDir)) {
4394
4579
  throw new LspInvalidPathError(`Directory does not exist: ${absDir}`);
4395
4580
  }
@@ -4746,6 +4931,96 @@ async function executeLspDiagnostics(params, signal) {
4746
4931
  throw error;
4747
4932
  }
4748
4933
  }
4934
+ // ../lsp-core/src/lsp/format-document.ts
4935
+ import { readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
4936
+ var DEFAULT_FORMATTING_OPTIONS = {
4937
+ tabSize: 4,
4938
+ insertSpaces: false,
4939
+ trimTrailingWhitespace: true,
4940
+ insertFinalNewline: true,
4941
+ trimFinalNewlines: true
4942
+ };
4943
+ var UNCHANGED = { status: "unchanged", linesAdded: 0, linesRemoved: 0 };
4944
+ async function formatDocumentWithClient(client, filePath, options = {}) {
4945
+ const edits = await client.formatDocument(filePath, options.formattingOptions ?? DEFAULT_FORMATTING_OPTIONS, options.signal);
4946
+ if (edits === null)
4947
+ return { status: "unavailable", reason: "capability_not_advertised" };
4948
+ if (edits.length === 0)
4949
+ return UNCHANGED;
4950
+ const before = readFileSync5(filePath, "utf-8");
4951
+ const normalized = normalizeTextEdits(before, edits, 0);
4952
+ if (normalized.text === before)
4953
+ return UNCHANGED;
4954
+ writeAtomically(filePath, normalized.text);
4955
+ await client.openFile(filePath);
4956
+ return {
4957
+ status: "formatted",
4958
+ ...lineDelta(normalized.edits)
4959
+ };
4960
+ }
4961
+ function lineDelta(edits) {
4962
+ let linesAdded = 0;
4963
+ let linesRemoved = 0;
4964
+ for (const edit of edits) {
4965
+ linesRemoved += edit.range.end.line - edit.range.start.line + 1;
4966
+ linesAdded += edit.newText.split(`
4967
+ `).length;
4968
+ }
4969
+ return { linesAdded, linesRemoved };
4970
+ }
4971
+ function writeAtomically(filePath, content) {
4972
+ const tempPath = `${filePath}.omo-format.tmp`;
4973
+ writeFileSync3(tempPath, content, "utf-8");
4974
+ try {
4975
+ renameSync3(tempPath, filePath);
4976
+ } catch (error) {
4977
+ try {
4978
+ unlinkSync(tempPath);
4979
+ } catch {}
4980
+ throw error;
4981
+ }
4982
+ }
4983
+
4984
+ // ../lsp-core/src/tools/format.ts
4985
+ async function executeLspFormat(params, signal) {
4986
+ const filePath = requireString(params, "filePath");
4987
+ try {
4988
+ const result = await withLspClient(filePath, async (client, _workspaceRoot, resolvedFilePath) => formatDocumentWithClient(client, resolvedFilePath, signal === undefined ? {} : { signal }), "format", clientOptions(signal));
4989
+ return text(describeResult(filePath, result), formatDetails(filePath, result));
4990
+ } catch (error) {
4991
+ const missingDependency = missingDependencyResult(error, {
4992
+ filePath,
4993
+ status: "unavailable",
4994
+ reason: "server_unavailable",
4995
+ linesAdded: 0,
4996
+ linesRemoved: 0
4997
+ });
4998
+ if (missingDependency)
4999
+ return missingDependency;
5000
+ throw error;
5001
+ }
5002
+ }
5003
+ function formatDetails(filePath, result) {
5004
+ if (result.status === "unavailable") {
5005
+ return { filePath, status: "unavailable", reason: result.reason, linesAdded: 0, linesRemoved: 0 };
5006
+ }
5007
+ return {
5008
+ filePath,
5009
+ status: result.status,
5010
+ linesAdded: result.linesAdded,
5011
+ linesRemoved: result.linesRemoved
5012
+ };
5013
+ }
5014
+ function describeResult(filePath, result) {
5015
+ if (result.status === "unavailable") {
5016
+ return `Formatting unavailable for ${filePath}: the language server does not advertise documentFormattingProvider.`;
5017
+ }
5018
+ if (result.status === "unchanged") {
5019
+ return `Already formatted: ${filePath}`;
5020
+ }
5021
+ return `Formatted ${filePath} (+${result.linesAdded}/-${result.linesRemoved} lines)`;
5022
+ }
5023
+
4749
5024
  // ../lsp-core/src/tools/install-decision.ts
4750
5025
  async function executeLspInstallDecision(params) {
4751
5026
  const serverId = requireString(params, "server_id");
@@ -5078,6 +5353,16 @@ var LSP_MCP_TOOLS = [
5078
5353
  }, ["filePath", "line", "character", "newName"]),
5079
5354
  execute: executeLspRename
5080
5355
  },
5356
+ {
5357
+ name: "format",
5358
+ aliases: ["lsp_format"],
5359
+ title: "LSP Format",
5360
+ description: "Format a source file with its language server and write the returned edits to disk.",
5361
+ inputSchema: objectSchema({
5362
+ filePath: { type: "string", description: "Source file path to format." }
5363
+ }, ["filePath"]),
5364
+ execute: executeLspFormat
5365
+ },
5081
5366
  {
5082
5367
  name: "install_decision",
5083
5368
  aliases: ["lsp_install_decision"],