Skip to content

fix(sidebar): a title that only names an agent reads "No status reported", not "Idle" - #24083

Open
brennanb2025 wants to merge 2 commits into
mainfrom
brennanb2025/b9b-title-no-status-reported
Open

brennanb2025 wants to merge 2 commits into
mainfrom
brennanb2025/b9b-title-no-status-reported

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 7 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​326 $\color{#cf222e}{\Huge{\mathbf{−}}}$​15 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​311
Prod 18 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​123 $\color{#cf222e}{\Huge{\mathbf{−}}}$​40 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​83

ELI5

When a terminal's title is just an agent's name (grok, agy, OpenCode, aider, a Codex pane with hooks off, …), Orca's sidebar showed that agent as Idle: a grey dot and the word "Idle", even while the agent was busy working. A name only tells Orca which agent is in the pane, never what it is doing, so "Idle" was a guess presented as a fact.

After this PR the same row says "No status reported" next to a muted dashed ring. The row stays; it just stops claiming the agent is resting.

What you will notice: agents without working hooks (hooks off, missing or silent: Grok, aider, Copilot, OpenCode, Antigravity, Codex with hooks off, and the rest in the table below) that sit at rest with a plain title go from "Idle" to "No status reported". That is the honest answer: the same title shows while they work, so the old "Idle" carried no information. While their title shows a spinner or another marker, they still read "Running" / "Needs input" / "Idle" exactly as before.

This PR is step 1 of a plan to stop Orca from reading a bare agent name as "Idle" anywhere. It changes only what the desktop sidebar and dashboard rows display; nothing Orca does on a title changes yet (see "Surfaces that still act on the bare name").

What Changed

The problem, as the user sees it. Start Antigravity in Orca and give it work: the sidebar row reads "Idle" the whole time it works (STA-7143). The same happens to any agent whose title carries only its name and whose hooks are off, missing, or silent. #23948 (merged 2026-09-30 as fceca5cecec, queued for the next release by STA-8993) made this more common: to keep a hooks-off Codex row from vanishing when Codex retitles its tab to the project name, it defaulted any plain title on a process-identified pane to Idle (const status = titleStatus ?? 'idle'), so a busy agent titled with its folder name now also reads "Idle".

Why it happened. One function, detectAgentStatusFromTitle, answers two questions: is there an agent in this pane? and what is it doing? For a title that holds nothing but an agent's name it has to answer the first with "yes" (a non-null value), so it answered the second with a guess: return 'idle'. null could not be the honest answer, because null already means "no agent here" and "the agent exited" to its readers.

Before → after (desktop sidebar and dashboard rows):

  • Bare-name title with no hook row (grok, agy, OpenCode, aider, Codex, …): "Idle" + grey dot → "No status reported" + muted dashed ring.
  • Plain title (e.g. the project name) on a pane whose foreground process Orca reads as an agent (fix(sidebar): an agent's row stays while it runs, whatever its tab title #23948's case): "Idle" → "No status reported".
  • Cursor's bare native cursor agent title ([Bug]: Cursor Agent session are not shown in the left sidebar #10258 kept this row from vanishing by calling it Idle): "Idle" → "No status reported". The row still appears.
  • The worktree card's summary uses the same phrase: "3 agents: 1 not reporting, 1 no status reported, 1 idle. Grok no status reported; Claude idle; Codex not reporting".
  • Unchanged: every title with a marker (spinner → Running, ✳/◇/rest keywords → Idle, permission words → Needs input), hook-reported rows, the stale-hook "No update in 34m" amber ring, the claude agents management screen (still Idle), and the order rows sort in (the new state sorts with idle and is never an attention item).

The mechanism.

  • src/shared/agent-title-core.ts: new type AgentTitleActivity = AgentStatus | 'unreported'. unreported means "an agent's name is in the title and nothing in it says what the agent is doing". null keeps its single meaning, "no agent in this title".
  • src/shared/agent-title-status.ts: the classifier's last line (a name and nothing else) now returns 'unreported', exposed as readAgentTitleActivity. detectAgentStatusFromTitle becomes a temporary wrapper that maps 'unreported' back to 'idle', so every reader that settles, delivers, notifies or cleans up on a title is byte-for-byte unchanged in this PR. classifyTitleActivity (the renderer's pass-through, about 22 call sites in 15 files before this PR, including cleanup and draft paste) keeps its contract too.
  • Only two readers move to the new answer: the sidebar row builder (worktree-title-derived-agent-rows.ts, including fix(sidebar): an agent's row stays while it runs, whatever its tab title #23948's plain-title default and the Cursor literal) and smart attention's title panes (smart-attention.ts, where unreported ranks with idle, never as attention).
  • Row and dot vocabulary: AgentRowState and AgentDotState gain unreported; the dot renders the existing dashed ring in text-muted-foreground (the unverifiable ring stays amber); the one label, "No status reported", is localized (auto.lib.agentRowState.noStatusReported, added to en, es, fr, ja, ko and zh) and used for the row text, the dot's tooltip and its aria label. The compiler found every exhaustive switch and Record over these types (activity pane grouping and labels, worktree summary order, subagent child rows).
  • Dashboard card: unreported stays renderer-side and publishes idle to the dashboard card and pop-out, exactly as unverifiable already does, because main validates card states against a fixed list and would drop an unknown value (the card would disappear).
  • entry.state for an unreported row is 'working', the same synthetic value today's title-derived Idle rows carry (entryState = rowState === 'waiting' ? 'waiting' : 'working'; the row's observation.origin = 'title' tag marks it). Readers of the row use row.state (unreported) for the dot, text, summary and bucket; the readers of that synthetic entry.state on a dashboard row are the verdict mark (agentVerdictDisplayMark, which only acts on done/failed/interrupted) and the finished-timestamp helper (acts only on done), so neither changes. The activity pane builds from hook entries, not title rows, so it never sees this row.
  • unreported is never persisted and never enters the host's agent-status store; it is re-derived from the current title on every render (cached per title string), so no stored flag can go stale and there is nothing to clean up.
  • A one-way guard test (src/shared/agent-title-idle-wrapper-ratchet.test.ts, called a "ratchet" in the test name) lists the 35 production files that still read the name-as-idle guess (detectAgentStatusFromTitle / classifyTitleActivity). It fails if a new file starts reading it, and fails if a listed file stops reading it until the list is shortened, so the list can only shrink. The wrapper's docstring names its removal (step 4).

Surfaces that still act on the bare name after this PR (for the same name-only pane):

  • Phone: still a green Done check (host maps any title status other than working/permission to done). Removed by step 5.
  • Pop-out dashboard card and dashboard bucket counts: still idle (the fixed card vocabulary above). Temporary; needs a card-validator extension that old pop-out windows tolerate, which no planned step includes yet.
  • orca terminal agent status over the wire: still status: 'idle'. Removed by step 4 ({ isRunningAgent: true, status: null }, a shape old clients already handle).
  • Worker-start readiness and terminal wait --for tui-idle: still settle on the bare name. Removed by step 2+3.
  • Orchestration mail push: still typed into the agent on a bare-name idle edge. Removed by step 2+3.
  • "Agent finished" notifications and the title tracker's became-idle edge: still fire on working → bare name. Removed by step 2+3.
  • Workspace cleanup: still treats a busy agent with a bare-name title as idle, so it does not show the "running terminal process" label. Removed by step 2+3.
  • Draft-paste ready wait and the Claude prompt-cache timer seeding: still accept a bare name. Removed by step 2+3.
  • Interrupt cursor reset (a cosmetic escape sequence): still fires. Removed by step 4.

A bare gemini pane still reads "Idle". Title normalization rewrites a bare gemini title to Gemini's own rest title ◇ Gemini CLI before it is stored, and ◇ is rest evidence. Verified live in the QA app below (the pane title is stored as ◇ Gemini CLI and the summary reads "Gemini idle"). The normalization fix belongs with the settle change (step 2+3), because the host also stores that rewritten title and settles readiness on it.

Per-agent table (all 43 agents). "Bare-name title row" is a pane whose title is only the agent's process name, with no hook row for that pane (hooks off, not installed, or silent). "Process-read row" is #23948's row for a pane whose foreground process Orca reads as that agent while the title carries no marker (local and daemon panes; WSL and tmux panes have no process read). Hook-reported rows, and titles with a marker, are unchanged for every agent.

Agent (process name) Hooks Bare-name title row: main → this PR Process-read row, plain title: main → this PR
claude (claude) yes Idle → No status reported (✳ … still Idle) Idle → No status reported
claude-agent-teams (claude) no Idle → No status reported (claude agents screen still Idle) Idle → No status reported; the process claude is read as Claude Code, so this row is labelled Claude Code
openclaude (openclaude) yes Idle → No status reported Idle → No status reported
codex, hooks off (codex) off Idle → No status reported Idle → No status reported (before #23948: no row at all). "Running" while its spinner shows
codex, hooks on (codex) yes covered by its hook row; synthetic Codex ready title still Idle covered by its hook row
devin (devin) yes Idle → No status reported Idle → No status reported
hermes (hermes) yes Idle → No status reported Idle → No status reported
opencode (opencode) status plugin Idle → No status reported (boot title OpenCode; OC | … still Idle) Idle → No status reported
mimo-code (mimo) no Idle → No status reported Idle → No status reported
antigravity (agy) yes (delivery often missing) Idle → No status reported Idle → No status reported
aider (aider) no Idle → No status reported Idle → No status reported
copilot (copilot) yes Idle → No status reported Idle → No status reported
grok (grok) yes Idle → No status reported Idle → No status reported
gemini (gemini) yes stays Idle (rewritten to ◇ Gemini CLI, see above) Idle → No status reported
opencode2, openclaw no no row from the bare name (the classifier now reads the name as unreported instead of idle, but the title yields no agent label, so no row; unchanged) Idle → No status reported
pi, omp no Idle, unchanged (a bare pi/omp is Pi's own rest-shaped title, which is evidence) Idle → No status reported
droid (droid) yes no row, unchanged (Droid's bare name already returns "no agent"; step 4 gives it a No status reported row) Idle → No status reported
cursor (cursor-agent) yes native cursor agent title: Idle → No status reported; a bare Cursor title yields no agent label, so no row (unchanged) Idle → No status reported
codebuddy, autohand, ante, trae, prime-agent, qoder, goose, amp, kilo, kiro, crush, aug, cline, freebuff, codebuff, command-code, continue, kimi, mistral-vibe, qwen-code, rovo, muse, dsh, zcode (24) mixed no row from the bare name (the title yields no agent label for them, so no row; unchanged. zcode is a name the classifier recognises, so it now reads unreported instead of idle, but it still yields no label and no row) Idle → No status reported

Counts: 12 + gemini + 2 + 2 + droid + cursor + 24 = 43 agents (codex has two rows, one per hook mode).

Why

Why a third answer instead of another per-agent exception. Orca already works around this guess in five separate places, each for one agent or one reader: the Droid escape hatch in the classifier, Cursor's literal title that the host records as no status (orca-runtime-apply-tracked-pty-title.ts, #10258) and the sidebar re-labels as Idle, the per-agent corroboration list in tui-idle-evidence.ts, the explicit-idle filter in terminal-wait-detection.ts, and its copy in agent-send-title-status.ts. Every new agent inherits the lie by default and needs its own opt-out at each reader. A bare name lies in every way the repo has recorded: mid-turn (Droid, Cursor), at boot before the agent can take input (Grok's recording writes grok at 274 ms and its input box at 607 ms), as the agent's own boot title (OpenCode's recording writes OpenCode three times before its session title), and when a shell auto-title writes the command name. No rule over the string separates those cases, so the uniform rule is: a name says which agent, never what it is doing. unreported is that rule as a value, for every agent at once.

Why not null. null already means "no agent here" (the row would vanish, the #10258/#23767 bug) and "the agent exited" (the title tracker reads working/idle → null as an exit). A non-null member keeps every === null check correct and makes every === 'idle' check safe by default.

Why not reuse unverifiable or "No recent update". That state means "Orca heard this agent and then the stream went quiet"; it carries a clock ("No update in 34m") and amber warning weight. A title-only row was never heard from, and nothing went wrong, so it gets its own phrase and the muted colour. The ring is the existing "incomplete information" glyph so the vocabulary does not grow a new shape.

Why only the display now. Moving the settle paths (readiness, turn end, mail, completion, cleanup) off the guess needs replacement signals first, per agent, from recorded terminal output; doing it without them would make worker start time out or automations wait for hours. That is steps 2+3. This PR removes the visible lie without changing any behaviour, and the one-way guard test keeps the old reader from spreading while the later steps retire it.

Linked Issue

This PR closes no ticket.

The whole plan (a terminal title that holds only an agent's name must never be read as "Idle"):

  1. This PR, step 1: display. The sidebar and dashboard rows say "No status reported".
  2. Steps 2+3 (one PR): readiness and turn end come from per-agent data rows in the shared agent table, each citing a recording; settle, mail, completion and cleanup stop acting on a bare name.
  3. Step 4: delete the guess (the temporary wrapper and the Droid hatch), fold in Cursor's host-side "no status" path, and return status: null on the wire.
  4. Step 5: the phone shows "No status reported" instead of a green Done check.
  5. Step 6: Antigravity shows "Running" from its own screen.
  6. Step 7: per-agent data rows from new recordings restore "Idle" and turn end where a recording proves an agent's title marks rest (Grok first).

Visual Proof

Same QA app, same three panes, before (main 7afa4ee3dc6) and after (this branch). Pane 1's title is grok (the hooks-off, name-only case). Pane 2's title is ✳ Claude Code (a real rest title, the Idle dot). Pane 3 has a hook row last heard 34 minutes ago (the amber "No update in 34m" ring, truncated to "No u…" by the sidebar width).

Light, before (main): Grok reads "Idle" with the same grey dot as Claude's real rest title.

Light theme, main: Grok row reads Idle with a grey dot

Light, after: Grok reads "No status reported" with a muted dashed ring; Idle and the amber ring are unchanged.

Light theme, this PR: Grok row reads No status reported with a muted dashed ring

Dark, before (main):

Dark theme, main: Grok row reads Idle

Dark, after:

Dark theme, this PR: Grok row reads No status reported

Close-ups of the workspace card (supplemental; same captures):

Before (main) After (this PR)
Light Light card, main Light card, this PR
Dark Dark card, main Dark card, this PR

How these were made: an isolated dev instance (its own profile and HOME, background launch with ORCA_BACKGROUND_LAUNCH=1, no window shown or focused), driven over the Chrome DevTools Protocol with CDP screenshots of the hidden renderer. No agent CLI was run: each pane's title was set by a plain shell loop, printf '\033]0;grok\007', so this reproduces the title mechanism, not a live Grok or Antigravity session. The stale hook row was seeded into the renderer store. "Before" is the same running app after checking out main's versions of the changed source files (the dev server reloaded them), so everything else on screen is identical. The DOM confirms the glyph: lucide-circle-dashed text-muted-foreground, computed colour rgb(161, 161, 161) in dark, aria-label="No status reported".

Testing

  • I manually tested these changes locally (macOS, in the isolated dev instance described above; light and dark)
  • Automated tests added/updated, explained below

New and changed tests. Every test that pinned a bare name as Idle was rewritten deliberately, not regenerated. Provenance is stated per test.

  • worktree-title-derived-agent-rows.test.ts: OpenCode's boot title read from committed recording bytes (src/main/daemon/__fixtures__/pty-transcripts/opencode.txt, first OSC title OpenCode, opencode 1.18.32) → unreported, "No status reported"; Grok's boot title from committed recording bytes (src/shared/__fixtures__/grok-startup-pty-trace.ts, grok at 274 ms, grok 1.0.0) → same; hand-built agy → same, and the row still exists; control, hand-built agy ready → Idle (passes on main too; shows the evidence path is untouched). Existing cases rewritten from Idle to unreported: Antigravity, Cursor's literal, the Codex retitle and process-read cases (fix(sidebar): an agent's row stays while it runs, whatever its tab title #23948), and four split-pane attribution cases whose "finished" pane was a bare name (all hand-built titles).
  • worktree-card-unreported-agent-row.test.ts (hand-built titles): the rendered compact row shows "No status reported", the dashed ring, text-muted-foreground, no amber, no "Idle"; the worktree summary reads "1 no status reported, 1 idle"; the dashboard card projection publishes idle, identical to an idle row.
  • AgentStateDot.test.ts: unreported renders the muted dashed ring with the right aria label; added to the exhaustive state list (a compile-time guard).
  • src/shared/agent-title-activity.test.ts (hand-built): bare names → unreported; marker titles unchanged; null titles unchanged (including Droid and Cursor Agent); the wrapper still returns idle for bare names.
  • smart-attention-bare-agent-name.test.ts (hand-built): regression guard, labelled as such (it passes on main, where a bare name read idle, which ranked the same): a bare-name pane ranks with idle; positive control ⠋ agy reaches the working class.
  • agent-title-idle-wrapper-ratchet.test.ts: the one-way guard on the files that still read the old guess (fails on a new reader and on a stale entry).
  • terminal-title-classification-corpus.test.ts and the other existing classifier suites pass unchanged, which is the evidence that detectAgentStatusFromTitle's answers did not change.

Ablation at the final head (revert one producer, run the six named test files, restore). Each reddens for its stated reason:

  • Classifier's bare-name line back to 'idle': 18 fail (both recording-backed cases, agy, the rendered-row and summary tests, the Antigravity/Codex/split-pane cases, six classifier cases).
  • fix(sidebar): an agent's row stays while it runs, whatever its tab title #23948's default back to ?? 'idle': 4 fail (the process-read row cases).
  • Cursor literal back to 'idle': 1 fails (the Cursor row case).
  • Builder reads the old guess: 13 fail, including the one-way guard's "admits no new reader".
  • Dot's unreported branch removed: 2 fail (dot test, rendered row).
  • Dashboard card publishes unreported: 1 fails.
  • Wrapper passes unreported through: 6 fail (the wrapper-contract cases).

Checks run: tc-gate pnpm tc:web and tc-gate pnpm tc:node pass; pnpm run check:code-quality:changed passes (0 findings, including the design-system gate); full oxlint on every touched file passes (no max-lines disables); check:react-doctor:changed passes; verify:localization-catalogs, -catalog, -extraction, -coverage and -runtime-catalog pass. At the final head, all 82 test files that import the changed modules pass (1,282 tests). An earlier, larger parallel run hit one unrelated 30 s timeout in WorktreeCard.compact-hover.test.tsx, which passes alone and in the final run.

Platforms. Tested on macOS only. The change is renderer-side string classification and styling with no platform branch; the colour token is theme-defined for light and dark. SSH relay, daemon and local panes derive these rows the same way from their titles. A paired remote client follows its own build for the sidebar: an older client keeps showing "Idle", which is today's behaviour. Nothing crosses the wire, and nothing is persisted.

Review

Please check:

  • That the rows you rely on at rest ("Idle" for hooks-off agents) turning into "No status reported" is acceptable as the honest answer until step 7 restores "Idle" per agent from recordings.
  • The "Surfaces that still act on the bare name" list: this PR deliberately leaves them; the wrapper plus the one-way guard test are a temporary bridge that steps 2+3 and 4 retire one reader at a time.
  • entry.state = 'working' on the synthetic entry of an unreported title row (same as today's Idle title rows; explained above).

Differences from the reference implementations (the desktop terminal app and the host-daemon app Orca's lifecycle design is compared against). Each row was checked against the reference sources in review:

  • Orca reads agent status from terminal titles at all; the references do not (one disables shell auto-titles, the other keys status on shell-integration marks and agent hooks). Temporary: title activity is a renderer-side producer; it moves into the host's agent-status store after presence moves there. No ticket exists yet for the title half.
  • Orca shows a visible "No status reported" row for an agent it cannot hear. Intended: the references keep an "unknown/pending" state separate from idle but show no row for terminals they cannot hear; Orca hosts 43 agents in plain terminals and hiding them is the vanishing-row bug fix(sidebar): an agent's row stays while it runs, whatever its tab title #23948 and [Bug]: Cursor Agent session are not shown in the left sidebar #10258 fixed. A hidden unknown looks like Idle, which is this bug.
  • unreported authorizes nothing. Matches the desktop reference's handling of its unknown state, which drives no lifecycle transition.
  • The dashboard card publishes idle for this state. Temporary, following the existing unverifiable precedent, until a card-validator extension that old pop-out windows tolerate.
  • The phone still shows Done. Temporary, removed by step 5.

What I verified and what I did not. Verified: the unit tests and ablations above; both typechecks; lint and localization gates; the rendered rows in light and dark in a real (dev) app; that a bare gemini title is stored as ◇ Gemini CLI and reads Idle. Not verified: any live agent session (no agent CLI was run; titles were set with printf); Linux, Windows, SSH relay or WSL panes (no platform-specific code changed); a packaged build; the phone (unchanged by design).

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation. (Not applicable: no agent skill is touched.)

Notes

  • Security: no new input, IPC, file or network path; the change is a new display value derived from a title the renderer already reads.
  • Cross-platform (Linux, Windows, Mac): no platform branch; the same renderer code runs everywhere. Tested on macOS only.
  • Remote SSH: rows derive from pane titles the same way for local, daemon and SSH relay panes; nothing new crosses the wire, and an older paired client keeps showing "Idle".
  • Mobile: unchanged; the phone still shows Done for this pane (step 5 changes it).
  • Backwards compatibility: unreported is never persisted and never published; the dashboard card and pop-out receive idle, which old windows already accept.
  • Performance: one cached classification per title string (the same cache as before) plus one comparison in the temporary wrapper; no new subscriptions or timers.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred): scoped typechecks, lint, localization and the affected tests ran locally; CI covers the full suite and build.

…ted", not "Idle"

A terminal title that holds just an agent's name (grok, agy, OpenCode, aider,
a hooks-off Codex) said which agent was in the pane, and the sidebar read it as
Idle, grey dot and all, even while the agent worked.

The title classifier gains a third answer, `unreported`: an agent's name is in
the title and nothing in it says what the agent is doing. The sidebar row
builder (including #23948's plain-title default) and smart attention read it
through `readAgentTitleActivity`; the row shows "No status reported" beside the
existing dashed ring in the muted colour. `detectAgentStatusFromTitle` and
`classifyTitleActivity` keep their contract (a bare name is still idle), so
readiness, mail, completion and cleanup are unchanged; a ratchet test pins
their importers so the list can only shrink.

The dashboard card and pop-out keep publishing `idle` for the new state, the
way `unverifiable` already does, because main validates card states against a
fixed list.
@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 4 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 11d3f269-0c20-457c-af29-39ee6c016482

📥 Commits

Reviewing files that changed from the base of the PR and between f4092c0 and f8f7303.

📒 Files selected for processing (25)
  • src/renderer/src/components/AgentStateDot.test.ts
  • src/renderer/src/components/AgentStateDot.tsx
  • src/renderer/src/components/activity/activity-thread-grouping.ts
  • src/renderer/src/components/activity/activity-thread-presentation.ts
  • src/renderer/src/components/dashboard/dashboard-row-bucket.ts
  • src/renderer/src/components/sidebar/WorktreeCardAgents.acknowledgement-emphasis.test.tsx
  • src/renderer/src/components/sidebar/smart-attention-bare-agent-name.test.ts
  • src/renderer/src/components/sidebar/smart-attention.ts
  • src/renderer/src/components/sidebar/worktree-card-agent-summary.ts
  • src/renderer/src/components/sidebar/worktree-card-unreported-agent-row.test.ts
  • src/renderer/src/components/sidebar/worktree-subagent-child-rows.ts
  • src/renderer/src/components/sidebar/worktree-title-derived-agent-rows.test.ts
  • src/renderer/src/components/sidebar/worktree-title-derived-agent-rows.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/lib/agent-row-decay-state.ts
  • src/renderer/src/lib/agent-row-dot-state.ts
  • src/shared/agent-title-activity.test.ts
  • src/shared/agent-title-core.ts
  • src/shared/agent-title-idle-wrapper-ratchet.test.ts
  • src/shared/agent-title-status.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

[Medium risk] Adds a new agent status state for title-only panes.

The PR does not appear safe to merge until scrolling works for mobile alternate-screen sessions restored from older snapshots.

Findings

  1. P2 Summary label remains English ▶

Summary

The PR changes name-only agent-title rows from “Idle” to “No status reported” while retaining the old title-status answer for lifecycle readers. Intervening changes also revise Windows Claude hooks, Codex quota fallback, native-chat delivery and paste handling, and terminal mouse replay.

  • Older snapshots can leave mobile alternate-screen scrolling without terminal input.
  • The earlier finding about the untranslated compact summary remains outstanding.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Terminal title] --> B[Title activity]
  B -->|Name only| C[Unreported sidebar row]
  B -->|Legacy wrapper| D[Existing lifecycle readers]
  E[Terminal snapshot] --> F[Mobile encoding scan]
  F -->|Encoding unproven| G[Local scroll rather than TUI input]
Loading

Reviews (2) · Last reviewed commit: "Merge remote-tracking branch 'origin/mai..."

Comment on lines +51 to +52
case 'unreported':
return 'no status reported'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Summary label remains English When the UI is set to Spanish, French, Japanese, Korean, or Chinese, the compact worktree summary and its accessible expansion label still say “no status reported” in English, while the agent row and dot use the translated phrase. Translating this summary label would keep the new state consistent across the same card.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No critical issues — one minor test-coverage gap. Behaviour looks correct and the strangler approach is sound.

Reviewed changes

  • New unreported title activity. computeAgentTitleActivity returns unreported for a title that names an agent and says nothing else; readAgentTitleActivity exposes it, and detectAgentStatusFromTitle becomes a temporary wrapper that maps it back to idle so every settle/deliver/notify/cleanup reader is byte-for-byte unchanged.
  • Only the display readers move. The title-derived row builder (worktree-title-derived-agent-rows.ts, including #23948's ?? 'unreported' default and the Cursor literal) and smart-attention.ts read the new value; unreported ranks with idle and is never attention.
  • Row/dot vocabulary. AgentRowState and AgentDotState gain unreported; the dot renders lucide-circle-dashed in text-muted-foreground, and auto.lib.agentRowState.noStatusReported is added to en/es/fr/ja/ko/zh.
  • Dashboard stays on the published vocabulary. dashboardCardDotState projects unreported to idle, so main's fixed DashboardCardDotState allowlist never sees an unknown value and the card is not dropped.
  • Tests. Committed-recording-backed cases (OpenCode boot title, Grok boot title), hand-built row/summary/dot tests, and agent-title-idle-wrapper-ratchet.test.ts pinning the 35 files that still read the name-as-idle guess.

I read the full diff, ran the six changed/added test files with the repo vitest config (82 passing), and confirmed verify:localization-catalogs passes. I also traced that unreported cannot reach activity threads (ActivityLiveAgentState excludes it) or the published dashboard card vocabulary.

ℹ️ agent-row-tool-preview.test.ts lost the exhaustiveness its comment promises

src/renderer/src/lib/agent-row-tool-preview.test.ts:7 builds ROW_STATES as [...AGENT_STATUS_STATES, 'idle', 'unverifiable'] and its comment states the enumeration exists "so a new one has to make this decision explicitly rather than defaulting into showing a stale tool." Nothing enforces that: readonly AgentRowState[] accepts a subset, so the new unreported member was silently omitted and showsAgentToolPreview('unreported') is untested. The runtime behaviour is correct (it returns false), so this is coverage, not a defect — but this is the first AgentRowState member added since the guard was written, and it did not force the decision it was meant to.

Technical details
# Make the tool-preview state enumeration exhaustive

## Affected sites
- `src/renderer/src/lib/agent-row-tool-preview.test.ts:7` — `ROW_STATES` omits the new `unreported` member; the array's type does not require completeness.

## Required outcome
- Adding a future `AgentRowState` member must fail typecheck until it appears in this list, matching the compile-time guard already used in `src/renderer/src/components/AgentStateDot.test.ts` (its `UncoveredState = Exclude<AgentDotState, (typeof ALL_STATES)[number]>` pattern).

## Suggested approach (optional)
- Add `'unreported'` to `ROW_STATES` now, and add an `Exclude<AgentRowState, (typeof ROW_STATES)[number]> extends never` assertion (as `AgentStateDot.test.ts` does) so the list can never drift again.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No new issues — the only commit since the previous review is a merge of main, with no change to the PR's diff.

Reviewed changes

  • No new code. Since my prior review at 0a89780, the branch gained one commit, f8f7303, which is Merge remote-tracking branch 'origin/main'. The resulting diff against the new merge-base is identical to the version I reviewed (same 25 files, same blob hashes).
  • The one minor point from the prior review therefore still stands: src/renderer/src/lib/agent-row-tool-preview.test.ts:7 omits the new unreported member from its ROW_STATES enumeration, whose comment claims the list forces an explicit decision for every state. Runtime behaviour is correct (showsAgentToolPreview('unreported') === false); adding 'unreported' plus a compile-time exhaustiveness assertion would restore the guard.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review status: ready for review, no blocking findings (head f8f730375d1)

The problem: when an agent's hooks are off, missing or silent (Grok, Antigravity, OpenCode, aider, Codex with hooks off, Cursor's native title), the sidebar decides what the agent is doing from its terminal title. A title that only names the agent (grok, OpenCode, or a project name on a pane Orca recognises by its process) read as "Idle". But a name only says which agent is there: it shows up at startup, mid-turn and whenever the shell auto-titles the pane. #23948 made this more common by giving every process-recognised pane without an activity marker the same "Idle".

What users see differently: those rows now read "No status reported" with a muted dashed ring, in the row text, its tooltip and aria label, the compact row and the worktree summary line. A title that carries an activity marker (spinner, ✳) reads exactly as before. The dashboard card still shows Idle for these rows, because the card's fixed state list can't carry a new value to older pop-out windows. Nothing else changes: the same rows appear in the same order, and settling, mail, cleanup, notifications, keep-awake and the phone behave as on main.

Honest loss (unchanged from the body): a hooks-off agent that really is at rest (for example Grok or Codex with hooks off) now reads "No status reported" where it used to read "Idle". A bare gemini title still reads "Gemini idle", on main and on this branch alike; the fix is planned for the readiness and turn-end step.

This PR is step 1 of the plan: the display. Readiness and turn end, deleting the old guess, the phone, and per-agent rows backed by recordings come in later PRs.

Review outcome

  • Three independent reviewers covered three areas: the title classifier and its temporary bridge; every renderer reader of the new state; and the architecture plus the binding conditions. None found a P0, P1 or P2 issue. The readiness checklist found none either.
  • Verified from code: the old reader (detectAgentStatusFromTitle) answers exactly as on main for every input, including all 167 titles in the committed recordings of real agents. Only the sidebar row builder and smart attention read the new answer. The one-way guard test lists 35 readers of the old guess, and it fails when a reader is added or removed.
  • Verified from code: the synthesized entry's state stays 'working' for these rows, as it already was for main's idle title rows. No reader sees it: title rows live only in the row list, never in the agent-status store. Notifications, keep-awake, automations, the activity pane, the phone and terminal.agentStatus all read that store. The only other value the entry could take, 'done', would switch on "done X ago" and retention, so keeping 'working' is correct.
  • Real agent titles reach the change: OpenCode 1.18.32 and Grok 1.0.0 paint their bare name at startup (committed recordings), and a hooks-off Codex pane titled with the project name goes through the process-recognised path.
  • Fixed in this review: the PR body. The per-agent table now truthfully covers zcode, opencode2, openclaw, bare Cursor and the claude-agent-teams row; [Bug]: Antigravity (agy) sessions show Idle while they are running #19829 is referenced (closes nothing); plain words replace jargon; the missing template sections are added. main was merged into the branch with no conflicts.
  • Deferred (P3 nits): the bridge's doc comment says "PR-4", which should name the guard test instead. The row builder and smart attention import the shared reader directly rather than through @/lib/pane-agent-evidence. Only "No status reported" is translated, while the neighbouring row labels ("Idle", "Running") and the summary words stay hard-coded English as on main, so a Japanese user sees both languages on one card until those labels are localized together.
  • Worth knowing when this lands: draft fix(agent-status): a turn a crash cut off reads Interrupted, an unproven end Couldn't confirm #23467 (the amber "unconfirmed" and "interrupted" states) conflicts with this PR in 5 files. Whichever lands second places unreported among its states and checks the three dashed rings side by side.

Live QA (M4Air mac, isolated rig with agent hooks off, main b99462ac1cf vs this branch f8f730375d1)

  • Driven by a Claude seat in an M4Air terminal: Grok's balance was exhausted and the orchestration worker start was refused.
  • Titles were set with bounded printf title escapes. The hooks-off Codex pane ran a sleep-only stub that Orca recognises by its process name. No real agent CLI ran.
  • Name-only rows for grok, agy, OpenCode, aider and hooks-off Codex: main shows "Idle"; this branch shows "No status reported" with a muted dashed ring, in light and dark. PASS.
  • Marker titles (⠋ grok, ✳ Claude Code) and a stale hook row ("No recent update", amber ring): identical on both. PASS.
  • Compact rows show Idle, "No recent update" and "No status reported" side by side, in light and dark. PASS.
  • The worktree summary reads "1 working, 1 not reporting, 5 no status reported, 1 idle", where main reads "6 idle". PASS.
  • The collapsed summary pill still shows its three highest-ranked groups plus "+N". With five name-only rows, "No status reported" takes the third slot and the lone Idle row folds into "+1". This follows the existing ranking, where "No status reported" sits beside "No recent update", above idle.
  • Dashboard (show idle): identical on both arms, so name-only rows show Idle cards, by design. PASS.
  • Japanese: the rows read ステータス未報告. PASS.

Name-only rows, main (light, dark):
main-s1-sidebar-light
main-s1-sidebar-dark

Name-only rows, this branch (light, dark):
head-s1-sidebar-crop-light
head-s1-sidebar-crop-dark

Compact rows side by side, this branch (light, dark):
head-s3-compact-light
head-s3-compact-dark

Collapsed summary pill, main then this branch, plus this branch's summary tooltip:
main-s4-summary-collapsed-light
head-s4-summary-collapsed-light
head-s4-summary-crop

Dashboard, main then this branch:
main-s5-dashboard-showidle
head-s5-dashboard-showidle

Japanese, this branch:
head-s6-ja-sidebar-crop

Checks

  • CI at f8f730375d1: 19 passed, 15 skipped, 0 failed.
  • CI's typecheck, static analysis, verify and all five unit-test shards pass on the merged head. Targeted tests (the six files this PR adds or changes, plus smart attention): 135 passed.

Not verified

  • A real agent session: titles were printf-set.
  • Windows, Linux, SSH or WSL panes, and a packaged build.
  • The phone (unchanged by design).
  • The popped-out dashboard window.
  • A main-arm comparison of a live language switch. On this branch, the whole worktree card keeps the old language until its rows remount; that includes strings older than this PR, so it isn't caused by this change.

This branch has not been deployed

No deployments
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