fix(gateway): stop a resuming viewer stealing a running session's stream - #83524
fix(gateway): stop a resuming viewer stealing a running session's stream#83524jaxmatrix wants to merge 1 commit into
Conversation
`session.resume` is not only "reattach me" — it is also how a second viewer PEEKS at a session from another window, tile, or client. `_live_session_payload` rebound `session["transport"]` unconditionally, so that peek took over the live stream: a turn already running for viewer A suddenly emitted to viewer B, and A watched its own answer stop mid-sentence with no error and no way to get it back. A running session now keeps the transport it is streaming to. Three cases still rebind, because none of them can interrupt anyone: - an IDLE session — nothing is in flight, and the next prompt would rebind anyway; - a session parked on `_detached_ws_transport` or `_stdio_transport` — a disconnect points detached sessions there, so there is no live reader and the resuming client is strictly better than nothing; - the viewer that already owns the stream re-asserting it. Four regression tests cover exactly those branches. They fail on the unpatched tree with `AttributeError` on the missing helper, and the behavioural one fails on the semantics rather than the symbol once it exists.
fix(gateway): stop a resuming viewer stealing a running session's stream
|
The bug
session.resumeis not only "reattach me" — it is also how a second viewer peeks at a session from another window, tile, or client._live_session_payloadrebinds the session's transport unconditionally:So the peek takes over the live stream. A turn already running for viewer A suddenly emits to viewer B, and A watches its own answer stop mid-sentence — no error, no indication, and no way to get the stream back short of another resume (which then steals it from B).
The window is exactly "a turn is in flight and someone else opens the same session", which is the normal case as soon as a client supports more than one view of a conversation.
The fix
A running session keeps the transport it is streaming to. Three cases still rebind, because none of them can interrupt anyone:
_detached_ws_transportor_stdio_transport— a disconnect points detached sessions at the former (_reap_or_detach_sessions_for_transport), so there is no live reader and the resuming client is strictly better than nothing;current is None).The predicate is a pure function on
(session, transport), so it is directly testable without standing up a gateway.Scope
tui_gateway/server.pyonly — one new helper plus one condition on an existing assignment. No API, wire-format, or config change; a client that only ever has one viewer sees no difference.Tests
Four regression tests in
tests/test_tui_gateway_ws.py, one per branch above.10 passed.server.pyand keeping the tests:4 failed, 6 passed.Provenance
This has been running in a downstream fork for some time, where the multi-viewer case (extra windows, tiles, a phone client on the same session) is common enough that the stolen-stream symptom showed up immediately. Sending it upstream because the root cause is in the shared gateway, not in anything fork-specific — the guard is worth having wherever more than one viewer can resume a session.
Confirmed still present on
mainat37e46c774c.