From 4f87ff8a4705e8336a5ee6b245fa69a7c78fee23 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 14 Aug 2026 16:13:05 -0700 Subject: [PATCH] fix(agent-status): invalidate background presentation mirrors --- ...k-status-session-tabs-invalidation.test.ts | 3 +- .../hook-status-session-tabs-invalidation.ts | 3 +- src/main/runtime/orca-runtime.test.ts | 18 +++++- src/main/runtime/orca-runtime.ts | 3 +- .../AgentTerminalDialog.test.tsx | 13 +++++ .../dashboard-popout/AgentTerminalDialog.tsx | 6 +- .../src/runtime/web-session-tabs-sync.test.ts | 58 +++++++++++++++++++ .../src/runtime/web-session-tabs-sync.ts | 2 + 8 files changed, 99 insertions(+), 7 deletions(-) diff --git a/src/main/agent-hooks/hook-status-session-tabs-invalidation.test.ts b/src/main/agent-hooks/hook-status-session-tabs-invalidation.test.ts index 14235654d37..b993ed14300 100644 --- a/src/main/agent-hooks/hook-status-session-tabs-invalidation.test.ts +++ b/src/main/agent-hooks/hook-status-session-tabs-invalidation.test.ts @@ -35,7 +35,8 @@ describe('createHookStatusSessionTabsInvalidator', () => { ['agentType', { agentType: 'codex' }], ['toolName', { toolName: 'Bash' }], ['interactivePrompt', { interactivePrompt: '{"questions":[]}' }], - ['interrupted', { interrupted: true }] + ['interrupted', { interrupted: true }], + ['backgroundOnly', { backgroundOnly: true }] ])('invalidates when %s changes', (_field, payload) => { const changed = createHookStatusSessionTabsInvalidator() changed(working()) diff --git a/src/main/agent-hooks/hook-status-session-tabs-invalidation.ts b/src/main/agent-hooks/hook-status-session-tabs-invalidation.ts index 59b259fa849..0f7e8b63771 100644 --- a/src/main/agent-hooks/hook-status-session-tabs-invalidation.ts +++ b/src/main/agent-hooks/hook-status-session-tabs-invalidation.ts @@ -28,7 +28,8 @@ export function createHookStatusSessionTabsInvalidator(): { (previous.agentType ?? null) !== (next.agentType ?? null) || (previous.toolName ?? null) !== (next.toolName ?? null) || (previous.interactivePrompt ?? null) !== (next.interactivePrompt ?? null) || - (previous.interrupted ?? false) !== (next.interrupted ?? false) + (previous.interrupted ?? false) !== (next.interrupted ?? false) || + (previous.backgroundOnly ?? false) !== (next.backgroundOnly ?? false) ) } // Why: a cleared pane must re-arm, else the memo swallows the first event of the diff --git a/src/main/runtime/orca-runtime.test.ts b/src/main/runtime/orca-runtime.test.ts index 3ee35d809df..550b3e65f44 100644 --- a/src/main/runtime/orca-runtime.test.ts +++ b/src/main/runtime/orca-runtime.test.ts @@ -19258,13 +19258,25 @@ describe('OrcaRuntimeService', () => { runtime.onPtyData( 'hook-only-pty', - '\x1b]9999;{"state":"waiting","prompt":"fix the tests","agentType":"opencode"}\x07', + '\x1b]9999;{"state":"working","prompt":"fix the tests","agentType":"opencode","backgroundOnly":true}\x07', 101 ) await waitForMobileSessionTabsEvents(events, 2) expect(events).toHaveLength(2) expect(events[1]?.tabs[0]?.type === 'terminal' && events[1].tabs[0].agentStatus).toEqual( + expect.objectContaining({ state: 'working', backgroundOnly: true }) + ) + + runtime.onPtyData( + 'hook-only-pty', + '\x1b]9999;{"state":"waiting","prompt":"fix the tests","agentType":"opencode"}\x07', + 102 + ) + + await waitForMobileSessionTabsEvents(events, 3) + expect(events).toHaveLength(3) + expect(events[2]?.tabs[0]?.type === 'terminal' && events[2].tabs[0].agentStatus).toEqual( expect.objectContaining({ state: 'waiting' }) ) @@ -36714,7 +36726,8 @@ describe('OrcaRuntimeService', () => { lastAssistantMessage: 'on it', connectionId: null, receivedAt: now, - stateStartedAt: now - 100 + stateStartedAt: now - 100, + backgroundOnly: true } ] }) @@ -36772,6 +36785,7 @@ describe('OrcaRuntimeService', () => { taskTitle: 'Dispatch prompt work', displayName: 'Review dispatch prompts and make worker labels distinct', lastAssistantMessage: 'on it', + backgroundOnly: true, stateStartedAt: now - 100, updatedAt: now }) diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index 62d60ff2280..f121e4abb9e 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -10928,7 +10928,8 @@ export class OrcaRuntimeService { (previous.payload.agentType ?? null) !== (payload.agentType ?? null) || (previous.payload.toolName ?? null) !== (payload.toolName ?? null) || (previous.payload.interactivePrompt ?? null) !== (payload.interactivePrompt ?? null) || - (previous.payload.interrupted ?? false) !== (payload.interrupted ?? false) + (previous.payload.interrupted ?? false) !== (payload.interrupted ?? false) || + (previous.payload.backgroundOnly ?? false) !== (payload.backgroundOnly ?? false) ) } diff --git a/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.test.tsx b/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.test.tsx index 7022d7e5731..0fe1643a9ba 100644 --- a/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.test.tsx +++ b/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.test.tsx @@ -118,6 +118,19 @@ describe('AgentTerminalDialog', () => { expect(screen.getByText(/Claude · Done/)).toBeInTheDocument() }) + it('labels background-only work without claiming the foreground turn is working', () => { + render( + {}} + onReveal={() => {}} + /> + ) + + expect(screen.getByText(/Claude · Background work/)).toBeInTheDocument() + expect(screen.queryByText(/Claude · Working/)).not.toBeInTheDocument() + }) + it('preserves the execution host when revealing a colliding worktree ID', () => { const onReveal = vi.fn() render( diff --git a/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx b/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx index a5eea33adc3..cb9292b6d13 100644 --- a/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx +++ b/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx @@ -39,6 +39,9 @@ function AgentTerminalFrame({ onOpenChange, onReveal }: AgentTerminalFrameProps): React.JSX.Element { + const displayState = dashboardCardDisplayState(card) + const foregroundState = + card.backgroundOnly === true && displayState === 'working' ? 'background' : displayState const reveal = (): void => { onReveal({ repoId: card.repoId, @@ -58,8 +61,7 @@ function AgentTerminalFrame({ {title} - {formatAgentTypeLabel(card.agentType)} ·{' '} - {agentStateLabel(dashboardCardDisplayState(card))} + {formatAgentTypeLabel(card.agentType)} · {agentStateLabel(foregroundState)}