feat: at-a-glance agent status (blocked / working / done) - #366
Conversation
Classify each agent pane from its live terminal screen, OSC title, and PTY activity into blocked / working / idle, debounced, and surface it on the sidebar session rows, the pane tabs, and the Pane Chat entry. Roll up per session (blocked > working > idle); a pane that finishes unseen reads as done. - pure manifest detection engine + Claude/Codex/generic rule sets (unit tested) - OSC title/progress capture in TerminalStateEmulator - AgentStatusMonitor: screen + PTY-activity arbitration with debounce, wired into terminalPanelManager on a 500ms poll, emits panel:agentStatus - panelStore keeps per-panel status keyed by sessionId; one shared rollup used by the store getter and the useAgentStatus hook - session-row accent bar (amber sweep), dot+spinner on tabs and Pane Chat - README Status Cues row updated Closes dcouple#365
|
Follow-up (tested locally on top of this branch): unifying the two status systems Why: this PR added a second status system next to the existing activity dots, and the two could disagree on screen. The old system keeps a pane "active" until 30 seconds of silence; the new one settles in about a second — so the same session could shimmer "in progress" from one system while showing a green "ready" dot from the other. And the new detection only covered Claude and Codex: a pane running any other CLI (opencode, aider, or a plain shell) got no status dot at all, whereas the old dots covered everything. What the follow-up changes:
The old activity tracking itself stays (it still feeds "unseen completion" state); only its UI role is retired. |
- every terminal panel now runs status detection: bespoke manifests for Claude/Codex, a generic tier (PTY activity + common permission-prompt shapes) for other CLI agents and plain shells - retire the legacy activity system from all UI: session rows, pane tabs, project dots, and the label shimmer read the agent-status model only - rewire away-notifications to the same model: immediate "needs your input" on blocked, debounced "finished" on working -> idle - working state uses the theme's status-info token across dot, spinner, and accent bar; label shimmer pacing locked to the sweep cycle (2.8s) - status indicators render in a fixed footprint (no layout shift between dot and spinner); tab titles truncate so squeezed tabs don't crush them
|
Pushed the follow-up described above (fafe19c) — the two status systems are now one.
Tested locally: typecheck/lint clean, all agent-status unit tests pass (incl. updated manifest-resolution expectations). |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fafe19cdd7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A command whose script path or cwd merely contains the word (e.g. bash /tmp/claude-501/demo.sh) was classified as that agent, selecting the wrong detection manifest and wrongly injecting Claude resume flags. Match the agent name as a standalone token instead.
|
Demo of the unified status system in action (recorded on this branch at aae7b46): What you're watching: a pane running a plain bash script — deliberately not Claude or Codex, so this is the generic tier. It flows through all three states: blue spinner + accent-bar sweep + label shimmer while output streams (working), settling to green when it goes quiet (idle), then flipping red the moment a Recording it also caught a real bug, fixed in aae7b46: the agent sniffer matched (GIF is hosted on the |
…te done, project-level done, a11y role - generic manifest: blocked now requires a live prompt on the last non-empty line, so answered/scrolled-past [y/n] text in scrollback no longer sticks a pane as blocked - mark unseen completion from the unified working -> idle transition, so a background agent finishing reads done (blue) immediately instead of after the legacy 30s activity flip - project dot rolls up display statuses (blocked > working > done > idle), keeping all-done-unseen visible as blue at the project level - TerminalLoadingSkeleton: role=status so its aria-label is permitted (fixes the axe aria-prohibited-attr failure in CI)
# Conflicts: # frontend/src/components/panels/TerminalPanel.tsx
|
Wrapping this one up — merging now. What ended up shipping here, start to finish:
Demo GIF is a few comments up. Thanks @frarredondo for the original architecture — the manifest engine + timer-free monitor made every follow-up here easy to build and test. 🚢 |
|
To use Codex here, create an environment for this repo. |

Summary
What. Every agent pane now reports a live status (blocked, working, or done) on the sidebar session rows, the pane tabs, and the Pane Chat entry, so a screen full of parallel agents reads in one glance instead of opening each one.
Why. The old indicator was binary (active/idle), so "waiting on your approval", "still thinking", and "finished a while ago" all looked the same. The one thing you actually want to spot, which pane is blocked on you, was invisible. See #365.
How. Each agent pane is classified continuously from three signals its terminal already produces: the live screen snapshot, the OSC window title, and whether PTY bytes are still flowing. A small state machine arbitrates those into blocked / working / idle, with debounce so spinner gaps and startup don't cause flicker. Panel states roll up per session (blocked wins, then working, then idle) and render with distinct colors. A session that finishes while you are looking elsewhere shows as "done" until you open it. Plain shell panes get no badge.
Built test first, so the detection rules, the arbitration, and the rollup all have unit tests.
What changed
Backend (main):
TerminalStateEmulatornow also captures the OSC window title and progress, which is where the CLIs encode spinner and "action required" state.AgentStatusMonitorcombines the screen classification with recent PTY activity (the working authority) and debounces working to idle plus a short startup grace. It runs on a 500ms poll insideterminalPanelManagerand emits a newpanel:agentStatusIPC event. Only agent panes register; plain shells never do.Frontend:
panelStoretracks each panel's status along with the sessionId from the event, and one shared rollup helper is used by both the store getter and theuseAgentStatushook, so background sessions and Pane Chat light up without their panels being loaded.status-workingsweep animation.Docs:
Visual overview
No asset yet. State to visual mapping:
Verification
Run on current head of the branch:
pnpm typecheckpnpm lintno-console/require-imports)mainagentStatus + emulator)pnpm --filter frontend testpnpm --filter main test(full)The 4 failures are the
better-sqlite3-multiple-ciphersnative-arch DB tests that fail under the ambient Node here, an environment artifact unrelated to this change (same set noted in #363).Manual tests
Non-goals
Residual risks
Closes #365