feat(chat): keep-alive + reattach for dashboard terminal sessions - #50084
Closed
TinkerOfThings wants to merge 6 commits into
Closed
feat(chat): keep-alive + reattach for dashboard terminal sessions#50084TinkerOfThings wants to merge 6 commits into
TinkerOfThings wants to merge 6 commits into
Conversation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keep-alive path when ?attach=<token> is present: PTY outlives the socket via PTY_REGISTRY, reattaches on reconnect. No token = unchanged legacy pump (_legacy_pump). detach (not close) on disconnect. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ChatPage sends ?attach=<localStorage token> so /chat reattaches to its live PTY across refresh. onclose: 4410=process-exit (session ended), 4409=superseded (quiet), else transient -> auto-reconnect. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
|
Merged in #60515 via rebase-merge — your commits landed on main with authorship preserved (e10e4bc..79f4f78). The branch had drifted ~2,400 commits behind, so we cherry-picked your 6 commits onto current main and added small integration fixes on top (to_thread offloads for the post-#53227 event-loop rules, a detach guard for superseded sockets, and attach-token rotation on forced-fresh starts). Thanks for the clean design — the registry + ring-buffer + close-code contract shipped essentially as you built it. |
This was referenced Jul 7, 2026
This was referenced Jul 31, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Dashboard terminals tie the PTY process 1:1 to the WebSocket:
pty_wsspawns on connect and callsbridge.close()on disconnect. So a browser refresh, tab close, or mobile signal-drop kills the running agent, and reconnecting spawns a brand-new session — losing an in-flight reply and the conversation.This makes a terminal session survive disconnect and reattach on reconnect, opt-in via a
?attach=<token>query param (no token ⇒ existing spawn-and-kill behavior, unchanged). A sharedPtySessionRegistryowns each PTY: a single drain task always reads the PTY into a bounded ring buffer and forwards to the attached socket when present. On reconnect with the same token the buffer is replayed (repainting the terminal) and live streaming resumes. Detached sessions are reaped after 30 min idle, or immediately when the agent process exits.Intentional non-goals: no cross-device handoff (token is per-browser
localStorage); no simultaneous multi-viewer (last-attach-wins); sessions don't persist across a dashboard restart (registry is in-process). The token is opaque and never bypasses auth — all existing per-connect auth/host/origin gates run unchanged before any reattach.Related Issue
Fixes #50074
Type of Change
Changes Made
hermes_cli/pty_session.py(new) —RingBuffer,PtySession(drain/attach/detach, EOF → WS close4410),PtySessionRegistry(reattach/reap/capacity),run_reaper.hermes_cli/web_server.py—/api/ptykeep-alive path behind?attach=; the original pump factored into_legacy_pump(no-token path, unchanged); module-levelPTY_REGISTRY; reaper started/cancelled in the app lifespan.web/src/pages/ChatPage.tsx— persist a per-browser token inlocalStorage, send it as?attach=, reconnect on a transient close, and distinguish process-exit (4410) / superseded (4409) from a transient drop.tests/test_pty_session.py,tests/test_pty_keepalive_ws.py(new) — unit + integration coverage.Close-code contract:
4410= process exited,4409= superseded by a newer tab on the same token, anything else = transient → client reconnects.How to Test
/chat, send a prompt./exitthe agent → the panel shows "session ended" (close4410), no reconnect loop.pytest tests/test_pty_session.py tests/test_pty_keepalive_ws.py -q→ pass.Checklist
feat(scope):)cli-config.yaml.example(no config keys)pty_ws(already gated by_PTY_BRIDGE_AVAILABLE); no Windows-specific paths