Closes the v1.44 long-lived-sidebar loop end-to-end. When the WS dies for
a transient reason (wifi blip, MV3 panel suspend, brief Chromium pause),
the sidebar now silently re-attaches to the SAME claude session inside the
server's 60s detach window. Scrollback replays cleanly; the user keeps
typing without noticing anything happened.
State machine:
* New STATE.RECONNECTING covers the in-flight re-attach window.
setState transitions out of this state reset reattachInFlight so a
concurrent user action (Restart click, panel navigate) short-circuits
cleanly.
* Backoff schedule REATTACH_BACKOFF_MS = [1000, 2000, 4000, 8000] then
8s steady until REATTACH_WINDOW_MS (60s) elapses. Past that point
the server has disposed our session and /pty-session/reattach
returns 410 Gone.
startReattachLoop(prevSessionId):
* Posts /pty-session/reattach with sessionId.
* On 200 with a valid 4-tuple, opens the post-reattach WS directly.
* On 410 (lease expired) — short-circuits to ENDED. No retry; the user
clicks Restart for a fresh session.
* On 401 — sticky-aborts the auto-connect loop. Same defense as 25ef24e9
so we don't spam "Auth invalid" every 2s.
* On network failure or other non-OK status — schedules the next
backoff tick.
openReattachWebSocket(terminalPort, attachToken, sessionId):
* Mostly a clone of connect()'s attach wiring. Reuses the live xterm
element — RIS clears the buffer cleanly when the agent's
{type:"reattach-begin"} arrives, so the visual flash is minimal.
* Handshake: on `{type:"reattach-begin"}` text frame → write `\x1bc`
(RIS) to xterm + set nextBinaryIsReplay = true. The next binary
frame IS the server-built replay payload (DECSTR soft-reset prefix
+ optional alt-screen re-enter + ring buffer contents).
* If THIS reattach WS also dies uncleanly, recurses into another
re-attach loop with the same sessionId — the server's detach window
may still be open. State guard prevents runaway recursion.
connect() + forceRestart() close handlers (existing):
* Both updated to call startReattachLoop on transient close codes
(anything other than 1000 / 4001 / 4404). Was just setState(ENDED).
* Clean codes still bypass — re-attaching to a force-restart's
pre-restart session would be the bug we're avoiding.
Test (browse/test/sidepanel-reattach.test.ts):
* 8 static-grep tripwires for the load-bearing properties: state
constant, backoff schedule, /pty-session/reattach wiring, 410
short-circuit (no retry past lease window), 401 sticky-abort,
reattach-begin → RIS handshake, all three close handlers route
through the loop, clean-code bypass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>