fix(gateway): route plain background live output to its spawn-time UI owner (#61719 residual) - #73351
fix(gateway): route plain background live output to its spawn-time UI owner (#61719 residual)#73351ayushnangia wants to merge 2 commits into
Conversation
Related: #62201 has the broader owner-routing implementation across notifications and delegation. This PR isolates the remaining plain-background live-output path. |
|
Correct framing, thanks — mapping the relationship precisely for whoever picks between them:
If maintainers prefer salvaging #62201's broader scope instead, the two red-on-main tests here (plain-background capture without notify/watch; delegated-child-shape routing with live-window guard) transfer directly as its regression matrix. |
|
Thanks for isolating the remaining live-output routing path. The underlying routing gap is present on current main: Problems
Suggested changes
Automated hermes-sweeper review. |
c98b378 to
574911b
Compare
|
Both points addressed in 574911b61 (rebased onto current main):
|
… owner (NousResearch#61719 residual) Merged 54d0948 landed owner-routing for post-turn completions, but the sweeper's flagged gap on NousResearch#61719 survived: terminal_tool captured routing metadata only when notify_on_complete/watch_patterns were set, while live agent.terminal.output chunks are emitted for EVERY background process. _owner_sid_for_process matched only by session_key, so a delegated child's process (subagent-internal key, never a live TUI session) emitted its live output with sid "" — dropped by write_json. - ProcessSession.origin_ui_session_id: spawn-time UI owner, persisted in the crash checkpoint like the watcher metadata - terminal_tool: capture HERMES_UI_SESSION_ID for every background spawn (same session-env pattern as desktop_ui.py:39 / delegate_tool.py:3191) - _owner_sid_for_process: prefer the recorded origin while that window is live; legacy session_key equality remains the fallback Salvages the remaining scope of NousResearch#61719 (bot-authored, sweeper problem unaddressed).
… reader starts Sweeper review: the post-spawn assignment races the local reader thread — spawn_local() starts it before returning, and _emit_output() can forward the first chunks while origin_ui_session_id is still empty. - spawn_local()/spawn_via_env() accept origin_ui_session_id and set it on ProcessSession at construction, before any reader/poller exists - terminal_tool captures HERMES_UI_SESSION_ID before the spawn call and passes it in; the post-spawn assignment is removed - capture test now asserts the kwarg is handed to the spawn (red on the post-spawn variant); new real-registry-lifecycle regression proves the first emitted chunk of a fast process already carries the owner
574911b to
8e8429a
Compare
Summary
Live
agent.terminal.outputchunks from a plainbackground=trueprocess (nonotify_on_complete, nowatch_patterns) — a delegated child's, in particular — are dropped by the desktop router: the process has no positive UI owner. This completes the remaining scope of #61719 after its core landed.Root cause
Owner-routing for post-turn completions landed in merged
54d0948d3("route post-turn completions by owner"), anddrain_notificationsnow requires positive ownership for routed events. But the sweeper's flagged problem on #61719 survived the merge:tools/terminal_tool.pycaptured routing metadata only inside thebackground and (notify_on_complete or watch_patterns)gate — while live output is emitted for every background process (process_registry.on_output)._owner_sid_for_process()(tui_gateway/server.py,_wire_agent_terminal_output) matched only bysession_keyequality against live TUI sessions. A delegated child'sProcessSession.session_keyis the subagent's internal key, which never matches a window — so its chunks were emitted withsid="", and a session-less event from a registry reader thread is dropped bywrite_json(documented at the_live_transportsregistry).Changes
tools/process_registry.py:ProcessSession.origin_ui_session_id— spawn-time UI owner, persisted/restored in the crash checkpoint alongside the existing watcher metadata.tools/terminal_tool.py: captureHERMES_UI_SESSION_IDfor every background spawn, outside the notify/watch gate — same session-env pattern the codebase already uses for UI routing (tools/desktop_ui.py:39,tools/delegate_tool.py:3191). Empty outside TUI/desktop contexts; best-effort with fallback.tui_gateway/server.py:_owner_sid_for_process()prefers the recorded origin while that window is still live, then falls back to the legacysession_keymatch — so existing routing behavior is byte-identical for processes without a recorded origin or after the owning window closes.Validation
background=truelive outputsid=""→ droppedsession_keymatch""(no leak to other windows)test_background_spawn_captures_ui_origin_without_notify(capture without notify/watch) andtest_agent_terminal_output_routes_by_spawn_time_ui_owner(child-shape routing + fallback + stale-origin cases)scripts/run_tests.sh tests/tools/test_notify_on_complete.py tests/test_tui_gateway_server.py tests/tools/test_process_registry.py— 636 passed, 0 failedScope notes
54d0948d3.Salvages the remaining scope of #61719 by @soria-clawd-bot (sweeper review problem: UI origin captured only when notify/watch enabled). Refs #54785,
54d0948d3.