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