* Handle WebSocket close
(closeCode: number)
| 231 | * Handle WebSocket close |
| 232 | */ |
| 233 | private handleClose(closeCode: number): void { |
| 234 | this.stopPingInterval() |
| 235 | |
| 236 | if (this.state === 'closed') { |
| 237 | return |
| 238 | } |
| 239 | |
| 240 | this.ws = null |
| 241 | |
| 242 | const previousState = this.state |
| 243 | this.state = 'closed' |
| 244 | |
| 245 | // Permanent codes: stop reconnecting — server has definitively ended the session |
| 246 | if (PERMANENT_CLOSE_CODES.has(closeCode)) { |
| 247 | logForDebugging( |
| 248 | `[SessionsWebSocket] Permanent close code ${closeCode}, not reconnecting`, |
| 249 | ) |
| 250 | this.callbacks.onClose?.() |
| 251 | return |
| 252 | } |
| 253 | |
| 254 | // 4001 (session not found) can be transient during compaction: the |
| 255 | // server may briefly consider the session stale while the CLI worker |
| 256 | // is busy with the compaction API call and not emitting events. |
| 257 | if (closeCode === 4001) { |
| 258 | this.sessionNotFoundRetries++ |
| 259 | if (this.sessionNotFoundRetries > MAX_SESSION_NOT_FOUND_RETRIES) { |
| 260 | logForDebugging( |
| 261 | `[SessionsWebSocket] 4001 retry budget exhausted (${MAX_SESSION_NOT_FOUND_RETRIES}), not reconnecting`, |
| 262 | ) |
| 263 | this.callbacks.onClose?.() |
| 264 | return |
| 265 | } |
| 266 | this.scheduleReconnect( |
| 267 | RECONNECT_DELAY_MS * this.sessionNotFoundRetries, |
| 268 | `4001 attempt ${this.sessionNotFoundRetries}/${MAX_SESSION_NOT_FOUND_RETRIES}`, |
| 269 | ) |
| 270 | return |
| 271 | } |
| 272 | |
| 273 | // Attempt reconnection if we were connected |
| 274 | if ( |
| 275 | previousState === 'connected' && |
| 276 | this.reconnectAttempts < MAX_RECONNECT_ATTEMPTS |
| 277 | ) { |
| 278 | this.reconnectAttempts++ |
| 279 | this.scheduleReconnect( |
| 280 | RECONNECT_DELAY_MS, |
| 281 | `attempt ${this.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS}`, |
| 282 | ) |
| 283 | } else { |
| 284 | logForDebugging('[SessionsWebSocket] Not reconnecting') |
| 285 | this.callbacks.onClose?.() |
| 286 | } |
| 287 | } |
| 288 | |
| 289 | private scheduleReconnect(delay: number, label: string): void { |
| 290 | this.callbacks.onReconnecting?.() |
no test coverage detected