Skip to content

feat(agent-activity): replace the popup with a full-screen fleet view - #1105

Merged
Juliusolsson05 merged 10 commits into
mainfrom
feat/agent-activity-redesign
Sep 25, 2026
Merged

Juliusolsson05 merged 10 commits into
mainfrom
feat/agent-activity-redesign

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Sep 20, 2026 •

Copy link
Copy Markdown
Owner

Fixes #1170

Replaces the Agent Activity popup with a full-screen view that answers three questions for every agent in the window, lane or pool: who needs me, who is working and on what, and what can I clean up. This PR started as Stage 1 (evidence and plan only). It now carries the whole redesign.

The problem

The owner's verdict on the old modal: "that modal is ages and just shit across the board". The evidence recorded in Stage 1, from the owner's own 33–48 agent window:

  • it had no "needs you" signal;
  • rows read as the folder name (3 of 33 sessions had a title);
  • it listed panes, not agents, so parked pool sessions were missing;
  • it still spoke in tabs;
  • it had no filter, and its actions were hover-only and one at a time.

Owner decisions (2026-09-24)

  1. Full screen.
  2. Close Old Agents and Close Idle Orchestration Agents stay as quick one-shot commands.
  3. An untitled row is named by its Goal, then its folder.

What it does

  • Sections, in this order: Needs you, Working, Idle (oldest first, for cleanup) and Exited. Needs-you rows say why in plain words:
    • wants permission / asked a question / trust prompt;
    • turn failed, or process error;
    • hit a usage limit (only while newer than the current turn);
    • your prompt did not send;
    • goal loop blocked or paused;
    • a queued prompt behind an idle agent (a queue behind a working agent is normal and is not flagged).
  • Names: title → Goal → folder. The row labels a Goal or folder name as such, so a folder is never mistaken for a chosen name. The TLDR is the second line.
  • Keyboard:
    • ↑/↓ move, Enter opens the agent, Space selects, ⌘A selects all shown, ⌫ closes.
    • Any letter jumps into the filter, and in the filter Space is just a space.
    • The first Esc clears the filter; the second dismisses.
  • Close: single rows and selections both go through closeGrantedSessions. It confirms once, carries its own bulk.agent-activity kill tag, records no Undo Close entries, and refuses an agent that started working after it was approved. A working agent the user deliberately selected does close.

Design decisions

  • A full-viewport Radix Dialog, not a fourth MainSurface takeover. Every hidden-workspace rule (the useKeybinds picker gate and Escape, lease hygiene, the control observation) names each takeover by hand, so a takeover would mean four more edits, each easy to forget. The Dialog already owns input ownership, the focus trap, Escape and focus restore, and no terminal resizes. The reasoning is also in the decomposition doc.
  • No new rules for "needs you". Each signal reuses its existing owner: conditionRequiresAttention, terminalProviderFailure, isLimitIdle, the goal-loop chip reading, and sessionActivity. The row model is a pure function, so the bulk close can re-ask it at the kill boundary.
  • Notes are read in one batched IPC call per kind (TLDR, Goal, Goal Loop), only while the view is open. A closed view reads and subscribes to nothing, and it joins closedModalDerivations.

Verification

  • activityRow.test.ts replays the recorded corpus from Stage 1: 71 real runtimes plus the owner's fleet with titles verbatim.
    • The corpus corrected the first draft. It holds no permission prompt or failed turn, but it does hold two prompts the app could not deliver, and exactly those two land in Needs you.
    • The 15 recorded queues, all behind a working agent, stay Working. Compaction banners are not attention.
  • agentActivityClose.renderer.test.tsx drives the real close executor.
  • AgentActivityView.renderer.test.tsx covers section order, one read per kind, and the keyboard grammar (typing a filter never selects or closes; a selection survives filtering).
  • I broke three rules on purpose (queue-behind-working, idle ordering, the filter keyboard guard). The tests caught all three.
  • tsc -b is clean except src/main/browserPocket/*. Those errors come from a local node_modules missing playwright-core, which origin/main added; CI installs it.
  • Full suite: 6419 passed. The only failure is the same browser-pocket Playwright test, for the same reason. check:keybindings and test:contract pass.

Review round (one round: Codex ×2, Pi, Grok)

None of the four reviewers found a way to close an agent without confirmation, and none found a crash. Fixed:

  • a failed compaction now needs you (the unread rule leaves compaction out);
  • a failed start that then exited shows its error, not "Exited";
  • a usage limit shows even while the provider still looks busy (Grok);
  • Enter/Space on the "Close N selected" button do what the button says;
  • Tab from the filter reaches the list with the query kept;
  • scrolling no longer jumps back on every runtime update, and the first highlight is pinned to its agent;
  • Goal text is searchable even when a title wins the name;
  • extension views are left out;
  • "may not have sent" is kept separate from "did not send";
  • the sections are ARIA groups.

Each of these has a test.

Limits and follow-ups

🤖 Generated with Claude Code

Juliusolsson05 and others added 4 commits September 20, 2026 16:00
…l fleet

T14. The evidence is the owner's live workspace, read through Agent
Code's own control MCP rather than imagined: 33 sessions, 28 of them in
one project, 3 with an explicit title, 0 with a spoken name, 11 on a
lane.

That says what is wrong with the current modal, in order:
- it has no attention signal at all, so it cannot answer the only
  question worth asking of 33 agents — which one needs me;
- with 3 titles across 33 sessions, rows fall back to the first user
  prompt, so the fleet reads as paragraphs, and three rows read
  "agent-code";
- it lists panes, so the 22 parked sessions most likely to be closable
  are the hardest to reach;
- its vocabulary (tab, Dispatch order, grid, tiled) was retired by
  #1013;
- no search, and actions hidden until hover while the real job is bulk.

Six stages, with the reconciliation isolated in one row model that the
view and the command are the only importers of. Six unknowns are listed
rather than assumed, two of them owner calls: whether this is a modal or
a takeover, and whether it replaces Close Old Agents and Close Idle
Orchestration Agents.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Stage 1 of docs/decomposition/agent-activity-redesign.md was half done: the
control-MCP recordings carry identity and placement for a whole window at one
instant, and nothing carried the RUNTIME fields a row's attention bucket is
computed from.

Agent Code already writes exactly that. Every debug bundle contains a
`state-snapshot.json` — the renderer's own SessionRuntime for one real session
at the moment the bundle was taken, written by the app with no interpretation
in between. So this extracts rather than instruments: 71 snapshots, 55 distinct
sessions, three providers, spanning months of real use.

What it contains that a capture taken this afternoon would have missed: 61
running / 9 idle / 1 exited, seven distinct stream phases, three live
conditions, 15 sessions with a queued prompt, and `activityStatus` strings like
"Honking…" that no hand-written fixture would have invented.

Two payload classes are recorded as counts instead of kept: the four screen
mirrors (~10 KB each, four near-copies, the most privacy-loaded field in the
file) and the two unbounded arrays the measurement pointed at —
`subAgents[*].toolCalls` (4.4 MB of 11.6 MB) and `workActivity.timeline` +
`.recentKeys` (3 MB). The row model branches on status, phase, process state,
conditions, queue length and timestamps; it never reads a rendered screen, a
subagent's tool call, or a touched-file timeline. Every structural field around
them is verbatim, and the counts are kept so an agent with 40 running tool
calls cannot look identical to an idle one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tage 1's findings

The first cut of this decomposition said sessionDisplayTitle resolves
'explicit title -> spoken agent name -> first prompt -> cwd'. It does not:
there is no spoken-name term and no first-prompt term, only title, the
terminal's live cwd, the spawn cwd and the raw path.

That is not a footnote. The document's headline finding -- rows are
unidentifiable -- was written believing a better source existed and was
merely being reached for last. With 3 titles and 0 spoken names across 33
sessions there is nothing to fall back to, so Stage 2 cannot fix identity by
reordering a chain; it has to introduce a source. Which one is now an owner
call in the unknowns.

Also recorded, from reading the substrate rather than assuming it: TLDR, Goal
and Goal Loop have no renderer store and are deliberately subscribed only
while a preview is visible, so the fleet-wide batched reader they need is a
stage this document did not have (now 2b); the conditions capability lookup
throws for terminals; limitHit never self-clears; terminalProviderFailure is
inert without meta; Grok can never read failed; a session can hold more than
one lane; and Stage 6's 'computed once' is reachable in the renderer but not
across the MCP surfaces, by #1080's own design.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Juliusolsson05 and others added 3 commits September 24, 2026 16:40
Full screen, keep the two quick close commands, and name untitled rows by
their Goal. The full-screen choice is recorded with its mechanism: a
full-viewport Dialog rather than a fourth MainSurface takeover, because every
hidden-workspace rule names each takeover by hand.

Refs #1170

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The old modal had no attention signal at all. This model sections every
agent in the window, lane or pool, into needs you / working / idle /
exited, reusing the rules the rest of the app already trusts: condition
attention, #1018's provider failure, the limit-idle guard, the goal loop
chip's reading and #915's last-active. A second copy of any of them would
drift from the surface the user compares it with.

Rows are named title, then Goal, then folder, and say which. TLDR, Goal and
Goal Loop are read in one batched call per kind, only while the view is
open. Bulk close goes through closeGrantedSessions with its own kill tag.

Tests replay the recorded corpus from #1105. It disagreed with the first
draft: the corpus has no permission prompt or failed turn, but it does hold
two prompts the app could not deliver, and those are what needs you.

Refs #1170

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Full viewport through the same Radix Dialog every modal uses, rather than a
fourth MainSurface takeover: the primitive already owns input ownership,
the focus trap, Escape and focus restore, and the workspace stays laid out
so no terminal resizes. One keyboard grammar: the list owns Space and
Backspace, so typing a filter can never select or close anything; the
first Esc leaves the filter, the second dismisses.

Close Old Agents and Close Idle Orchestration Agents are unchanged (owner
decision). The old AgentActivityModal is deleted; the closed-view
derivation test now covers the new view.

Refs #1170

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Juliusolsson05 Juliusolsson05 changed the title docs(agent-activity): decompose the redesign against a real 48-agent fleet feat(agent-activity): replace the popup with a full-screen fleet view Sep 25, 2026
Juliusolsson05 and others added 3 commits September 24, 2026 17:09
- A failed compaction now needs you. The unread rule leaves compaction out,
  and the policy labels only its failed phase, so the label is read on its
  own instead of only inside the attention branch.
- A start that failed and then exited shows the error, not "Exited":
  process failures are checked before the exit, as the orchestration
  lifecycle already orders them.
- A focused footer button keeps its native Enter/Space.
- Tab from the filter reaches the list with the query kept, so filtered rows
  can be selected from the keyboard.
- Scrolling follows the highlighted session id, not row objects rebuilt on
  every runtime update, and the fallback highlight is pinned to its session.
- A Goal is searchable when a title wins the name.

Refs #1170

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Extension views are left out again: no process, nothing to report. The
  index lists them as pool citizens, which the first cut inherited.
- An unconfirmed prompt says it "may not have sent", distinct from one the
  app knows was not delivered: re-send versus go and look.
- Sections are labelled groups inside the listbox rather than region
  landmarks, so screen readers keep the options attached to the list.

Refs #1170

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s busy

Grok's review: Claude can sit on its usage-limit banner with the process
alive, so processActive and the derived status say running, and the
"not working" guard hid the one case the rule exists for. isLimitIdle
already drops a hit older than the current turn. Extension views are also
no longer asked about goal loops.

Refs #1170

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

feat(agent-activity): replace the Agent Activity popup with a full-screen fleet view

1 participant