From 920cbdbe64f004afc3d1854789d4b25e17235461 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Mon, 20 Jul 2026 22:14:47 -0700 Subject: [PATCH] fix(codex): preserve session home on automatic resume --- .../codex/codex-session-resume-home.test.ts | 88 +++++++++++++++++ src/main/codex/codex-session-resume-home.ts | 69 +++++++++++++ src/main/index.ts | 74 +++++++++++++- src/main/ipc/pty.test.ts | 68 +++++++++++++ src/main/ipc/pty.ts | 97 ++++++++++++++++--- src/main/providers/local-pty-provider.ts | 2 + src/main/providers/types.ts | 2 + src/main/runtime/orca-runtime.ts | 11 ++- src/main/runtime/rpc/methods/terminal.ts | 10 ++ .../window/attach-main-window-services.ts | 9 +- src/preload/api-types.ts | 7 +- src/preload/index.ts | 8 +- .../terminal-pane/pty-connection-types.ts | 6 +- .../terminal-pane/pty-connection.test.ts | 11 ++- .../terminal-pane/pty-connection.ts | 15 +++ .../terminal-pane/pty-transport-types.ts | 7 +- .../terminal-pane/pty-transport.test.ts | 15 +++ .../components/terminal-pane/pty-transport.ts | 6 ++ .../remote-runtime-pty-transport.test.ts | 12 ++- .../remote-runtime-pty-transport.ts | 5 + .../use-terminal-pane-lifecycle.ts | 7 +- src/renderer/src/hooks/useIpcEvents.ts | 5 + src/shared/runtime-types.ts | 6 +- 23 files changed, 514 insertions(+), 26 deletions(-) create mode 100644 src/main/codex/codex-session-resume-home.test.ts create mode 100644 src/main/codex/codex-session-resume-home.ts diff --git a/src/main/codex/codex-session-resume-home.test.ts b/src/main/codex/codex-session-resume-home.test.ts new file mode 100644 index 00000000000..5abcc2d4b61 --- /dev/null +++ b/src/main/codex/codex-session-resume-home.test.ts @@ -0,0 +1,88 @@ +import { describe, expect, it } from 'vitest' +import { + findTrustedCodexSessionResume, + resolveTrustedCodexSessionResumeHome +} from './codex-session-resume-home' + +describe('resolveTrustedCodexSessionResumeHome', () => { + it('returns the trusted home containing a persisted rollout', () => { + expect( + resolveTrustedCodexSessionResumeHome({ + transcriptPath: '/Users/example/.codex/sessions/2026/07/20/rollout-session.jsonl', + trustedCodexHomes: ['/managed/account/home', '/Users/example/.codex'], + fileExists: () => true + }) + ).toBe('/Users/example/.codex') + }) + + it('accepts Windows paths case-insensitively', () => { + expect( + resolveTrustedCodexSessionResumeHome({ + transcriptPath: 'C:\\Users\\Example\\.codex\\sessions\\2026\\07\\20\\rollout-a.jsonl', + trustedCodexHomes: ['c:\\users\\example\\.codex'], + fileExists: () => true + }) + ).toBe('c:\\users\\example\\.codex') + }) + + it('rejects paths outside trusted homes or outside the rollout layout', () => { + const fileExists = (): boolean => true + expect( + resolveTrustedCodexSessionResumeHome({ + transcriptPath: '/tmp/sessions/2026/07/20/rollout-a.jsonl', + trustedCodexHomes: ['/Users/example/.codex'], + fileExists + }) + ).toBeNull() + expect( + resolveTrustedCodexSessionResumeHome({ + transcriptPath: '/Users/example/.codex/sessions/index.jsonl', + trustedCodexHomes: ['/Users/example/.codex'], + fileExists + }) + ).toBeNull() + }) + + it('rejects a trusted-looking path when the rollout no longer exists', () => { + expect( + resolveTrustedCodexSessionResumeHome({ + transcriptPath: '/Users/example/.codex/sessions/2026/07/20/rollout-a.jsonl', + trustedCodexHomes: ['/Users/example/.codex'], + fileExists: () => false + }) + ).toBeNull() + }) + + it('finds older saved sessions by id when transcript provenance is absent', async () => { + const sessionId = '019f81b9-19a9-7651-a8d1-352d9420bd11' + const rolloutPath = `/managed/account/home/sessions/2026/07/20/rollout-2026-07-20T15-50-19-${sessionId}.jsonl` + const listSessionFiles = async function* (sessionsRoot: string): AsyncIterable { + if (sessionsRoot === '/managed/account/home/sessions') { + yield rolloutPath + } + } + + await expect( + findTrustedCodexSessionResume({ + sessionId, + transcriptPath: undefined, + trustedCodexHomes: ['/Users/example/.codex', '/managed/account/home'], + listSessionFiles + }) + ).resolves.toEqual({ homePath: '/managed/account/home', transcriptPath: rolloutPath }) + }) + + it('does not scan homes for an untrusted legacy session id shape', async () => { + const listSessionFiles = (): AsyncIterable => { + throw new Error('must not scan') + } + await expect( + findTrustedCodexSessionResume({ + sessionId: '../session', + transcriptPath: undefined, + trustedCodexHomes: ['/Users/example/.codex'], + listSessionFiles + }) + ).resolves.toBeNull() + }) +}) diff --git a/src/main/codex/codex-session-resume-home.ts b/src/main/codex/codex-session-resume-home.ts new file mode 100644 index 00000000000..c1a609e7628 --- /dev/null +++ b/src/main/codex/codex-session-resume-home.ts @@ -0,0 +1,69 @@ +import { existsSync } from 'node:fs' +import { join } from 'node:path' +import { + getRuntimePathBasename, + normalizeRuntimePathForComparison, + relativePathInsideRoot +} from '../../shared/cross-platform-path' +import { listCodexSessionJsonlFilesIncrementally } from './codex-session-file-listing' + +const ROLLOUT_RELATIVE_PATH = /^\d{4}\/\d{2}\/\d{2}\/rollout-.+\.jsonl$/ + +export function resolveTrustedCodexSessionResumeHome(args: { + transcriptPath: string | undefined + trustedCodexHomes: readonly string[] + fileExists?: (filePath: string) => boolean +}): string | null { + const transcriptPath = args.transcriptPath?.trim() + if (!transcriptPath || !(args.fileExists ?? existsSync)(transcriptPath)) { + return null + } + + for (const homePath of args.trustedCodexHomes) { + const relativePath = relativePathInsideRoot(join(homePath, 'sessions'), transcriptPath) + if (relativePath && ROLLOUT_RELATIVE_PATH.test(relativePath.replace(/\\/g, '/'))) { + return homePath + } + } + return null +} + +export async function findTrustedCodexSessionResume(args: { + sessionId: string + transcriptPath: string | undefined + trustedCodexHomes: readonly string[] + fileExists?: (filePath: string) => boolean + listSessionFiles?: (sessionsRoot: string) => AsyncIterable +}): Promise<{ homePath: string; transcriptPath: string } | null> { + const directHome = resolveTrustedCodexSessionResumeHome(args) + if (directHome && args.transcriptPath) { + return { homePath: directHome, transcriptPath: args.transcriptPath.trim() } + } + if (!/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(args.sessionId)) { + return null + } + + const listSessionFiles = + args.listSessionFiles ?? + ((sessionsRoot: string) => + listCodexSessionJsonlFilesIncrementally(sessionsRoot, { batchSize: 64, yieldMs: 0 })) + const expectedSuffix = `-${args.sessionId}.jsonl`.toLowerCase() + const seenHomes = new Set() + for (const homePath of args.trustedCodexHomes) { + const comparisonHome = normalizeRuntimePathForComparison(homePath) + if (seenHomes.has(comparisonHome)) { + continue + } + seenHomes.add(comparisonHome) + const sessionsRoot = join(homePath, 'sessions') + if (!args.listSessionFiles && !existsSync(sessionsRoot)) { + continue + } + for await (const filePath of listSessionFiles(sessionsRoot)) { + if (getRuntimePathBasename(filePath).toLowerCase().endsWith(expectedSuffix)) { + return { homePath, transcriptPath: filePath } + } + } + } + return null +} diff --git a/src/main/index.ts b/src/main/index.ts index 6af71bd5f85..0916124c1e2 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -147,6 +147,10 @@ import { startCodexSessionIndexHealInBackground } from './codex/codex-session-in import { createCodexSessionMigrationScheduler } from './codex/codex-session-migration-scheduler' import { prepareLegacySharedCodexSessionResume } from './codex/codex-legacy-session-resume' import { resolveHostCodexSessionSourceHome } from './codex/codex-session-source-home' +import { findTrustedCodexSessionResume } from './codex/codex-session-resume-home' +import { getSystemCodexHomePath } from './codex/codex-home-paths' +import { normalizeRuntimePathForComparison } from '../shared/cross-platform-path' +import type { AgentProviderSessionMetadata } from '../shared/agent-session-resume' import { getDefaultWslDistro } from './wsl' import { ClaudeAccountService } from './claude-accounts/service' import { ClaudeRuntimeAuthService } from './claude-accounts/runtime-auth-service' @@ -784,6 +788,72 @@ function prepareCodexRuntimeHomeForLaunch( return runtimeHomePath } +async function prepareCodexSessionResumeForLaunch(args: { + providerSession: AgentProviderSessionMetadata + target: CodexAccountSelectionTarget + launchEnv?: NodeJS.ProcessEnv + workspacePath?: string +}): Promise<{ codexHomePath: string | null } | null> { + if (args.target.runtime === 'wsl' || !codexRuntimeHome || !store) { + return null + } + const configuredSourceHome = resolveHostCodexSessionSourceHome(store.getSettings()) + const systemHomePath = getSystemCodexHomePath() + const trustedHomes = [ + configuredSourceHome ?? systemHomePath, + ...codexRuntimeHome.getHostCodexHomePathsForSessionDiscovery() + ] + const sessionSource = await findTrustedCodexSessionResume({ + sessionId: args.providerSession.id, + transcriptPath: args.providerSession.transcriptPath, + trustedCodexHomes: trustedHomes + }) + if (!sessionSource) { + return null + } + + const migrated = await prepareLegacySharedCodexSessionResume( + { + agent: 'codex', + executionHostId: 'local', + filePath: sessionSource.transcriptPath, + codexHome: sessionSource.homePath + }, + { + isHostSystemDefaultRealHome: () => codexRuntimeHome!.isHostSystemDefaultRealHome(), + systemCodexHomePath: configuredSourceHome + } + ) + const resumeHome = migrated.useRealCodexHome + ? (configuredSourceHome ?? systemHomePath) + : sessionSource.homePath + + if (args.workspacePath) { + try { + markCodexProjectTrusted(args.workspacePath) + } catch (error) { + console.warn('[codex-project-trust] failed to pre-mark resumed workspace:', error) + } + } + const isSystemHome = + normalizeRuntimePathForComparison(resumeHome) === + normalizeRuntimePathForComparison(systemHomePath) + const hooksEnabled = isAgentStatusHooksEnabled(store.getSettings()) + try { + if (isSystemHome) { + ensureRealHomeCodexHookState({ hooksEnabled, userDataPath: app.getPath('userData') }) + } else if (hooksEnabled) { + codexHookService.install(resumeHome) + } else { + codexHookService.refreshRuntimeUserHooks(resumeHome) + } + } catch (error) { + // Why: hook repair is best-effort; session provenance must still win over the currently selected home. + console.warn('[codex-hook-service] failed to prepare automatic resume home:', error) + } + return { codexHomePath: isSystemHome ? null : resumeHome } +} + // Why: restore the window the close handler may have hidden to tray, or reopen it (dock-reactivation style) if fully torn down. function showMainWindowFromTray(): void { if (mainWindow && !mainWindow.isDestroyed()) { @@ -1050,6 +1120,7 @@ function openMainWindow(): BrowserWindow { prepareCodexRuntimeHomeForLaunch, (target) => claudeRuntimeAuth!.prepareForClaudeLaunch(target), { + prepareCodexSessionResume: prepareCodexSessionResumeForLaunch, awaitLocalPtyStartup: () => localPtyStartupReady, awaitLocalPtyProviderStartup: () => localPtyProviderStartupReady, onBeforeRendererReload: ({ ignoreCache, webContentsId }) => { @@ -2200,7 +2271,8 @@ app.whenReady().then(async () => { prepareCodexRuntimeHomeForLaunch, () => store!.getSettings(), (target) => claudeRuntimeAuth!.prepareForClaudeLaunch(target), - store + store, + prepareCodexSessionResumeForLaunch ) // Why: headless servers can't mount panes; use offscreen WebContents, gated on a real display so browser.headless.v1 stays honest. if (headlessBrowserDisplayAvailable) { diff --git a/src/main/ipc/pty.test.ts b/src/main/ipc/pty.test.ts index bafc64347c7..0cf3000bd01 100644 --- a/src/main/ipc/pty.test.ts +++ b/src/main/ipc/pty.test.ts @@ -931,6 +931,74 @@ describe('registerPtyHandlers', () => { expect(env.ORCA_CODEX_HOME).toBe(TEST_CODEX_HOME) }) + it('resumes an automatic Codex session from its prepared originating home', async () => { + const selectedHome = vi.fn(() => '/managed/current/home') + const prepareResume = vi.fn(async () => ({ codexHomePath: '/managed/origin/home' })) + registerPtyHandlers( + mainWindow as never, + undefined, + selectedHome, + undefined, + undefined, + undefined, + { prepareCodexSessionResume: prepareResume } + ) + + await handlers.get('pty:spawn')!(null, { + cols: 80, + rows: 24, + command: 'codex resume session-a', + launchAgent: 'codex', + resumeProviderSession: { + key: 'session_id', + id: 'session-a', + transcriptPath: '/managed/origin/home/sessions/2026/07/20/rollout-a.jsonl' + } + }) + + const env = spawnMock.mock.calls.at(-1)![2].env as Record + expect(prepareResume).toHaveBeenCalledWith( + expect.objectContaining({ + providerSession: expect.objectContaining({ id: 'session-a' }), + target: { runtime: 'host' } + }) + ) + expect(selectedHome).not.toHaveBeenCalled() + expect(env.CODEX_HOME).toBe('/managed/origin/home') + expect(env.ORCA_CODEX_HOME).toBe('/managed/origin/home') + }) + + it('strips the currently selected managed home when the resumed session originated in real home', async () => { + const selectedHome = vi.fn(() => '/managed/current/home') + registerPtyHandlers( + mainWindow as never, + undefined, + selectedHome, + undefined, + undefined, + undefined, + { prepareCodexSessionResume: async () => ({ codexHomePath: null }) } + ) + + await handlers.get('pty:spawn')!(null, { + cols: 80, + rows: 24, + command: 'codex resume session-a', + env: { CODEX_HOME: '/managed/current/home', ORCA_CODEX_HOME: '/managed/current/home' }, + launchAgent: 'codex', + resumeProviderSession: { + key: 'session_id', + id: 'session-a', + transcriptPath: '/Users/example/.codex/sessions/2026/07/20/rollout-a.jsonl' + } + }) + + const env = spawnMock.mock.calls.at(-1)![2].env as Record + expect(selectedHome).not.toHaveBeenCalled() + expect(env.CODEX_HOME).toBeUndefined() + expect(env.ORCA_CODEX_HOME).toBeUndefined() + }) + it('prepares Codex launch state for the workspace before spawning an interactive tab', async () => { const workspacePath = '/repo/worktrees/new-feature' const resolveHome = vi.fn( diff --git a/src/main/ipc/pty.ts b/src/main/ipc/pty.ts index 407ae52503f..f7568cfcc3a 100644 --- a/src/main/ipc/pty.ts +++ b/src/main/ipc/pty.ts @@ -32,7 +32,11 @@ import { } from '../../shared/pty-delivery-diagnostics' import { recordCrashBreadcrumb } from '../crash-reporting/crash-breadcrumb-store' import { isTuiAgent } from '../../shared/tui-agent-config' -import type { SleepingAgentLaunchConfig } from '../../shared/agent-session-resume' +import { + normalizeAgentProviderSession, + type AgentProviderSessionMetadata, + type SleepingAgentLaunchConfig +} from '../../shared/agent-session-resume' import type { ProjectExecutionRuntimeResolution } from '../../shared/project-execution-runtime' import { isWslShellName, @@ -624,6 +628,12 @@ export type GetSelectedCodexHomePath = ( launchEnv?: NodeJS.ProcessEnv, launchContext?: { workspacePath?: string; launchAgent?: TuiAgent } ) => string | null +export type PrepareCodexSessionResume = (args: { + providerSession: AgentProviderSessionMetadata + target: CodexAccountSelectionTarget + launchEnv?: NodeJS.ProcessEnv + workspacePath?: string +}) => Promise<{ codexHomePath: string | null } | null> type PrepareClaudeAuth = ( target?: ClaudeAccountSelectionTarget ) => Promise @@ -1381,6 +1391,7 @@ export function registerPtyHandlers( prepareClaudeAuth?: PrepareClaudeAuth, store?: Store, options?: { + prepareCodexSessionResume?: PrepareCodexSessionResume awaitLocalPtyStartup?: () => Promise awaitLocalPtyProviderStartup?: () => Promise // Why: returns true once for the crash-recovery reload so its did-finish-load skips the orphan sweep and keeps live PTYs (#5787). @@ -1453,10 +1464,12 @@ export function registerPtyHandlers( : { runtime: 'host' } const selectedCodexHomePath = getCompatibleSelectedCodexHomePath( codexSelectionTarget, - getSelectedCodexHomePath?.(codexSelectionTarget, baseEnv, { - workspacePath: ctx?.cwd, - launchAgent: ctx?.launchAgent - }) ?? null + ctx?.codexHomePathOverride + ? ctx.codexHomePathOverride.value + : (getSelectedCodexHomePath?.(codexSelectionTarget, baseEnv, { + workspacePath: ctx?.cwd, + launchAgent: ctx?.launchAgent + }) ?? null) ) const skipCodexHomeEnv = ctx?.isWsl === true && !selectedCodexHomePath const env = buildPtyHostEnv(id, baseEnv, { @@ -2699,6 +2712,29 @@ export function registerPtyHandlers( } } + const prepareCodexResumeHome = (args: { + connectionId?: string | null + launchAgent?: TuiAgent + providerSession?: AgentProviderSessionMetadata + target: CodexAccountSelectionTarget + launchEnv?: NodeJS.ProcessEnv + workspacePath?: string + }): Promise<{ codexHomePath: string | null } | null> | null => { + if (args.connectionId || args.launchAgent !== 'codex' || !options?.prepareCodexSessionResume) { + return null + } + const providerSession = normalizeAgentProviderSession(args.providerSession) + if (!providerSession) { + return null + } + return options.prepareCodexSessionResume({ + providerSession, + target: args.target, + launchEnv: args.launchEnv, + workspacePath: args.workspacePath + }) + } + // Why: route through getProviderForPty() so CLI commands work for remote PTYs too; localProvider would silently fail for them. runtime?.setPtyController({ spawn: async (args) => { @@ -2729,6 +2765,15 @@ export function registerPtyHandlers( cwd, terminalRuntimeOptions.terminalWindowsWslDistro ?? null ) + const codexResumePreparation = prepareCodexResumeHome({ + connectionId: args.connectionId, + launchAgent: args.launchAgent, + providerSession: args.resumeProviderSession, + target: codexSelectionTarget, + launchEnv: args.env, + workspacePath: cwd + }) + const codexResumeHome = codexResumePreparation ? await codexResumePreparation : null const claudeAuth = isClaudeLaunch && prepareClaudeAuth ? await prepareClaudeAuth(codexSelectionTarget) : null if (isClaudeLaunch && isClaudeAuthSwitchInProgress()) { @@ -2798,10 +2843,12 @@ export function registerPtyHandlers( const selectedCodexHomePath = isDaemonHostSpawn ? getCompatibleSelectedCodexHomePath( codexSelectionTarget, - getSelectedCodexHomePath?.(codexSelectionTarget, env, { - workspacePath: cwd, - launchAgent: isTuiAgent(args.launchAgent) ? args.launchAgent : undefined - }) ?? null + codexResumeHome + ? codexResumeHome.codexHomePath + : (getSelectedCodexHomePath?.(codexSelectionTarget, env, { + workspacePath: cwd, + launchAgent: isTuiAgent(args.launchAgent) ? args.launchAgent : undefined + }) ?? null) ) : null const skipCodexHomeEnv = @@ -2849,6 +2896,9 @@ export function registerPtyHandlers( env, ...(isMintedSessionId ? { isNewSession: true } : {}) } + if (!isDaemonHostSpawn && codexResumeHome) { + spawnOptions.codexHomePathOverride = { value: codexResumeHome.codexHomePath } + } const startupTerminalColorQueryReplyColors = getStartupTerminalColorQueryReplyColors(args) if (startupTerminalColorQueryReplyColors) { spawnOptions.startupIngress = { @@ -3480,6 +3530,7 @@ export function registerPtyHandlers( command?: string commandDelivery?: 'renderer' | 'provider' launchConfig?: SleepingAgentLaunchConfig + resumeProviderSession?: AgentProviderSessionMetadata launchAgent?: TuiAgent startupCommandDelivery?: StartupCommandDelivery connectionId?: string | null @@ -3700,13 +3751,24 @@ export function registerPtyHandlers( cwd, terminalRuntimeOptions.terminalWindowsWslDistro ?? null ) + const codexResumePreparation = prepareCodexResumeHome({ + connectionId: args.connectionId, + launchAgent: args.launchAgent, + providerSession: args.resumeProviderSession, + target: codexSelectionTarget, + launchEnv: baseEnv, + workspacePath: cwd + }) + const codexResumeHome = codexResumePreparation ? await codexResumePreparation : null const selectedCodexHomePath = isDaemonHostSpawn ? getCompatibleSelectedCodexHomePath( codexSelectionTarget, - getSelectedCodexHomePath?.(codexSelectionTarget, baseEnv, { - workspacePath: cwd, - launchAgent: isTuiAgent(args.launchAgent) ? args.launchAgent : undefined - }) ?? null + codexResumeHome + ? codexResumeHome.codexHomePath + : (getSelectedCodexHomePath?.(codexSelectionTarget, baseEnv, { + workspacePath: cwd, + launchAgent: isTuiAgent(args.launchAgent) ? args.launchAgent : undefined + }) ?? null) ) : null const skipCodexHomeEnv = @@ -3792,6 +3854,9 @@ export function registerPtyHandlers( env: spawnEnv, ...(isMintedSessionId ? { isNewSession: true } : {}) } + if (!isDaemonHostSpawn && codexResumeHome) { + spawnOptions.codexHomePathOverride = { value: codexResumeHome.codexHomePath } + } if (combinedEnvToDelete) { spawnOptions.envToDelete = combinedEnvToDelete } @@ -4899,7 +4964,8 @@ export function registerHeadlessPtyRuntime( getSelectedCodexHomePath?: GetSelectedCodexHomePath, getSettings?: () => GlobalSettings, prepareClaudeAuth?: PrepareClaudeAuth, - store?: Store + store?: Store, + prepareCodexSessionResume?: PrepareCodexSessionResume ): void { // Why: headless `orca serve` has no renderer window but still needs the same PTY handlers so remote clients can drive terminals. const headlessWindow = { @@ -4916,7 +4982,8 @@ export function registerHeadlessPtyRuntime( getSelectedCodexHomePath, getSettings, prepareClaudeAuth, - store + store, + { prepareCodexSessionResume } ) } diff --git a/src/main/providers/local-pty-provider.ts b/src/main/providers/local-pty-provider.ts index e4a30c8af45..41d8708e36a 100644 --- a/src/main/providers/local-pty-provider.ts +++ b/src/main/providers/local-pty-provider.ts @@ -473,6 +473,7 @@ export type LocalPtyProviderOptions = { ctx?: { command?: string launchAgent?: PtySpawnOptions['launchAgent'] + codexHomePathOverride?: PtySpawnOptions['codexHomePathOverride'] cwd?: string shellPath?: string isWsl?: boolean @@ -675,6 +676,7 @@ export class LocalPtyProvider implements IPtyProvider { ? this.opts.buildSpawnEnv(id, spawnEnv, { command: args.command, launchAgent: args.launchAgent, + codexHomePathOverride: args.codexHomePathOverride, cwd, shellPath, isWsl: isWslShell, diff --git a/src/main/providers/types.ts b/src/main/providers/types.ts index 6523736fd2b..fca74501c10 100644 --- a/src/main/providers/types.ts +++ b/src/main/providers/types.ts @@ -56,6 +56,8 @@ export type PtySpawnOptions = { cwd?: string env?: Record envToDelete?: string[] + /** Main-validated home provenance for an automatic Codex session resume. */ + codexHomePathOverride?: { value: string | null } command?: string commandDelivery?: 'renderer' | 'provider' startupCommandDelivery?: StartupCommandDelivery diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index 6c6cc553610..c1ba73cc6e6 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -152,7 +152,10 @@ import { parseExecutionHostId, type ExecutionHostId } from '../../shared/execution-host' -import type { SleepingAgentLaunchConfig } from '../../shared/agent-session-resume' +import type { + AgentProviderSessionMetadata, + SleepingAgentLaunchConfig +} from '../../shared/agent-session-resume' import type { RuntimeClientEvent } from '../../shared/runtime-client-events' import { toRuntimeActivateWorktreeEvent } from '../../shared/runtime-client-events' import { @@ -1089,6 +1092,7 @@ type TerminalCreateOptions = { env?: Record envToDelete?: string[] launchConfig?: WorktreeStartupLaunch['launchConfig'] + resumeProviderSession?: AgentProviderSessionMetadata launchToken?: string launchAgent?: TuiAgent terminalColorQueryReplies?: TerminalOscColorQueryReplyColors @@ -1283,6 +1287,7 @@ type RuntimePtyController = { startupCommandDelivery?: WorktreeStartupLaunch['startupCommandDelivery'] env?: Record envToDelete?: string[] + resumeProviderSession?: AgentProviderSessionMetadata telemetry?: WorktreeStartupLaunch['telemetry'] connectionId?: string | null worktreeId?: string @@ -19317,6 +19322,7 @@ export class OrcaRuntimeService { launchOpts.envToDelete, agentTeamsPlan?.envToDelete ), + resumeProviderSession: launchOpts.resumeProviderSession, telemetry: launchOpts.telemetry, connectionId: workspace.connectionId, worktreeId: workspace.id, @@ -19461,6 +19467,9 @@ export class OrcaRuntimeService { cwd, ...(launchOpts.env ? { env: launchOpts.env } : {}), ...(launchOpts.launchConfig ? { launchConfig: launchOpts.launchConfig } : {}), + ...(launchOpts.resumeProviderSession + ? { resumeProviderSession: launchOpts.resumeProviderSession } + : {}), ...(launchOpts.launchToken ? { launchToken: launchOpts.launchToken } : {}), ...(launchOpts.launchAgent ? { launchAgent: launchOpts.launchAgent } : {}), ...(launchOpts.viewMode ? { viewMode: launchOpts.viewMode } : {}), diff --git a/src/main/runtime/rpc/methods/terminal.ts b/src/main/runtime/rpc/methods/terminal.ts index 7721ebc4d59..48458ea0d07 100644 --- a/src/main/runtime/rpc/methods/terminal.ts +++ b/src/main/runtime/rpc/methods/terminal.ts @@ -921,6 +921,13 @@ const TerminalCreateParams = z.object({ agentEnv: z.record(z.string(), z.string()) }) .optional(), + resumeProviderSession: z + .object({ + key: z.enum(['session_id', 'conversation_id']), + id: z.string().min(1).max(512), + transcriptPath: z.string().min(1).max(32_768).optional() + }) + .optional(), launchToken: OptionalString, launchAgent: z.string().refine(isTuiAgent).optional(), terminalColorQueryReplies: z @@ -1362,6 +1369,9 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [ env: params.env, envToDelete: params.envToDelete, ...(params.launchConfig ? { launchConfig: params.launchConfig } : {}), + ...(params.resumeProviderSession + ? { resumeProviderSession: params.resumeProviderSession } + : {}), ...(params.launchToken ? { launchToken: params.launchToken } : {}), ...(params.launchAgent ? { launchAgent: params.launchAgent } : {}), ...(params.terminalColorQueryReplies diff --git a/src/main/window/attach-main-window-services.ts b/src/main/window/attach-main-window-services.ts index 55f1265ea48..39ea7ff2a6a 100644 --- a/src/main/window/attach-main-window-services.ts +++ b/src/main/window/attach-main-window-services.ts @@ -12,7 +12,12 @@ import type { import { registerRepoHandlers } from '../ipc/repos' import { registerWorktreeHandlers } from '../ipc/worktrees' import { registerWorkspaceCleanupHandlers } from '../ipc/workspace-cleanup' -import { getLocalPtyProvider, registerPtyHandlers, type GetSelectedCodexHomePath } from '../ipc/pty' +import { + getLocalPtyProvider, + registerPtyHandlers, + type GetSelectedCodexHomePath, + type PrepareCodexSessionResume +} from '../ipc/pty' import { registerDaemonManagementHandlers } from '../ipc/pty-management' import { registerSshHandlers } from '../ipc/ssh' import { registerRemoteWorkspaceHandlers } from '../ipc/remote-workspace' @@ -70,6 +75,7 @@ export function attachMainWindowServices( target?: ClaudeAccountSelectionTarget ) => Promise, options?: { + prepareCodexSessionResume?: PrepareCodexSessionResume awaitLocalPtyStartup?: () => Promise awaitLocalPtyProviderStartup?: () => Promise onBeforeRendererReload?: (args: { webContentsId: number; ignoreCache: boolean }) => void @@ -93,6 +99,7 @@ export function attachMainWindowServices( prepareClaudeAuth, store, { + prepareCodexSessionResume: options?.prepareCodexSessionResume, awaitLocalPtyStartup: options?.awaitLocalPtyStartup, awaitLocalPtyProviderStartup: options?.awaitLocalPtyProviderStartup, isRecoveryReloadInFlight: options?.isRecoveryReloadInFlight diff --git a/src/preload/api-types.ts b/src/preload/api-types.ts index d85797fdc1d..b031ff3aff4 100644 --- a/src/preload/api-types.ts +++ b/src/preload/api-types.ts @@ -63,7 +63,10 @@ import type { TaskSourceContext } from '../shared/task-source-context' import type { LinearIssueAttributeFilter } from '../shared/linear-issue-attribute-filter' import type { ProjectExecutionRuntimeResolution } from '../shared/project-execution-runtime' import type { StartupCommandDelivery } from '../shared/codex-startup-delivery' -import type { SleepingAgentLaunchConfig } from '../shared/agent-session-resume' +import type { + AgentProviderSessionMetadata, + SleepingAgentLaunchConfig +} from '../shared/agent-session-resume' import type { LocalhostWorktreeLabelResult, LocalhostWorktreeLabelRoute @@ -1290,6 +1293,7 @@ export type PreloadApi = { envToDelete?: string[] command?: string launchConfig?: SleepingAgentLaunchConfig + resumeProviderSession?: AgentProviderSessionMetadata launchToken?: string launchAgent?: TuiAgent startupCommandDelivery?: StartupCommandDelivery @@ -2878,6 +2882,7 @@ export type PreloadApi = { cwd?: string env?: Record launchConfig?: SleepingAgentLaunchConfig + resumeProviderSession?: AgentProviderSessionMetadata launchToken?: string launchAgent?: TuiAgent viewMode?: 'terminal' | 'chat' diff --git a/src/preload/index.ts b/src/preload/index.ts index 04181c3b812..eed0de1c6d5 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -14,7 +14,10 @@ import type { AgentHookInstallStatus } from '../shared/agent-hook-types' import type { TerminalPaneSplitSource } from '../shared/feature-education-telemetry' import type { ProjectExecutionRuntimeResolution } from '../shared/project-execution-runtime' import type { StartupCommandDelivery } from '../shared/codex-startup-delivery' -import type { SleepingAgentLaunchConfig } from '../shared/agent-session-resume' +import type { + AgentProviderSessionMetadata, + SleepingAgentLaunchConfig +} from '../shared/agent-session-resume' import type { MobileRelayStatus } from '../shared/mobile-relay-status' import type { MobilePairingConnectionMode } from '../shared/mobile-pairing-connection-mode' import type { @@ -797,6 +800,7 @@ const api = { envToDelete?: string[] command?: string launchConfig?: SleepingAgentLaunchConfig + resumeProviderSession?: AgentProviderSessionMetadata launchToken?: string launchAgent?: TuiAgent startupCommandDelivery?: StartupCommandDelivery @@ -3487,6 +3491,7 @@ const api = { cwd?: string env?: Record launchConfig?: SleepingAgentLaunchConfig + resumeProviderSession?: AgentProviderSessionMetadata launchToken?: string launchAgent?: TuiAgent viewMode?: 'terminal' | 'chat' @@ -3510,6 +3515,7 @@ const api = { cwd?: string env?: Record launchConfig?: SleepingAgentLaunchConfig + resumeProviderSession?: AgentProviderSessionMetadata launchToken?: string launchAgent?: TuiAgent viewMode?: 'terminal' | 'chat' diff --git a/src/renderer/src/components/terminal-pane/pty-connection-types.ts b/src/renderer/src/components/terminal-pane/pty-connection-types.ts index e783514977a..f03f009b56d 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection-types.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection-types.ts @@ -6,7 +6,10 @@ import type { EventProps } from '../../../../shared/telemetry-events' import type { TerminalColorSchemeMode } from '../../../../shared/terminal-color-scheme-protocol' import type { StartupCommandDelivery } from '../../../../shared/codex-startup-delivery' import type { SetupSplitDirection, TuiAgent } from '../../../../shared/types' -import type { SleepingAgentLaunchConfig } from '../../../../shared/agent-session-resume' +import type { + AgentProviderSessionMetadata, + SleepingAgentLaunchConfig +} from '../../../../shared/agent-session-resume' import type { TerminalKittyKeyboardModeTracker } from '../../../../shared/terminal-kitty-keyboard-mode-tracker' export type PtyConnectionDeps = { @@ -22,6 +25,7 @@ export type PtyConnectionDeps = { env?: Record envToDelete?: string[] launchConfig?: SleepingAgentLaunchConfig + resumeProviderSession?: AgentProviderSessionMetadata launchToken?: string launchAgent?: TuiAgent draftPrompt?: string diff --git a/src/renderer/src/components/terminal-pane/pty-connection.test.ts b/src/renderer/src/components/terminal-pane/pty-connection.test.ts index 7900adf3fe5..b3b5383ee28 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection.test.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection.test.ts @@ -7444,7 +7444,11 @@ describe('connectPanePty', () => { updatedAt: 1, stateStartedAt: 1, stateHistory: [], - providerSession: { key: 'session_id', id: 'codex-session-1' } + providerSession: { + key: 'session_id', + id: 'codex-session-1', + transcriptPath: '/Users/example/.codex/sessions/2026/07/20/rollout-session.jsonl' + } } } } as StoreState @@ -7470,6 +7474,11 @@ describe('connectPanePty', () => { expect.objectContaining({ sessionId: 'lost-pty', command: "codex '--dangerously-bypass-approvals-and-sandbox' 'resume' 'codex-session-1'", + resumeProviderSession: { + key: 'session_id', + id: 'codex-session-1', + transcriptPath: '/Users/example/.codex/sessions/2026/07/20/rollout-session.jsonl' + }, env: expect.objectContaining({ ORCA_PANE_KEY: paneKey, ORCA_TAB_ID: 'tab-1', diff --git a/src/renderer/src/components/terminal-pane/pty-connection.ts b/src/renderer/src/components/terminal-pane/pty-connection.ts index 78b96cdafc9..4889b090b3d 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection.ts @@ -235,6 +235,7 @@ import { agentProviderSessionsEqual, isResumableTuiAgent, normalizeAgentProviderSession, + type AgentProviderSessionMetadata, type ResumableTuiAgent, type SleepingAgentSessionRecord } from '../../../../shared/agent-session-resume' @@ -481,6 +482,7 @@ type FreshSpawnOptions = { type ColdRestoreAgentResumeStartup = PendingStartupCommand & { agent: ResumableTuiAgent + resumeProviderSession: AgentProviderSessionMetadata launchConfig: NonNullable>['launchConfig'] launchToken: string useLiveEntry: boolean @@ -3348,6 +3350,9 @@ export function connectPanePty( ...(projectRuntime ? { projectRuntime } : {}), ...(terminalColorQueryReplies ? { terminalColorQueryReplies } : {}), ...(paneStartup?.launchConfig ? { launchConfig: paneStartup.launchConfig } : {}), + ...(paneStartup?.resumeProviderSession + ? { resumeProviderSession: paneStartup.resumeProviderSession } + : {}), ...(launchToken ? { launchToken } : {}), ...(paneStartup?.launchAgent ? { launchAgent: paneStartup.launchAgent } : {}), ...(paneStartup?.telemetry ? { telemetry: paneStartup.telemetry } : {}), @@ -4487,6 +4492,7 @@ export function connectPanePty( ORCA_AGENT_LAUNCH_TOKEN: coldRestoreLaunchToken }, launchConfig: startupPlan.launchConfig, + resumeProviderSession: providerSession, launchToken: coldRestoreLaunchToken, useLiveEntry: Boolean(useLiveEntry), hasSleepingRecord: Boolean(sleepingRecord), @@ -4700,6 +4706,9 @@ export function connectPanePty( ? { env: mergeStartupEnvWithPaneIdentity(startupOverride.env) } : {}), ...(coldRestoreOverride ? { launchConfig: coldRestoreOverride.launchConfig } : {}), + ...(coldRestoreOverride + ? { resumeProviderSession: coldRestoreOverride.resumeProviderSession } + : {}), ...(coldRestoreOverride ? { launchToken: coldRestoreOverride.launchToken } : {}), ...(coldRestoreOverride ? { launchAgent: coldRestoreOverride.agent } : {}), ...(shouldDeclareHiddenAtSpawn() ? { initiallyHidden: true } : {}), @@ -7552,6 +7561,9 @@ export function connectPanePty( ...(coldRestoreStartup?.launchConfig ? { launchConfig: coldRestoreStartup.launchConfig } : {}), + ...(coldRestoreStartup?.resumeProviderSession + ? { resumeProviderSession: coldRestoreStartup.resumeProviderSession } + : {}), ...(coldRestoreStartup?.launchToken ? { launchToken: coldRestoreStartup.launchToken } : {}), @@ -7763,6 +7775,9 @@ export function connectPanePty( ...(coldRestoreStartup?.launchConfig ? { launchConfig: coldRestoreStartup.launchConfig } : {}), + ...(coldRestoreStartup?.resumeProviderSession + ? { resumeProviderSession: coldRestoreStartup.resumeProviderSession } + : {}), ...(coldRestoreStartup?.launchToken ? { launchToken: coldRestoreStartup.launchToken } : {}), ...(coldRestoreStartup?.agent ? { launchAgent: coldRestoreStartup.agent } : {}), ...(shouldDeclareHiddenAtSpawn() ? { initiallyHidden: true } : {}), diff --git a/src/renderer/src/components/terminal-pane/pty-transport-types.ts b/src/renderer/src/components/terminal-pane/pty-transport-types.ts index b1cda244784..4507b91e185 100644 --- a/src/renderer/src/components/terminal-pane/pty-transport-types.ts +++ b/src/renderer/src/components/terminal-pane/pty-transport-types.ts @@ -1,5 +1,8 @@ import type { ParsedAgentStatusPayload } from '../../../../shared/agent-status-types' -import type { SleepingAgentLaunchConfig } from '../../../../shared/agent-session-resume' +import type { + AgentProviderSessionMetadata, + SleepingAgentLaunchConfig +} from '../../../../shared/agent-session-resume' import type { StartupCommandDelivery } from '../../../../shared/codex-startup-delivery' import type { ProjectExecutionRuntimeResolution } from '../../../../shared/project-execution-runtime' import type { EventProps } from '../../../../shared/telemetry-events' @@ -86,6 +89,7 @@ export type PtyTransport = { env?: Record envToDelete?: string[] launchConfig?: SleepingAgentLaunchConfig + resumeProviderSession?: AgentProviderSessionMetadata launchToken?: string launchAgent?: TuiAgent startupCommandDelivery?: StartupCommandDelivery @@ -144,6 +148,7 @@ export type IpcPtyTransportOptions = { envToDelete?: string[] command?: string launchConfig?: SleepingAgentLaunchConfig + resumeProviderSession?: AgentProviderSessionMetadata launchToken?: string launchAgent?: TuiAgent startupCommandDelivery?: StartupCommandDelivery diff --git a/src/renderer/src/components/terminal-pane/pty-transport.test.ts b/src/renderer/src/components/terminal-pane/pty-transport.test.ts index 48f4f919ed8..e2c89d57da0 100644 --- a/src/renderer/src/components/terminal-pane/pty-transport.test.ts +++ b/src/renderer/src/components/terminal-pane/pty-transport.test.ts @@ -104,6 +104,21 @@ describe('createIpcPtyTransport', () => { ) }) + it('forwards automatic resume provenance to the PTY spawn', async () => { + const { createIpcPtyTransport } = await import('./pty-transport') + const spawn = window.api.pty.spawn as unknown as ReturnType + const resumeProviderSession = { + key: 'session_id' as const, + id: 'session-a', + transcriptPath: '/Users/example/.codex/sessions/2026/07/20/rollout-a.jsonl' + } + const transport = createIpcPtyTransport({ resumeProviderSession }) + + await transport.connect({ url: '', callbacks: {} }) + + expect(spawn).toHaveBeenCalledWith(expect.objectContaining({ resumeProviderSession })) + }) + it('leaves the transport silently unbound after a failed connect — sendInput drops with no write IPC (frozen-terminal repro)', async () => { const { createIpcPtyTransport } = await import('./pty-transport') const spawn = window.api.pty.spawn as unknown as ReturnType diff --git a/src/renderer/src/components/terminal-pane/pty-transport.ts b/src/renderer/src/components/terminal-pane/pty-transport.ts index e40c5d9103c..8e1a0380a58 100644 --- a/src/renderer/src/components/terminal-pane/pty-transport.ts +++ b/src/renderer/src/components/terminal-pane/pty-transport.ts @@ -468,6 +468,7 @@ export function createIpcPtyTransport(opts: IpcPtyTransportOptions = {}): PtyTra envToDelete, command, launchConfig, + resumeProviderSession, launchToken, launchAgent, startupCommandDelivery, @@ -686,6 +687,11 @@ export function createIpcPtyTransport(opts: IpcPtyTransportOptions = {}): PtyTra ...((options.launchConfig ?? launchConfig) ? { launchConfig: options.launchConfig ?? launchConfig } : {}), + ...((options.resumeProviderSession ?? resumeProviderSession) + ? { + resumeProviderSession: options.resumeProviderSession ?? resumeProviderSession + } + : {}), ...((options.launchToken ?? launchToken) ? { launchToken: options.launchToken ?? launchToken } : {}), diff --git a/src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.test.ts b/src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.test.ts index 242b53921d2..916bffa7372 100644 --- a/src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.test.ts +++ b/src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.test.ts @@ -1182,6 +1182,11 @@ describe('createRemoteRuntimePtyTransport', () => { }, launchToken: 'fresh-token', launchAgent: 'codex', + resumeProviderSession: { + key: 'session_id', + id: 'session-1', + transcriptPath: '/home/example/.codex/sessions/2026/07/20/rollout-a.jsonl' + }, callbacks: {} }) @@ -1197,7 +1202,12 @@ describe('createRemoteRuntimePtyTransport', () => { agentEnv: { CODEX_PROFILE: 'captured' } }, launchToken: 'fresh-token', - launchAgent: 'codex' + launchAgent: 'codex', + resumeProviderSession: { + key: 'session_id', + id: 'session-1', + transcriptPath: '/home/example/.codex/sessions/2026/07/20/rollout-a.jsonl' + } }) }) ) diff --git a/src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.ts b/src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.ts index dae2338cd71..204be73898f 100644 --- a/src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.ts +++ b/src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.ts @@ -68,6 +68,7 @@ export function createRemoteRuntimePtyTransport( env, envToDelete, launchConfig, + resumeProviderSession, launchToken, launchAgent, terminalColorQueryReplies, @@ -781,6 +782,7 @@ export function createRemoteRuntimePtyTransport( const envToSend = options.env ?? env const envToDeleteToSend = options.envToDelete ?? envToDelete const launchConfigToSend = options.launchConfig ?? launchConfig + const resumeProviderSessionToSend = options.resumeProviderSession ?? resumeProviderSession const launchTokenToSend = options.launchToken ?? launchToken const launchAgentToSend = options.launchAgent ?? launchAgent const created = await callRuntime<{ terminal: RuntimeTerminalCreate }>('terminal.create', { @@ -792,6 +794,9 @@ export function createRemoteRuntimePtyTransport( ...(envToSend !== undefined ? { env: envToSend } : {}), ...(envToDeleteToSend !== undefined ? { envToDelete: envToDeleteToSend } : {}), ...(launchConfigToSend !== undefined ? { launchConfig: launchConfigToSend } : {}), + ...(resumeProviderSessionToSend !== undefined + ? { resumeProviderSession: resumeProviderSessionToSend } + : {}), ...(launchTokenToSend !== undefined ? { launchToken: launchTokenToSend } : {}), ...(launchAgentToSend !== undefined ? { launchAgent: launchAgentToSend } : {}), ...(terminalColorQueryReplies ? { terminalColorQueryReplies } : {}), diff --git a/src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts b/src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts index 05ddb589fc1..453cb1d23dd 100644 --- a/src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts +++ b/src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts @@ -50,7 +50,10 @@ import type { import type { TerminalPaneSplitSource } from '../../../../shared/feature-education-telemetry' import type { EventProps } from '../../../../shared/telemetry-events' import type { StartupCommandDelivery } from '../../../../shared/codex-startup-delivery' -import type { SleepingAgentLaunchConfig } from '../../../../shared/agent-session-resume' +import type { + AgentProviderSessionMetadata, + SleepingAgentLaunchConfig +} from '../../../../shared/agent-session-resume' import { resolveTerminalFontWeights } from '../../../../shared/terminal-fonts' import { buildFontFamily, @@ -209,7 +212,9 @@ type UseTerminalPaneLifecycleDeps = { delivery?: 'terminal-paste' startupCommandDelivery?: StartupCommandDelivery env?: Record + envToDelete?: string[] launchConfig?: SleepingAgentLaunchConfig + resumeProviderSession?: AgentProviderSessionMetadata launchToken?: string launchAgent?: TuiAgent draftPrompt?: string diff --git a/src/renderer/src/hooks/useIpcEvents.ts b/src/renderer/src/hooks/useIpcEvents.ts index 5d02b630ce6..7ad315bbbfe 100644 --- a/src/renderer/src/hooks/useIpcEvents.ts +++ b/src/renderer/src/hooks/useIpcEvents.ts @@ -1387,6 +1387,7 @@ export function useIpcEvents(): void { cwd, env, launchConfig, + resumeProviderSession, launchToken, launchAgent, viewMode, @@ -1572,6 +1573,7 @@ export function useIpcEvents(): void { command, ...(env ? { env } : {}), ...(launchConfig ? { launchConfig } : {}), + ...(resumeProviderSession ? { resumeProviderSession } : {}), ...(launchToken ? { launchToken } : {}), ...(launchAgent ? { launchAgent } : {}) }) @@ -1723,6 +1725,9 @@ export function useIpcEvents(): void { ...(data.env ? { env: data.env } : {}), ...(data.envToDelete ? { envToDelete: data.envToDelete } : {}), ...(data.launchConfig ? { launchConfig: data.launchConfig } : {}), + ...(data.resumeProviderSession + ? { resumeProviderSession: data.resumeProviderSession } + : {}), ...(data.launchToken ? { launchToken: data.launchToken } : {}), ...(data.launchAgent ? { launchAgent: data.launchAgent } : {}), ...(data.startupCommandDelivery diff --git a/src/shared/runtime-types.ts b/src/shared/runtime-types.ts index ec626b47767..f03ab399090 100644 --- a/src/shared/runtime-types.ts +++ b/src/shared/runtime-types.ts @@ -32,7 +32,10 @@ import type { } from './mobile-markdown-document' import type { RuntimeCapability } from './protocol-version' import type { RemoteRuntimeSharedConnectionDiagnostics } from './remote-runtime-shared-control-types' -import type { SleepingAgentLaunchConfig } from './agent-session-resume' +import type { + AgentProviderSessionMetadata, + SleepingAgentLaunchConfig +} from './agent-session-resume' import type { StartupCommandDelivery } from './codex-startup-delivery' export type { RuntimeMarkdownReadTabResult, RuntimeMarkdownSaveTabResult } @@ -506,6 +509,7 @@ type RuntimeTerminalCreateBaseRequestPayload = { env?: Record envToDelete?: string[] launchConfig?: SleepingAgentLaunchConfig + resumeProviderSession?: AgentProviderSessionMetadata launchToken?: string launchAgent?: TuiAgent viewMode?: 'terminal' | 'chat'