diff --git a/src/renderer/src/components/terminal-pane/pane-agent-session-id.test.ts b/src/renderer/src/components/terminal-pane/pane-agent-session-id.test.ts index 5c429202552..ea0b24a2d56 100644 --- a/src/renderer/src/components/terminal-pane/pane-agent-session-id.test.ts +++ b/src/renderer/src/components/terminal-pane/pane-agent-session-id.test.ts @@ -7,11 +7,13 @@ const PANE_KEY = 'tab-1:11111111-1111-4111-8111-111111111111' function state( live?: AgentStatusEntry, - sleeping?: SleepingAgentSessionRecord + sleeping?: SleepingAgentSessionRecord, + shellForeground = false ): PaneAgentSessionIdState { return { agentStatusByPaneKey: live ? { [PANE_KEY]: live } : {}, - sleepingAgentSessionsByPaneKey: sleeping ? { [PANE_KEY]: sleeping } : {} + sleepingAgentSessionsByPaneKey: sleeping ? { [PANE_KEY]: sleeping } : {}, + paneForegroundAgentByPaneKey: { [PANE_KEY]: { agent: 'claude', shellForeground } } } } @@ -68,6 +70,39 @@ describe('resolvePaneAgentSessionId', () => { ).toBe('confirmed-session') }) + describe('liveness', () => { + it('is absent once the pane is proven back at the shell', () => { + expect( + resolvePaneAgentSessionId(state(live('live-session'), undefined, true), PANE_KEY) + ).toBe(null) + }) + + it('is absent at the shell even when a durable record survives the exit', () => { + expect( + resolvePaneAgentSessionId(state(undefined, sleeping('sleeping-session'), true), PANE_KEY) + ).toBeNull() + }) + + it('keeps a session whose foreground evidence is only that an agent runs', () => { + expect( + resolvePaneAgentSessionId(state(live('live-session'), undefined, false), PANE_KEY) + ).toBe('live-session') + }) + + it('keeps a session for a pane with no foreground evidence at all', () => { + expect( + resolvePaneAgentSessionId( + { + agentStatusByPaneKey: { [PANE_KEY]: live('live-session') }, + sleepingAgentSessionsByPaneKey: {}, + paneForegroundAgentByPaneKey: {} + }, + PANE_KEY + ) + ).toBe('live-session') + }) + }) + it('does not read identity from a sibling pane', () => { const sibling = 'tab-1:22222222-2222-4222-8222-222222222222' expect(resolvePaneAgentSessionId(state(undefined, sleeping('session-1')), sibling)).toBeNull() diff --git a/src/renderer/src/components/terminal-pane/pane-agent-session-id.ts b/src/renderer/src/components/terminal-pane/pane-agent-session-id.ts index 682f8de175c..59ab6f38e86 100644 --- a/src/renderer/src/components/terminal-pane/pane-agent-session-id.ts +++ b/src/renderer/src/components/terminal-pane/pane-agent-session-id.ts @@ -1,16 +1,24 @@ import type { AgentStatusEntry } from '../../../../shared/agent-status-types' import type { SleepingAgentSessionRecord } from '../../../../shared/agent-session-resume' +import type { PaneForegroundAgentEntry } from '../../store/slices/pane-foreground-agent' export type PaneAgentSessionIdState = { agentStatusByPaneKey: Record sleepingAgentSessionsByPaneKey: Record + paneForegroundAgentByPaneKey: Record } -/** Resolves the provider session owned by one exact terminal pane. */ +/** Resolves the provider session owned by one exact terminal pane, while its agent is still live. */ export function resolvePaneAgentSessionId( state: PaneAgentSessionIdState, paneKey: string ): string | null { + // OSC 133;D proves the pane is back at the shell. The durable record outlives that exit on + // purpose (cold restore resumes from it), so gate it here too — otherwise the gate would only + // hold for panes whose agent has no resumable record. + if (state.paneForegroundAgentByPaneKey[paneKey]?.shellForeground === true) { + return null + } const live = state.agentStatusByPaneKey[paneKey] if (live && live.restoredUnconfirmed !== true) { return live.providerSession?.id ?? null