* Pipe stdin → socket and socket → stdout. Resolves once either end closes * so the process can exit. Note: we deliberately do NOT use * `process.stdin.pipe(socket)` because pipe propagates 'end' onto the * downstream, which would close the socket prematurely if stdin happens to * end early — th
(socket: net.Socket)
| 502 | * end early — the MCP spec allows it to stay open across reconnects. |
| 503 | */ |
| 504 | function pipeUntilClose(socket: net.Socket): Promise<void> { |
| 505 | return new Promise((resolve) => { |
| 506 | let resolved = false; |
| 507 | const done = () => { if (!resolved) { resolved = true; resolve(); } }; |
| 508 | |
| 509 | process.stdin.on('data', (chunk) => { |
| 510 | try { socket.write(chunk); } catch { /* socket may have errored — close path catches it */ } |
| 511 | }); |
| 512 | process.stdin.on('end', () => { |
| 513 | try { socket.end(); } catch { /* ignore */ } |
| 514 | done(); |
| 515 | }); |
| 516 | // 'close' and 'error' both tear down: a socket-backed stdin can fail with |
| 517 | // an 'error' (ECONNRESET/hangup) rather than a clean close; destroying it |
| 518 | // stops a hung fd from busy-spinning the event loop (#799). |
| 519 | const teardown = () => { |
| 520 | try { process.stdin.destroy(); } catch { /* ignore */ } |
| 521 | try { socket.destroy(); } catch { /* ignore */ } |
| 522 | done(); |
| 523 | }; |
| 524 | process.stdin.on('close', teardown); |
| 525 | process.stdin.on('error', teardown); |
| 526 | |
| 527 | socket.on('data', (chunk) => { |
| 528 | try { process.stdout.write(chunk); } catch { /* ignore */ } |
| 529 | }); |
| 530 | socket.on('end', () => done()); |
| 531 | socket.on('close', () => done()); |
| 532 | socket.on('error', (err) => { |
| 533 | process.stderr.write(`[CodeGraph MCP] daemon socket error: ${err.message}\n`); |
| 534 | done(); |
| 535 | }); |
| 536 | }); |
| 537 | } |
| 538 | |
| 539 | /** |
| 540 | * PPID watchdog mirroring the one in `MCPServer.start` — kills the proxy if |