fix: bind structured TUI before rollout proof

This commit is contained in:
Brennan Benson
2026-08-11 05:20:06 -07:00
parent 1ab28b5fc0
commit 6bc01085d0
4 changed files with 248 additions and 2 deletions
@@ -0,0 +1,126 @@
import { describe, expect, it, vi } from 'vitest'
import type { StructuredAgentSessionHandoffTransport } from '../native-chat/agent-session-wire/structured-agent-session-handoff-types'
import { OrcaRuntimeService } from './orca-runtime'
const readStructuredTuiProcessIdentity = vi.hoisted(() => vi.fn())
vi.mock('./structured-tui-process-identity', () => ({ readStructuredTuiProcessIdentity }))
const WORKTREE_ID = 'repo-1::/tmp/structured-handoff'
function notifier(revealTerminalSession: ReturnType<typeof vi.fn>) {
return {
worktreesChanged: vi.fn(),
reposChanged: vi.fn(),
activateWorktree: vi.fn(),
createTerminal: vi.fn(),
revealTerminalSession,
splitTerminal: vi.fn(),
renameTerminal: vi.fn(),
focusTerminal: vi.fn(),
closeTerminal: vi.fn(),
sleepWorktree: vi.fn(),
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
}
}
describe('structured TUI launch tab binding', () => {
it('proves the published launch tab before returning its revealed renderer binding', async () => {
const revealTerminalSession = vi.fn(
(_worktreeId: string, _options: { tabId?: string; leafId?: string; ptyId?: string }) =>
Promise.resolve({ tabId: 'tab-renderer' })
)
const runtime = new OrcaRuntimeService({
getSettings: () => ({
disabledTuiAgents: [],
agentCmdOverrides: {},
agentDefaultArgs: {},
agentDefaultEnv: {}
})
} as never)
runtime.setNotifier(notifier(revealTerminalSession) as never)
runtime.setPtyController({
spawn: vi.fn().mockResolvedValue({ id: 'pty-structured', pid: 4242 }),
write: () => true,
kill: () => true,
getForegroundProcess: async () => null
})
const internal = runtime as unknown as {
createStructuredAgentSessionHandoffTransport(): StructuredAgentSessionHandoffTransport
resolveTerminalWorkspaceLaunchScope(): Promise<{
id: string
path: string
connectionId: null
repo: null
folderWorkspace: null
}>
markLocalWorkspaceTrustedForAgent(): void
waitForTerminal(): Promise<unknown>
waitForStructuredTuiProof(): Promise<{ transcriptPath?: string }>
}
internal.resolveTerminalWorkspaceLaunchScope = vi.fn(async () => ({
id: WORKTREE_ID,
path: '/tmp/structured-handoff',
connectionId: null,
repo: null,
folderWorkspace: null
}))
internal.markLocalWorkspaceTrustedForAgent = vi.fn()
const waitForTerminal = vi.fn(async () => ({}))
internal.waitForTerminal = waitForTerminal
const waitForStructuredTuiProof = vi.fn(async () => {
const snapshot = await runtime.listMobileSessionTabs(`id:${WORKTREE_ID}`)
expect(snapshot.tabs).toContainEqual(
expect.objectContaining({
type: 'terminal',
parentTabId: expect.any(String),
leafId: expect.any(String),
ptyId: 'pty-structured',
terminal: expect.any(String)
})
)
expect(revealTerminalSession).not.toHaveBeenCalled()
return { transcriptPath: '/tmp/rollout.jsonl' }
})
internal.waitForStructuredTuiProof = waitForStructuredTuiProof
readStructuredTuiProcessIdentity.mockResolvedValue({
hostId: 'local',
pid: 4243,
processStartTimeMs: 10,
spawnToken: 'spawn-token'
})
const transport = internal.createStructuredAgentSessionHandoffTransport()
const owner = await transport.launchTui({
record: {
sessionId: 'session-1',
location: { workspaceId: WORKTREE_ID, executionHostId: 'local' },
accountHome: { variable: 'CODEX_HOME', path: '/tmp/codex-home' },
providerHandleChain: [
{ handle: { provider: 'codex', threadId: 'thread-1' }, observedAt: 1 }
]
} as never,
fence: 3,
spawnToken: 'spawn-token'
})
const reveal = revealTerminalSession.mock.calls[0]?.[1] as {
tabId: string
leafId: string
}
expect(owner.terminal).toMatchObject({
tabId: 'tab-renderer',
paneKey: `${reveal.tabId}:${reveal.leafId}`
})
expect(waitForTerminal).toHaveBeenCalledWith(
owner.terminal.handle,
expect.objectContaining({ condition: 'tui-idle' })
)
expect(waitForStructuredTuiProof).toHaveBeenCalledOnce()
expect(waitForStructuredTuiProof.mock.invocationCallOrder[0]).toBeLessThan(
revealTerminalSession.mock.invocationCallOrder[0]!
)
})
})
+6 -2
View File
@@ -9123,6 +9123,10 @@ export class OrcaRuntimeService {
if (!terminal.processId || !terminal.paneKey || !terminal.tabId) {
throw new Error('The resumed terminal did not publish a process identity.')
}
await this.waitForTerminal(terminal.handle, {
condition: 'tui-idle',
timeoutMs: 30_000
})
const proof = await this.waitForStructuredTuiProof({
handle: terminal.handle,
paneKey: terminal.paneKey,
@@ -9131,11 +9135,11 @@ export class OrcaRuntimeService {
codexHome: record.accountHome.path,
sessionId: record.sessionId
})
await this.focusTerminal(terminal.handle)
const revealed = await this.focusTerminal(terminal.handle)
return {
terminal: {
handle: terminal.handle,
tabId: terminal.tabId,
tabId: revealed.tabId,
paneKey: terminal.paneKey
},
process: await readStructuredTuiProcessIdentity({
@@ -0,0 +1,104 @@
import { describe, expect, it } from 'vitest'
import type { RuntimeMobileSessionTabsResult } from '../../../shared/runtime-types'
import type { Tab } from '../../../shared/types'
import { applyWebSessionTabsSnapshot, type WebSessionTabsSyncState } from './web-session-tabs-sync'
const WORKTREE_ID = 'repo-1::/worktree'
const GROUP_ID = 'group-1'
function structuredTab(sessionId: string, sortOrder: number): Tab {
return {
id: `structured-agent-session-${sessionId}`,
entityId: sessionId,
groupId: GROUP_ID,
worktreeId: WORKTREE_ID,
contentType: 'agent-session',
agentSessionAgent: 'codex',
label: 'Codex Chat',
customLabel: null,
color: null,
sortOrder,
createdAt: sortOrder + 1
}
}
describe('web session structured tab focus', () => {
it('keeps the exact active structured tab across a host snapshot', () => {
const first = structuredTab('session-1', 0)
const second = structuredTab('session-2', 1)
const state = {
activeBrowserTabId: null,
activeBrowserTabIdByWorktree: {},
activeFileId: null,
activeFileIdByWorktree: {},
activeGroupIdByWorktree: { [WORKTREE_ID]: GROUP_ID },
activeTabId: null,
activeTabIdByWorktree: {},
activeTabType: 'terminal',
activeTabTypeByWorktree: { [WORKTREE_ID]: 'terminal' },
activeWorktreeId: WORKTREE_ID,
agentStatusByPaneKey: {},
agentStatusEpoch: 0,
browserCertificateFailuresByPageId: {},
browserPagesByWorkspace: {},
browserTabsByWorktree: {},
groupsByWorktree: {
[WORKTREE_ID]: [
{
id: GROUP_ID,
worktreeId: WORKTREE_ID,
activeTabId: second.id,
tabOrder: [first.id, second.id]
}
]
},
layoutByWorktree: {},
openFiles: [],
ptyIdsByTabId: {},
remoteBrowserPageHandlesByPageId: {},
tabBarOrderByWorktree: { [WORKTREE_ID]: [first.id, second.id] },
tabsByWorktree: {},
terminalLayoutsByTabId: {},
unifiedTabsByWorktree: { [WORKTREE_ID]: [first, second] },
unreadTerminalTabs: {},
sortEpoch: 0
} as WebSessionTabsSyncState
const snapshot: RuntimeMobileSessionTabsResult = {
worktree: WORKTREE_ID,
publicationEpoch: 'epoch-1',
snapshotVersion: 2,
activeGroupId: GROUP_ID,
activeTabId: 'agent-session:session-1',
activeTabType: 'agent-session',
tabGroups: [
{
id: GROUP_ID,
activeTabId: 'agent-session:session-1',
tabOrder: ['agent-session:session-1', 'agent-session:session-2']
}
],
tabs: [
{
type: 'agent-session',
id: 'agent-session:session-1',
title: 'Codex Chat',
sessionId: 'session-1',
agent: 'codex',
isActive: true
},
{
type: 'agent-session',
id: 'agent-session:session-2',
title: 'Codex Chat',
sessionId: 'session-2',
agent: 'codex',
isActive: false
}
]
}
const patch = applyWebSessionTabsSnapshot(state, snapshot, 'environment-1', 10)
expect(patch.groupsByWorktree?.[WORKTREE_ID]?.[0]?.activeTabId).toBe(second.id)
})
})
@@ -2004,6 +2004,18 @@ function findCurrentVisibleUnifiedTabId(args: {
if (!nextUnifiedTabs) {
return null
}
const activeGroupId = state.activeGroupIdByWorktree[worktreeId]
const activeGroupTabId = state.groupsByWorktree[worktreeId]?.find(
(group) => group.id === activeGroupId
)?.activeTabId
if (
activeGroupTabId &&
nextUnifiedTabs.some(
(tab) => tab.id === activeGroupTabId && tab.contentType === 'agent-session'
)
) {
return activeGroupTabId
}
const currentVisibleType =
state.activeTabTypeByWorktree[worktreeId] ??
(state.activeWorktreeId === worktreeId ? state.activeTabType : null)