oh-my-opencode 5.0.0-beta.30 → 5.0.0-beta.31

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 (134) hide show
  1. package/dist/cli/index.js +75 -26
  2. package/dist/cli-node/index.js +75 -26
  3. package/dist/config/schema/background-task.d.ts +1 -0
  4. package/dist/config/schema/oh-my-opencode-config.d.ts +1 -0
  5. package/dist/features/background-agent/constants.d.ts +0 -7
  6. package/dist/features/background-agent/manager.d.ts +2 -1
  7. package/dist/features/background-agent/parent-wake-dispatched-tracker.d.ts +0 -1
  8. package/dist/features/background-agent/subagent-spawn-limits.d.ts +8 -0
  9. package/dist/features/task-toast-manager/manager.d.ts +0 -24
  10. package/dist/hooks/anthropic-context-window-limit-recovery/message-storage-directory.d.ts +0 -3
  11. package/dist/hooks/anthropic-context-window-limit-recovery/state.d.ts +1 -0
  12. package/dist/hooks/anthropic-context-window-limit-recovery/storage/empty-messages.d.ts +0 -1
  13. package/dist/hooks/claude-code-hooks/session-hook-state.d.ts +1 -0
  14. package/dist/hooks/claude-code-hooks/stop.d.ts +2 -0
  15. package/dist/hooks/claude-code-hooks/todo.d.ts +0 -1
  16. package/dist/hooks/claude-code-hooks/transcript.d.ts +2 -0
  17. package/dist/hooks/fsync-skip-warning/index.d.ts +4 -0
  18. package/dist/hooks/hashline-edit-diff-enhancer/hook.d.ts +1 -0
  19. package/dist/hooks/hashline-edit-diff-enhancer/pending-captures.d.ts +13 -0
  20. package/dist/hooks/keyword-detector/hook.d.ts +7 -0
  21. package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts +0 -1
  22. package/dist/hooks/model-fallback/hook.d.ts +1 -1
  23. package/dist/index.js +439 -218
  24. package/dist/oh-my-opencode.schema.json +5 -0
  25. package/dist/plugin-dispose.d.ts +3 -0
  26. package/dist/shared/agent-tool-restrictions.d.ts +0 -1
  27. package/dist/tools/delegate-task/sync-session-cleanup.d.ts +3 -0
  28. package/dist/tools/delegate-task/sync-session-poller.d.ts +1 -0
  29. package/dist/tools/delegate-task/types.d.ts +1 -0
  30. package/dist/tui.js +56 -3
  31. package/package.json +15 -15
  32. package/packages/git-bash-mcp/dist/cli.js +1 -0
  33. package/packages/lsp-core/src/lsp/client-wrapper.test.ts +97 -0
  34. package/packages/lsp-core/src/lsp/client-wrapper.ts +21 -6
  35. package/packages/lsp-core/src/lsp/constants.ts +5 -0
  36. package/packages/lsp-core/src/lsp/errors.ts +15 -0
  37. package/packages/lsp-core/src/lsp/manager-lifecycle.test.ts +262 -0
  38. package/packages/lsp-core/src/lsp/manager.ts +106 -13
  39. package/packages/lsp-core/src/lsp/workspace-markers.ts +18 -1
  40. package/packages/lsp-core/src/mcp.ts +3 -0
  41. package/packages/lsp-daemon/dist/cli.js +114 -18
  42. package/packages/lsp-daemon/dist/client.js +114 -18
  43. package/packages/lsp-daemon/dist/index.js +114 -18
  44. package/packages/lsp-daemon/dist/proxy.js +3 -0
  45. package/packages/lsp-tools-mcp/dist/cli.js +114 -18
  46. package/packages/lsp-tools-mcp/dist/lsp/manager.js +93 -12
  47. package/packages/lsp-tools-mcp/dist/mcp.js +114 -18
  48. package/packages/lsp-tools-mcp/dist/tools.js +113 -18
  49. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  50. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  51. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  52. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +31 -2
  53. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +31 -2
  54. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  55. package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +12 -0
  56. package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable-idle.test.ts +47 -0
  57. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  58. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  59. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  60. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  61. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  62. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  63. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  64. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +113 -18
  65. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  66. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  67. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  68. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  69. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  70. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  71. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  72. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  73. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  74. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  75. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
  76. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  77. package/packages/omo-codex/plugin/components/ulw-loop/AGENTS.md +1 -0
  78. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +2 -0
  79. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +100 -54
  80. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.d.ts +2 -0
  81. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +9 -6
  82. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.d.ts +4 -2
  83. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +7 -10
  84. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +3 -2
  85. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.d.ts +15 -0
  86. package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.js +50 -0
  87. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  88. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  89. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +2 -0
  90. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +16 -5
  91. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +11 -14
  92. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +3 -2
  93. package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +73 -0
  94. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +44 -1
  95. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +25 -0
  96. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/quality-gate-builder.ts +7 -3
  97. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-roles.test.ts +45 -2
  98. package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +20 -0
  99. package/packages/omo-codex/plugin/components/ulw-loop/test/surface.test.ts +74 -0
  100. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  101. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  102. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  103. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  104. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  105. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  106. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  107. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  108. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  109. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  110. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  111. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  112. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  113. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  114. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  115. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  116. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  117. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  118. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
  119. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  120. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  121. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  122. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  123. package/packages/omo-codex/plugin/package-lock.json +13 -13
  124. package/packages/omo-codex/plugin/package.json +1 -1
  125. package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
  126. package/dist/hooks/ralph-loop/completion-promise-detector-test-input.d.ts +0 -11
  127. package/dist/hooks/ralph-loop/loop-session-recovery.d.ts +0 -7
  128. package/dist/tools/call-omo-agent/message-storage-directory.d.ts +0 -1
  129. package/dist/tools/delegate-task/model-string-parser.d.ts +0 -1
  130. package/packages/lsp-tools-mcp/dist/lsp/cleanup-errors.d.ts +0 -1
  131. package/packages/lsp-tools-mcp/dist/lsp/constants.d.ts +0 -1
  132. package/packages/lsp-tools-mcp/dist/lsp/language-mappings.d.ts +0 -1
  133. package/packages/lsp-tools-mcp/dist/lsp/process-signal-cleanup.d.ts +0 -1
  134. package/packages/lsp-tools-mcp/dist/missing-dependency-result.d.ts +0 -1
@@ -27,6 +27,8 @@ var MAX_RESIDENT_CLIENTS = 6;
27
27
  var REAPER_INTERVAL_MS = 60000;
28
28
  var STOP_HARD_KILL_TIMEOUT_MS = 5000;
29
29
  var STOP_SIGKILL_GRACE_MS = 1000;
30
+ var CLIENT_RESPAWN_RETRY_LIMIT = 2;
31
+ var CLIENT_RESPAWN_COOLDOWN_MS = 60000;
30
32
 
31
33
  // ../lsp-core/src/lsp/errors.ts
32
34
  class LspConnectionClosedError extends Error {
@@ -91,6 +93,16 @@ class LspProcessSpawnError extends Error {
91
93
  this.name = "LspProcessSpawnError";
92
94
  }
93
95
  }
96
+
97
+ class LspClientRespawnBudgetExceededError extends Error {
98
+ constructor(serverId, root, retryLimit) {
99
+ super(`LSP server ${serverId} at ${root} failed to stay alive; respawn budget exhausted ` + `(${retryLimit} consecutive dead generations). Retrying after the cooldown may succeed.`);
100
+ this.serverId = serverId;
101
+ this.root = root;
102
+ this.retryLimit = retryLimit;
103
+ this.name = "LspClientRespawnBudgetExceededError";
104
+ }
105
+ }
94
106
  function isLspDeadConnectionError(err) {
95
107
  return err instanceof LspConnectionClosedError || err instanceof LspProcessExitedError;
96
108
  }
@@ -2777,6 +2789,8 @@ function awaitWithSignal(promise, signal) {
2777
2789
  class LspManager {
2778
2790
  constructor(options = {}) {
2779
2791
  this.clients = new Map;
2792
+ this.pendingStops = new Map;
2793
+ this.respawnBudgets = new Map;
2780
2794
  this.reaperHandle = null;
2781
2795
  this.signalDisposer = null;
2782
2796
  this.disposed = false;
@@ -2802,17 +2816,52 @@ class LspManager {
2802
2816
  getKey(root, serverId) {
2803
2817
  return `${root}::${serverId}`;
2804
2818
  }
2819
+ tombstoneStop(key, client) {
2820
+ const stop = stopClientBestEffort(client);
2821
+ this.pendingStops.set(key, stop);
2822
+ stop.finally(() => {
2823
+ if (this.pendingStops.get(key) === stop) {
2824
+ this.pendingStops.delete(key);
2825
+ }
2826
+ });
2827
+ return stop;
2828
+ }
2829
+ recordDeadGeneration(key) {
2830
+ let budget = this.respawnBudgets.get(key);
2831
+ if (budget === undefined) {
2832
+ budget = { deadGenerations: 0, exhaustedAt: null };
2833
+ this.respawnBudgets.set(key, budget);
2834
+ }
2835
+ budget.deadGenerations += 1;
2836
+ }
2837
+ markHealthyGeneration(key) {
2838
+ this.respawnBudgets.delete(key);
2839
+ }
2840
+ ensureRespawnAllowed(key, root, serverId) {
2841
+ const budget = this.respawnBudgets.get(key);
2842
+ if (budget === undefined || budget.deadGenerations < CLIENT_RESPAWN_RETRY_LIMIT)
2843
+ return;
2844
+ if (budget.exhaustedAt === null) {
2845
+ budget.exhaustedAt = this.now();
2846
+ throw new LspClientRespawnBudgetExceededError(serverId, root, CLIENT_RESPAWN_RETRY_LIMIT);
2847
+ }
2848
+ if (this.now() - budget.exhaustedAt < CLIENT_RESPAWN_COOLDOWN_MS) {
2849
+ throw new LspClientRespawnBudgetExceededError(serverId, root, CLIENT_RESPAWN_RETRY_LIMIT);
2850
+ }
2851
+ budget.deadGenerations = 0;
2852
+ budget.exhaustedAt = null;
2853
+ }
2805
2854
  reapStale() {
2806
2855
  const t = this.now();
2807
2856
  for (const [key, managed] of this.clients) {
2808
2857
  if (managed.isInitializing && managed.initializingSince !== null && t - managed.initializingSince > this.initTimeoutMs) {
2809
- stopClientBestEffort(managed.client);
2810
2858
  this.clients.delete(key);
2859
+ this.tombstoneStop(key, managed.client);
2811
2860
  continue;
2812
2861
  }
2813
2862
  if (!managed.isInitializing && managed.refCount === 0 && managed.pendingWaiters === 0 && t - managed.lastUsedAt > this.idleTimeoutMs) {
2814
- stopClientBestEffort(managed.client);
2815
2863
  this.clients.delete(key);
2864
+ this.tombstoneStop(key, managed.client);
2816
2865
  }
2817
2866
  }
2818
2867
  }
@@ -2822,7 +2871,7 @@ class LspManager {
2822
2871
  if (!victim)
2823
2872
  return;
2824
2873
  this.clients.delete(victim.key);
2825
- stopClientBestEffort(victim.managed.client);
2874
+ this.tombstoneStop(victim.key, victim.managed.client);
2826
2875
  }
2827
2876
  }
2828
2877
  leastRecentlyUsedIdleClient() {
@@ -2839,7 +2888,7 @@ class LspManager {
2839
2888
  async tryDeleteIfOrphaned(key, managed) {
2840
2889
  if (managed.refCount === 0 && managed.pendingWaiters === 0 && !managed.isInitializing && this.clients.get(key) === managed) {
2841
2890
  this.clients.delete(key);
2842
- await stopClientBestEffort(managed.client);
2891
+ await this.tombstoneStop(key, managed.client);
2843
2892
  }
2844
2893
  }
2845
2894
  async getClient(root, server, signal) {
@@ -2848,12 +2897,22 @@ class LspManager {
2848
2897
  }
2849
2898
  signal?.throwIfAborted();
2850
2899
  const key = this.getKey(root, server.id);
2900
+ for (;; ) {
2901
+ const pendingStop = this.pendingStops.get(key);
2902
+ if (pendingStop === undefined)
2903
+ break;
2904
+ await awaitWithSignal(pendingStop, signal);
2905
+ signal?.throwIfAborted();
2906
+ }
2907
+ if (this.disposed) {
2908
+ throw new Error("LspManager has been disposed");
2909
+ }
2851
2910
  let managed = this.clients.get(key);
2852
2911
  if (managed) {
2853
2912
  const t = this.now();
2854
2913
  if (managed.isInitializing && managed.initializingSince !== null && t - managed.initializingSince > this.initTimeoutMs) {
2855
- await stopClientBestEffort(managed.client);
2856
2914
  this.clients.delete(key);
2915
+ await this.tombstoneStop(key, managed.client);
2857
2916
  managed = undefined;
2858
2917
  }
2859
2918
  }
@@ -2874,15 +2933,20 @@ class LspManager {
2874
2933
  signal.throwIfAborted();
2875
2934
  }
2876
2935
  if (!managed.client.isAlive()) {
2877
- await stopClientBestEffort(managed.client);
2878
- this.clients.delete(key);
2936
+ this.recordDeadGeneration(key);
2937
+ if (this.clients.get(key) === managed) {
2938
+ this.clients.delete(key);
2939
+ }
2940
+ await this.tombstoneStop(key, managed.client);
2879
2941
  return this.getClient(root, server, signal);
2880
2942
  }
2943
+ this.markHealthyGeneration(key);
2881
2944
  managed.refCount++;
2882
2945
  managed.lastUsedAt = this.now();
2883
2946
  return managed.client;
2884
2947
  }
2885
2948
  this.evictForAdmission();
2949
+ this.ensureRespawnAllowed(key, root, server.id);
2886
2950
  const client = this.clientFactory(root, server);
2887
2951
  const initStartedAt = this.now();
2888
2952
  const initPromise = (async () => {
@@ -2906,7 +2970,7 @@ class LspManager {
2906
2970
  if (this.clients.get(key) === newManaged) {
2907
2971
  this.clients.delete(key);
2908
2972
  }
2909
- await stopClientBestEffort(client);
2973
+ await this.tombstoneStop(key, client);
2910
2974
  throw err;
2911
2975
  }
2912
2976
  newManaged.pendingWaiters--;
@@ -2917,6 +2981,15 @@ class LspManager {
2917
2981
  await this.tryDeleteIfOrphaned(key, newManaged);
2918
2982
  signal.throwIfAborted();
2919
2983
  }
2984
+ if (!client.isAlive()) {
2985
+ this.recordDeadGeneration(key);
2986
+ if (this.clients.get(key) === newManaged) {
2987
+ this.clients.delete(key);
2988
+ }
2989
+ await this.tombstoneStop(key, client);
2990
+ return this.getClient(root, server, signal);
2991
+ }
2992
+ this.markHealthyGeneration(key);
2920
2993
  newManaged.refCount++;
2921
2994
  newManaged.lastUsedAt = this.now();
2922
2995
  return client;
@@ -2937,15 +3010,20 @@ class LspManager {
2937
3010
  if (client && managed.client !== client)
2938
3011
  return;
2939
3012
  this.clients.delete(key);
2940
- stopClientBestEffort(managed.client);
3013
+ this.tombstoneStop(key, managed.client);
2941
3014
  }
2942
3015
  warmupClient(root, server) {
2943
3016
  if (this.disposed)
2944
3017
  return;
2945
3018
  const key = this.getKey(root, server.id);
2946
- if (this.clients.has(key))
3019
+ if (this.clients.has(key) || this.pendingStops.has(key))
2947
3020
  return;
2948
3021
  this.evictForAdmission();
3022
+ try {
3023
+ this.ensureRespawnAllowed(key, root, server.id);
3024
+ } catch {
3025
+ return;
3026
+ }
2949
3027
  const client = this.clientFactory(root, server);
2950
3028
  const initStartedAt = this.now();
2951
3029
  const initPromise = (async () => {
@@ -2971,7 +3049,7 @@ class LspManager {
2971
3049
  if (this.clients.get(key) === managed) {
2972
3050
  this.clients.delete(key);
2973
3051
  }
2974
- stopClientBestEffort(client);
3052
+ this.tombstoneStop(key, client);
2975
3053
  });
2976
3054
  }
2977
3055
  isServerInitializing(root, serverId) {
@@ -3015,8 +3093,11 @@ class LspManager {
3015
3093
  for (const managed of this.clients.values()) {
3016
3094
  stopPromises.push(stopClientBestEffort(managed.client));
3017
3095
  }
3096
+ const tombstonedStops = [...this.pendingStops.values()];
3018
3097
  this.clients.clear();
3019
- await Promise.allSettled(stopPromises);
3098
+ this.respawnBudgets.clear();
3099
+ await Promise.allSettled([...stopPromises, ...tombstonedStops]);
3100
+ this.pendingStops.clear();
3020
3101
  }
3021
3102
  }
3022
3103
  var _defaultInstance = null;
@@ -175,6 +175,7 @@ async function runJsonRpcStdioServer(config) {
175
175
  const idleTimer = createIdleTimer(idleTimeoutMs, log, () => {
176
176
  isClosed = true;
177
177
  config.onIdleTimeout?.();
178
+ config.input.destroy();
178
179
  });
179
180
  const watchdog = createParentWatchdog(config.parentWatchdog, (parentPid, pollIntervalMs) => {
180
181
  isClosed = true;
@@ -564,6 +565,16 @@ class LspProcessSpawnError extends Error {
564
565
  this.name = "LspProcessSpawnError";
565
566
  }
566
567
  }
568
+
569
+ class LspClientRespawnBudgetExceededError extends Error {
570
+ constructor(serverId, root, retryLimit) {
571
+ super(`LSP server ${serverId} at ${root} failed to stay alive; respawn budget exhausted ` + `(${retryLimit} consecutive dead generations). Retrying after the cooldown may succeed.`);
572
+ this.serverId = serverId;
573
+ this.root = root;
574
+ this.retryLimit = retryLimit;
575
+ this.name = "LspClientRespawnBudgetExceededError";
576
+ }
577
+ }
567
578
  function isLspDeadConnectionError(err) {
568
579
  return err instanceof LspConnectionClosedError || err instanceof LspProcessExitedError;
569
580
  }
@@ -597,6 +608,8 @@ var MAX_RESIDENT_CLIENTS = 6;
597
608
  var REAPER_INTERVAL_MS = 60000;
598
609
  var STOP_HARD_KILL_TIMEOUT_MS = 5000;
599
610
  var STOP_SIGKILL_GRACE_MS = 1000;
611
+ var CLIENT_RESPAWN_RETRY_LIMIT = 2;
612
+ var CLIENT_RESPAWN_COOLDOWN_MS = 60000;
600
613
 
601
614
  // ../lsp-core/src/lsp/json-rpc-connection.ts
602
615
  var HEADER_SEPARATOR2 = `\r
@@ -3270,6 +3283,8 @@ function awaitWithSignal(promise, signal) {
3270
3283
  class LspManager {
3271
3284
  constructor(options = {}) {
3272
3285
  this.clients = new Map;
3286
+ this.pendingStops = new Map;
3287
+ this.respawnBudgets = new Map;
3273
3288
  this.reaperHandle = null;
3274
3289
  this.signalDisposer = null;
3275
3290
  this.disposed = false;
@@ -3295,17 +3310,52 @@ class LspManager {
3295
3310
  getKey(root, serverId) {
3296
3311
  return `${root}::${serverId}`;
3297
3312
  }
3313
+ tombstoneStop(key, client) {
3314
+ const stop = stopClientBestEffort(client);
3315
+ this.pendingStops.set(key, stop);
3316
+ stop.finally(() => {
3317
+ if (this.pendingStops.get(key) === stop) {
3318
+ this.pendingStops.delete(key);
3319
+ }
3320
+ });
3321
+ return stop;
3322
+ }
3323
+ recordDeadGeneration(key) {
3324
+ let budget = this.respawnBudgets.get(key);
3325
+ if (budget === undefined) {
3326
+ budget = { deadGenerations: 0, exhaustedAt: null };
3327
+ this.respawnBudgets.set(key, budget);
3328
+ }
3329
+ budget.deadGenerations += 1;
3330
+ }
3331
+ markHealthyGeneration(key) {
3332
+ this.respawnBudgets.delete(key);
3333
+ }
3334
+ ensureRespawnAllowed(key, root, serverId) {
3335
+ const budget = this.respawnBudgets.get(key);
3336
+ if (budget === undefined || budget.deadGenerations < CLIENT_RESPAWN_RETRY_LIMIT)
3337
+ return;
3338
+ if (budget.exhaustedAt === null) {
3339
+ budget.exhaustedAt = this.now();
3340
+ throw new LspClientRespawnBudgetExceededError(serverId, root, CLIENT_RESPAWN_RETRY_LIMIT);
3341
+ }
3342
+ if (this.now() - budget.exhaustedAt < CLIENT_RESPAWN_COOLDOWN_MS) {
3343
+ throw new LspClientRespawnBudgetExceededError(serverId, root, CLIENT_RESPAWN_RETRY_LIMIT);
3344
+ }
3345
+ budget.deadGenerations = 0;
3346
+ budget.exhaustedAt = null;
3347
+ }
3298
3348
  reapStale() {
3299
3349
  const t = this.now();
3300
3350
  for (const [key, managed] of this.clients) {
3301
3351
  if (managed.isInitializing && managed.initializingSince !== null && t - managed.initializingSince > this.initTimeoutMs) {
3302
- stopClientBestEffort(managed.client);
3303
3352
  this.clients.delete(key);
3353
+ this.tombstoneStop(key, managed.client);
3304
3354
  continue;
3305
3355
  }
3306
3356
  if (!managed.isInitializing && managed.refCount === 0 && managed.pendingWaiters === 0 && t - managed.lastUsedAt > this.idleTimeoutMs) {
3307
- stopClientBestEffort(managed.client);
3308
3357
  this.clients.delete(key);
3358
+ this.tombstoneStop(key, managed.client);
3309
3359
  }
3310
3360
  }
3311
3361
  }
@@ -3315,7 +3365,7 @@ class LspManager {
3315
3365
  if (!victim)
3316
3366
  return;
3317
3367
  this.clients.delete(victim.key);
3318
- stopClientBestEffort(victim.managed.client);
3368
+ this.tombstoneStop(victim.key, victim.managed.client);
3319
3369
  }
3320
3370
  }
3321
3371
  leastRecentlyUsedIdleClient() {
@@ -3332,7 +3382,7 @@ class LspManager {
3332
3382
  async tryDeleteIfOrphaned(key, managed) {
3333
3383
  if (managed.refCount === 0 && managed.pendingWaiters === 0 && !managed.isInitializing && this.clients.get(key) === managed) {
3334
3384
  this.clients.delete(key);
3335
- await stopClientBestEffort(managed.client);
3385
+ await this.tombstoneStop(key, managed.client);
3336
3386
  }
3337
3387
  }
3338
3388
  async getClient(root, server2, signal) {
@@ -3341,12 +3391,22 @@ class LspManager {
3341
3391
  }
3342
3392
  signal?.throwIfAborted();
3343
3393
  const key = this.getKey(root, server2.id);
3394
+ for (;; ) {
3395
+ const pendingStop = this.pendingStops.get(key);
3396
+ if (pendingStop === undefined)
3397
+ break;
3398
+ await awaitWithSignal(pendingStop, signal);
3399
+ signal?.throwIfAborted();
3400
+ }
3401
+ if (this.disposed) {
3402
+ throw new Error("LspManager has been disposed");
3403
+ }
3344
3404
  let managed = this.clients.get(key);
3345
3405
  if (managed) {
3346
3406
  const t = this.now();
3347
3407
  if (managed.isInitializing && managed.initializingSince !== null && t - managed.initializingSince > this.initTimeoutMs) {
3348
- await stopClientBestEffort(managed.client);
3349
3408
  this.clients.delete(key);
3409
+ await this.tombstoneStop(key, managed.client);
3350
3410
  managed = undefined;
3351
3411
  }
3352
3412
  }
@@ -3367,15 +3427,20 @@ class LspManager {
3367
3427
  signal.throwIfAborted();
3368
3428
  }
3369
3429
  if (!managed.client.isAlive()) {
3370
- await stopClientBestEffort(managed.client);
3371
- this.clients.delete(key);
3430
+ this.recordDeadGeneration(key);
3431
+ if (this.clients.get(key) === managed) {
3432
+ this.clients.delete(key);
3433
+ }
3434
+ await this.tombstoneStop(key, managed.client);
3372
3435
  return this.getClient(root, server2, signal);
3373
3436
  }
3437
+ this.markHealthyGeneration(key);
3374
3438
  managed.refCount++;
3375
3439
  managed.lastUsedAt = this.now();
3376
3440
  return managed.client;
3377
3441
  }
3378
3442
  this.evictForAdmission();
3443
+ this.ensureRespawnAllowed(key, root, server2.id);
3379
3444
  const client = this.clientFactory(root, server2);
3380
3445
  const initStartedAt = this.now();
3381
3446
  const initPromise = (async () => {
@@ -3399,7 +3464,7 @@ class LspManager {
3399
3464
  if (this.clients.get(key) === newManaged) {
3400
3465
  this.clients.delete(key);
3401
3466
  }
3402
- await stopClientBestEffort(client);
3467
+ await this.tombstoneStop(key, client);
3403
3468
  throw err;
3404
3469
  }
3405
3470
  newManaged.pendingWaiters--;
@@ -3410,6 +3475,15 @@ class LspManager {
3410
3475
  await this.tryDeleteIfOrphaned(key, newManaged);
3411
3476
  signal.throwIfAborted();
3412
3477
  }
3478
+ if (!client.isAlive()) {
3479
+ this.recordDeadGeneration(key);
3480
+ if (this.clients.get(key) === newManaged) {
3481
+ this.clients.delete(key);
3482
+ }
3483
+ await this.tombstoneStop(key, client);
3484
+ return this.getClient(root, server2, signal);
3485
+ }
3486
+ this.markHealthyGeneration(key);
3413
3487
  newManaged.refCount++;
3414
3488
  newManaged.lastUsedAt = this.now();
3415
3489
  return client;
@@ -3430,15 +3504,20 @@ class LspManager {
3430
3504
  if (client && managed.client !== client)
3431
3505
  return;
3432
3506
  this.clients.delete(key);
3433
- stopClientBestEffort(managed.client);
3507
+ this.tombstoneStop(key, managed.client);
3434
3508
  }
3435
3509
  warmupClient(root, server2) {
3436
3510
  if (this.disposed)
3437
3511
  return;
3438
3512
  const key = this.getKey(root, server2.id);
3439
- if (this.clients.has(key))
3513
+ if (this.clients.has(key) || this.pendingStops.has(key))
3440
3514
  return;
3441
3515
  this.evictForAdmission();
3516
+ try {
3517
+ this.ensureRespawnAllowed(key, root, server2.id);
3518
+ } catch {
3519
+ return;
3520
+ }
3442
3521
  const client = this.clientFactory(root, server2);
3443
3522
  const initStartedAt = this.now();
3444
3523
  const initPromise = (async () => {
@@ -3464,7 +3543,7 @@ class LspManager {
3464
3543
  if (this.clients.get(key) === managed) {
3465
3544
  this.clients.delete(key);
3466
3545
  }
3467
- stopClientBestEffort(client);
3546
+ this.tombstoneStop(key, client);
3468
3547
  });
3469
3548
  }
3470
3549
  isServerInitializing(root, serverId) {
@@ -3508,8 +3587,11 @@ class LspManager {
3508
3587
  for (const managed of this.clients.values()) {
3509
3588
  stopPromises.push(stopClientBestEffort(managed.client));
3510
3589
  }
3590
+ const tombstonedStops = [...this.pendingStops.values()];
3511
3591
  this.clients.clear();
3512
- await Promise.allSettled(stopPromises);
3592
+ this.respawnBudgets.clear();
3593
+ await Promise.allSettled([...stopPromises, ...tombstonedStops]);
3594
+ this.pendingStops.clear();
3513
3595
  }
3514
3596
  }
3515
3597
  var _defaultInstance = null;
@@ -3532,7 +3614,16 @@ import { existsSync as existsSync6, realpathSync as realpathSync4 } from "node:f
3532
3614
  import { dirname as dirname5, join as join3 } from "node:path";
3533
3615
 
3534
3616
  // ../lsp-core/src/lsp/workspace-markers.ts
3535
- var WORKSPACE_MARKERS = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
3617
+ var GIT_WORKSPACE_MARKER = ".git";
3618
+ var PROJECT_WORKSPACE_MARKERS = [
3619
+ "package.json",
3620
+ "pyproject.toml",
3621
+ "Cargo.toml",
3622
+ "go.mod",
3623
+ "pom.xml",
3624
+ "build.gradle"
3625
+ ];
3626
+ var WORKSPACE_MARKERS = [GIT_WORKSPACE_MARKER, ...PROJECT_WORKSPACE_MARKERS];
3536
3627
 
3537
3628
  // ../lsp-core/src/lsp/outside-context-workspace.ts
3538
3629
  function findWorkspaceRootOutsideContext(directory) {
@@ -4221,20 +4312,25 @@ function findWorkspaceRoot(filePath) {
4221
4312
  if (!isPathInside(cwd, abs))
4222
4313
  return findWorkspaceRootOutsideContext(dir);
4223
4314
  const fallbackRoot = nearestExistingDirectoryInsideContext(dir, cwd) ?? cwd;
4315
+ let nearestPackageRoot;
4224
4316
  while (isPathInside(cwd, dir)) {
4225
4317
  const canonicalDir = existingDirectoryInsideContext(dir, cwd);
4226
4318
  if (canonicalDir !== undefined) {
4227
- for (const marker of WORKSPACE_MARKERS) {
4228
- if (existsSync10(join5(dir, marker))) {
4229
- return canonicalDir;
4230
- }
4319
+ if (existsSync10(join5(dir, GIT_WORKSPACE_MARKER))) {
4320
+ return canonicalDir;
4321
+ }
4322
+ if (nearestPackageRoot === undefined && hasProjectWorkspaceMarker(dir)) {
4323
+ nearestPackageRoot = canonicalDir;
4231
4324
  }
4232
4325
  }
4233
4326
  if (dir === cwd)
4234
4327
  break;
4235
4328
  dir = dirname8(dir);
4236
4329
  }
4237
- return fallbackRoot;
4330
+ return nearestPackageRoot ?? fallbackRoot;
4331
+ }
4332
+ function hasProjectWorkspaceMarker(directory) {
4333
+ return PROJECT_WORKSPACE_MARKERS.some((marker) => existsSync10(join5(directory, marker)));
4238
4334
  }
4239
4335
  function resolveReadablePathInsideContext(filePath) {
4240
4336
  const cwd = contextCwd();