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
@@ -311,7 +311,7 @@ function createIdleTimer(idleTimeoutMs, log, onTimeout) {
311
311
  }
312
312
  // ../lsp-core/src/lsp/client-wrapper.ts
313
313
  import { existsSync as existsSync10, realpathSync as realpathSync5, statSync as statSync3 } from "node:fs";
314
- import { basename as basename3, dirname as dirname7, join as join5, resolve as resolve7 } from "node:path";
314
+ import { basename as basename3, dirname as dirname8, join as join5, resolve as resolve8 } from "node:path";
315
315
 
316
316
  // ../lsp-core/src/request-context.ts
317
317
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -593,6 +593,7 @@ var DEFAULT_MAX_DIRECTORY_FILES = 50;
593
593
  var REQUEST_TIMEOUT_MS = 15000;
594
594
  var INIT_TIMEOUT_MS = 60000;
595
595
  var IDLE_TIMEOUT_MS = 5 * 60000;
596
+ var MAX_RESIDENT_CLIENTS = 6;
596
597
  var REAPER_INTERVAL_MS = 60000;
597
598
  var STOP_HARD_KILL_TIMEOUT_MS = 5000;
598
599
  var STOP_SIGKILL_GRACE_MS = 1000;
@@ -1118,6 +1119,7 @@ class LspClientTransport {
1118
1119
  this.diagnosticsStore = new Map;
1119
1120
  this.workspaceApplyEditHandler = null;
1120
1121
  this.diagnosticPullSupported = false;
1122
+ this.documentFormattingSupported = false;
1121
1123
  this.requestTimeoutMs = timeouts.requestTimeoutMs ?? REQUEST_TIMEOUT_MS;
1122
1124
  this.initializeTimeoutMs = timeouts.initializeTimeoutMs ?? INIT_TIMEOUT_MS;
1123
1125
  }
@@ -1139,6 +1141,12 @@ class LspClientTransport {
1139
1141
  isDiagnosticPullSupported() {
1140
1142
  return this.diagnosticPullSupported;
1141
1143
  }
1144
+ setDocumentFormattingSupported(supported) {
1145
+ this.documentFormattingSupported = supported;
1146
+ }
1147
+ isDocumentFormattingSupported() {
1148
+ return this.documentFormattingSupported;
1149
+ }
1142
1150
  handlePublishDiagnostics(params) {
1143
1151
  this.diagnosticsStore.set(params.uri, [...params.diagnostics]);
1144
1152
  }
@@ -1352,6 +1360,14 @@ function supportsDiagnosticPull(capabilities) {
1352
1360
  return false;
1353
1361
  return Object.hasOwn(capabilities, "diagnosticProvider");
1354
1362
  }
1363
+ function supportsDocumentFormatting(capabilities) {
1364
+ if (capabilities === undefined)
1365
+ return false;
1366
+ const provider = capabilities.documentFormattingProvider;
1367
+ if (provider === undefined || provider === null || provider === false)
1368
+ return false;
1369
+ return provider === true || typeof provider === "object";
1370
+ }
1355
1371
 
1356
1372
  class LspClientConnection extends LspClientTransport {
1357
1373
  async initialize() {
@@ -1368,6 +1384,7 @@ class LspClientConnection extends LspClientTransport {
1368
1384
  references: {},
1369
1385
  documentSymbol: { hierarchicalDocumentSymbolSupport: true },
1370
1386
  publishDiagnostics: {},
1387
+ formatting: { dynamicRegistration: false },
1371
1388
  rename: {
1372
1389
  prepareSupport: true,
1373
1390
  prepareSupportDefaultBehavior: 1
@@ -1409,6 +1426,7 @@ class LspClientConnection extends LspClientTransport {
1409
1426
  initializationOptions: this.server.initialization
1410
1427
  }, { timeoutMs: this.initializeTimeoutMs });
1411
1428
  this.setDiagnosticPullSupported(supportsDiagnosticPull(result?.capabilities));
1429
+ this.setDocumentFormattingSupported(supportsDocumentFormatting(result?.capabilities));
1412
1430
  await this.sendNotification("initialized", {});
1413
1431
  await this.sendNotification("workspace/didChangeConfiguration", {
1414
1432
  settings: { json: { validate: { enable: true } } }
@@ -2993,6 +3011,18 @@ class LspClient extends LspClientConnection {
2993
3011
  textDocument: { uri: pathToFileURL3(absPath).href }
2994
3012
  }, options);
2995
3013
  }
3014
+ async formatDocument(filePath, options, signal) {
3015
+ if (!this.isDocumentFormattingSupported())
3016
+ return null;
3017
+ const absPath = this.resolveWorkspacePath(filePath);
3018
+ await this.openFile(absPath);
3019
+ const requestOptions = signal === undefined ? {} : { signal };
3020
+ const edits = await this.sendRequest("textDocument/formatting", {
3021
+ textDocument: { uri: pathToFileURL3(absPath).href },
3022
+ options
3023
+ }, requestOptions);
3024
+ return edits ?? [];
3025
+ }
2996
3026
  async workspaceSymbols(query, signal) {
2997
3027
  const options = signal === undefined ? {} : { signal };
2998
3028
  return this.sendRequest("workspace/symbol", { query }, options);
@@ -3245,6 +3275,7 @@ class LspManager {
3245
3275
  this.disposed = false;
3246
3276
  this.idleTimeoutMs = options.idleTimeoutMs ?? IDLE_TIMEOUT_MS;
3247
3277
  this.initTimeoutMs = options.initTimeoutMs ?? INIT_TIMEOUT_MS;
3278
+ this.maxResidentClients = options.maxResidentClients ?? MAX_RESIDENT_CLIENTS;
3248
3279
  this.reaperIntervalMs = options.reaperIntervalMs ?? REAPER_INTERVAL_MS;
3249
3280
  this.clientFactory = options.clientFactory ?? ((root, server2) => new LspClient(root, server2));
3250
3281
  this.now = options.now ?? (() => Date.now());
@@ -3278,6 +3309,26 @@ class LspManager {
3278
3309
  }
3279
3310
  }
3280
3311
  }
3312
+ evictForAdmission() {
3313
+ while (this.clients.size >= this.maxResidentClients) {
3314
+ const victim = this.leastRecentlyUsedIdleClient();
3315
+ if (!victim)
3316
+ return;
3317
+ this.clients.delete(victim.key);
3318
+ stopClientBestEffort(victim.managed.client);
3319
+ }
3320
+ }
3321
+ leastRecentlyUsedIdleClient() {
3322
+ let candidate = null;
3323
+ for (const [key, managed] of this.clients) {
3324
+ if (managed.refCount > 0 || managed.pendingWaiters > 0 || managed.isInitializing)
3325
+ continue;
3326
+ if (candidate === null || managed.lastUsedAt < candidate.managed.lastUsedAt) {
3327
+ candidate = { key, managed };
3328
+ }
3329
+ }
3330
+ return candidate;
3331
+ }
3281
3332
  async tryDeleteIfOrphaned(key, managed) {
3282
3333
  if (managed.refCount === 0 && managed.pendingWaiters === 0 && !managed.isInitializing && this.clients.get(key) === managed) {
3283
3334
  this.clients.delete(key);
@@ -3324,6 +3375,7 @@ class LspManager {
3324
3375
  managed.lastUsedAt = this.now();
3325
3376
  return managed.client;
3326
3377
  }
3378
+ this.evictForAdmission();
3327
3379
  const client = this.clientFactory(root, server2);
3328
3380
  const initStartedAt = this.now();
3329
3381
  const initPromise = (async () => {
@@ -3386,6 +3438,7 @@ class LspManager {
3386
3438
  const key = this.getKey(root, server2.id);
3387
3439
  if (this.clients.has(key))
3388
3440
  return;
3441
+ this.evictForAdmission();
3389
3442
  const client = this.clientFactory(root, server2);
3390
3443
  const initStartedAt = this.now();
3391
3444
  const initPromise = (async () => {
@@ -3511,57 +3564,6 @@ function nearestExistingAncestor(directory) {
3511
3564
  return current;
3512
3565
  }
3513
3566
 
3514
- // ../lsp-core/src/lsp/server-install-state.ts
3515
- import { existsSync as existsSync7, mkdirSync, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
3516
- import { dirname as dirname6 } from "node:path";
3517
- function getInstallDecisionsPath() {
3518
- return lspRequestContext().installDecisionsPath;
3519
- }
3520
- function loadInstallDecisions() {
3521
- const path = getInstallDecisionsPath();
3522
- if (!existsSync7(path))
3523
- return {};
3524
- try {
3525
- const parsed = JSON.parse(readFileSync3(path, "utf8"));
3526
- return isInstallDecisions(parsed) ? parsed : {};
3527
- } catch {
3528
- return {};
3529
- }
3530
- }
3531
- function loadInstallDecision(serverId) {
3532
- return loadInstallDecisions()[serverId];
3533
- }
3534
- function recordInstallDecision(serverId, decision, decidedAt = new Date().toISOString()) {
3535
- const decisions = loadInstallDecisions();
3536
- decisions[serverId] = { decision, decidedAt };
3537
- writeInstallDecisions(decisions);
3538
- }
3539
- function isInstallDecision(value) {
3540
- return value === "declined" || value === "allowed";
3541
- }
3542
- function writeInstallDecisions(decisions) {
3543
- const path = getInstallDecisionsPath();
3544
- mkdirSync(dirname6(path), { recursive: true });
3545
- const tmpPath = `${path}.tmp`;
3546
- writeFileSync2(tmpPath, `${JSON.stringify(decisions, null, 2)}
3547
- `, "utf8");
3548
- renameSync2(tmpPath, path);
3549
- }
3550
- function isInstallDecisions(value) {
3551
- return isRecord5(value) && Object.values(value).every(isInstallDecisionRecord);
3552
- }
3553
- function isInstallDecisionRecord(value) {
3554
- if (!isRecord5(value))
3555
- return false;
3556
- return isInstallDecision(value["decision"]) && typeof value["decidedAt"] === "string";
3557
- }
3558
- function isRecord5(value) {
3559
- return typeof value === "object" && value !== null && !Array.isArray(value);
3560
- }
3561
-
3562
- // ../lsp-core/src/lsp/config-loader.ts
3563
- import { existsSync as existsSync8, readFileSync as readFileSync4 } from "node:fs";
3564
-
3565
3567
  // ../lsp-core/src/lsp/server-definitions.ts
3566
3568
  var LSP_INSTALL_HINTS = {
3567
3569
  typescript: "npm install -g typescript-language-server typescript",
@@ -3607,6 +3609,26 @@ var LSP_INSTALL_HINTS = {
3607
3609
  julials: `julia -e 'using Pkg; Pkg.add("LanguageServer")'`,
3608
3610
  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"
3609
3611
  };
3612
+ var LSP_LOCAL_INSTALL_HINTS = {
3613
+ typescript: "bun add -d typescript-language-server typescript",
3614
+ vue: "bun add -d @vue/language-server",
3615
+ eslint: "bun add -d vscode-langservers-extracted",
3616
+ oxlint: "bun add -d oxlint",
3617
+ biome: "bun add -d @biomejs/biome",
3618
+ svelte: "bun add -d svelte-language-server",
3619
+ astro: "bun add -d @astrojs/language-server",
3620
+ bash: "bun add -d bash-language-server",
3621
+ "bash-ls": "bun add -d bash-language-server",
3622
+ "yaml-ls": "bun add -d yaml-language-server",
3623
+ php: "bun add -d intelephense",
3624
+ prisma: "bun add -d prisma",
3625
+ dockerfile: "bun add -d dockerfile-language-server-nodejs",
3626
+ pyright: "uv add --dev pyright",
3627
+ basedpyright: "uv add --dev basedpyright",
3628
+ ruff: "uv add --dev ruff",
3629
+ ty: "uv add --dev ty",
3630
+ "ruby-lsp": "bundle add ruby-lsp --group development"
3631
+ };
3610
3632
  var BUILTIN_SERVERS = {
3611
3633
  typescript: {
3612
3634
  command: ["typescript-language-server", "--stdio"],
@@ -3709,7 +3731,56 @@ var BUILTIN_SERVERS = {
3709
3731
  }
3710
3732
  };
3711
3733
 
3734
+ // ../lsp-core/src/lsp/server-install-state.ts
3735
+ import { existsSync as existsSync7, mkdirSync, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
3736
+ import { dirname as dirname6 } from "node:path";
3737
+ function getInstallDecisionsPath() {
3738
+ return lspRequestContext().installDecisionsPath;
3739
+ }
3740
+ function loadInstallDecisions() {
3741
+ const path = getInstallDecisionsPath();
3742
+ if (!existsSync7(path))
3743
+ return {};
3744
+ try {
3745
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
3746
+ return isInstallDecisions(parsed) ? parsed : {};
3747
+ } catch {
3748
+ return {};
3749
+ }
3750
+ }
3751
+ function loadInstallDecision(serverId) {
3752
+ return loadInstallDecisions()[serverId];
3753
+ }
3754
+ function recordInstallDecision(serverId, decision, decidedAt = new Date().toISOString()) {
3755
+ const decisions = loadInstallDecisions();
3756
+ decisions[serverId] = { decision, decidedAt };
3757
+ writeInstallDecisions(decisions);
3758
+ }
3759
+ function isInstallDecision(value) {
3760
+ return value === "declined" || value === "allowed";
3761
+ }
3762
+ function writeInstallDecisions(decisions) {
3763
+ const path = getInstallDecisionsPath();
3764
+ mkdirSync(dirname6(path), { recursive: true });
3765
+ const tmpPath = `${path}.tmp`;
3766
+ writeFileSync2(tmpPath, `${JSON.stringify(decisions, null, 2)}
3767
+ `, "utf8");
3768
+ renameSync2(tmpPath, path);
3769
+ }
3770
+ function isInstallDecisions(value) {
3771
+ return isRecord5(value) && Object.values(value).every(isInstallDecisionRecord);
3772
+ }
3773
+ function isInstallDecisionRecord(value) {
3774
+ if (!isRecord5(value))
3775
+ return false;
3776
+ return isInstallDecision(value["decision"]) && typeof value["decidedAt"] === "string";
3777
+ }
3778
+ function isRecord5(value) {
3779
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3780
+ }
3781
+
3712
3782
  // ../lsp-core/src/lsp/config-loader.ts
3783
+ import { existsSync as existsSync8, readFileSync as readFileSync4 } from "node:fs";
3713
3784
  function getProjectConfigPaths() {
3714
3785
  return lspRequestContext().projectConfigPaths;
3715
3786
  }
@@ -3906,53 +3977,154 @@ function getDisabledServerIds() {
3906
3977
 
3907
3978
  // ../lsp-core/src/lsp/server-installation.ts
3908
3979
  import { existsSync as existsSync9 } from "node:fs";
3909
- import { delimiter as delimiter3, join as join4 } from "node:path";
3910
- function isServerInstalled(command, _workingDirectory) {
3911
- if (command.length === 0)
3912
- return false;
3913
- const [cmd] = command;
3914
- if (!cmd)
3915
- return false;
3916
- if (cmd.includes("/") || cmd.includes("\\")) {
3917
- if (existsSync9(cmd))
3918
- return true;
3980
+ import { delimiter as delimiter3, dirname as dirname7, isAbsolute as isAbsolute3, join as join4, resolve as resolve7 } from "node:path";
3981
+ var LOCAL_BIN_RULES = [
3982
+ {
3983
+ markers: ["package.json", "bun.lock", "bun.lockb", "package-lock.json", "yarn.lock", "pnpm-lock.yaml"],
3984
+ binDirs: [join4("node_modules", ".bin")]
3985
+ },
3986
+ {
3987
+ markers: [
3988
+ "pyproject.toml",
3989
+ "ty.toml",
3990
+ "requirements.txt",
3991
+ "setup.py",
3992
+ "setup.cfg",
3993
+ "Pipfile",
3994
+ "pyrightconfig.json",
3995
+ "ruff.toml",
3996
+ ".ruff.toml"
3997
+ ],
3998
+ binDirs: [
3999
+ join4(".venv", "bin"),
4000
+ join4(".venv", "Scripts"),
4001
+ join4("venv", "bin"),
4002
+ join4("venv", "Scripts"),
4003
+ join4(".env", "bin"),
4004
+ join4(".env", "Scripts")
4005
+ ]
4006
+ },
4007
+ {
4008
+ markers: ["Gemfile", "Gemfile.lock"],
4009
+ binDirs: [join4("vendor", "bundle", "bin"), "bin"]
4010
+ },
4011
+ {
4012
+ markers: ["go.mod", "go.sum", "go.work"],
4013
+ binDirs: ["bin"]
3919
4014
  }
3920
- const isWindows = process.platform === "win32";
3921
- let exts = [""];
3922
- if (isWindows) {
3923
- const pathExt = process.env["PATHEXT"] ?? "";
3924
- if (pathExt) {
3925
- const systemExts = pathExt.split(";").filter(Boolean);
3926
- exts = [...new Set([...exts, ...systemExts, ".exe", ".cmd", ".bat", ".ps1"])];
3927
- } else {
3928
- exts = ["", ".exe", ".cmd", ".bat", ".ps1"];
4015
+ ];
4016
+ var resolutionCache = new Map;
4017
+ var probeCount = 0;
4018
+ function probe(path) {
4019
+ probeCount += 1;
4020
+ return existsSync9(path);
4021
+ }
4022
+ function executableSuffixes(platform, pathExt) {
4023
+ if (platform !== "win32")
4024
+ return [""];
4025
+ const configured = pathExt ?? process.env["PATHEXT"] ?? "";
4026
+ const systemExts = configured.split(";").filter((entry) => entry.length > 0).map((entry) => entry.toLowerCase());
4027
+ return [...new Set(["", ...systemExts, ".exe", ".cmd", ".bat", ".ps1"])];
4028
+ }
4029
+ function probeWithSuffixes(directory, command, suffixes) {
4030
+ for (const suffix of suffixes) {
4031
+ const candidate = join4(directory, command + suffix);
4032
+ if (probe(candidate))
4033
+ return candidate;
4034
+ }
4035
+ return null;
4036
+ }
4037
+ function resolveLocal(command, workingDirectory, suffixes, stopAt) {
4038
+ const boundary = stopAt === undefined ? undefined : resolve7(stopAt);
4039
+ let current = resolve7(workingDirectory);
4040
+ while (true) {
4041
+ for (const rule of LOCAL_BIN_RULES) {
4042
+ if (!rule.markers.some((marker) => probe(join4(current, marker))))
4043
+ continue;
4044
+ for (const binDir of rule.binDirs) {
4045
+ const found = probeWithSuffixes(join4(current, binDir), command, suffixes);
4046
+ if (found !== null)
4047
+ return found;
4048
+ }
3929
4049
  }
4050
+ if (probe(join4(current, ".git")))
4051
+ return null;
4052
+ if (boundary !== undefined && current === boundary)
4053
+ return null;
4054
+ const parent = dirname7(current);
4055
+ if (parent === current)
4056
+ return null;
4057
+ current = parent;
3930
4058
  }
3931
- let pathEnv = process.env["PATH"] ?? "";
3932
- if (isWindows && !pathEnv) {
4059
+ }
4060
+ function resolveFromPath(command, suffixes, platform, pathEnvOverride) {
4061
+ let pathEnv = pathEnvOverride ?? process.env["PATH"] ?? "";
4062
+ if (platform === "win32" && !pathEnv) {
3933
4063
  pathEnv = process.env["Path"] ?? "";
3934
4064
  }
3935
- const paths = pathEnv.split(delimiter3);
3936
- for (const p of paths) {
3937
- for (const suffix of exts) {
3938
- if (existsSync9(join4(p, cmd + suffix))) {
3939
- return true;
3940
- }
3941
- }
4065
+ for (const entry of pathEnv.split(delimiter3)) {
4066
+ if (entry.length === 0)
4067
+ continue;
4068
+ const found = probeWithSuffixes(entry, command, suffixes);
4069
+ if (found !== null)
4070
+ return found;
3942
4071
  }
3943
- if (cmd === "node")
3944
- return true;
3945
- return false;
4072
+ return null;
4073
+ }
4074
+ function resolveServerBinary(command, workingDirectory, options = {}) {
4075
+ if (command.length === 0)
4076
+ return null;
4077
+ const [cmd] = command;
4078
+ if (cmd === undefined || cmd.length === 0)
4079
+ return null;
4080
+ const platform = options.platform ?? process.platform;
4081
+ const suffixes = executableSuffixes(platform, options.pathExt);
4082
+ if (cmd.includes("/") || cmd.includes("\\")) {
4083
+ const explicit = isAbsolute3(cmd) ? cmd : resolve7(workingDirectory ?? process.cwd(), cmd);
4084
+ return probe(explicit) ? explicit : null;
4085
+ }
4086
+ const cacheKey = JSON.stringify([
4087
+ workingDirectory ?? "",
4088
+ cmd,
4089
+ platform,
4090
+ options.stopAt ?? "",
4091
+ options.pathExt ?? "",
4092
+ options.pathEnv ?? process.env["PATH"] ?? ""
4093
+ ]);
4094
+ const cached = resolutionCache.get(cacheKey);
4095
+ if (cached !== undefined)
4096
+ return cached;
4097
+ let resolved = null;
4098
+ if (workingDirectory !== undefined && workingDirectory.length > 0) {
4099
+ resolved = resolveLocal(cmd, workingDirectory, suffixes, options.stopAt);
4100
+ }
4101
+ resolved ??= resolveFromPath(cmd, suffixes, platform, options.pathEnv);
4102
+ if (resolved === null && cmd === "node")
4103
+ resolved = cmd;
4104
+ resolutionCache.set(cacheKey, resolved);
4105
+ return resolved;
4106
+ }
4107
+ function isServerInstalled(command, workingDirectory, options = {}) {
4108
+ if (command.length === 0)
4109
+ return false;
4110
+ return resolveServerBinary(command, workingDirectory, options) !== null;
3946
4111
  }
3947
4112
 
3948
4113
  // ../lsp-core/src/lsp/server-resolution.ts
4114
+ function withResolvedCommand(command, binaryPath) {
4115
+ return [binaryPath, ...command.slice(1)];
4116
+ }
3949
4117
  function findServerForExtension(ext) {
3950
4118
  const servers = getMergedServers();
4119
+ const workingDirectory = contextCwd();
3951
4120
  for (const server2 of servers) {
3952
- if (server2.extensions.includes(ext) && isServerInstalled(server2.command)) {
4121
+ if (!server2.extensions.includes(ext))
4122
+ continue;
4123
+ const binaryPath = resolveServerBinary(server2.command, workingDirectory);
4124
+ if (binaryPath !== null) {
3953
4125
  const resolvedServer = {
3954
4126
  id: server2.id,
3955
- command: server2.command,
4127
+ command: withResolvedCommand(server2.command, binaryPath),
3956
4128
  extensions: server2.extensions,
3957
4129
  priority: server2.priority
3958
4130
  };
@@ -3999,6 +4171,7 @@ function findServerForExtension(ext) {
3999
4171
  function getAllServers() {
4000
4172
  const servers = getMergedServers();
4001
4173
  const disabled = getDisabledServerIds();
4174
+ const workingDirectory = contextCwd();
4002
4175
  const result = [];
4003
4176
  const seen = new Set;
4004
4177
  for (const server2 of servers) {
@@ -4006,7 +4179,7 @@ function getAllServers() {
4006
4179
  continue;
4007
4180
  result.push({
4008
4181
  id: server2.id,
4009
- installed: isServerInstalled(server2.command),
4182
+ installed: isServerInstalled(server2.command, workingDirectory),
4010
4183
  extensions: server2.extensions,
4011
4184
  disabled: false,
4012
4185
  source: server2.source,
@@ -4020,7 +4193,7 @@ function getAllServers() {
4020
4193
  const builtin = BUILTIN_SERVERS[id];
4021
4194
  result.push({
4022
4195
  id,
4023
- installed: builtin ? isServerInstalled(builtin.command) : false,
4196
+ installed: builtin ? isServerInstalled(builtin.command, workingDirectory) : false,
4024
4197
  extensions: builtin?.extensions ?? [],
4025
4198
  disabled: true,
4026
4199
  source: "disabled",
@@ -4043,7 +4216,7 @@ function findWorkspaceRoot(filePath) {
4043
4216
  const abs = resolveReadablePathInsideContext(filePath);
4044
4217
  let dir = abs;
4045
4218
  if (!isDirectoryPath(dir)) {
4046
- dir = dirname7(dir);
4219
+ dir = dirname8(dir);
4047
4220
  }
4048
4221
  if (!isPathInside(cwd, abs))
4049
4222
  return findWorkspaceRootOutsideContext(dir);
@@ -4059,13 +4232,13 @@ function findWorkspaceRoot(filePath) {
4059
4232
  }
4060
4233
  if (dir === cwd)
4061
4234
  break;
4062
- dir = dirname7(dir);
4235
+ dir = dirname8(dir);
4063
4236
  }
4064
4237
  return fallbackRoot;
4065
4238
  }
4066
4239
  function resolveReadablePathInsideContext(filePath) {
4067
4240
  const cwd = contextCwd();
4068
- const abs = resolve7(cwd, filePath);
4241
+ const abs = resolve8(cwd, filePath);
4069
4242
  if (isPathInside(cwd, abs))
4070
4243
  return abs;
4071
4244
  const rebased = rebaseThroughCanonicalAncestor(abs, cwd);
@@ -4091,7 +4264,7 @@ function rebaseThroughCanonicalAncestor(path, cwd) {
4091
4264
  if (isPathInside(cwd, canonical))
4092
4265
  return suffix.length === 0 ? canonical : join5(canonical, ...suffix);
4093
4266
  }
4094
- const parent = dirname7(current);
4267
+ const parent = dirname8(current);
4095
4268
  if (parent === current)
4096
4269
  return;
4097
4270
  suffix.unshift(basename3(current));
@@ -4114,7 +4287,7 @@ function nearestExistingDirectoryInsideContext(directory, cwd) {
4114
4287
  return canonical;
4115
4288
  if (current === cwd)
4116
4289
  return;
4117
- current = dirname7(current);
4290
+ current = dirname8(current);
4118
4291
  }
4119
4292
  return;
4120
4293
  }
@@ -4141,6 +4314,19 @@ function formatServerLookupError(result) {
4141
4314
  ].join(`
4142
4315
  `);
4143
4316
  }
4317
+ function formatInstallOptions(serverId, installHint) {
4318
+ const localHint = LSP_LOCAL_INSTALL_HINTS[serverId];
4319
+ if (localHint === undefined) {
4320
+ return ["To install, run:", ` ${installHint}`];
4321
+ }
4322
+ return [
4323
+ "To install in THIS repository (preferred — no global install needed):",
4324
+ ` ${localHint}`,
4325
+ "",
4326
+ "Or install it globally:",
4327
+ ` ${installHint}`
4328
+ ];
4329
+ }
4144
4330
  function formatNotInstalled(result) {
4145
4331
  const { server: server2, installHint } = result;
4146
4332
  const extensions = server2.extensions.join(", ");
@@ -4155,19 +4341,19 @@ function formatNotInstalled(result) {
4155
4341
  `Command not found: ${server2.command[0]}`,
4156
4342
  ""
4157
4343
  ];
4344
+ const installOptions = formatInstallOptions(server2.id, installHint);
4158
4345
  if (decision === "allowed") {
4159
4346
  return [
4160
4347
  ...header,
4161
4348
  "The user has pre-authorized LSP installation. Run the install command, then retry this tool:",
4162
- ` ${installHint}`
4349
+ ...installOptions.slice(1)
4163
4350
  ].join(`
4164
4351
  `);
4165
4352
  }
4166
4353
  if (!context.capabilities.installDecisionTool) {
4167
4354
  return [
4168
4355
  ...header,
4169
- "To install, run:",
4170
- ` ${installHint}`,
4356
+ ...installOptions,
4171
4357
  "",
4172
4358
  "ACTION REQUIRED — ASK THE USER whether to install this LSP server.",
4173
4359
  "Install-decision recording is unavailable in this harness; proceed without LSP if the user declines."
@@ -4176,8 +4362,7 @@ function formatNotInstalled(result) {
4176
4362
  }
4177
4363
  return [
4178
4364
  ...header,
4179
- "To install, run:",
4180
- ` ${installHint}`,
4365
+ ...installOptions,
4181
4366
  "",
4182
4367
  "ACTION REQUIRED — ASK THE USER whether to install this LSP server.",
4183
4368
  "- If the user agrees: run the install command above, then retry this tool.",
@@ -4233,7 +4418,7 @@ async function withLspClient(filePath, fn, toolName, options = {}) {
4233
4418
 
4234
4419
  // ../lsp-core/src/lsp/directory-diagnostics.ts
4235
4420
  import { existsSync as existsSync11, lstatSync as lstatSync4, readdirSync as readdirSync3 } from "node:fs";
4236
- import { join as join6, resolve as resolve8 } from "node:path";
4421
+ import { join as join6, resolve as resolve9 } from "node:path";
4237
4422
 
4238
4423
  // ../lsp-core/src/lsp/formatters.ts
4239
4424
  import { fileURLToPath as fileURLToPath2 } from "node:url";
@@ -4388,7 +4573,7 @@ async function aggregateDiagnosticsForDirectory(directory, extension, severity,
4388
4573
  if (!extension.startsWith(".")) {
4389
4574
  throw new LspInvalidPathError(`Extension must start with a dot (e.g., ".ts", not "${extension}"). Use ".${extension}" instead.`);
4390
4575
  }
4391
- const absDir = resolve8(options.workspaceRoot ?? contextCwd(), directory);
4576
+ const absDir = resolve9(options.workspaceRoot ?? contextCwd(), directory);
4392
4577
  if (!existsSync11(absDir)) {
4393
4578
  throw new LspInvalidPathError(`Directory does not exist: ${absDir}`);
4394
4579
  }
@@ -4745,6 +4930,96 @@ async function executeLspDiagnostics(params, signal) {
4745
4930
  throw error;
4746
4931
  }
4747
4932
  }
4933
+ // ../lsp-core/src/lsp/format-document.ts
4934
+ import { readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
4935
+ var DEFAULT_FORMATTING_OPTIONS = {
4936
+ tabSize: 4,
4937
+ insertSpaces: false,
4938
+ trimTrailingWhitespace: true,
4939
+ insertFinalNewline: true,
4940
+ trimFinalNewlines: true
4941
+ };
4942
+ var UNCHANGED = { status: "unchanged", linesAdded: 0, linesRemoved: 0 };
4943
+ async function formatDocumentWithClient(client, filePath, options = {}) {
4944
+ const edits = await client.formatDocument(filePath, options.formattingOptions ?? DEFAULT_FORMATTING_OPTIONS, options.signal);
4945
+ if (edits === null)
4946
+ return { status: "unavailable", reason: "capability_not_advertised" };
4947
+ if (edits.length === 0)
4948
+ return UNCHANGED;
4949
+ const before = readFileSync5(filePath, "utf-8");
4950
+ const normalized = normalizeTextEdits(before, edits, 0);
4951
+ if (normalized.text === before)
4952
+ return UNCHANGED;
4953
+ writeAtomically(filePath, normalized.text);
4954
+ await client.openFile(filePath);
4955
+ return {
4956
+ status: "formatted",
4957
+ ...lineDelta(normalized.edits)
4958
+ };
4959
+ }
4960
+ function lineDelta(edits) {
4961
+ let linesAdded = 0;
4962
+ let linesRemoved = 0;
4963
+ for (const edit of edits) {
4964
+ linesRemoved += edit.range.end.line - edit.range.start.line + 1;
4965
+ linesAdded += edit.newText.split(`
4966
+ `).length;
4967
+ }
4968
+ return { linesAdded, linesRemoved };
4969
+ }
4970
+ function writeAtomically(filePath, content) {
4971
+ const tempPath = `${filePath}.omo-format.tmp`;
4972
+ writeFileSync3(tempPath, content, "utf-8");
4973
+ try {
4974
+ renameSync3(tempPath, filePath);
4975
+ } catch (error) {
4976
+ try {
4977
+ unlinkSync(tempPath);
4978
+ } catch {}
4979
+ throw error;
4980
+ }
4981
+ }
4982
+
4983
+ // ../lsp-core/src/tools/format.ts
4984
+ async function executeLspFormat(params, signal) {
4985
+ const filePath = requireString(params, "filePath");
4986
+ try {
4987
+ const result = await withLspClient(filePath, async (client, _workspaceRoot, resolvedFilePath) => formatDocumentWithClient(client, resolvedFilePath, signal === undefined ? {} : { signal }), "format", clientOptions(signal));
4988
+ return text(describeResult(filePath, result), formatDetails(filePath, result));
4989
+ } catch (error) {
4990
+ const missingDependency = missingDependencyResult(error, {
4991
+ filePath,
4992
+ status: "unavailable",
4993
+ reason: "server_unavailable",
4994
+ linesAdded: 0,
4995
+ linesRemoved: 0
4996
+ });
4997
+ if (missingDependency)
4998
+ return missingDependency;
4999
+ throw error;
5000
+ }
5001
+ }
5002
+ function formatDetails(filePath, result) {
5003
+ if (result.status === "unavailable") {
5004
+ return { filePath, status: "unavailable", reason: result.reason, linesAdded: 0, linesRemoved: 0 };
5005
+ }
5006
+ return {
5007
+ filePath,
5008
+ status: result.status,
5009
+ linesAdded: result.linesAdded,
5010
+ linesRemoved: result.linesRemoved
5011
+ };
5012
+ }
5013
+ function describeResult(filePath, result) {
5014
+ if (result.status === "unavailable") {
5015
+ return `Formatting unavailable for ${filePath}: the language server does not advertise documentFormattingProvider.`;
5016
+ }
5017
+ if (result.status === "unchanged") {
5018
+ return `Already formatted: ${filePath}`;
5019
+ }
5020
+ return `Formatted ${filePath} (+${result.linesAdded}/-${result.linesRemoved} lines)`;
5021
+ }
5022
+
4748
5023
  // ../lsp-core/src/tools/install-decision.ts
4749
5024
  async function executeLspInstallDecision(params) {
4750
5025
  const serverId = requireString(params, "server_id");
@@ -5077,6 +5352,16 @@ var LSP_MCP_TOOLS = [
5077
5352
  }, ["filePath", "line", "character", "newName"]),
5078
5353
  execute: executeLspRename
5079
5354
  },
5355
+ {
5356
+ name: "format",
5357
+ aliases: ["lsp_format"],
5358
+ title: "LSP Format",
5359
+ description: "Format a source file with its language server and write the returned edits to disk.",
5360
+ inputSchema: objectSchema({
5361
+ filePath: { type: "string", description: "Source file path to format." }
5362
+ }, ["filePath"]),
5363
+ execute: executeLspFormat
5364
+ },
5080
5365
  {
5081
5366
  name: "install_decision",
5082
5367
  aliases: ["lsp_install_decision"],