fix(gateway): session.active_list reads waiting while a viewer request holds the turn

Greptile on #148880 (Some waiting sessions stay silent): the status came from shared controls only,
so a turn blocked on a clarify batch, sudo, secret or vault prompt (session_server_requests) read
working, and the TUI never announced a background session waiting on one.
This commit is contained in:
Teknium committed 2026-11-04 20:54:25 -08:00
1 parent 3349029085
commit 787954a49f
2 files changed
+60 -2

No files matched your search

+6 -2
View File
@@ -14,8 +14,12 @@ from hermes_state_runtime import RuntimeStoreError
def _status(live, handle, pending) -> str:
if handle.execution_state == 'running':
prompts = live.controls.snapshot(handle.ref.session_id, handle.execution_generation)
return 'waiting' if prompts else 'working'
from gateway.session_server_requests import open_requests
sid = handle.ref.session_id
# A question holds the turn: a shared control (approval) or a viewer request (clarify batch,
# sudo, secret, vault) still open on the stream.
asked = live.controls.snapshot(sid, handle.execution_generation) or open_requests(live.event_stream, sid)
return 'waiting' if asked else 'working'
# Admitted, not yet claimed by the drain: the turn exists, its agent has not started.
return 'starting' if any(row['status'] == 'queued' for row in pending) else 'idle'
@@ -0,0 +1,54 @@
"""``session.active_list`` reads ``waiting`` while a viewer request holds the turn.
A TUI kept a session working in the background (``/new`` mid-turn) and polls this verb to tell the
user when that session needs an answer. Clarify batches, sudo, secret and vault prompts are viewer
requests (``gateway.session_server_requests``), not shared controls, so a status read from the
controls alone said ``working`` while the turn sat blocked on the question.
"""
import asyncio
import threading
import pytest
from tests.gateway.test_authority_active_list import owner as listing_owner
listing = listing_owner # the active-list fixture, under a name the gateway conftest's `owner` does not shadow
class _Peer:
def __init__(self):
self.frames = []
def write(self, frame, **_):
self.frames.append(frame)
return True
def close(self):
pass
@pytest.mark.asyncio
async def test_a_turn_blocked_on_a_viewer_request_reads_waiting(listing):
from gateway.session_server_requests import ask_viewer, withdraw
await listing.submit('hi')
events = listing.authority.sessions['chat'].event_stream
peer = _Peer()
events.fanout.attach(peer)
assert (await listing.active())['chat']['status'] == 'working'
asked = threading.Thread(target=ask_viewer, args=(events, 'chat', 'sudo', {'command': 'id'}),
kwargs={'timeout': 10}, daemon=True)
asked.start()
for _ in range(200):
if peer.frames:
break
await asyncio.sleep(0.01)
assert peer.frames, 'the sudo request never reached the viewer'
try:
assert (await listing.active())['chat']['status'] == 'waiting'
finally:
withdraw(events, 'chat', 'sudo')
asked.join(5)
assert (await listing.active())['chat']['status'] == 'working'