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.
- package/dist/cli/index.js +75 -26
- package/dist/cli-node/index.js +75 -26
- package/dist/config/schema/background-task.d.ts +1 -0
- package/dist/config/schema/oh-my-opencode-config.d.ts +1 -0
- package/dist/features/background-agent/constants.d.ts +0 -7
- package/dist/features/background-agent/manager.d.ts +2 -1
- package/dist/features/background-agent/parent-wake-dispatched-tracker.d.ts +0 -1
- package/dist/features/background-agent/subagent-spawn-limits.d.ts +8 -0
- package/dist/features/task-toast-manager/manager.d.ts +0 -24
- package/dist/hooks/anthropic-context-window-limit-recovery/message-storage-directory.d.ts +0 -3
- package/dist/hooks/anthropic-context-window-limit-recovery/state.d.ts +1 -0
- package/dist/hooks/anthropic-context-window-limit-recovery/storage/empty-messages.d.ts +0 -1
- package/dist/hooks/claude-code-hooks/session-hook-state.d.ts +1 -0
- package/dist/hooks/claude-code-hooks/stop.d.ts +2 -0
- package/dist/hooks/claude-code-hooks/todo.d.ts +0 -1
- package/dist/hooks/claude-code-hooks/transcript.d.ts +2 -0
- package/dist/hooks/fsync-skip-warning/index.d.ts +4 -0
- package/dist/hooks/hashline-edit-diff-enhancer/hook.d.ts +1 -0
- package/dist/hooks/hashline-edit-diff-enhancer/pending-captures.d.ts +13 -0
- package/dist/hooks/keyword-detector/hook.d.ts +7 -0
- package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts +0 -1
- package/dist/hooks/model-fallback/hook.d.ts +1 -1
- package/dist/index.js +439 -218
- package/dist/oh-my-opencode.schema.json +5 -0
- package/dist/plugin-dispose.d.ts +3 -0
- package/dist/shared/agent-tool-restrictions.d.ts +0 -1
- package/dist/tools/delegate-task/sync-session-cleanup.d.ts +3 -0
- package/dist/tools/delegate-task/sync-session-poller.d.ts +1 -0
- package/dist/tools/delegate-task/types.d.ts +1 -0
- package/dist/tui.js +56 -3
- package/package.json +15 -15
- package/packages/git-bash-mcp/dist/cli.js +1 -0
- package/packages/lsp-core/src/lsp/client-wrapper.test.ts +97 -0
- package/packages/lsp-core/src/lsp/client-wrapper.ts +21 -6
- package/packages/lsp-core/src/lsp/constants.ts +5 -0
- package/packages/lsp-core/src/lsp/errors.ts +15 -0
- package/packages/lsp-core/src/lsp/manager-lifecycle.test.ts +262 -0
- package/packages/lsp-core/src/lsp/manager.ts +106 -13
- package/packages/lsp-core/src/lsp/workspace-markers.ts +18 -1
- package/packages/lsp-core/src/mcp.ts +3 -0
- package/packages/lsp-daemon/dist/cli.js +114 -18
- package/packages/lsp-daemon/dist/client.js +114 -18
- package/packages/lsp-daemon/dist/index.js +114 -18
- package/packages/lsp-daemon/dist/proxy.js +3 -0
- package/packages/lsp-tools-mcp/dist/cli.js +114 -18
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +93 -12
- package/packages/lsp-tools-mcp/dist/mcp.js +114 -18
- package/packages/lsp-tools-mcp/dist/tools.js +113 -18
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +31 -2
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +31 -2
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-unavailable.ts +12 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve-unavailable-idle.test.ts +47 -0
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +113 -18
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/AGENTS.md +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +100 -54
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.d.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +9 -6
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.d.ts +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +7 -10
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +3 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.d.ts +15 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.js +50 -0
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +16 -5
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +11 -14
- package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +3 -2
- package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +73 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +44 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +25 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/quality-gate-builder.ts +7 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-roles.test.ts +45 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +20 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/surface.test.ts +74 -0
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +13 -13
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
- package/dist/hooks/ralph-loop/completion-promise-detector-test-input.d.ts +0 -11
- package/dist/hooks/ralph-loop/loop-session-recovery.d.ts +0 -7
- package/dist/tools/call-omo-agent/message-storage-directory.d.ts +0 -1
- package/dist/tools/delegate-task/model-string-parser.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/lsp/cleanup-errors.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/lsp/constants.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/lsp/language-mappings.d.ts +0 -1
- package/packages/lsp-tools-mcp/dist/lsp/process-signal-cleanup.d.ts +0 -1
- 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
|
-
|
|
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
|
|
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
|
-
|
|
2878
|
-
this.clients.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
3371
|
-
this.clients.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
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();
|