Skip to content

feat(mobile): show live sub-agents in the agent list and chat - #779

Merged
alchaplinsky merged 3 commits into
mainfrom
feat/mobile-subagents
Sep 19, 2026
Merged

alchaplinsky merged 3 commits into
mainfrom
feat/mobile-subagents

Conversation

@alchaplinsky

Copy link
Copy Markdown
Member

Summary

Mobile half of the sub-agent visibility series, stacked on #776. The phone receives the same agent:event stream as the desktop, so it folds Claude's system task events through the shared reducer from #776 (imported via @desktop/adapters/shared/backgroundTasks) into its own store. No shared src/ files touched.

What the user sees

  • Agent list row: while top-level sub-agents run under an idle agent, the dot reads running, the live slot says "2 sub-agents", the meta label says "Working", and the elapsed timer keeps ticking. A failure within the last 10 minutes shows a red "sub-agent failed" chip, below the existing error and needs-you chips in priority.
  • Agent header: "N sub-agents working" for an idle agent.
  • Chat tab: a strip pinned under the tabs lists running and recently failed sub-agents: description (fallback sub-agent type), N tools · elapsed, a warn-toned quiet Nm after 3 silent minutes (heuristic wording, never "stalled"), and the failure status for failed ones. Tapping scrolls to the launching tool row.
  • Tool row: running dots while its task runs (not only while streaming), red when failed, and expandable to the sub-agent's nested turns rendered recursively with the same block renderer and display policy as the main log.

Store lifecycle

backgroundTasks[agentId] is folded before the chat reducer, cleared on agent stopped/error (not idle, since tasks outlive the turn), on chat delete and unpair, and reset on every handshake because task frames missed while the socket was down can never be replayed. Never persisted.

Mock host

Any Claude prompt matching sub-agent, delegate, or in parallel plays a turn that backgrounds an Agent, ends the turn, then keeps forwarding tagged sub-agent turns, progress, notification, and task-list events, so the UI can be previewed without a host.

Changes

New: mobile/src/store/backgroundTasks.ts, mobile/src/lib/subagents.ts, mobile/src/screens/Agent/Transcript.tsx (absorbs the old ToolRow.tsx so rows can render nested turns), mobile/src/screens/Agent/SubagentStrip.tsx, mobile/tests/subagents.test.ts.
Modified: store index/events/chats, AgentRow, ChatTab, agent screen, lib/tools.ts, styles/agent.css, mock script and host.

Tests

Inside mobile/: bun run check, bun run lint, bun run test (159 tests) pass.

Claude keeps emitting background-task system events after the main
turn's result lands, so an agent could read as idle on the phone while
its sub-agents were still working — or had failed. The phone now folds
those events (through the shared reducer PR 1 added) into its own store
and draws them in three places.

- store: backgroundTasks[agentId][taskId], fed from agent:event via
  foldTaskEvent before the chat reducer sees the frame. Cleared on
  agent:status stopped/error (idle is not a clear), on deleteChat, on
  unpair, and on every handshake — missed frames can never be replayed,
  so a stale "running" would otherwise never end. Never persisted.
- lib/subagents: pure derivations (top-level sub-agents only, recent
  failures kept ~10 min, "quiet Nm" hint past 3 min) shared by the row,
  the header line and the strip. Covered by tests/subagents.test.ts.
- AgentRow: reads as working while sub-agents run under an idle agent
  ("2 sub-agents" in the live slot, running dot, "Working" label); a
  recent failure shows a "sub-agent failed" marker.
- Agent screen: a strip pinned above the transcript lists running and
  recently failed sub-agents with tool count, elapsed and the quiet hint;
  tapping one scrolls to the launching tool row (data-tool-use-id). The
  header subtitle says "N sub-agents working" for an idle agent.
- Transcript (absorbs ToolRow and the block rendering ChatTab held): a
  tool row keeps its running dots while its task runs, turns red with the
  failure status when it fails, and expands to the sub-agent's threaded
  turns — the same renderer, recursively.
- mock host: a prompt mentioning sub-agents / delegate plays a turn that
  backgrounds an Agent, ends, then keeps forwarding the sub-agent's tagged
  turns, progress and notification; runTurn ends the turn at the last
  persisted step and plays the rest as the background tail.
…y policy

The threaded `children` under an Agent tool row were rendered raw, so the
sub-agent's own `result` drew a "Turn complete" divider inside the row and
any policy-hidden notice leaked through. The Transcript now carries the
provider's DisplayPolicy and applies it to nested logs on the way in — the
same pass the chat tab makes over the main log and the desktop's
SubagentThread makes over its children.
A failed sub-agent is shown for RECENT_FAILURE_MS, but the strip only ticked
while a task was running and the agent row's chip had no tick at all, so once
the last task failed both stayed on screen until something unrelated
re-rendered them.

One clock (useSubagentClock) now drives both surfaces: every second while a
sub-agent runs or the row has its own turn timer, once a minute while only a
recent failure is left, and no timer when there is nothing to show. The
decision is the pure subagentTickMs; the rendered expiry of both the strip and
the row is covered under fake timers.
Base automatically changed from feat/subagent-task-state to main September 19, 2026 15:28
@alchaplinsky
alchaplinsky merged commit dbd9e13 into main Sep 19, 2026
2 checks passed
@alchaplinsky
alchaplinsky deleted the feat/mobile-subagents branch September 19, 2026 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant