feat(desktop): stream subagent activity into watch windows (#47060)

* feat(desktop): stream subagent replies into watch windows

A desktop watch window resumes a child session lazily (no full agent) and
mirrors the parent-relayed `subagent.*` events into native child-session
stream events. The child's streamed reply text was never relayed, so the
window sat blank while the subagent "talked".

- delegate_tool: forward the child's `run_conversation` stream tokens up the
  progress relay as `subagent.text` (inert under CLI/TUI — their progress
  handlers ignore non-tool event types; only a gateway watch window mirrors it).
- server: mirror `subagent.text` -> `message.delta` on the child sid only, and
  skip the parent emit (per-token frames are meaningless on the parent session,
  which shows the child via the spawn tree). Demote `subagent.start` to a
  one-time goal header and drop the noisy `subagent.progress` mirror — tools
  already mirror natively.
- server: guard `_start_agent_build` so a lazy watch session spectating an
  in-flight child stays lazy; incidental RPCs were upgrading it to a full
  agent mid-stream and silently killing the mirror.

* fix(desktop): keep watch-window chat clear of titlebar chrome

Secondary windows (new-session scratch, subagent watch, cmd-click pop-out)
hide the titlebar tool cluster + session header, so the transcript ran to the
window's top edge and streamed text slid up under the OS traffic lights.

- Gate the hidden chrome on `isSecondaryWindow()` everywhere (app-shell,
  chat header, thread list) instead of the narrower new-session flag.
- Add a fixed opaque drag-strip at the top of the secondary-window transcript:
  content padding alone scrolls away with the text, so the strip masks
  anything behind it and keeps the window draggable like the main header.

* fix: WSL subagent window

* fix: subagent window top padding

---------

Co-authored-by: Austin Pickett <pickett.austin@gmail.com>
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
This commit is contained in:
brooklyn!
2026-06-16 14:30:11 -04:00
committed by GitHub
co-authored by Austin Pickett Teknium
parent 6ebc449915
commit 44e5848e74
9 changed files with 261 additions and 26 deletions
+21
View File
@@ -867,6 +867,15 @@ def _build_child_progress_callback(
_relay("subagent.complete", preview=preview, **kwargs)
return
if event_type == "subagent.text":
# Streamed assistant reply text from the child. Relay verbatim so a
# gateway watch window can mirror the child "talking" as it streams.
# No spinner echo — the CLI shows the child via the tree, and the
# CLI/TUI progress handlers ignore non-tool event types, so this is
# inert there; only a gateway watch window consumes it.
_relay("subagent.text", preview=preview)
return
# Normalise legacy strings, new-style "delegate.*" strings, and
# DelegateEvent enum values all to a single DelegateEvent. The
# original implementation only accepted the five legacy strings;
@@ -1626,11 +1635,23 @@ def _run_single_child(
# Python stack (see #14726 — 0-API-call hangs are opaque without it).
_worker_thread_holder: Dict[str, Optional[threading.Thread]] = {"t": None}
def _relay_child_text(delta: str) -> None:
# Forward the child's streamed reply text up the progress relay so
# gateway watch windows mirror it live (subagent.text → message.delta).
# Inert under CLI/TUI: their progress handlers ignore non-tool events.
if not delta or not child_progress_cb:
return
try:
child_progress_cb("subagent.text", preview=delta)
except Exception as e:
logger.debug("Child text relay failed: %s", e)
def _run_with_thread_capture():
_worker_thread_holder["t"] = threading.current_thread()
return child.run_conversation(
user_message=goal,
task_id=child_task_id,
stream_callback=_relay_child_text,
)
_child_future = _timeout_executor.submit(_run_with_thread_capture)