@kenkaiiii/ggcoder 5.34.3 → 5.35.1

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 (55) hide show
  1. package/dist/app-sidecar.js +11 -0
  2. package/dist/app-sidecar.js.map +1 -1
  3. package/dist/core/lsp/manager.d.ts +44 -9
  4. package/dist/core/lsp/manager.d.ts.map +1 -1
  5. package/dist/core/lsp/manager.js +64 -69
  6. package/dist/core/lsp/manager.js.map +1 -1
  7. package/dist/core/lsp/pool.d.ts +113 -0
  8. package/dist/core/lsp/pool.d.ts.map +1 -0
  9. package/dist/core/lsp/pool.js +255 -0
  10. package/dist/core/lsp/pool.js.map +1 -0
  11. package/dist/core/lsp/pool.test.d.ts +2 -0
  12. package/dist/core/lsp/pool.test.d.ts.map +1 -0
  13. package/dist/core/lsp/pool.test.js +286 -0
  14. package/dist/core/lsp/pool.test.js.map +1 -0
  15. package/dist/core/lsp/servers.d.ts.map +1 -1
  16. package/dist/core/lsp/servers.js +44 -3
  17. package/dist/core/lsp/servers.js.map +1 -1
  18. package/dist/core/lsp/servers.test.d.ts +2 -0
  19. package/dist/core/lsp/servers.test.d.ts.map +1 -0
  20. package/dist/core/lsp/servers.test.js +67 -0
  21. package/dist/core/lsp/servers.test.js.map +1 -0
  22. package/dist/core/lsp/windows.test.js +2 -14
  23. package/dist/core/lsp/windows.test.js.map +1 -1
  24. package/dist/core/mcp/client.d.ts +40 -0
  25. package/dist/core/mcp/client.d.ts.map +1 -1
  26. package/dist/core/mcp/client.js +119 -1
  27. package/dist/core/mcp/client.js.map +1 -1
  28. package/dist/core/mcp/index.d.ts +2 -0
  29. package/dist/core/mcp/index.d.ts.map +1 -1
  30. package/dist/core/mcp/index.js +1 -0
  31. package/dist/core/mcp/index.js.map +1 -1
  32. package/dist/core/mcp/session-elicitation-wiring.test.d.ts +2 -0
  33. package/dist/core/mcp/session-elicitation-wiring.test.d.ts.map +1 -0
  34. package/dist/core/mcp/session-elicitation-wiring.test.js +138 -0
  35. package/dist/core/mcp/session-elicitation-wiring.test.js.map +1 -0
  36. package/dist/core/mcp/shared-pool.d.ts +158 -0
  37. package/dist/core/mcp/shared-pool.d.ts.map +1 -0
  38. package/dist/core/mcp/shared-pool.js +235 -0
  39. package/dist/core/mcp/shared-pool.js.map +1 -0
  40. package/dist/core/mcp/shared-pool.test.d.ts +2 -0
  41. package/dist/core/mcp/shared-pool.test.d.ts.map +1 -0
  42. package/dist/core/mcp/shared-pool.test.js +425 -0
  43. package/dist/core/mcp/shared-pool.test.js.map +1 -0
  44. package/dist/core/mcp/types.d.ts +12 -0
  45. package/dist/core/mcp/types.d.ts.map +1 -1
  46. package/dist/core/persistent-shell.d.ts.map +1 -1
  47. package/dist/core/persistent-shell.js +4 -1
  48. package/dist/core/persistent-shell.js.map +1 -1
  49. package/dist/tools/grep.d.ts.map +1 -1
  50. package/dist/tools/grep.js +2 -1
  51. package/dist/tools/grep.js.map +1 -1
  52. package/dist/tools/web-fetch.d.ts.map +1 -1
  53. package/dist/tools/web-fetch.js +4 -1
  54. package/dist/tools/web-fetch.js.map +1 -1
  55. package/package.json +4 -4
@@ -0,0 +1,255 @@
1
+ import { log } from "../logger.js";
2
+ import { LspClient } from "./client.js";
3
+ /**
4
+ * How long a server may sit unused before it is reclaimed. Long enough to cover
5
+ * a normal think-edit-verify rhythm, short enough that a project the user has
6
+ * moved on from stops costing hundreds of megabytes.
7
+ */
8
+ const DEFAULT_IDLE_TTL_MS = 5 * 60 * 1000;
9
+ const DEFAULT_SWEEP_INTERVAL_MS = 30 * 1000;
10
+ const INIT_TIMEOUT_MS = 10_000;
11
+ /**
12
+ * Stable identity per catalog entry. Two sessions share the module-level
13
+ * catalog, so the same spec object means the same server; a test injecting its
14
+ * own spec gets its own pool entries instead of colliding with another test's
15
+ * fixture on the same id and root.
16
+ */
17
+ const specIds = new WeakMap();
18
+ let nextSpecId = 0;
19
+ function specIdentity(spec) {
20
+ let id = specIds.get(spec);
21
+ if (!id) {
22
+ id = `${spec.id}#${nextSpecId++}`;
23
+ specIds.set(spec, id);
24
+ }
25
+ return id;
26
+ }
27
+ export class LspClientPool {
28
+ entries = new Map();
29
+ /**
30
+ * Build counter per key, kept OUTSIDE `entries` so it survives eviction —
31
+ * that is the whole point: it tells a caller its warm server is gone.
32
+ */
33
+ generations = new Map();
34
+ idleTtlMs;
35
+ sweepIntervalMs;
36
+ sweepTimer;
37
+ constructor(options) {
38
+ this.idleTtlMs = Math.max(0, options?.idleTtlMs ?? DEFAULT_IDLE_TTL_MS);
39
+ this.sweepIntervalMs = Math.max(1, options?.sweepIntervalMs ?? DEFAULT_SWEEP_INTERVAL_MS);
40
+ }
41
+ keyFor(spec, root) {
42
+ return `${specIdentity(spec)}\u0000${root}`;
43
+ }
44
+ /**
45
+ * Get-or-spawn the client for (spec, root) and record `holder` as retaining
46
+ * it. Concurrent callers share one spawn: the entry and its pending promise
47
+ * are registered synchronously before the first `await`.
48
+ */
49
+ async retain(spec, root, holder) {
50
+ const key = this.keyFor(spec, root);
51
+ let entry = this.entries.get(key);
52
+ if (!entry) {
53
+ entry = {
54
+ key,
55
+ pending: this.spawn(spec, root),
56
+ holders: new Set(),
57
+ activeCalls: 0,
58
+ lastUsedAt: Date.now(),
59
+ generation: (this.generations.get(key) ?? 0) + 1,
60
+ };
61
+ this.generations.set(key, entry.generation);
62
+ this.entries.set(key, entry);
63
+ this.startSweep();
64
+ }
65
+ entry.holders.add(holder);
66
+ const resolved = await entry.pending;
67
+ // Only a WORKING server counts as use. A failure stays cached so a broken
68
+ // toolchain is not respawned on every write — the long-standing contract —
69
+ // but its timestamp is left alone so the idle sweep can retire it like any
70
+ // other stale entry. Without that, a pooled failure would outlive every
71
+ // session in the daemon and disable diagnostics for that root permanently,
72
+ // where pre-pool a new session simply got a fresh manager and retried.
73
+ if (resolved.status === "ready")
74
+ entry.lastUsedAt = Date.now();
75
+ return resolved;
76
+ }
77
+ /**
78
+ * Identity of the server currently live for (spec, root), or 0 when none is.
79
+ *
80
+ * A caller records this alongside its own "I have warmed this server" state,
81
+ * and a later mismatch means the server it warmed is gone. Reporting the LIVE
82
+ * entry rather than the next build number is essential: callers ask before
83
+ * `retain`, when a reclaimed server has not been rebuilt yet, and a stale
84
+ * number there would look like a match and hand a cold server a warm budget.
85
+ */
86
+ generationFor(spec, root) {
87
+ return this.entries.get(this.keyFor(spec, root))?.generation ?? 0;
88
+ }
89
+ /**
90
+ * Mark a diagnostics pass as in flight against (spec, root), returning the
91
+ * function that ends it. A first-file pass can take seconds; without this the
92
+ * sweep could shut the server down while it was still answering.
93
+ */
94
+ beginCall(spec, root) {
95
+ const entry = this.entries.get(this.keyFor(spec, root));
96
+ if (!entry)
97
+ return () => { };
98
+ entry.activeCalls += 1;
99
+ let ended = false;
100
+ return () => {
101
+ if (ended)
102
+ return;
103
+ ended = true;
104
+ entry.activeCalls = Math.max(0, entry.activeCalls - 1);
105
+ entry.lastUsedAt = Date.now();
106
+ };
107
+ }
108
+ /**
109
+ * Record that this client died after starting, so later passes report
110
+ * `server_failed` instead of talking to a corpse.
111
+ *
112
+ * The failure is CACHED rather than cleared, preserving the pre-pool
113
+ * contract: a server that dies on a document is not respawned on every
114
+ * following write. The dead process is reaped either way.
115
+ */
116
+ markDead(spec, root) {
117
+ const key = this.keyFor(spec, root);
118
+ const entry = this.entries.get(key);
119
+ if (!entry)
120
+ return;
121
+ const dead = entry.pending;
122
+ entry.pending = Promise.resolve({ status: "server_failed" });
123
+ void dead
124
+ .then((resolved) => {
125
+ if (resolved.status === "ready")
126
+ resolved.client.terminate();
127
+ })
128
+ .catch(() => { });
129
+ }
130
+ /**
131
+ * Drop `holder`'s claim on every entry it retained. Servers still held by
132
+ * another manager keep running; those left with no holders shut down now
133
+ * rather than waiting for the idle sweep, because a disposed session is
134
+ * proof the work is over.
135
+ */
136
+ release(holder) {
137
+ for (const entry of [...this.entries.values()]) {
138
+ if (!entry.holders.delete(holder))
139
+ continue;
140
+ if (entry.holders.size === 0)
141
+ this.evict(entry, "last holder released");
142
+ }
143
+ if (this.entries.size === 0)
144
+ this.stopSweep();
145
+ }
146
+ /** Shut every pooled server down regardless of holders (process exit). */
147
+ shutdownAll() {
148
+ for (const entry of [...this.entries.values()])
149
+ this.evict(entry, "pool shutdown");
150
+ this.stopSweep();
151
+ }
152
+ /** Live pooled servers. Test/diagnostic use. */
153
+ get size() {
154
+ return this.entries.size;
155
+ }
156
+ /** Reference count for one (spec, root), or 0 when nothing is pooled. */
157
+ refCount(spec, root) {
158
+ return this.entries.get(this.keyFor(spec, root))?.holders.size ?? 0;
159
+ }
160
+ /**
161
+ * Retained stderr of a server `holder` is using. Diagnostic probe for tests
162
+ * and failure reporting — a server's own last words are usually the only
163
+ * explanation of why it went quiet.
164
+ */
165
+ async stderrTail(holder) {
166
+ for (const entry of [...this.entries.values()]) {
167
+ if (!entry.holders.has(holder))
168
+ continue;
169
+ const resolved = await entry.pending.catch(() => undefined);
170
+ if (resolved?.status === "ready")
171
+ return resolved.client.stderrTail();
172
+ }
173
+ return "";
174
+ }
175
+ /** Force one idle sweep. Exposed so tests need not wait on the interval. */
176
+ sweepNow(now = Date.now()) {
177
+ for (const entry of [...this.entries.values()]) {
178
+ if (entry.activeCalls > 0)
179
+ continue;
180
+ if (now - entry.lastUsedAt < this.idleTtlMs)
181
+ continue;
182
+ this.evict(entry, `idle for ${Math.round((now - entry.lastUsedAt) / 1000)}s`);
183
+ }
184
+ if (this.entries.size === 0)
185
+ this.stopSweep();
186
+ }
187
+ evict(entry, reason) {
188
+ // Only remove the entry if it is still the live one for its key, so a
189
+ // rebuild that raced this eviction is not torn down by it.
190
+ if (this.entries.get(entry.key) === entry)
191
+ this.entries.delete(entry.key);
192
+ log("INFO", "lsp", "releasing pooled language server", { key: entry.key, reason });
193
+ void entry.pending
194
+ .then((resolved) => {
195
+ if (resolved.status === "ready")
196
+ resolved.client.shutdown();
197
+ })
198
+ .catch(() => { });
199
+ }
200
+ /**
201
+ * The sweep only runs while something is pooled, and is unref'd so it can
202
+ * never hold the CLI (or a test worker) open on its own.
203
+ */
204
+ startSweep() {
205
+ if (this.sweepTimer || this.idleTtlMs === 0)
206
+ return;
207
+ this.sweepTimer = setInterval(() => this.sweepNow(), this.sweepIntervalMs);
208
+ this.sweepTimer.unref?.();
209
+ }
210
+ stopSweep() {
211
+ if (!this.sweepTimer)
212
+ return;
213
+ clearInterval(this.sweepTimer);
214
+ this.sweepTimer = undefined;
215
+ }
216
+ async spawn(spec, root) {
217
+ const command = spec.resolveCommand(root);
218
+ if (!command) {
219
+ log("INFO", "lsp", `${spec.id} language server not available`, { root });
220
+ return { status: "unavailable" };
221
+ }
222
+ // `client` is declared outside the try so one that fails to initialize can
223
+ // still be killed. `new LspClient` SPAWNS the process, so discarding the
224
+ // reference on a throw leaked the server forever — one orphan per
225
+ // (server, root) every time initialize timed out. Invisible on POSIX; on
226
+ // Windows the orphan keeps handles open in the project directory.
227
+ let client;
228
+ try {
229
+ const startedAt = Date.now();
230
+ client = new LspClient(spec, root, command);
231
+ await client.initialize(INIT_TIMEOUT_MS);
232
+ if (!client.isAlive)
233
+ return { status: "server_failed" };
234
+ log("INFO", "lsp", `${spec.id} server initialized`, {
235
+ root,
236
+ ms: String(Date.now() - startedAt),
237
+ });
238
+ return { status: "ready", client };
239
+ }
240
+ catch (error) {
241
+ log("WARN", "lsp", `${spec.id} server failed to start`, {
242
+ root,
243
+ error: error instanceof Error ? error.message : String(error),
244
+ // The server's own last words — usually the only explanation of why the
245
+ // handshake never completed.
246
+ stderr: client?.stderrTail() || "(none)",
247
+ });
248
+ client?.terminate();
249
+ return { status: "server_failed" };
250
+ }
251
+ }
252
+ }
253
+ /** The daemon-wide pool. One per process, which is exactly the sharing scope. */
254
+ export const lspClientPool = new LspClientPool();
255
+ //# sourceMappingURL=pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.js","sourceRoot":"","sources":["../../../src/core/lsp/pool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=pool.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.test.d.ts","sourceRoot":"","sources":["../../../src/core/lsp/pool.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,286 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { LspManager } from "./manager.js";
7
+ import { LspClientPool } from "./pool.js";
8
+ import { removeWhenReleased } from "./test-support.js";
9
+ const FIXTURE = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../tools/__fixtures__/fake-lsp-server.mjs");
10
+ function fakeSpec(serverArgs = []) {
11
+ return {
12
+ id: "fake",
13
+ extensions: [".fake"],
14
+ rootMarkers: ["fake-root.json"],
15
+ languageIdFor: () => "fake",
16
+ resolveCommand: () => ({ command: process.execPath, args: [FIXTURE, ...serverArgs] }),
17
+ };
18
+ }
19
+ /** True while a pid is a live process. */
20
+ function alive(pid) {
21
+ try {
22
+ process.kill(pid, 0);
23
+ return true;
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ async function waitForFile(file, timeoutMs = 5000) {
30
+ const deadline = Date.now() + timeoutMs;
31
+ for (;;) {
32
+ const body = await fs.readFile(file, "utf8").catch(() => undefined);
33
+ if (body)
34
+ return body;
35
+ if (Date.now() > deadline)
36
+ throw new Error(`timed out waiting for ${file}`);
37
+ await new Promise((resolve) => setTimeout(resolve, 25));
38
+ }
39
+ }
40
+ async function waitUntilDead(pid, timeoutMs = 5000) {
41
+ const deadline = Date.now() + timeoutMs;
42
+ while (Date.now() < deadline) {
43
+ if (!alive(pid))
44
+ return true;
45
+ await new Promise((resolve) => setTimeout(resolve, 25));
46
+ }
47
+ return false;
48
+ }
49
+ describe("LspClientPool", () => {
50
+ let tmpDir;
51
+ let pool;
52
+ const managers = [];
53
+ beforeEach(async () => {
54
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "lsp-pool-test-"));
55
+ await fs.writeFile(path.join(tmpDir, "fake-root.json"), "{}");
56
+ // Long TTL by default so only the tests that mean to exercise expiry do.
57
+ pool = new LspClientPool({ idleTtlMs: 60_000 });
58
+ });
59
+ afterEach(async () => {
60
+ for (const manager of managers.splice(0))
61
+ manager.shutdownAll();
62
+ pool.shutdownAll();
63
+ await removeWhenReleased(tmpDir);
64
+ });
65
+ /**
66
+ * Two managers sharing one catalog spec is exactly the production shape: the
67
+ * server catalog is module-level, so every session sees the same spec object.
68
+ */
69
+ function twoSessions(spec) {
70
+ const build = () => {
71
+ const manager = new LspManager(tmpDir, {
72
+ catalog: [spec],
73
+ pool,
74
+ warmBudgetMs: 5000,
75
+ firstBudgetMs: 5000,
76
+ });
77
+ managers.push(manager);
78
+ return manager;
79
+ };
80
+ return [build(), build()];
81
+ }
82
+ it("spawns ONE server for two sessions on the same project root", async () => {
83
+ const pidFile = path.join(tmpDir, "server.pid");
84
+ const spec = fakeSpec([`--pid-file=${pidFile}`]);
85
+ const [a, b] = twoSessions(spec);
86
+ const first = await a.diagnosticsAfterWrite(path.join(tmpDir, "a.fake"), "has ERROR here\n");
87
+ const second = await b.diagnosticsAfterWrite(path.join(tmpDir, "b.fake"), "has ERROR here\n");
88
+ // Both sessions get real diagnostics from the one shared server.
89
+ expect(first).toContain("Diagnostics in a.fake");
90
+ expect(second).toContain("Diagnostics in b.fake");
91
+ expect(pool.size).toBe(1);
92
+ expect(pool.refCount(spec, tmpDir)).toBe(2);
93
+ });
94
+ it("keeps the server alive when one session disposes, and kills it when the last does", async () => {
95
+ const pidFile = path.join(tmpDir, "server.pid");
96
+ const spec = fakeSpec([`--pid-file=${pidFile}`]);
97
+ const [a, b] = twoSessions(spec);
98
+ await a.diagnosticsAfterWrite(path.join(tmpDir, "a.fake"), "ok\n");
99
+ await b.diagnosticsAfterWrite(path.join(tmpDir, "b.fake"), "ok\n");
100
+ const pid = Number(await waitForFile(pidFile));
101
+ expect(alive(pid)).toBe(true);
102
+ a.shutdownAll();
103
+ expect(pool.refCount(spec, tmpDir)).toBe(1);
104
+ // The surviving session's server must still be running and usable.
105
+ expect(alive(pid)).toBe(true);
106
+ const afterFirstDispose = await b.diagnosticsAfterWrite(path.join(tmpDir, "c.fake"), "has ERROR here\n");
107
+ expect(afterFirstDispose).toContain("Diagnostics in c.fake");
108
+ b.shutdownAll();
109
+ expect(pool.size).toBe(0);
110
+ expect(await waitUntilDead(pid)).toBe(true);
111
+ }, 20_000);
112
+ it("gives different project roots their own servers", async () => {
113
+ const otherRoot = path.join(tmpDir, "nested");
114
+ await fs.mkdir(otherRoot, { recursive: true });
115
+ await fs.writeFile(path.join(otherRoot, "fake-root.json"), "{}");
116
+ const spec = fakeSpec();
117
+ const [a] = twoSessions(spec);
118
+ await a.diagnosticsAfterWrite(path.join(tmpDir, "a.fake"), "ok\n");
119
+ await a.diagnosticsAfterWrite(path.join(otherRoot, "b.fake"), "ok\n");
120
+ expect(pool.size).toBe(2);
121
+ });
122
+ /** The pinning bug: a root touched once used to stay resident forever. */
123
+ it("reclaims a server that has gone idle even while a session still holds it", async () => {
124
+ const pidFile = path.join(tmpDir, "server.pid");
125
+ // Expiry is driven by passing a future `now` to sweepNow rather than by
126
+ // sleeping past a short TTL: a background sweep racing the assertions made
127
+ // this flaky under parallel test load.
128
+ const idleTtlMs = 60_000;
129
+ const idlePool = new LspClientPool({ idleTtlMs, sweepIntervalMs: 10_000 });
130
+ const spec = fakeSpec([`--pid-file=${pidFile}`]);
131
+ const manager = new LspManager(tmpDir, { catalog: [spec], pool: idlePool });
132
+ managers.push(manager);
133
+ await manager.diagnosticsAfterWrite(path.join(tmpDir, "a.fake"), "ok\n");
134
+ const pid = Number(await waitForFile(pidFile));
135
+ expect(idlePool.size).toBe(1);
136
+ idlePool.sweepNow(Date.now() + idleTtlMs + 1);
137
+ expect(idlePool.size).toBe(0);
138
+ expect(await waitUntilDead(pid)).toBe(true);
139
+ // ...and the session still works afterwards: the next edit rebuilds it.
140
+ const revived = await manager.diagnosticsAfterWrite(path.join(tmpDir, "b.fake"), "has ERROR here\n");
141
+ expect(revived).toContain("Diagnostics in b.fake");
142
+ idlePool.shutdownAll();
143
+ }, 20_000);
144
+ /**
145
+ * A cached failure must not outlive the daemon's patience. Not retrying on
146
+ * every write is deliberate, but pooling made the failure process-wide, so
147
+ * without expiry one broken spawn would disable that root for every present
148
+ * AND future session — where pre-pool a new session simply got a fresh
149
+ * manager and tried again.
150
+ */
151
+ it("lets the idle sweep clear a cached failure so a later session can retry", async () => {
152
+ let spawns = 0;
153
+ // Real elapsed time rather than a synthetic future `now`: the bug is that
154
+ // continued use refreshes a failed entry's timestamp, which only shows up
155
+ // when the writes are actually spread across the TTL.
156
+ const idleTtlMs = 600;
157
+ const failPool = new LspClientPool({ idleTtlMs, sweepIntervalMs: 10_000 });
158
+ const spec = {
159
+ ...fakeSpec(),
160
+ resolveCommand: () => {
161
+ spawns++;
162
+ return { command: process.execPath, args: [FIXTURE, "--init-error"] };
163
+ },
164
+ };
165
+ const manager = new LspManager(tmpDir, {
166
+ catalog: [spec],
167
+ pool: failPool,
168
+ firstBudgetMs: 3000,
169
+ warmBudgetMs: 3000,
170
+ });
171
+ managers.push(manager);
172
+ const file = path.join(tmpDir, "a.fake");
173
+ const start = Date.now();
174
+ expect((await manager.diagnosticsAfterWriteDetailed(file, "x\n")).kind).toBe("server_failed");
175
+ // A second write PAST the halfway mark. This is the discriminating step: if
176
+ // a failed retain refreshed `lastUsedAt`, continued writing would keep the
177
+ // entry looking fresh forever and the sweep below would spare it.
178
+ await new Promise((resolve) => setTimeout(resolve, idleTtlMs * 0.7));
179
+ expect((await manager.diagnosticsAfterWriteDetailed(file, "y\n")).kind).toBe("server_failed");
180
+ // Still cached: no respawn on the next write while the entry lives.
181
+ expect(spawns).toBe(1);
182
+ // Now past the TTL as measured from the FAILURE, not from the last write.
183
+ await new Promise((resolve) => setTimeout(resolve, idleTtlMs * 0.5));
184
+ failPool.sweepNow();
185
+ expect(Date.now() - start).toBeLessThan(idleTtlMs * 2);
186
+ expect(failPool.size).toBe(0);
187
+ await manager.diagnosticsAfterWriteDetailed(file, "z\n");
188
+ expect(spawns).toBe(2);
189
+ failPool.shutdownAll();
190
+ }, 20_000);
191
+ /**
192
+ * After reclamation the replacement server is genuinely cold. Treating it as
193
+ * warm would hand it the short budget and skip the cold-load settle guard,
194
+ * which turns tsserver's premature empty publish into a reported "clean" — a
195
+ * false all-clear, the worst failure mode for a diagnostics tool.
196
+ */
197
+ it("treats a rebuilt server as cold again rather than inheriting warm state", async () => {
198
+ const idleTtlMs = 60_000;
199
+ const idlePool = new LspClientPool({ idleTtlMs, sweepIntervalMs: 10_000 });
200
+ // The server needs longer than the WARM budget but less than the FIRST
201
+ // budget, so which budget the manager picks is directly observable: a cold
202
+ // pass returns diagnostics, a wrongly-warm one times out.
203
+ const spec = fakeSpec(["--delay-ms=700"]);
204
+ const manager = new LspManager(tmpDir, {
205
+ catalog: [spec],
206
+ pool: idlePool,
207
+ firstBudgetMs: 5000,
208
+ warmBudgetMs: 250,
209
+ settleMs: 0,
210
+ });
211
+ managers.push(manager);
212
+ const first = await manager.diagnosticsAfterWriteDetailed(path.join(tmpDir, "a.fake"), "has ERROR here\n");
213
+ expect(first.kind).toBe("diagnostics");
214
+ expect(idlePool.generationFor(spec, tmpDir)).toBe(1);
215
+ idlePool.sweepNow(Date.now() + idleTtlMs + 1);
216
+ expect(idlePool.size).toBe(0);
217
+ // The replacement is a new generation, so the warm mark is stale and this
218
+ // pass must get the first-file budget again.
219
+ const revived = await manager.diagnosticsAfterWriteDetailed(path.join(tmpDir, "b.fake"), "has ERROR here\n");
220
+ expect(idlePool.generationFor(spec, tmpDir)).toBe(2);
221
+ expect(revived.kind).toBe("diagnostics");
222
+ idlePool.shutdownAll();
223
+ }, 20_000);
224
+ it("does not reclaim a server while a diagnostics pass is still in flight", async () => {
225
+ const idlePool = new LspClientPool({ idleTtlMs: 0, sweepIntervalMs: 10_000 });
226
+ // Publishes only after 400ms, so the sweep below lands mid-pass.
227
+ const spec = fakeSpec(["--delay-ms=400"]);
228
+ const manager = new LspManager(tmpDir, {
229
+ catalog: [spec],
230
+ pool: idlePool,
231
+ firstBudgetMs: 5000,
232
+ warmBudgetMs: 5000,
233
+ });
234
+ managers.push(manager);
235
+ const pending = manager.diagnosticsAfterWrite(path.join(tmpDir, "slow.fake"), "has ERROR here\n");
236
+ // Let the client spawn and the pass register before sweeping.
237
+ await new Promise((resolve) => setTimeout(resolve, 200));
238
+ idlePool.sweepNow();
239
+ expect(idlePool.size).toBe(1);
240
+ // The in-flight pass still gets its answer rather than a dead server.
241
+ expect(await pending).toContain("Diagnostics in slow.fake");
242
+ idlePool.shutdownAll();
243
+ }, 20_000);
244
+ it("releasing a holder twice does not disturb another session's server", async () => {
245
+ const spec = fakeSpec();
246
+ const [a, b] = twoSessions(spec);
247
+ await a.diagnosticsAfterWrite(path.join(tmpDir, "a.fake"), "ok\n");
248
+ await b.diagnosticsAfterWrite(path.join(tmpDir, "b.fake"), "ok\n");
249
+ a.shutdownAll();
250
+ a.shutdownAll();
251
+ expect(pool.refCount(spec, tmpDir)).toBe(1);
252
+ expect(await b.diagnosticsAfterWrite(path.join(tmpDir, "c.fake"), "has ERROR here\n")).toContain("Diagnostics in c.fake");
253
+ }, 20_000);
254
+ /**
255
+ * Caching a start failure is the pre-existing contract (see manager.test.ts:
256
+ * "marks a server broken after spawn failure and never retries"). Pooling must
257
+ * not turn one broken toolchain into a fresh spawn on every write.
258
+ */
259
+ it("caches a start failure across BOTH sessions instead of respawning per write", async () => {
260
+ let spawns = 0;
261
+ const spec = {
262
+ ...fakeSpec(["--init-error"]),
263
+ resolveCommand: () => {
264
+ spawns++;
265
+ return { command: process.execPath, args: [FIXTURE, "--init-error"] };
266
+ },
267
+ };
268
+ const [a, b] = twoSessions(spec);
269
+ for (const manager of [a, b, a, b]) {
270
+ const outcome = await manager.diagnosticsAfterWriteDetailed(path.join(tmpDir, "a.fake"), "has ERROR here\n");
271
+ expect(outcome.kind).toBe("server_failed");
272
+ }
273
+ expect(spawns).toBe(1);
274
+ }, 20_000);
275
+ it("shuts every pooled server down on pool shutdown regardless of holders", async () => {
276
+ const pidFile = path.join(tmpDir, "server.pid");
277
+ const spec = fakeSpec([`--pid-file=${pidFile}`]);
278
+ const [a] = twoSessions(spec);
279
+ await a.diagnosticsAfterWrite(path.join(tmpDir, "a.fake"), "ok\n");
280
+ const pid = Number(await waitForFile(pidFile));
281
+ pool.shutdownAll();
282
+ expect(pool.size).toBe(0);
283
+ expect(await waitUntilDead(pid)).toBe(true);
284
+ }, 20_000);
285
+ });
286
+ //# sourceMappingURL=pool.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.test.js","sourceRoot":"","sources":["../../../src/core/lsp/pool.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"servers.d.ts","sourceRoot":"","sources":["../../../src/core/lsp/servers.ts"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,+EAA+E;IAC/E,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,gEAAgE;IAChE,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,2EAA2E;IAC3E,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,4CAA4C;IAC5C,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IACzC,8EAA8E;IAC9E,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAAC;CAC7D;AAiCD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqB/E;AAqFD,eAAO,MAAM,kBAAkB,EAAE,SAAS,aAAa,EA2EtD,CAAC;AAEF,gFAAgF;AAChF,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,SAAS,aAAa,EAAuB,GACrD,aAAa,GAAG,IAAI,CAItB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,MAAM,GACd,MAAM,CAqBR"}
1
+ {"version":3,"file":"servers.d.ts","sourceRoot":"","sources":["../../../src/core/lsp/servers.ts"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,+EAA+E;IAC/E,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,gEAAgE;IAChE,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,2EAA2E;IAC3E,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,4CAA4C;IAC5C,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IACzC,8EAA8E;IAC9E,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAAC;CAC7D;AAiCD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqB/E;AAsHD,eAAO,MAAM,kBAAkB,EAAE,SAAS,aAAa,EAqFtD,CAAC;AAEF,gFAAgF;AAChF,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,SAAS,aAAa,EAAuB,GACrD,aAAa,GAAG,IAAI,CAItB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,MAAM,GACd,MAAM,CAqBR"}
@@ -134,6 +134,38 @@ const TS_LANGUAGE_IDS = {
134
134
  ".cjs": "javascript",
135
135
  ".jsx": "javascriptreact",
136
136
  };
137
+ /**
138
+ * tsserver tuning applied to every TypeScript/JavaScript project root.
139
+ *
140
+ * Shape verified against typescript-language-server 5.3.0's `initialize`
141
+ * handler: `disableAutomaticTypingAcquisition` and `maxTsServerMemory` are
142
+ * read from the TOP level of initializationOptions, while `useSyntaxServer`
143
+ * is read from the nested `tsserver` object.
144
+ *
145
+ * Untuned, one root costs FOUR processes: the language-server bridge, the
146
+ * semantic tsserver, a second `--serverMode partialSemantic` tsserver, and
147
+ * `typingsInstaller`. Both extras exist for an interactive editor, not for us:
148
+ *
149
+ * - The syntax server keeps completions and highlighting responsive while the
150
+ * semantic server loads a project. We only ever call
151
+ * `diagnosticsAfterWrite`, so it is a whole second tsserver holding a second
152
+ * copy of the program for features we never request. `"never"` collapses the
153
+ * pair into a single server (CompositeServerType.Single).
154
+ * - Automatic typing acquisition downloads `@types` packages over the network
155
+ * for untyped JS. This catalog never installs anything at runtime (see the
156
+ * module header), so it only costs the `typingsInstaller` process.
157
+ *
158
+ * Net effect: four processes per root become two.
159
+ */
160
+ const TS_INITIALIZATION_OPTIONS = {
161
+ disableAutomaticTypingAcquisition: true,
162
+ /**
163
+ * Emitted as `--max-old-space-size`. A blast-radius guard rather than a
164
+ * saving — measured roots sit well under this — matching the VS Code default.
165
+ */
166
+ maxTsServerMemory: 3072,
167
+ tsserver: { useSyntaxServer: "never" },
168
+ };
137
169
  export const LSP_SERVER_CATALOG = [
138
170
  {
139
171
  id: "typescript",
@@ -153,12 +185,21 @@ export const LSP_SERVER_CATALOG = [
153
185
  return null;
154
186
  // Prefer the project's own typescript (correct version semantics); fall
155
187
  // back to ggcoder's bundled copy so bare projects still get diagnostics.
156
- if (projectTsserverPath(projectRoot))
157
- return command;
188
+ // Tuning applies to BOTH paths: a project with its own TypeScript is the
189
+ // common case, so skipping it there would leave the flags nearly dead.
190
+ if (projectTsserverPath(projectRoot)) {
191
+ return { ...command, initializationOptions: TS_INITIALIZATION_OPTIONS };
192
+ }
158
193
  const tsserver = bundledTsserverPath();
159
194
  if (!tsserver)
160
195
  return null;
161
- return { ...command, initializationOptions: { tsserver: { path: tsserver } } };
196
+ return {
197
+ ...command,
198
+ initializationOptions: {
199
+ ...TS_INITIALIZATION_OPTIONS,
200
+ tsserver: { ...TS_INITIALIZATION_OPTIONS.tsserver, path: tsserver },
201
+ },
202
+ };
162
203
  },
163
204
  },
164
205
  {