* 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)
| 448 | * never wedges a session. |
| 449 | */ |
| 450 | private async runProxyWithLocalHandshake(root: string): Promise<void> { |
| 451 | // The daemon may relocate its socket past an in-project filesystem that can't |
| 452 | // host one (ExFAT/FAT volumes, WSL2 DrvFs; #997) to the deterministic tmpdir |
| 453 | // fallback. We don't read the bound path from the lockfile — both sides walk |
| 454 | // the SAME ordered candidate list, so we converge on whichever the daemon |
| 455 | // bound with zero coordination. The in-project candidate is tried first, so a |
| 456 | // normal repo pays nothing extra (it connects on the very first probe). |
| 457 | const candidates = getDaemonSocketCandidates(root); |
| 458 | const connectAnyCandidate = async (): Promise<Awaited<ReturnType<typeof connectWithHello>>> => { |
| 459 | for (const candidate of candidates) { |
| 460 | const s = await connectWithHello(candidate); |
| 461 | // A wrong-version daemon IS up — definitive; propagate so the caller |
| 462 | // serves in-process instead of spawning + polling for 6s. Don't keep |
| 463 | // probing fallbacks past it. |
| 464 | if (s === 'version-mismatch') return s; |
| 465 | if (s) return s; |
| 466 | } |
| 467 | return null; |
| 468 | }; |
| 469 | const getDaemonSocket = async () => { |
| 470 | // Fast path: a daemon may already be listening (on either candidate). |
| 471 | const probe = await connectAnyCandidate(); |
| 472 | if (probe === 'version-mismatch') return null; // definitive — serve in-process, don't poll for 6s |
| 473 | if (probe) return probe; |
| 474 | // None reachable — spawn one (detached) and poll for its bind. |
| 475 | spawnDetachedDaemon(root); |
| 476 | for (let attempt = 0; attempt < DAEMON_CONNECT_MAX_RETRIES; attempt++) { |
| 477 | await sleep(DAEMON_CONNECT_RETRY_DELAY_MS); |
| 478 | const s = await connectAnyCandidate(); |
| 479 | if (s === 'version-mismatch') return null; |
| 480 | if (s) return s; |
| 481 | } |
| 482 | return null; // never bound — the proxy serves this session in-process |
| 483 | }; |
| 484 | await runLocalHandshakeProxy({ getDaemonSocket, makeEngine: () => new MCPEngine(), root }); |
| 485 | } |
| 486 | |
| 487 | /** Standard SIGINT/SIGTERM handlers that route to our `stop()` (direct mode). */ |
| 488 | private installSignalHandlers(): void { |
no test coverage detected