Repository navigation
fix(sidebar): clear the red Failed mark on a crash-cut turn once the chat is seen - #25293
brennanb2025 wants to merge 6 commits into
Conversation
…n it When the agent stopped mid-reply without anyone asking (a crash, a quit, an eviction), every agent-status surface marked the turn Failed, and nothing cleared it after the user had read the chat. The verdict's display now takes Orca's existing acknowledgement as a required part of what it reads, judged by one shared rule against the turn's attention clock (the later of the row's and the main agent's, since a subagent can hold the row open past the main agent's cut). Each surface joins it once where it builds its rows: the sidebar's agent rows (and through them the compact summary and the notes send menu), the workspace card summary, the tab bar, Cmd-J recent rows and Activity threads. Auto-acknowledgement of the chat on screen reads the same clock, and its stamp covers it. Failures, Stops, replaced turns and unproven ends keep their marks; the phone, which has no acknowledgement record, keeps the unseen reading.
…pied The Activity unread count and dashboard row buckets each spelled the same "acknowledged at or after the current state began" comparison; they now call the shared rule, with the row's clock as before.
…ipt change Only the mark's lines change here, so the label change can touch the rest of this test without the two changes meeting.
Review status: ready for review (part 2 of 2)Split from #25043. This part makes the red Failed mark on a reply cut off by an Orca crash, quit or idle close clear once you have seen the chat. Part 1, #25043, changes how that reply reads in the chat. Each part lands safely on its own, in either order. The problem. After such a cut, the sidebar, the chat's tab, Cmd+J recent rows, the review-notes send menu and the Activity page marked the agent Failed in red, and that mark never cleared, even after you had opened and read the chat. What changes for you.
How it works.
Deferred (listed in the PR body)
Verified
Not verified: a real person's click at the desktop, the phone, SSH hosts, and the Activity page and review-notes menu live. |
There was a problem hiding this comment.
ℹ️ No critical issues — one low-stakes clock question inline.
Reviewed changes — part 2 of two: a turn cut short when the agent process vanished (interruption) now shows the red Failed mark only until the user has seen the chat, then reads Done. A real failure, a user Stop, a superseded turn and an unproven end keep their marks; stored outcome and the wire are unchanged, and the phone keeps the unseen reading.
- New shared acknowledgement rule —
src/shared/agent-turn-acknowledgement.tsaddsisAgentTurnAcknowledgedandagentAttentionStartedAt(the later of the row's clock and the main agent's own, for a subagent holding the row open past a cut). Auto-acknowledgement, dashboard buckets, Activity unread and notification acknowledgement now route through it. - The mark gates on seen-ness only for cuts —
agentVerdictDisplayMarktakes a requiredacknowledgedAt; theinterruptionarm returnsnullonce acknowledged, whilefailure/cancellation/superseded/unconfirmedare unchanged. The required field makes a surface that omits the ack a compile error. - Every surface joins the acknowledgement where it builds rows —
useWorktreeAgentRows(and through it the card summary and notes send menu), the worktree card dot, the terminal tab dot, Activity threads, recent and jump-palette rows, Cmd+J, and the dashboard row; the card and tab caches now invalidate on the acknowledgement map. - Tests — table-driven per-outcome mark before/after acknowledgement, the held-open-by-subagent cut clock, live and retained card summaries, tab-bar, Activity line/group, the rows hook, and join object reuse.
Reviewed changes, cont.
Verified locally: agent-main-agent-verdict, agent-entry-acknowledgement, agent-attention-acknowledgement, worktree-agent-activity-summary, terminal-tab-activity-status, recent-workspace-tab-rows, worktree-card-agent-summary and DashboardAgentRow tests all pass (137 tests across the two runs).
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
| const ackAt = acknowledgedAgentTimes[index] ?? 0 | ||
| out[agent.paneKey] = ackAt < agent.entry.stateStartedAt | ||
| for (const agent of agents) { | ||
| out[agent.paneKey] = !isAgentTurnAcknowledged(agent.entry) |
There was a problem hiding this comment.
This row bold emphasis judges seen-ness on the row's own clock (agent.entry.stateStartedAt), while the red failed mark this PR gates and auto-acknowledgement both judge it on the cut's clock (agentAttentionStartedAt). When a subagent holds a row working after the main agent was cut — row began working at 1000, cut at 3000, user acknowledged at 2000 — the mark reads Failed but the row is not bold, the opposite of the PR's "the bold row and the red mark now always agree."
This may be deliberate (the user did see the working row; only the dot is new news), in which case the rationale wording is what should give. Flagging so the two clocks not disagreeing is a conscious choice.
Technical details
# Bold uses the row clock; the mark and auto-ack use the attention clock
## Affected sites
- `src/renderer/src/components/sidebar/WorktreeCardAgents.tsx:98` — `isAgentTurnAcknowledged(agent.entry)` compares the ack against `entry.stateStartedAt`.
- `src/shared/agent-main-agent-verdict.ts:72-77` — the `interruption` mark compares the same ack against `agentAttentionStartedAt(row)` (`max(stateStartedAt, mainAgent.stateStartedAt)`).
- `src/renderer/src/attention/agent-attention-acknowledgement.ts:56-69` — `computeAgentAcknowledgementTargets` (what auto-acknowledgement clears) uses the attention clock.
## Required outcome
- Decide whether the bold emphasis should follow the cut's clock like the mark, or intentionally stay on the row clock. If the latter, soften the "always agree" rationale / note the divergence on `agentAttentionStartedAt`.
## Open questions for the human
- Should `unvisitedByPaneKey` use `agentAttentionStartedAt(agent.entry)` so a cut the user has not seen keeps the row emphasized, matching the dot?|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Important Review skippedReview was skipped as selected files did not have any reviewable changes. ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (1)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthroughThe changes add shared acknowledgement timing for agent turns. An acknowledged interruption no longer displays as failed, while failure verdicts remain failed. Renderer row, activity-thread, terminal-tab, and recent-workspace status paths now receive acknowledgement timestamps and apply them when deriving display states. Tests cover acknowledgement timing, live and retained entries, and row identity. Priority: ➖ Normal Merge Risk: 🔵 Low · up to The recent-workspace palette can retain an outdated Failed mark after a chat is seen, and a zero-time interruption can be cleared without being seen. These are bounded display issues; other live status paths receive the acknowledgement update. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The inspected changes affect how interrupted replies are displayed after viewing, rather than granting additional access or permission to send work. Remaining uncertainty concerns clock edge cases and recovery behavior; no increased authority was demonstrated. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
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: 2
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
dfb04950-9502-4a63-a668-ab5904bc3159
📒 Files selected for processing (81)
mobile/src/worktree/agent-row-display.test.tsmobile/src/worktree/agent-row-display.tssrc/main/native-chat/agent-session-wire/structured-agent-session-recovered-turn-clock.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-stop-event-restart.test.tssrc/renderer/src/attention/agent-attention-acknowledgement.test.tssrc/renderer/src/attention/agent-attention-acknowledgement.tssrc/renderer/src/components/activity/ActivityPrototypePage-test-fixtures.tssrc/renderer/src/components/activity/ActivityPrototypePage.test.tssrc/renderer/src/components/activity/ActivityPrototypePage.thread-grouping.test.tssrc/renderer/src/components/activity/activity-clear-completed-hmr.test.tssrc/renderer/src/components/activity/activity-clear-completed-state-lifetime.test.tssrc/renderer/src/components/activity/activity-clear-completed.test.tssrc/renderer/src/components/activity/activity-event-builder.identity-reuse.test.tssrc/renderer/src/components/activity/activity-event-builder.live-cap.test.tssrc/renderer/src/components/activity/activity-scope-filter.test.tssrc/renderer/src/components/activity/activity-status-group-header.test.tssrc/renderer/src/components/activity/activity-thread-actions.test.tssrc/renderer/src/components/activity/activity-thread-builder.tssrc/renderer/src/components/activity/activity-thread-child-agent.test.tssrc/renderer/src/components/activity/activity-thread-grouping.search-cache.test.tssrc/renderer/src/components/activity/activity-thread-hover-card.test.tsxsrc/renderer/src/components/activity/activity-thread-list-pane-collapsible.test.tsxsrc/renderer/src/components/activity/activity-thread-list-pane.virtualization.test.tsxsrc/renderer/src/components/activity/activity-thread-presentation.test.tssrc/renderer/src/components/activity/activity-thread-presentation.tssrc/renderer/src/components/activity/activity-thread-types.tssrc/renderer/src/components/activity/activity-thread-virtual-items.test.tssrc/renderer/src/components/activity/use-agent-pane-threads.tssrc/renderer/src/components/activity/useActivityUnreadCount.tssrc/renderer/src/components/agent-child-row-parity.test.tsxsrc/renderer/src/components/cmd-j/palette-live-status.tsxsrc/renderer/src/components/dashboard/DashboardAgentChildDisclosure.test.tsxsrc/renderer/src/components/dashboard/DashboardAgentRow.test.tsxsrc/renderer/src/components/dashboard/DashboardAgentRow.tsxsrc/renderer/src/components/dashboard/agent-finished-timestamp.tssrc/renderer/src/components/dashboard/dashboard-row-bucket.tssrc/renderer/src/components/editor/ReviewNotesSendMenuContent.tsxsrc/renderer/src/components/native-chat/structured-child-records-switch.test.tsxsrc/renderer/src/components/sidebar/WorktreeCardAgents.acknowledgement-emphasis.test.tsxsrc/renderer/src/components/sidebar/WorktreeCardAgents.test.tsxsrc/renderer/src/components/sidebar/WorktreeCardAgents.tsxsrc/renderer/src/components/sidebar/stale-agent-row-unverifiable.test.tssrc/renderer/src/components/sidebar/subagent-child-row-fallback-pixels.test.tsxsrc/renderer/src/components/sidebar/use-worktree-activity-status.test.tsxsrc/renderer/src/components/sidebar/use-worktree-activity-statuses.test.tssrc/renderer/src/components/sidebar/use-worktree-activity-statuses.tssrc/renderer/src/components/sidebar/useWorktreeAgentRows.acknowledgement.test.tsxsrc/renderer/src/components/sidebar/useWorktreeAgentRows.tssrc/renderer/src/components/sidebar/worktree-agent-activity-summary.test.tssrc/renderer/src/components/sidebar/worktree-agent-activity-summary.tssrc/renderer/src/components/sidebar/worktree-card-agent-summary.test.tssrc/renderer/src/components/sidebar/worktree-card-agent-summary.tssrc/renderer/src/components/sidebar/worktree-card-compact-agent-row.stable-message.test.tsxsrc/renderer/src/components/sidebar/worktree-card-compact-agent-row.tsxsrc/renderer/src/components/sidebar/worktree-card-compact-agents.tsxsrc/renderer/src/components/tab-bar/SortableTab.tsxsrc/renderer/src/components/tab-bar/terminal-tab-activity-status.test.tssrc/renderer/src/components/tab-bar/terminal-tab-activity-status.tssrc/renderer/src/components/tab-bar/terminal-tab-spinner-launch-agent.test.tssrc/renderer/src/components/use-worktree-jump-palette-recent-tabs.tssrc/renderer/src/components/use-worktree-jump-palette-store-state.tssrc/renderer/src/components/worktree-jump-palette-recent-inclusion.test.tssrc/renderer/src/components/worktree-jump-palette-recent-inclusion.tssrc/renderer/src/components/worktree-jump-palette-status-inputs.test.tssrc/renderer/src/components/worktree-jump-palette-status-inputs.tssrc/renderer/src/lib/activity-thread-display.test.tssrc/renderer/src/lib/activity-thread-display.tssrc/renderer/src/lib/agent-entry-acknowledgement.test.tssrc/renderer/src/lib/agent-entry-acknowledgement.tssrc/renderer/src/lib/agent-pane-activity-flags.tssrc/renderer/src/lib/agent-row-dot-state.tssrc/renderer/src/lib/agent-verdict-status-line.tssrc/renderer/src/lib/recent-workspace-tab-rows.test.tssrc/renderer/src/lib/recent-workspace-tab-rows.tssrc/renderer/src/runtime/paired-agent-row-turn-outcome-arms.test.tssrc/renderer/src/store/slices/agent-status-observation-neutrality.test.tssrc/renderer/src/store/slices/ui-acknowledge-agents-clock-skew.test.tssrc/renderer/src/store/slices/ui/ui-slice-agent-notification-acknowledgement.tssrc/shared/agent-main-agent-verdict.test.tssrc/shared/agent-main-agent-verdict.tssrc/shared/agent-turn-acknowledgement.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.
| unreadTerminalTabs, | ||
| unreadAgentCompletionPanes | ||
| unreadAgentCompletionPanes, | ||
| acknowledgedAgentsByPaneKey |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
#!/bin/bash
printf '%s\n' '--- diff for target file ---'
git diff --unified=40 ddefd523e052cc83897937569d322a727f55dd78 9067b967bedf93ca4bcc84ce5c32fc993d095b4d -- src/renderer/src/components/use-worktree-jump-palette-store-state.ts
printf '%s\n' '--- target at reviewed head ---'
git show 9067b967bedf93ca4bcc84ce5c32fc993d095b4d:src/renderer/src/components/use-worktree-jump-palette-store-state.ts | nl -ba | sed -n '1,180p'
printf '%s\n' '--- relevant symbol references ---'
rg -n -C 3 'acknowledgedAgentsByPaneKey|paletteStatusInputsActive|useWorktreeJumpPaletteStoreState' src/renderer/srcRepository: stablyai/orca
Length of output: 45665
🏁 Script executed:
#!/bin/bash
printf '%s\n' '--- selector implementation ---'
nl -ba src/renderer/src/components/worktree-jump-palette-status-inputs.ts | sed -n '1,240p'
printf '%s\n' '--- production acknowledgement references ---'
rg -n -C 2 'acknowledgedAgentsByPaneKey' src/renderer/src -g '!*.test.*'
printf '%s\n' '--- palette status resolver consumers ---'
rg -n -C 3 'resolve.*Recent|recent.*status|acknowledgedAgentsByPaneKey' src/renderer/src/components/worktree-jump-palette* src/renderer/src/components/use-worktree-jump-palette* -g '!*.test.*'
printf '%s\n' '--- palette lingering lifecycle references ---'
rg -n -C 3 'lingering|statusInputsLingering' src/renderer/src/components/worktree-jump-palette* src/renderer/src/components/use-worktree-jump-palette* -g '!*.test.*'Repository: stablyai/orca
Length of output: 42252
🏁 Script executed:
#!/bin/bash
printf '%s\n' '--- auto-ack hook ---'
nl -ba src/renderer/src/hooks/useAutoAckViewedAgent.ts | sed -n '1,180p'
printf '%s\n' '--- auto-ack input and action references ---'
nl -ba src/renderer/src/hooks/agent-auto-ack-surfaces.ts | sed -n '1,130p'
rg -n -C 3 'acknowledgeAgent|acknowledge.*Pane|acknowledgedAgentsByPaneKey:' src/renderer/src/store/slices src/renderer/src/hooks -g '!*.test.*'
printf '%s\n' '--- palette controller callers and lingering owner ---'
rg -n -C 4 'useWorktreeJumpPaletteController|setLingering|lingering' src/renderer/src/components/cmd-j src/renderer/src/components -g '*worktree-jump-palette*' -g '!*.test.*'
printf '%s\n' '--- recent-tab memo and source forwarding ---'
nl -ba src/renderer/src/components/use-worktree-jump-palette-recent-tabs.ts | sed -n '35,145p'
nl -ba src/renderer/src/components/worktree-jump-palette-recent-inclusion.ts | sed -n '25,65p'Repository: stablyai/orca
Length of output: 42519
🏁 Script executed:
#!/bin/bash
printf '%s\n' '--- acknowledgement action bindings ---'
rg -n -C 5 'acknowledgeAgents' src/renderer/src/store src/renderer/src -g '!*.test.*'
printf '%s\n' '--- all palette controller references ---'
rg -n -C 5 'useWorktreeJumpPaletteController|statusInputsLingering|lingering=' src/renderer/src -g '!*.test.*'Repository: stablyai/orca
Length of output: 41586
🏁 Script executed:
#!/bin/bash
printf '%s\n' '--- acknowledgeAgents implementation ---'
nl -ba src/renderer/src/store/slices/ui/ui-slice-activity-actions.ts | sed -n '21,102p'
printf '%s\n' '--- palette visible and lingering lifecycle ---'
nl -ba src/renderer/src/components/WorktreeJumpPalette.tsx | sed -n '1,55p'Repository: stablyai/orca
Length of output: 6288
Refresh the palette snapshot when acknowledgements change.
If acknowledgedAgentsByPaneKey changes while visible || lingering is true and the tab maps stay unchanged, a later render reuses the old paletteIndexStatus snapshot. acknowledgeAgents can update the map without changing those tab maps, and recent-tab status resolution uses its acknowledgement timestamp. Subscribe to the map only while status inputs are active, and include it in the snapshot memo dependencies.
Suggested fix
const paletteStatusInputsActive = visible || lingering
+ const acknowledgedAgentsByPaneKeyInput = useAppStore((state) =>
+ paletteStatusInputsActive ? state.acknowledgedAgentsByPaneKey : null
+ )
const { ptyIdsByTabId, terminalLayoutsByTabId, tabsByWorktree } = useAppStore(
useShallow((state) => selectPaletteStatusInputs(state, paletteStatusInputsActive))
)
...
- [paletteStatusInputsActive, tabsByWorktree, unifiedTabsByWorktree]
+ [
+ paletteStatusInputsActive,
+ tabsByWorktree,
+ unifiedTabsByWorktree,
+ acknowledgedAgentsByPaneKeyInput
+ ]| stateStartedAt, | ||
| acknowledgedAt | ||
| }: AgentTurnAcknowledgement): boolean { | ||
| return (acknowledgedAt ?? 0) >= stateStartedAt |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
sed -n '1,100p' src/shared/agent-turn-acknowledgement.ts
sed -n '105,132p' mobile/src/worktree/agent-row-display.test.ts
sed -n '1,55p' src/renderer/src/components/sidebar/worktree-card-agent-ack-inputs.ts
rg -n 'stateStartedAt: 0|stateStartedAt \?\? 0|stateStartedAt \|\| 0' src/renderer/src src/shared mobile/src/worktree | head -75Repository: stablyai/orca
Length of output: 5209
🏁 Script executed:
printf '%s\n' '--- symbol references ---'
rg -n 'agentVerdictDisplayMark|computeAgentAcknowledgementTargets|isAgentTurnAcknowledged|agentAttentionStartedAt|acknowledgedAgentsByPaneKey|stateStartedAt' src/renderer/src mobile/src/worktree src/shared
printf '%s\n' '--- source file inventory ---'
git ls-files 'src/renderer/src/*' 'mobile/src/worktree/*' 'src/shared/*' | rg 'agent|status|acknowledg|useDashboard|store/types' | head -100Repository: stablyai/orca
Length of output: 41838
🏁 Script executed:
printf '%s\n' '--- shared verdict and acknowledgement ---'
sed -n '1,130p' src/shared/agent-main-agent-verdict.ts
sed -n '1,90p' src/shared/agent-turn-acknowledgement.ts
printf '%s\n' '--- acknowledgement target selector ---'
sed -n '1,115p' src/renderer/src/attention/agent-attention-acknowledgement.ts
printf '%s\n' '--- mobile row implementation and fixture ---'
sed -n '1,205p' mobile/src/worktree/agent-row-display.ts
sed -n '1,145p' mobile/src/worktree/agent-row-display.test.ts
printf '%s\n' '--- renderer row mark binding and acknowledgement projection ---'
rg -n 'agentVerdictDisplayMark|selectAcknowledgedAgentTimes|acknowledgedAt:' src/renderer/src/components/sidebar src/renderer/src/components/dashboard src/renderer/src/attention
printf '%s\n' '--- status entry builder and contract ---'
sed -n '80,235p' src/renderer/src/store/slices/agent-status-live-entry-builder.ts
sed -n '1,145p' src/renderer/src/store/slices/agent-status-contract.ts
printf '%s\n' '--- state start time zero/default handling ---'
rg -n -C 3 'stateStartedAt.*(Date\.now|now|0)|(?:Date\.now|now|0).*stateStartedAt|stateStartedAt\s*\?\?' src/renderer/src/store src/renderer/src/components/terminal-pane src/renderer/src/runtime mobile/src/worktreeRepository: stablyai/orca
Length of output: 45655
🏁 Script executed:
printf '%s\n' '--- status builder ---'
sed -n '90,225p' src/renderer/src/store/slices/agent-status-live-entry-builder.ts
printf '%s\n' '--- status contract ---'
sed -n '1,125p' src/renderer/src/store/slices/agent-status-contract.ts
printf '%s\n' '--- acknowledgement projection ---'
cat -n src/renderer/src/components/sidebar/worktree-card-agent-ack-inputs.ts
rg -n -C 5 'selectAcknowledgedAgentTimes|agentVerdictDisplayMark' src/renderer/src/components/sidebar src/renderer/src/components/dashboard
printf '%s\n' '--- acknowledgement state hydration/persistence ---'
rg -n -C 3 'acknowledgedAgentsByPaneKey|read.*Persisted|hydrate.*acknowledged|acknowledged.*hydrate' src/renderer/src/store src/renderer/src/app-shell
printf '%s\n' '--- runtime zero-valued production assignments ---'
rg -n 'stateStartedAt\s*:\s*(0|[^,;]*\?\?\s*0)|stateStartedAt\s*=\s*(0|[^;]*\?\?\s*0)' src/renderer/src --glob '!**/*.test.*' --glob '!**/*.spec.*' mobile/src --glob '!**/*.test.*' --glob '!**/*.spec.*'Repository: stablyai/orca
Length of output: 41735
🏁 Script executed:
printf '%s\n' '--- production stateStartedAt references ---'
rg -n 'stateStartedAt' src/renderer/src --glob '!**/*.test.*' --glob '!**/*.spec.*' | head -100
printf '%s\n' '--- timing producers and agent status parser ---'
rg -n -C 2 'AgentStatusTiming|timing:.*stateStartedAt|stateStartedAt:' src/renderer/src src/main src/shared --glob '!**/*.test.*' --glob '!**/*.spec.*' | head -180
rg -n -C 3 'stateStartedAt|parseAgentStatus' src/shared/agent-status-types.ts
printf '%s\n' '--- acknowledgement display bindings ---'
rg -n 'selectAcknowledgedAgentTimes|acknowledgedAt|AcknowledgedAgentRow|agentVerdictDisplayMark' src/renderer/src/components/sidebar src/renderer/src/components/dashboard src/renderer/src/lib/agent-entry-acknowledgement.ts --glob '!**/*.test.*'
sed -n '1,140p' src/renderer/src/lib/agent-entry-acknowledgement.ts
sed -n '130,185p' src/renderer/src/components/dashboard/DashboardAgentRow.tsxRepository: stablyai/orca
Length of output: 42297
🏁 Script executed:
printf '%s\n' '--- status event timestamp ingress ---'
sed -n '210,255p' src/renderer/src/hooks/ipc-events/agent-status-event-applicator.ts
sed -n '270,305p' src/shared/agent-status-types.ts
printf '%s\n' '--- card acknowledgement join ---'
sed -n '145,180p' src/renderer/src/components/sidebar/useWorktreeAgentRows.ts
sed -n '155,180p' src/renderer/src/components/sidebar/worktree-agent-activity-summary.ts
printf '%s\n' '--- acknowledgement storage contract ---'
sed -n '125,155p' src/renderer/src/store/slices/ui/ui-slice-contract-core.ts
sed -n '130,190p' src/renderer/src/store/slices/ui/ui-slice-hydration-values.ts
sed -n '1,75p' src/renderer/src/store/slices/ui/ui-slice-agent-notification-acknowledgement.ts
printf '%s\n' '--- status builder timestamp selection ---'
sed -n '100,120p' src/renderer/src/store/slices/agent-status-live-entry-builder.tsRepository: stablyai/orca
Length of output: 11543
🏁 Script executed:
printf '%s\n' '--- production hook/status clock producers ---'
rg -n -C 2 'stateStartedAt' src/main/agent-hooks src/shared/agent-hook-listener --glob '!**/*.test.*' --glob '!**/*.spec.*' | head -180
printf '%s\n' '--- explicit source timestamp contracts ---'
rg -n -C 2 'stateStartedAt|statusStartedAt' src/main/agent-hooks src/shared/agent-hook-listener/main-agent-turn-state.ts src/shared/structured-agent-session-status-started-at.ts --glob '!**/*.test.*' --glob '!**/*.spec.*' | head -140Repository: stablyai/orca
Length of output: 29228
🏁 Script executed:
printf '%s\n' '--- structured status timestamp contracts ---'
rg -n -C 3 'statusStartedAt|AgentSessionStatusSummary' src/shared src/main/agent-hooks --glob '!**/*.test.*' --glob '!**/*.spec.*' | head -170
sed -n '88,108p' src/main/agent-hooks/server/server-ingest-structured.ts
printf '%s\n' '--- acknowledgement hydration sanitizer ---'
rg -n -A 28 -B 3 'sanitizeAcknowledgedAgentsByPaneKey' src/renderer/src/store/slices/ui/ui-slice-hydration-values.tsRepository: stablyai/orca
Length of output: 14590
🏁 Script executed:
printf '%s\n' '--- structured timestamp producer ---'
sed -n '1,105p' src/shared/structured-agent-session-status-started-at.ts
sed -n '226,291p' src/shared/structured-agent-session-projection.ts
printf '%s\n' '--- acknowledgement value sanitizer ---'
rg -n -C 8 'sanitizeAcknowledgedAgentsByPaneKey' src/renderer/src/store/slices/ui/ui-slice-hydration-sanitizers.tsRepository: stablyai/orca
Length of output: 8237
🏁 Script executed:
printf '%s\n' '--- turn/journal timestamp contracts ---'
rg -n -C 4 'readAgentJournalTurn|requestedAt|settledAt|observedAt|submittedAt|Number\.isFinite|> 0' src/shared/agent-session-turn-record.ts src/shared/agent-session-journal-types.ts src/shared/agent-session-journal-producer.ts src/shared/agent-session-latest-request.tsRepository: stablyai/orca
Length of output: 6792
🏁 Script executed:
printf '%s\n' '--- structured latest request clock ---'
rg -n -C 5 'settledAt|requestedAt|completedAt|Date\.now|Number\.isFinite|> 0' src/shared/structured-agent-session-latest-request.ts
printf '%s\n' '--- structured ingestion binding ---'
sed -n '35,110p' src/main/agent-hooks/server/server-ingest-structured.tsRepository: stablyai/orca
Length of output: 5778
🏁 Script executed:
rg -n -C 6 'continueMainAgentStatus' src/shared/agent-hook-listener src/main/agent-hooks --glob '!**/*.test.*' --glob '!**/*.spec.*'Repository: stablyai/orca
Length of output: 12179
🏁 Script executed:
rg -n -A 34 -B 5 'function continueMainAgentStatus|const continueMainAgentStatus' src/shared/agent-lead-status-fold.tsRepository: stablyai/orca
Length of output: 1050
🏁 Script executed:
printf '%s\n' '--- production display-mark bindings ---'
rg -n -C 4 'agentVerdictDisplayMark|isAgentTurnAcknowledged' src/renderer/src/lib src/renderer/src/components/sidebar/worktree-card-agent-summary.ts src/renderer/src/components/sidebar/worktree-agent-activity-summary.ts
printf '%s\n' '--- acknowledgement target call sites ---'
rg -n -C 2 'computeAgentAcknowledgementTargets' src/renderer/srcRepository: stablyai/orca
Length of output: 15722
🏁 Script executed:
sed -n '1,105p' src/shared/structured-agent-session-agent-status.ts
printf '%s\n' '--- structured summary outcome field ---'
sed -n '210,263p' src/shared/agent-session-wire.tsRepository: stablyai/orca
Length of output: 5653
Treat zero-sentinel acknowledgements as unseen.
When the effective attention clock is 0 and no acknowledgement exists, isAgentTurnAcknowledged returns true. computeAgentAcknowledgementTargets then omits the subject. Live structured status can preserve a zero clock. The sidebar selector also maps a missing acknowledgement to 0, so checking only for undefined would still suppress the Failed mark. The mobile parity test avoids this case by setting the desktop clock to 1.
Suggested fix
- return (acknowledgedAt ?? 0) >= stateStartedAt
+ return acknowledgedAt !== undefined && acknowledgedAt > 0 && acknowledgedAt >= stateStartedAt📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return (acknowledgedAt ?? 0) >= stateStartedAt | |
| return acknowledgedAt !== undefined && acknowledgedAt > 0 && acknowledgedAt >= stateStartedAt |
…2025/crash-cut-seen-gating
…2025/crash-cut-seen-gating
|
Back to draft, to be closed: this is the wrong model. A chat interrupted by a crash, quit or restart is still interrupted after you open it, so its status shouldn't clear when you view it. It should read Interrupted until the chat's state actually changes (a new turn starts, or it moves to another state). A replacement PR will show an Interrupted status derived from the chat's latest turn, with no "seen" record. This PR will be closed when that one is up. |
|
Closed in favour of #25670: an interrupted chat reads Interrupted until its state actually changes. Viewing it changes nothing, and there is no seen record or timer. |

ELI5
Sometimes Claude or Codex stops in the middle of a reply without you asking: Orca crashed, you quit Orca, Orca closed an idle chat, or the agent's own process died. When that happened, Orca put a red Failed mark on that agent in the sidebar, on the chat's tab and in a few other places. The mark never went away. It stayed red even after you had opened and read the chat.
Now the red mark lasts only until you have seen the chat. After that it reads Done, like any finished reply.
What Changed
This is part 2 of 2. Part 1, #25043, is merged: a crash-cut reply reads "Worked for N" with one explanation. This PR makes its red mark clear once you have seen the chat.
The problem. When the agent's process goes away mid-reply and nobody pressed Stop, Orca records the turn as ended with no verdict, which it reads as the outcome "interruption". Every agent-status surface turned that into a red Failed mark, and nothing cleared it once you had seen the chat:
Before (as you see it): a red Failed mark on all of those, which stays after you read the chat.
After (as you see it): the mark reads Failed until you open the chat. Then every one of those surfaces reads Done, like any finished turn.
What did not change:
Mechanism:
isAgentTurnAcknowledged(src/shared/agent-turn-acknowledgement.ts). It is Orca's existing acknowledgement: acknowledged at or after the state began. That is the rule that already un-bolds an agent row when you visit it. It adds no new state.agentAttentionStartedAt).agentVerdictDisplayMark) takes that acknowledgement as a required part of what it reads. A surface that leaves it out fails to compile.useWorktreeAgentRows), which also feed the collapsed summary and the notes send menuWhy
In the common pattern, the unread watermark is part of the record the indicator is computed from, so every surface reads it the same way. An item that errored is marked only while unread. Orca already records when you last looked at each agent, so this adds no state, and the bold row and the red mark now always agree.
Alternatives considered:
Differences from the common pattern
mobile/src/worktree/agent-row-display.ts). Follow-up: keep the acknowledgement on the host so desktop and phone share it.Linked Issue
None. Found during live testing of #24233.
Visual Proof
Live check on a second Mac with a dev build carrying both parts and a real Claude chat (isolated profile, app hidden, driven over the DevTools protocol). These are the sidebar's screenshots; the transcript's are in part 1.
Orca killed mid-reply, then relaunched. Before the chat is opened, the sidebar row reads Failed in red:
Once the chat is acknowledged as seen, the row, the workspace card and the chat's own tab read Done (before, then after):
Unchanged case. A real failure (bad model) stays Failed after viewing:
Testing
New tests, one per rule:
Sweep: 661 renderer test files that fake the store without the acknowledgement map, plus the activity-status test that needed it, all pass (662 files, 5,605 tests).
Ablations. I removed each rule in turn, and its tests went red (17 rules):
AI Disclosure
Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)