d6bc3f2bca
Without SO_KEEPALIVE a silently-dropped client (SSH tunnel reset, laptop sleep, NAT timeout) leaves the TCP leg half-open forever: receive_text() blocks indefinitely and the disconnect teardown (detach, orphan reap, resume replay) never runs. The server then leaks the session and never reclaims its orphans. _disable_nagle already reaches the raw socket, so enable keepalive there: SO_KEEPALIVE on, plus TCP_KEEPIDLE=30s / TCP_KEEPINTVL=10s / TCP_KEEPCNT=3 on Linux and TCP_KEEPALIVE=30s on macOS. A dead peer is now detected in ~60s instead of never. Best-effort like the Nagle tuning — any failure to reach the socket is logged at debug and skipped. Tests: new tests/tui_gateway/test_ws_keepalive.py fakes the socket and pins SO_KEEPALIVE + the platform-specific idle tuning, plus the no-transport no-raise path. tests/tui_gateway: 336 passed.