| 453 | clearTimeout(timer); |
| 454 | }; |
| 455 | const onData = (chunk: string | Buffer) => { |
| 456 | buffer += typeof chunk === 'string' ? chunk : chunk.toString('utf8'); |
| 457 | const idx = buffer.indexOf('\n'); |
| 458 | if (idx === -1) { |
| 459 | if (buffer.length > MAX_HELLO_LINE_BYTES) { |
| 460 | cleanup(); |
| 461 | reject(new Error('daemon hello line exceeded size limit')); |
| 462 | } |
| 463 | return; |
| 464 | } |
| 465 | const line = buffer.slice(0, idx); |
| 466 | // Re-emit anything past the newline so the pipe-stage sees it. |
| 467 | const tail = buffer.slice(idx + 1); |
| 468 | cleanup(); |
| 469 | if (tail.length > 0) { |
| 470 | // Push back via unshift — Node's net.Socket supports it on readable streams. |
| 471 | socket.unshift(tail); |
| 472 | } |
| 473 | try { |
| 474 | const parsed = JSON.parse(line) as DaemonHello; |
| 475 | if (typeof parsed.codegraph !== 'string' || typeof parsed.pid !== 'number') { |
| 476 | reject(new Error('daemon hello missing required fields')); |
| 477 | return; |
| 478 | } |
| 479 | resolve(parsed); |
| 480 | } catch (err) { |
| 481 | reject(new Error(`daemon hello not JSON: ${err instanceof Error ? err.message : String(err)}`)); |
| 482 | } |
| 483 | }; |
| 484 | const onError = (err: Error) => { cleanup(); reject(err); }; |
| 485 | const onClose = () => { cleanup(); reject(new Error('daemon closed connection before hello')); }; |
| 486 | const timer = setTimeout(() => { |