From 43756a7dbe9413bb6df1051a447ff00b50e67592 Mon Sep 17 00:00:00 2001
From: Francisco Arredondo <95440147+frarredondo@users.noreply.github.com>
Date: Thu, 23 Jul 2026 21:17:02 -0700
Subject: [PATCH 1/4] feat: at-a-glance agent status (blocked / working / done)
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 #365
---
README.md | 2 +-
frontend/src/App.tsx | 8 +
.../src/components/ProjectSessionList.tsx | 15 +-
.../src/components/SessionStatusBadge.tsx | 31 ++
frontend/src/components/Sidebar.tsx | 24 +-
.../components/panels/PanelTabStatusDot.tsx | 33 ++
.../src/components/panels/PanelTabStrip.tsx | 9 +-
frontend/src/components/ui/AgentStatusDot.tsx | 61 ++++
.../src/components/ui/StatusAccentBar.tsx | 50 +++
.../components/ui/agentStatusVisual.test.ts | 15 +
.../src/components/ui/agentStatusVisual.ts | 31 ++
frontend/src/hooks/useAgentStatus.ts | 25 ++
frontend/src/stores/panelStore.test.ts | 50 +++
frontend/src/stores/panelStore.ts | 26 ++
frontend/src/types/electron.d.ts | 2 +
frontend/src/types/panelStore.ts | 7 +
frontend/src/utils/agentStatus.test.ts | 52 +++
frontend/src/utils/agentStatus.ts | 48 +++
frontend/tailwind.config.js | 6 +
main/src/preload.ts | 6 +
.../agentStatus/agentStatusMonitor.test.ts | 80 +++++
.../agentStatus/agentStatusMonitor.ts | 129 ++++++++
.../agentStatus/agentStatusPipeline.test.ts | 46 +++
.../agentStatus/manifestEngine.test.ts | 141 +++++++++
.../services/agentStatus/manifestEngine.ts | 253 +++++++++++++++
.../services/agentStatus/manifests.test.ts | 117 +++++++
main/src/services/agentStatus/manifests.ts | 295 ++++++++++++++++++
main/src/services/terminalPanelManager.ts | 99 ++++++
.../services/terminalStateEmulator.test.ts | 25 ++
main/src/services/terminalStateEmulator.ts | 22 ++
shared/types/agentStatus.ts | 52 +++
31 files changed, 1734 insertions(+), 26 deletions(-)
create mode 100644 frontend/src/components/SessionStatusBadge.tsx
create mode 100644 frontend/src/components/panels/PanelTabStatusDot.tsx
create mode 100644 frontend/src/components/ui/AgentStatusDot.tsx
create mode 100644 frontend/src/components/ui/StatusAccentBar.tsx
create mode 100644 frontend/src/components/ui/agentStatusVisual.test.ts
create mode 100644 frontend/src/components/ui/agentStatusVisual.ts
create mode 100644 frontend/src/hooks/useAgentStatus.ts
create mode 100644 frontend/src/stores/panelStore.test.ts
create mode 100644 frontend/src/utils/agentStatus.test.ts
create mode 100644 frontend/src/utils/agentStatus.ts
create mode 100644 main/src/services/agentStatus/agentStatusMonitor.test.ts
create mode 100644 main/src/services/agentStatus/agentStatusMonitor.ts
create mode 100644 main/src/services/agentStatus/agentStatusPipeline.test.ts
create mode 100644 main/src/services/agentStatus/manifestEngine.test.ts
create mode 100644 main/src/services/agentStatus/manifestEngine.ts
create mode 100644 main/src/services/agentStatus/manifests.test.ts
create mode 100644 main/src/services/agentStatus/manifests.ts
create mode 100644 shared/types/agentStatus.ts
diff --git a/README.md b/README.md
index 2c414b19..2a26189e 100644
--- a/README.md
+++ b/README.md
@@ -112,7 +112,7 @@ Each of these is a small thing. Together they compound fast.
| **Terminal Popover** | Highlight any text in a terminal and an intelligent popover offers the right action: copy, open in browser, or show in explorer. |
|
| **Built-in Browser** | Preview any URL in a tab next to your terminals so every pane can see its own running dev server without alt-tabbing. |
|
| **Resource Manager** | Built-in CPU and memory monitor broken down per pane and per process, so you can catch a runaway agent before it eats your laptop. |
|
-| **Status Cues** | Project dots show where work is happening, pane names breathe while active, and a dashed underline marks panes that finished while you were looking elsewhere. |
|
+| **Status Cues** | Every AI pane reports its state at a glance — a red dot when an agent is blocked waiting on your approval, an amber pulse while it works, and a "done" cue when it finishes while you're looking elsewhere. The same rollup colors the project dots and pane tabs, so a whole screen of parallel agents reads in one glance. |
|
| **Jump + Refresh** | Jump to top, jump to bottom, or hard-refresh any terminal from the toolbar to unstick a frozen state in one click. |
|
| **Auto Secrets Copy** | Every pane automatically mirrors `.env` files and secrets from your root project so your worktree is runnable the moment it's created. | |
| **Isolated Ports** | Each pane runs on its own port range automatically, so you can spin up five dev servers in parallel without a single conflict. | |
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 28891b70..52bc6e0f 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -174,6 +174,14 @@ function App() {
return () => unsubscribe?.();
}, []);
+ // Global agent status listener (blocked / working / done) for AI/CLI panels.
+ useEffect(() => {
+ const unsubscribe = window.electronAPI?.events?.onPanelAgentStatus?.((data) => {
+ usePanelStore.getState().setAgentStatus(data.panelId, data.sessionId, data.state);
+ });
+ return () => unsubscribe?.();
+ }, []);
+
useEffect(() => {
const clearViewedCompletedActivity = (event: Event) => {
const sessionId = (event as CustomEvent<{ sessionId?: string }>).detail?.sessionId;
diff --git a/frontend/src/components/ProjectSessionList.tsx b/frontend/src/components/ProjectSessionList.tsx
index de3cb2d8..2a97373d 100644
--- a/frontend/src/components/ProjectSessionList.tsx
+++ b/frontend/src/components/ProjectSessionList.tsx
@@ -8,7 +8,11 @@ import { CreateSessionDialog } from './CreateSessionDialog';
import { AddProjectDialog } from './AddProjectDialog';
import { Dropdown } from './ui/Dropdown';
import { Tooltip } from './ui/Tooltip';
+import { StatusAccentBar } from './ui/StatusAccentBar';
+import { AgentStatusDot } from './ui/AgentStatusDot';
import type { DropdownItem } from './ui/Dropdown';
+import { useSessionAgentDisplayStatus } from '../hooks/useAgentStatus';
+import { PANE_CHAT_SESSION_ID } from '../../../shared/types/paneChat';
import { API } from '../utils/api';
import { cn } from '../utils/cn';
import type { Session, GitStatus } from '../types/session';
@@ -69,6 +73,7 @@ export function ProjectSessionList({
const activeView = useNavigationStore(s => s.activeView);
const navigateToSessions = useNavigationStore(s => s.navigateToSessions);
const navigateToPaneChat = useNavigationStore(s => s.navigateToPaneChat);
+ const paneChatStatus = useSessionAgentDisplayStatus(PANE_CHAT_SESSION_ID);
const navigateToProject = useNavigationStore(s => s.navigateToProject);
const setSidebarNavigationScope = useNavigationStore(s => s.setSidebarNavigationScope);
// Expansion state lives in the navigation store so the always-mounted
@@ -322,6 +327,7 @@ export function ProjectSessionList({
>