merge: PR #19736 into integration stack

This commit is contained in:
Neil
2026-09-10 15:04:35 -07:00
3 changed files with 90 additions and 4 deletions
@@ -4,6 +4,10 @@ import { useAppStore, type AppState } from '@/store'
import { resumeSleepingAgentSessionsForWorktree } from './resume-sleeping-agent-session'
import { makeCreatedAgentWorktree } from '@/lib/worktree-activation-created-agent-test-state'
import { makePaneKey } from '../../../shared/stable-pane-id'
import {
RESUMABLE_TUI_AGENTS,
type AgentProviderSessionMetadata
} from '../../../shared/agent-session-resume'
import {
hasHostSessionMirrorHydrated,
markHostSessionMirrorHydrated,
@@ -194,6 +198,72 @@ describe('parked mirrored-pane resume replay', () => {
expect(Object.keys(after.automaticAgentResumeClaimsByTabId)).toHaveLength(0)
})
it.each(
RESUMABLE_TUI_AGENTS.flatMap((agent) =>
(['working', 'done'] as const).flatMap((checkpointState) =>
[
{ livePtyIds: ['pty-host-old-1'], expectedLaunches: 0 },
{ livePtyIds: [], expectedLaunches: 1 },
{ livePtyIds: ['other-pane-pty'], expectedLaunches: 1 }
].map((scenario) => ({ agent, checkpointState, ...scenario }))
)
)
)(
'checks exact live ownership before resuming $agent $checkpointState with $livePtyIds',
({ agent, checkpointState, livePtyIds, expectedLaunches }) => {
const worktree = makeRuntimeOwnedWorktree()
seedState(worktree)
const paneKey = seedSleepingRecord(worktree.id, 'completed-session')
const providerSession: AgentProviderSessionMetadata = {
key: agent === 'antigravity' ? 'conversation_id' : 'session_id',
id: 'completed-session',
...(agent === 'pi' || agent === 'prime-agent'
? { transcriptPath: `/remote/${agent}/session.jsonl` }
: {})
}
const record = {
...useAppStore.getState().sleepingAgentSessionsByPaneKey[paneKey]!,
agent,
providerSession
}
const stalePaneKey = makePaneKey('old-tab', LEAF_ID)
useAppStore.setState({
ptyIdsByTabId: { [WEB_TAB_ID]: [...livePtyIds] },
sleepingAgentSessionsByPaneKey: {
[stalePaneKey]: {
...record,
paneKey: stalePaneKey,
tabId: 'old-tab',
state: checkpointState,
origin: 'quit'
}
},
agentStatusByPaneKey: {
[paneKey]: {
paneKey,
tabId: WEB_TAB_ID,
worktreeId: worktree.id,
agentType: record.agent,
providerSession: record.providerSession,
state: 'done',
prompt: '',
updatedAt: 2000,
stateStartedAt: 2000,
stateHistory: []
}
}
})
markHostSessionMirrorHydrated(RUNTIME_ENV_ID)
expect(resumeSleepingAgentSessionsForWorktree(worktree.id)).toBe(expectedLaunches)
const after = useAppStore.getState()
expect(after.tabsByWorktree[worktree.id]).toHaveLength(1 + expectedLaunches)
expect(after.tabsByWorktree[worktree.id]?.[0]?.id).toBe(WEB_TAB_ID)
expect(after.sleepingAgentSessionsByPaneKey[stalePaneKey]).toBeUndefined()
expect(Object.keys(after.pendingStartupByTabId)).toHaveLength(expectedLaunches)
}
)
it('drops the hydrated verdict when the host reconnects under a new runtime id', () => {
connectRuntime('runtime-a')
markHostSessionMirrorHydrated(RUNTIME_ENV_ID)
@@ -4,10 +4,12 @@ import {
type SleepingAgentSessionRecord
} from '../../../shared/agent-session-resume'
import { AGENT_STATUS_STALE_AFTER_MS } from '../../../shared/agent-status-types'
import { parsePaneKey } from '../../../shared/stable-pane-id'
import {
getProviderSessionClaimKey,
isPassiveCompletedHibernationEvidence,
recordPaneIsOwnedByPreservedPane
recordPaneIsOwnedByPreservedPane,
stablePaneHasLivePty
} from './sleeping-agent-pane-ownership'
import {
launchSleepingAgentSession,
@@ -100,11 +102,25 @@ function activeOrQueuedResumeClaimsProviderSession(
if (samePaneOwnsRecovery && entry.paneKey === record.paneKey) {
continue
}
const tabId = getAgentStatusTabId(entry)
const pane = parsePaneKey(entry.paneKey)
// A completed turn still owns its transcript while its exact PTY is live.
const completedPaneIsLive = Boolean(
entry.state === 'done' &&
pane &&
tabId === pane.tabId &&
stablePaneHasLivePty(
pane.tabId,
pane.leafId,
state.ptyIdsByTabId,
state.terminalLayoutsByTabId[pane.tabId]
)
)
if (
worktreeTabIds.has(getAgentStatusTabId(entry) ?? '') &&
worktreeTabIds.has(tabId ?? '') &&
entry.worktreeId === record.worktreeId &&
entry.agentType === record.agent &&
entry.state !== 'done' &&
(entry.state !== 'done' || completedPaneIsLive) &&
agentProviderSessionsEqual(record.agent, entry.providerSession, record.providerSession)
) {
return true
@@ -94,7 +94,7 @@ function hasRestorableStablePanePty(
// the pane that reconnects on activation. Liveness comes from the runtime
// live-PTY map (ptyIdsByTabId), not the layout's ptyIdsByLeafId snapshot, which
// persists stale across sleep/restart.
function stablePaneHasLivePty(
export function stablePaneHasLivePty(
tabId: string,
leafId: string,
ptyIdsByTabId: Record<string, string[]>,