diff --git a/src/main/runtime/orca-runtime-structured-tui-tab-binding.test.ts b/src/main/runtime/orca-runtime-structured-tui-tab-binding.test.ts index 34c963c3203..e11b4d6109a 100644 --- a/src/main/runtime/orca-runtime-structured-tui-tab-binding.test.ts +++ b/src/main/runtime/orca-runtime-structured-tui-tab-binding.test.ts @@ -3,12 +3,15 @@ import type { StructuredAgentSessionHandoffTransport } from '../native-chat/agen import { createEphemeralAgentSessionClaimSigner } from './agent-session-claim-identity' import { OrcaRuntimeService } from './orca-runtime' -const { probeAgentSessionProcessIdentity, readStructuredTuiProcessIdentity } = vi.hoisted(() => ({ - probeAgentSessionProcessIdentity: vi.fn(), - readStructuredTuiProcessIdentity: vi.fn() -})) +const { probeAgentSessionProcessIdentity, proveCodexTuiRollout, readStructuredTuiProcessIdentity } = + vi.hoisted(() => ({ + probeAgentSessionProcessIdentity: vi.fn(), + proveCodexTuiRollout: vi.fn(), + readStructuredTuiProcessIdentity: vi.fn() + })) vi.mock('./structured-tui-process-identity', () => ({ readStructuredTuiProcessIdentity })) +vi.mock('../codex/codex-tui-rollout-proof', () => ({ proveCodexTuiRollout })) vi.mock('./agent-session-process-identity-probe', async (importOriginal) => ({ ...(await importOriginal()), probeAgentSessionProcessIdentity @@ -49,9 +52,17 @@ describe('structured TUI launch tab binding', () => { }) const terminalHandle = 'term_cold_owner' const leafId = '23013912-13f8-44e5-818f-d40a1ff4e8c5' - const waitForStructuredTuiProof = vi.fn(async () => ({ - transcriptPath: '/tmp/codex-home/sessions/thread-1.jsonl' - })) + proveCodexTuiRollout.mockImplementation( + async (input: { + readOutput(): { text: string; lastOutputAt: number | null } + write(data: string): boolean + }) => { + input.readOutput() + expect(input.write('/status')).toBe(true) + input.readOutput() + return { transcriptPath: '/tmp/codex-home/sessions/thread-1.jsonl' } + } + ) const runtime = new OrcaRuntimeService(undefined, undefined, { agentSessionClaimSigner: signer }) @@ -82,6 +93,7 @@ describe('structured TUI launch tab binding', () => { ]), write: () => true, kill: () => true, + writeAgentSessionProof: vi.fn(() => true), getForegroundProcess: async () => null }) const internal = runtime as unknown as { @@ -96,7 +108,6 @@ describe('structured TUI launch tab binding', () => { folderWorkspace: null }> getAgentSessionExecutionNamespace(): typeof namespace - waitForStructuredTuiProof: typeof waitForStructuredTuiProof ptysById: Map< string, { launchToken: string | null; launchAgent: string | null; agentSessionOwners: unknown[] } @@ -113,7 +124,6 @@ describe('structured TUI launch tab binding', () => { folderWorkspace: null })) internal.getAgentSessionExecutionNamespace = () => namespace - internal.waitForStructuredTuiProof = waitForStructuredTuiProof probeAgentSessionProcessIdentity.mockResolvedValue({ outcome: 'identity-matched', matchedOn: ['process-start-time'] @@ -146,13 +156,7 @@ describe('structured TUI launch tab binding', () => { paneKey: `tab-cold-owner:${leafId}`, ptyId: 'pty-cold-owner' }) - expect(waitForStructuredTuiProof).toHaveBeenCalledWith( - expect.objectContaining({ - handle: terminalHandle, - paneKey: `tab-cold-owner:${leafId}`, - spawnToken: 'spawn-token' - }) - ) + expect(proveCodexTuiRollout).toHaveBeenCalledOnce() }) it('proves the published launch tab before returning its revealed renderer binding', async () => { diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index c31ccfd1de0..f7eca52d70c 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -81,6 +81,7 @@ import type { } from '../native-chat/agent-session-wire/structured-agent-session-handoff-types' import type { AgentSessionRecord } from '../../shared/agent-session-record' import { + agentSessionOwnerBindingsEqual, cloneAgentSessionOwnerBinding, scopedAgentSessionClaimsEqual } from '../../shared/claimed-agent-pty-owner-snapshot' @@ -9226,6 +9227,7 @@ export class OrcaRuntimeService { pty.paneKey ) let handle = candidate ? this.issueStructuredTuiPtyHandle(candidate) : null + let durableOwner: { binding: AgentSessionOwnerBinding; incarnationId: string } | undefined if (!candidate) { const workspace = await this.resolveTerminalWorkspaceLaunchScope( `id:${record.location.workspaceId}` @@ -9273,6 +9275,13 @@ export class OrcaRuntimeService { recovered.owner.surface.leafId ) handle = this.issueRecoveredStructuredTuiPtyHandle(candidate, recovered.owner.surface) + const recoveredIncarnationId = candidate.incarnationId + if (handle && recoveredIncarnationId) { + durableOwner = { + binding: cloneAgentSessionOwnerBinding(recovered.owner), + incarnationId: recoveredIncarnationId + } + } } if (!candidate?.tabId || !candidate.paneKey || !handle) { throw new Error('The owning agent terminal could not be recovered.') @@ -9283,7 +9292,8 @@ export class OrcaRuntimeService { threadId: head.handle.threadId, spawnToken: identity.spawnToken, codexHome: record.accountHome.path, - sessionId: record.sessionId + sessionId: record.sessionId, + ...(durableOwner ? { durableOwner } : {}) }) return { terminal: { @@ -9470,14 +9480,25 @@ export class OrcaRuntimeService { spawnToken: string codexHome: string sessionId: string + durableOwner?: { binding: AgentSessionOwnerBinding; incarnationId: string } }): Promise<{ transcriptPath?: string }> { const readBoundPty = (): RuntimePtyWorktreeRecord => { const pty = this.getLivePtyForHandle(input.handle)?.pty + const durableOwner = input.durableOwner + const launchIdentityMatches = + pty?.launchAgent === 'codex' && pty.launchToken === input.spawnToken + const durableIdentityMatches = Boolean( + pty && + durableOwner && + pty.incarnationId === durableOwner.incarnationId && + pty.agentSessionOwners.some((owner) => + agentSessionOwnerBindingsEqual(owner, durableOwner.binding) + ) + ) if ( !pty?.connected || pty.paneKey !== input.paneKey || - pty.launchAgent !== 'codex' || - pty.launchToken !== input.spawnToken + (!launchIdentityMatches && !durableIdentityMatches) ) { throw new Error('The resumed terminal lost its launch identity.') }