Add AI Vault subagent display#7423
Conversation
AI Vault now lists Claude Task subagents under their parent session with live run-status dots, shows subagent counts and the model label in session rows, and drops the inconsistent Codex-only effort display. The right sidebar gains an Arc-style edge peek: hovering the window's right edge reveals it as a floating overlay without reflowing the editor. Subagent transcripts are pruned from local and SSH scan discovery and read lazily through a provider- and host-gated IPC endpoint with path containment. The edge peek uses geometry-based arm/dismiss listeners (no intercepting DOM strip) and a transient, unpersisted store flag.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds Claude subagent transcript tracking: discovery, counting, and status-resolved listing of "Task subagent" transcripts colocated under a session's 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
src/renderer/src/components/right-sidebar/ai-vault-session-display.ts (1)
83-85: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueMinor: prefer
??over||for the null check.
session.model || nullalso nulls out an empty-string model. Using??matches the declaredstring | nulltype more precisely.♻️ Proposed tweak
export function sessionModelLabel(session: AiVaultSession): string | null { - return session.model || null + return session.model ?? null }src/renderer/src/components/AgentStateDot.tsx (1)
105-119: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider a distinct glyph for
failed, similar todone's check icon.
failedcurrently renders as a plain red dot (same visual weight asblocked/interrupted), whiledonegets a dedicatedCircleCheckicon for scannability. Since subagent failures are meant to be an attention-worthy outcome, a distinct icon (e.g. an X/alert glyph) could make failures easier to spot at a glance in a long session list.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 37ce0884-6d18-409c-9952-2b7386544fad
📒 Files selected for processing (46)
src/main/ai-vault/remote-session-scanner-sources.tssrc/main/ai-vault/remote-session-scanner.test.tssrc/main/ai-vault/session-scanner-accumulator.tssrc/main/ai-vault/session-scanner-agent-parser.tssrc/main/ai-vault/session-scanner-claude-subagent-prune.test.tssrc/main/ai-vault/session-scanner-claude-subagents.test.tssrc/main/ai-vault/session-scanner-claude-subagents.tssrc/main/ai-vault/session-scanner-codex-parser.test.tssrc/main/ai-vault/session-scanner-codex-parser.tssrc/main/ai-vault/session-scanner-discovery.tssrc/main/ai-vault/session-scanner-scan-roots-fixture.tssrc/main/ai-vault/session-scanner-source-discovery.tssrc/main/ai-vault/session-scanner.test.tssrc/main/ipc/ai-vault.test.tssrc/main/ipc/ai-vault.tssrc/preload/api-types.tssrc/preload/index.tssrc/renderer/src/App.tsxsrc/renderer/src/components/AgentStateDot.tsxsrc/renderer/src/components/right-sidebar/AiVaultPanel.tsxsrc/renderer/src/components/right-sidebar/AiVaultSessionDetails.tsxsrc/renderer/src/components/right-sidebar/AiVaultSessionRow.tsxsrc/renderer/src/components/right-sidebar/AiVaultSessionSubagents.test.tsxsrc/renderer/src/components/right-sidebar/AiVaultSessionSubagents.tsxsrc/renderer/src/components/right-sidebar/AiVaultSessionVirtualList.tsxsrc/renderer/src/components/right-sidebar/ai-vault-original-pane-actions.tssrc/renderer/src/components/right-sidebar/ai-vault-original-pane.test.tssrc/renderer/src/components/right-sidebar/ai-vault-original-pane.tssrc/renderer/src/components/right-sidebar/ai-vault-session-display.test.tssrc/renderer/src/components/right-sidebar/ai-vault-session-display.tssrc/renderer/src/components/right-sidebar/ai-vault-session-filters.test.tssrc/renderer/src/components/right-sidebar/ai-vault-session-projects.test.tssrc/renderer/src/components/right-sidebar/ai-vault-session-row-display.tsxsrc/renderer/src/components/right-sidebar/ai-vault-session-worktree.test.tssrc/renderer/src/components/right-sidebar/index.tsxsrc/renderer/src/components/right-sidebar/right-sidebar-edge-peek.test.tsxsrc/renderer/src/components/right-sidebar/right-sidebar-edge-peek.tsxsrc/renderer/src/components/sidebar/worktree-card-agent-summary.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.jsonsrc/renderer/src/store/slices/editor.tssrc/renderer/src/web/web-preload-api.tssrc/shared/ai-vault-types.ts
…, guard subagent IPC args The edge-peek flag now resets when the zone unmounts so a view change can't leave a ghost peek behind; the row badge picks singular/plural subagent keys per the catalog convention; and malformed aiVault:listSubagentSessions payloads resolve empty instead of throwing, matching the sibling handler.
ff3c45b to
0129b65
Compare
…ts-and-edge-peek # Conflicts: # src/main/ai-vault/session-scanner-discovery.ts # src/main/ai-vault/session-scanner-source-discovery.ts # src/main/ai-vault/session-scanner.test.ts
…ts-and-edge-peek # Conflicts: # src/main/ai-vault/session-scanner-codex-parser.ts
…vault-subagents-and-edge-peek # Conflicts: # src/main/ai-vault/remote-session-scanner-sources.ts # src/main/ai-vault/remote-session-scanner.test.ts # src/main/ipc/ai-vault.test.ts # src/main/ipc/ai-vault.ts # src/renderer/src/components/right-sidebar/AiVaultSessionDetails.tsx # src/renderer/src/components/right-sidebar/ai-vault-original-pane.test.ts # src/renderer/src/components/right-sidebar/ai-vault-session-display.test.ts # src/renderer/src/components/right-sidebar/ai-vault-session-filters.test.ts # src/renderer/src/components/right-sidebar/ai-vault-session-projects.test.ts # src/renderer/src/components/right-sidebar/ai-vault-session-row-display.tsx # src/renderer/src/components/right-sidebar/ai-vault-session-worktree.test.ts # src/renderer/src/i18n/locales/es.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/web/web-preload-api.ts
|
@brennanb2025 can we review this? |
The edge peek will be reviewed as its own PR (preserved on feat/right-sidebar-edge-peek-extracted). Also restores main's formatting on files this PR never meant to touch and removes the superseded AiVaultSessionRow.subagentCount locale key (the singular/plural pair replaced it). Co-authored-by: Orca <help@stably.ai>
Main already counts sibling subagent transcripts (stablyai#7889) for zero-turn recovery; this PR was written before that landed and added a parallel subagentCount doing the same readdir. Keep one field: count every local session's transcripts in the shared Claude finalize (one readdir per parse instead of two extra call sites), attach remote counts from the walk listing for all sessions, and gate the on-demand subagent listing UI to local sessions. Net effect vs the PR: SSH rows now show a real subagent count badge; listing stays local-only. Co-authored-by: Orca <help@stably.ai>
The status marker sits after <tool-use-id>/<output-file> lines, past the 96-char title cap, so user-message-delivered notifications lost their <status> and fell back to transcript recency. Read the notification text untruncated and only honor records whose text is the notification itself, so a user prompt quoting one can't overwrite a real terminal status. Co-authored-by: Orca <help@stably.ai>
…cation A sync-Task completion carries its status in toolUseResult, but when the subagent's report text mentions <task-notification> the raw-line prefilter routed the record into the notification branch, whose guard rejected the non-notification text and then unconditionally continued — dropping the real 'completed' status to the recency fallback. Only consume the record in the notification branch when its text actually is the notification. Co-authored-by: Orca <help@stably.ai>
|
Thanks @hmrserver — the subagents feature is in good shape and I'm landing it after some reworking on the branch. What changed:
Nice work on the on-demand listing design and the IPC containment checks — both held up well under review. |
The merge from main left formatting-only reflows on cloud-profile and IME/xterm test files. Revert them to main's exact content so unrelated surfaces have zero diff. Co-authored-by: Orca <help@stably.ai>
…tus pass - Share one agent-*.jsonl predicate (isSubagentTranscriptFileName) across the row-badge count, the recoverable-empty signal, and the on-demand lister (and the remote SUBAGENT_DIRECT_CHILD_PATTERN) so the "N subagents" badge can't disagree with the expanded list. - Gate collectSubagentTaskStatuses on a second "agentId" marker so the status pass no longer JSON-parses the large tool-output records that dominate a transcript's bytes on every on-demand fetch. Behavior-preserving: the toolUseResult branch only sets a status when result.agentId && result.status, so a status-setting line always contains "agentId"; notification lines stay ungated via hasNotification. - Do NOT add a runtime arm to ownsTranscriptDisk: a 'runtime:' host never reaches finalizeClaudeSessionParseState. The local scan (used for local AND runtime-host scans) finalizes without an executionHostId and stamps the host after parse (withSessionExecutionHost), so runtime already counts via the undefined branch; only the SSH parse path threads an executionHostId and is correctly skipped. A runtime arm would be dead code (recorded in a comment on the guard). SSH-skip is covered by session-scanner-recoverable-empty.test.ts. - Document the accepted forged-status limitation (a user message beginning with a verbatim task-notification). Co-authored-by: Orca <help@stably.ai>
A stray non-agent .jsonl or a directory named like a transcript must not inflate the row badge past what the on-demand lister shows. Co-authored-by: Orca <help@stably.ai>
|
Pushed the final review-hardening commits on top of the rework:
Full typecheck, lint, 131 main-process + 1190 renderer tests green on the branch merged with current main, plus live validation in a dev build against real Claude session data (badge/model/live dot, expanded subagent list with statuses, recount-on-refresh matching disk exactly, no refetch flicker). PR description updated to match the subagents-only scope (edge peek lives in #8015). Waiting on CI, then this is ready to land. |
AiVaultSession.subagent became required on main; the five fixtures added on this branch predate it. Top-level scanned sessions carry null.
* Show agent session history on mobile Bring the desktop "Agent Session History" panel to Orca Mobile as a per-worktree screen: browse past agent transcript sessions across the host with scope tabs (Workspace/Project/All), search, grouping, session cards, and tap-to-read message previews. The transcript scan previously ran only over Electron IPC, so mobile could not reach it. Expose it over the runtime RPC protocol mobile already speaks (aiVault.listSessions) so the scan runs on whichever host owns the transcripts — correct for local and SSH/remote hosts. Both the desktop IPC handler and the new RPC method share one cache, so opening the desktop panel and the mobile screen never double-scan. The pure filter/group/display logic is lifted into /shared (the renderer re-exports it) so the standalone mobile package can reuse it. Mobile narrows scoped tabs client-side by cwd path-prefix because the host scan treats scope paths as a widening union. Resume-from-mobile is intentionally a follow-up. * Fix mobile agent history list rendering and RPC authorization - Authorize aiVault.listSessions in the mobile RPC allowlist so the mobile client's call is not rejected before dispatch (without this the screen could never load sessions at runtime). - Name each SectionList section's rows `data` (the field React Native reads) instead of `cards`, fixing a type error and silent empty-section rendering. * Address review feedback on agent session history - Match quoted repo:/path: search operator values so labels and paths with spaces match (e.g. path:"/Users/ada/My Project"). - Hold a scoped tab in loading until the worktree list resolves instead of firing an unscoped fetch that briefly shows unrelated host history; proceed once loaded even if the worktree is absent (no stuck spinner). - Clear cached host capabilities on disconnect/host-switch and failed status.get so a capability-gated action can't linger for a host that doesn't support it. - Cover the real OrcaRuntimeService codex-home forwarding path and the quoted-operator parser with tests. * Hide redundant mobile current worktree badges Co-authored-by: Orca <help@stably.ai> * Resume agent sessions from mobile history (#6969) Co-authored-by: Orca <help@stably.ai> * Adapt merged seams to main's lint and reply-sender hardening Co-authored-by: Orca <help@stably.ai> * Cap mobile project-scope paths to the aiVault RPC bound Co-authored-by: Orca <help@stably.ai> * Share the aiVault scopePaths bound between the RPC schema and mobile Co-authored-by: Orca <help@stably.ai> * Guard shared AI Vault inflight cleanup against concurrent key replacement The extracted cache module's .finally() cleared inflight tracking unconditionally, dropping the if (inflightKey === key) guard its sibling outer cache kept: an older scan resolving after a different-key scan replaced the tracking would null the newer scan's dedup slot, so a re-request started a duplicate transcript rescan. Mirrors the sibling guard; the regression test flushes a macrotask so a reverted guard fails fast on the call count instead of hanging. Co-authored-by: Orca <help@stably.ai> * Harden aiVault.listSessions contract and gate mobile header entry on capability - Clamp scopePaths (64) instead of rejecting, cap limit at 2000, and make executionHostId optional so mobile can omit it; restamp per caller. - Retain successful mobile terminal-create mutation ids for 60s so resume retries dedupe after transient socket drops. - Gate the session-header Agent History action on the aiVault.v1 capability (mirrors the host-list action) so old hosts never show a dead-end entry. - Fix stale contract comments (scopePaths clamp semantics; filters move includes quoted repo:/path: operator parsing). * Add subagent field to session test fixtures after #7423 merge AiVaultSession.subagent became required on main; the five fixtures added on this branch predate it. Top-level scanned sessions carry null. --------- Co-authored-by: Orca <help@stably.ai> Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
PR #7423 resized the working spinner ring from the dot size to the icon size (sm: 6px -> 10px, md: 8px -> 12px) as a side effect of the AI Vault subagent display work. The size change was unintentional in the sidebar agent rows, so restore the original dot sizing. Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
Summary
AI Vault now shows Claude Task subagents under their parent session. Session rows show
N subagentswhen available, live status dots for active sessions, and the model label; expanding a session shows each subagent's status, type, message count, and log link.Screenshots
Visual change — media attached in the PR conversation:
Testing
pnpm lintpnpm typecheckpnpm testpnpm buildLatest state (after the review-hardening commits): full
pnpm typecheckandpnpm lintpass on the branch merged with current main. Targeted Vitest: allsrc/main/ai-vault/+src/main/ipc/ai-vault.test.ts(131 tests) and allsrc/renderer/src/components/right-sidebar/tests (1190 tests) green. The subagent display was also validated live in a dev build against real Claude session data: row badge, model label, and working dot render; the expanded Subagents section lists live/finished subagents with statuses; the badge recounts on refresh and exactly matches the transcripts on disk; refetches keep prior rows (no flicker); zero renderer console errors.AI Review Report
The review focused on scan correctness, IPC path handling, SSH/local behavior, UI interaction edge cases, performance in scanner hot paths, and supported-agent compatibility. It flagged and this PR now fixes:
subagents/.Review hardening on top (maintainer review pass):
agent-*.jsonlpredicate, so theN subagentsbadge can never disagree with the expanded list; a fails-on-revert exclusion test pins the predicate.toolUseResultlines on"agentId", so it no longer JSON-parses the large tool-output records that dominate a transcript's bytes on every on-demand fetch (measured ~192ms of main-process CPU per expand on a 65MB transcript before the fix; behavior-preserving, notification lines stay ungated).Cross-platform coverage was explicitly checked for macOS, Linux, and Windows. There are no shortcut or label changes. Path handling uses Node path utilities, WSL Claude roots are included, and remote path segments handle
/and\.Security Audit
The new IPC surface is
aiVault:listSubagentSessions. It is provider-gated to Claude, host-gated to local sessions, and resolvesparentFilePathbefore checking containment under known Claude project roots, including WSL roots. Dot-segment traversal is covered by a test.The endpoint only lists
*/subagents/agent-*.jsonltranscripts and their meta sidecars. It does not execute commands, read arbitrary paths after containment, add network access, touch secrets, or add dependencies. Symlink escape would require prior write access inside~/.claude/projects;realpathis intentionally not required because those roots may not exist on fresh installs or WSL setups.Notes
SSH sessions show the subagent count (derived from the remote walk listing, no extra round-trips), but
listSubagentSessionsintentionally returns empty for non-local hosts — their transcripts live on the remote disk. Remote subagent listing is future work; this is honest UI behavior instead of trying to read local paths for remote sessions.Known limitations, documented in code: depth-2+ nested subagents fall back to a 5-minute recency heuristic for their status dot (their terminal status lives in the spawner's transcript, not the parent's), and a user message that literally begins with a pasted task-notification can forge a cosmetic status dot on a view-only row.
This overlaps with #7369 in Claude discovery wiring, so a small merge conflict is likely whichever lands second.
Non-English locale entries are synced with English placeholder text for the new strings, matching the existing translation-pass workflow.