* Proxy mode (the common case). Serve the MCP handshake LOCALLY for instant * tool registration, forwarding tool calls to the shared daemon — which is * connected in the background (probed, then spawned + polled if absent) so the * handshake never waits ~600ms on it. Runs until the host dis
(root: string)
| 376 | * never wedges a session. |
| 377 | */ |
| 378 | private async runProxyWithLocalHandshake(root: string): Promise<void> { |
| 379 | // The daemon may relocate its socket past an in-project filesystem that can't |
| 380 | // host one (ExFAT/FAT volumes, WSL2 DrvFs; #997) to the deterministic tmpdir |
| 381 | // fallback. We don't read the bound path from the lockfile — both sides walk |
| 382 | // the SAME ordered candidate list, so we converge on whichever the daemon |
| 383 | // bound with zero coordination. The in-project candidate is tried first, so a |
| 384 | // normal repo pays nothing extra (it connects on the very first probe). |
| 385 | const candidates = getDaemonSocketCandidates(root); |
| 386 | const connectAnyCandidate = async (): Promise<Awaited<ReturnType<typeof connectWithHello>>> => { |
| 387 | for (const candidate of candidates) { |
| 388 | const s = await connectWithHello(candidate); |
| 389 | // A wrong-version daemon IS up — definitive; propagate so the caller |
| 390 | // serves in-process instead of spawning + polling for 6s. Don't keep |
| 391 | // probing fallbacks past it. |
| 392 | if (s === 'version-mismatch') return s; |
| 393 | if (s) return s; |
| 394 | } |
| 395 | return null; |
| 396 | }; |
| 397 | const getDaemonSocket = async () => { |
| 398 | // Fast path: a daemon may already be listening (on either candidate). |
| 399 | const probe = await connectAnyCandidate(); |
| 400 | if (probe === 'version-mismatch') return null; // definitive — serve in-process, don't poll for 6s |
| 401 | if (probe) return probe; |
| 402 | // None reachable — spawn one (detached) and poll for its bind. |
| 403 | spawnDetachedDaemon(root); |
| 404 | for (let attempt = 0; attempt < DAEMON_CONNECT_MAX_RETRIES; attempt++) { |
| 405 | await sleep(DAEMON_CONNECT_RETRY_DELAY_MS); |
| 406 | const s = await connectAnyCandidate(); |
| 407 | if (s === 'version-mismatch') return null; |
| 408 | if (s) return s; |
| 409 | } |
| 410 | return null; // never bound — the proxy serves this session in-process |
| 411 | }; |
| 412 | await runLocalHandshakeProxy({ getDaemonSocket, makeEngine: () => new MCPEngine(), root }); |
| 413 | } |
| 414 | |
| 415 | /** Standard SIGINT/SIGTERM handlers that route to our `stop()` (direct mode). */ |
| 416 | private installSignalHandlers(): void { |
no test coverage detected