Introduce DisconnectInfo with reason/error so consumers can distinguish
heartbeat timeouts from remote close, write errors, and parse failures.
Wire reasons through TCP/WS read and write paths.
Fix the heartbeat wait-timer to re-check waitingHBResponse when firing:
in paired idle (both peers heartbeat in the same cycle) the response
could arrive while the wait timer was being installed, causing a stale
disconnect. The timer now re-arms the interval instead of disconnecting
when the response was already observed.
Add regression tests: TestHeartbeatSurvivesPairedIdle, and assert
DisconnectInfo.Reason == heartbeat_timeout in the legitimate timeout
tests for both TCP and WebSocket.