Skip to content

feat(chat): keep-alive + reattach for dashboard terminal sessions (#50084 salvage) - #60515

Merged
teknium1 merged 6 commits into
mainfrom
hermes/hermes-f6ee1291
Jul 7, 2026
Merged

feat(chat): keep-alive + reattach for dashboard terminal sessions (#50084 salvage)#60515
teknium1 merged 6 commits into
mainfrom
hermes/hermes-f6ee1291

Conversation

@teknium1

@teknium1 teknium1 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Dashboard chat sessions now survive browser refreshes, sleep/wake, and network drops: the /api/pty PTY can outlive its WebSocket and be reattached with full output replay, instead of the running agent being killed on every disconnect.

Salvages PR #50084 by @TinkerOfThings (rebase-merge, authorship preserved) onto current main, resolving the cluster behind #22027 / #50074 / #15915 / #53839.

Infographic

Keep-alive PTY sessions

Root cause

pty_ws ties the PTY 1:1 to the WebSocket — spawn on connect, bridge.close() on disconnect. Any transport drop kills the running agent mid-reply; reconnect spawns a fresh PTY (the session-id breadcrumb resumes the conversation, but the in-flight turn and scrollback are lost).

Changes

  • hermes_cli/pty_session.py (new): RingBuffer (1MB), PtySession (single drain task, attach/detach, EOF → WS close 4410), PtySessionRegistry (token-keyed reattach, 16-session cap, 30-min idle reap), run_reaper.
  • hermes_cli/web_server.py: keep-alive path on /api/pty behind ?attach=<token>; no-token path factored into _legacy_pump byte-identical to today's behavior; reaper wired into the app lifespan.
  • web/src/pages/ChatPage.tsx: persistent per-browser attach token (localStorage), sent on every connect; close-code contract 4410 = process exited (restart affordance), 4409 = superseded tab (quiet), anything else = transient → existing backoff reconnect now reattaches to the living PTY.
  • Tests: tests/test_pty_session.py (10 unit), tests/test_pty_keepalive_ws.py (WS integration).

Follow-up fixes on top of the salvage (integrating with main's post-#50084 hardening):

Validation

Check Result
scripts/run_tests.sh tests/test_pty_session.py tests/test_pty_keepalive_ws.py 11/11 pass
scripts/run_tests.sh tests/hermes_cli/test_pty_bridge.py tests/hermes_cli/test_web_server.py pass
web: tsc -b --force + npm run build green
Live E2E (real uvicorn + real websockets client + real bash PTY child) 6/6 pass

Live E2E specifics: hard transport abort mid-session → PTY survives; reconnect with same token → ring buffer replays and the SAME shell process is reattached (env var set before the drop still present); child exit → close 4410; no-token legacy path → PTY killed on disconnect, unchanged.

Non-goals (as designed in #50084)

No cross-device handoff (token is per-browser localStorage), no multi-viewer (last-attach-wins via 4409), no persistence across a dashboard restart (in-process registry). The token never bypasses auth — all existing per-connect auth/host/origin gates run before any reattach.

Credit: @TinkerOfThings (design doc + implementation, PR #50084). Related earlier attempts: #34039 by @cpmidnite, #37682, #47297.

Fixes #50074. Closes #22027. Closes #15915. Addresses the web-dashboard half of #53839.

TinkerOfThings and others added 6 commits July 7, 2026 14:15
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>
@alt-glitch alt-glitch added type/feature New feature or request comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/cli CLI entry point, hermes_cli/, setup wizard sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state P3 Low — cosmetic, nice to have labels Jul 7, 2026
@teknium1
teknium1 merged commit 79f4f78 into main Jul 7, 2026
36 checks passed
@teknium1
teknium1 deleted the hermes/hermes-f6ee1291 branch July 7, 2026 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/dashboard Web dashboard / control panel UI (dashboard/, landing) P3 Low — cosmetic, nice to have sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

3 participants