Skip to content

feat: at-a-glance agent status (blocked / working / done) - #366

Merged
parsakhaz merged 6 commits into
dcouple:mainfrom
frarredondo:feat/agent-status-cues
Aug 6, 2026
Merged

feat: at-a-glance agent status (blocked / working / done)#366
parsakhaz merged 6 commits into
dcouple:mainfrom
frarredondo:feat/agent-status-cues

Conversation

@frarredondo

Copy link
Copy Markdown
Contributor

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):

  • Detection engine. A pure, dependency-free rule engine plus per-agent rule sets for Claude, Codex, and a generic fallback. It turns a screen snapshot and OSC title into blocked / working / idle. No side effects, fully unit tested.
  • Terminal screen model. TerminalStateEmulator now also captures the OSC window title and progress, which is where the CLIs encode spinner and "action required" state.
  • Continuous monitor. A new AgentStatusMonitor combines 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 inside terminalPanelManager and emits a new panel:agentStatus IPC event. Only agent panes register; plain shells never do.

Frontend:

  • State. panelStore tracks each panel's status along with the sessionId from the event, and one shared rollup helper is used by both the store getter and the useAgentStatus hook, so background sessions and Pane Chat light up without their panels being loaded.
  • UI. A left accent bar on the session rows (amber with an up and down loading sweep for working), a dot with an amber spinner for working on the pane tabs and the Pane Chat entry, and a status badge on the narrow sidebar rail. New Tailwind status-working sweep animation.

Docs:

  • The README "Status Cues" row was rewritten to describe the three states.

Visual overview

No asset yet. State to visual mapping:

Status Meaning Session row bar Tab / Pane Chat dot
blocked waiting on your approval red red dot
working actively running amber, up/down sweep amber spinner
done finished while you were away blue blue dot
idle finished and seen green green dot
(no agent) plain shell pane neutral none

Verification

Run on current head of the branch:

Check Result
pnpm typecheck pass, all 4 workspaces
pnpm lint 0 errors (163 main / 165 frontend warnings, all pre-existing no-console / require-imports)
feature unit tests (main agentStatus + emulator) pass, 42 tests
pnpm --filter frontend test pass, 108 tests
pnpm --filter main test (full) 427 pass, 4 fail

The 4 failures are the better-sqlite3-multiple-ciphers native-arch DB tests that fail under the ambient Node here, an environment artifact unrelated to this change (same set noted in #363).

Manual tests

  • Run a Claude pane, trigger a permission prompt, confirm the session row bar turns red.
  • While it generates, confirm the bar sweeps amber and the tab shows an amber spinner.
  • Let it finish in a background session, confirm blue "done", then open it and confirm it goes green.
  • Open Pane Chat, start the orchestrator, confirm the Pane Chat entry spins amber then settles.
  • Confirm a plain (non-agent) terminal pane shows no badge.

Non-goals

  • Persisting status across restarts. It is live only, so a session you never opened this run shows nothing. Possible follow-up.
  • Changing the existing 30s active/idle pipeline. This is a separate, faster layer alongside it.
  • Line by line transcript parsing. Classification is screen, title, and activity based on purpose.

Residual risks

  • The screen classification is a heuristic. A prompt style the rules do not recognize falls back to idle or working instead of blocked. The rules are structured per agent so they are easy to extend.
  • "Done vs idle" reuses the existing "finished while you were elsewhere" flag, which is session scoped.

Closes #365

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
@parsakhaz

Copy link
Copy Markdown
Member

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:

  • Every terminal pane now goes through the new status engine. Claude and Codex keep their tailored detection; everything else gets a generic tier — working/idle from terminal output, plus detection of common "waiting for y/n"-style prompts for blocked. So opencode and friends light up too.
  • The old activity system no longer drives any UI. Session rows, pane tabs, project dots, and the label shimmer all read from the one status model. One source of truth, no contradicting indicators.
  • Notifications come from the same model: an immediate "needs your input" when a pane flips to blocked (new — the old system had no concept of blocked), and a debounced "finished" on working → idle.
  • Tab content is now squeeze-proof: when tabs overflow and shrink, the title truncates instead of crushing the status dot/icon, and the dot reserves the same footprint whether it's a dot or a spinner (no layout shift on status 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
@parsakhaz

Copy link
Copy Markdown
Member

Pushed the follow-up described above (fafe19c) — the two status systems are now one.

  • Every terminal pane runs through the status engine: Claude/Codex keep their tailored detection, everything else (opencode, aider, plain shells) gets the generic tier — working/idle from terminal output plus common permission-prompt detection for blocked.
  • The legacy activity system no longer drives any UI. Session rows, pane tabs, project dots, and the label shimmer all read the one model, so indicators can't contradict each other anymore.
  • Away-notifications key off the same model: immediate "needs your input" when a pane flips to blocked, debounced "finished" on working → idle. What you see in the sidebar is exactly what decides the pings.
  • Visual polish while in there: the working state uses the theme's status-info token (matching the label shimmer, theme-aware) instead of amber, the shimmer's pacing is locked to the accent bar's sweep cycle so they breathe together, status indicators hold a fixed footprint (no more layout shift when a dot becomes a spinner), and squeezed tabs truncate their title instead of crushing the dot/icon.

Tested locally: typecheck/lint clean, all agent-status unit tests pass (incl. updated manifest-resolution expectations).

@parsakhaz

Copy link
Copy Markdown
Member

@codex review this PR with fresh eyes — full pass over the current state of the branch (the unification follow-up in fafe19c included), not just the latest diff.

@parsakhaz

Copy link
Copy Markdown
Member

@claude review this PR with fresh eyes — full pass over the current state of the branch (the unification follow-up in fafe19c included), not just the latest diff.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread main/src/services/agentStatus/manifests.ts Outdated
Comment thread frontend/src/App.tsx
Comment thread frontend/src/components/ProjectSessionList.tsx Outdated
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.
@parsakhaz

Copy link
Copy Markdown
Member

Demo of the unified status system in action (recorded on this branch at aae7b46):

agent status demo

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 Do you want to proceed? [y/n] prompt appears (blocked) — session row, project dot, and pane tab all in lockstep.

Recording it also caught a real bug, fixed in aae7b46: the agent sniffer matched claude/codex as substrings of the whole command, so a script living at a path like /tmp/claude-501/demo.sh was misclassified as a Claude pane — wrong detection manifest and a wrongly injected --session-id resume flag. It now matches the agent name as a standalone token only.

(GIF is hosted on the pr-366-assets prerelease so it doesn't bloat the repo.)

…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
@parsakhaz

Copy link
Copy Markdown
Member

Wrapping this one up — merging now. What ended up shipping here, start to finish:

  • The feature: at-a-glance agent status (blocked / working / done / idle) detected from the live terminal screen, with bespoke rule sets for Claude and Codex — surfaced on session rows, pane tabs, project dots, and Pane Chat.
  • The unification follow-up: every terminal pane (any CLI agent, plain shells included) now runs through the one status engine via a generic tier, the legacy activity system no longer drives any UI, and away-notifications key off the same model — immediate "needs your input" on blocked, debounced "finished" on working → idle.
  • Review hardening (thanks @chatgpt-codex-connector 🤖): blocked detection anchors to a live prompt so stale [y/n] scrollback can't stick, background completions read done/blue immediately, and the project dot honors done.
  • Bonus fixes found along the way: agent commands are matched as tokens (a script path containing "claude" no longer misclassifies a pane), and the status indicators keep a fixed footprint with theme-aware colors and phase-locked animations.

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. 🚢

@parsakhaz
parsakhaz merged commit 2cd69bc into dcouple:main Aug 6, 2026
18 checks passed
@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@frarredondo
frarredondo deleted the feat/agent-status-cues branch August 7, 2026 01:42
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: at-a-glance agent status (blocked / working / done)

2 participants