### Added
- WebSocket heartbeat (ping/pong) mechanism: server sends `ping` every 30 seconds, clients auto-respond with `pong`. Connections missing 3 consecutive pings (90s) are closed gracefully, replacing the need for client-side staleness detection.

### Changed
- Reverting from the first user message now clears the entire session, resulting in a completely empty conversation instead of being blocked.
- Increased default `JEAN2_COMPACTION_MAX_TOKENS` from 2000 to 8000, giving compaction summaries more room and reducing the likelihood of truncated outputs.

### Fixed
- Fixed sessions becoming unrecoverable after interrupting during tool execution. Previously, tool calls left in `pending` or `running` state had no matching tool result, causing API errors on session resume. Now, interrupted tool calls are properly transitioned to `interrupted` status with a synthesized error result, sessions are automatically reconciled at startup and on resume, and the message conversion layer provides a safety net for any orphaned tool states.
- Fixed compaction "death spiral" that could permanently break sessions through three cascading failures: token accumulation across successive compactions (now replaces instead of accumulating), undetected truncated summaries (now detects `finishReason === 'length'` and marks incomplete summaries), and infinite retry loops on persistent failures (now enforces a cooldown after consecutive failures).
- Fixed Codex provider crash during compaction by switching to `getModelWithMetadata()` to correctly preserve the `omitMaxOutputTokens` flag.
