* 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)
| 404 | * never wedges a session. |
| 405 | */ |
| 406 | private async runProxyWithLocalHandshake(root: string): Promise<void> { |
| 407 | // The daemon may relocate its socket past an in-project filesystem that can't |
| 408 | // host one (ExFAT/FAT volumes, WSL2 DrvFs; #997) to the deterministic tmpdir |
| 409 | // fallback. We don't read the bound path from the lockfile — both sides walk |
| 410 | // the SAME ordered candidate list, so we converge on whichever the daemon |
| 411 | // bound with zero coordination. The in-project candidate is tried first, so a |
| 412 | // normal repo pays nothing extra (it connects on the very first probe). |
| 413 | const candidates = getDaemonSocketCandidates(root); |
| 414 | const connectAnyCandidate = async (): Promise<Awaited<ReturnType<typeof connectWithHello>>> => { |
| 415 | for (const candidate of candidates) { |
| 416 | const s = await connectWithHello(candidate); |
| 417 | // A wrong-version daemon IS up — definitive; propagate so the caller |
| 418 | // serves in-process instead of spawning + polling for 6s. Don't keep |
| 419 | // probing fallbacks past it. |
| 420 | if (s === 'version-mismatch') return s; |
| 421 | if (s) return s; |
| 422 | } |
| 423 | return null; |
| 424 | }; |
| 425 | const getDaemonSocket = async () => { |
| 426 | // Fast path: a daemon may already be listening (on either candidate). |
| 427 | const probe = await connectAnyCandidate(); |
| 428 | if (probe === 'version-mismatch') return null; // definitive — serve in-process, don't poll for 6s |
| 429 | if (probe) return probe; |
| 430 | // None reachable — spawn one (detached) and poll for its bind. |
| 431 | spawnDetachedDaemon(root); |
| 432 | for (let attempt = 0; attempt < DAEMON_CONNECT_MAX_RETRIES; attempt++) { |
| 433 | await sleep(DAEMON_CONNECT_RETRY_DELAY_MS); |
| 434 | const s = await connectAnyCandidate(); |
| 435 | if (s === 'version-mismatch') return null; |
| 436 | if (s) return s; |
| 437 | } |
| 438 | return null; // never bound — the proxy serves this session in-process |
| 439 | }; |
| 440 | await runLocalHandshakeProxy({ getDaemonSocket, makeEngine: () => new MCPEngine(), root }); |
| 441 | } |
| 442 | |
| 443 | /** Standard SIGINT/SIGTERM handlers that route to our `stop()` (direct mode). */ |
| 444 | private installSignalHandlers(): void { |
no test coverage detected