mirror of
https://github.com/stablyai/orca.git
synced 2026-10-29 16:17:51 +00:00
fix(codex): preserve session home on automatic resume
This commit is contained in:
@@ -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<string> {
|
||||
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<string> => {
|
||||
throw new Error('must not scan')
|
||||
}
|
||||
await expect(
|
||||
findTrustedCodexSessionResume({
|
||||
sessionId: '../session',
|
||||
transcriptPath: undefined,
|
||||
trustedCodexHomes: ['/Users/example/.codex'],
|
||||
listSessionFiles
|
||||
})
|
||||
).resolves.toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -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<string>
|
||||
}): 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<string>()
|
||||
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
|
||||
}
|
||||
+73
-1
@@ -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 <webview> panes; use offscreen WebContents, gated on a real display so browser.headless.v1 stays honest.
|
||||
if (headlessBrowserDisplayAvailable) {
|
||||
|
||||
@@ -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<string, string>
|
||||
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<string, string>
|
||||
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(
|
||||
|
||||
+82
-15
@@ -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<ClaudeRuntimeAuthPreparation>
|
||||
@@ -1381,6 +1391,7 @@ export function registerPtyHandlers(
|
||||
prepareClaudeAuth?: PrepareClaudeAuth,
|
||||
store?: Store,
|
||||
options?: {
|
||||
prepareCodexSessionResume?: PrepareCodexSessionResume
|
||||
awaitLocalPtyStartup?: () => Promise<void>
|
||||
awaitLocalPtyProviderStartup?: () => Promise<void>
|
||||
// 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 }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -56,6 +56,8 @@ export type PtySpawnOptions = {
|
||||
cwd?: string
|
||||
env?: Record<string, string>
|
||||
envToDelete?: string[]
|
||||
/** Main-validated home provenance for an automatic Codex session resume. */
|
||||
codexHomePathOverride?: { value: string | null }
|
||||
command?: string
|
||||
commandDelivery?: 'renderer' | 'provider'
|
||||
startupCommandDelivery?: StartupCommandDelivery
|
||||
|
||||
@@ -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<string, string>
|
||||
envToDelete?: string[]
|
||||
launchConfig?: WorktreeStartupLaunch['launchConfig']
|
||||
resumeProviderSession?: AgentProviderSessionMetadata
|
||||
launchToken?: string
|
||||
launchAgent?: TuiAgent
|
||||
terminalColorQueryReplies?: TerminalOscColorQueryReplyColors
|
||||
@@ -1283,6 +1287,7 @@ type RuntimePtyController = {
|
||||
startupCommandDelivery?: WorktreeStartupLaunch['startupCommandDelivery']
|
||||
env?: Record<string, string>
|
||||
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 } : {}),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<ClaudeRuntimeAuthPreparation>,
|
||||
options?: {
|
||||
prepareCodexSessionResume?: PrepareCodexSessionResume
|
||||
awaitLocalPtyStartup?: () => Promise<void>
|
||||
awaitLocalPtyProviderStartup?: () => Promise<void>
|
||||
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
|
||||
|
||||
@@ -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<string, string>
|
||||
launchConfig?: SleepingAgentLaunchConfig
|
||||
resumeProviderSession?: AgentProviderSessionMetadata
|
||||
launchToken?: string
|
||||
launchAgent?: TuiAgent
|
||||
viewMode?: 'terminal' | 'chat'
|
||||
|
||||
@@ -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<string, string>
|
||||
launchConfig?: SleepingAgentLaunchConfig
|
||||
resumeProviderSession?: AgentProviderSessionMetadata
|
||||
launchToken?: string
|
||||
launchAgent?: TuiAgent
|
||||
viewMode?: 'terminal' | 'chat'
|
||||
@@ -3510,6 +3515,7 @@ const api = {
|
||||
cwd?: string
|
||||
env?: Record<string, string>
|
||||
launchConfig?: SleepingAgentLaunchConfig
|
||||
resumeProviderSession?: AgentProviderSessionMetadata
|
||||
launchToken?: string
|
||||
launchAgent?: TuiAgent
|
||||
viewMode?: 'terminal' | 'chat'
|
||||
|
||||
@@ -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<string, string>
|
||||
envToDelete?: string[]
|
||||
launchConfig?: SleepingAgentLaunchConfig
|
||||
resumeProviderSession?: AgentProviderSessionMetadata
|
||||
launchToken?: string
|
||||
launchAgent?: TuiAgent
|
||||
draftPrompt?: string
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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<ReturnType<typeof buildAgentResumeStartupPlan>>['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 } : {}),
|
||||
|
||||
@@ -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<string, string>
|
||||
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
|
||||
|
||||
@@ -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<typeof vi.fn>
|
||||
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<typeof vi.fn>
|
||||
|
||||
@@ -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 }
|
||||
: {}),
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
@@ -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 } : {}),
|
||||
|
||||
@@ -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<string, string>
|
||||
envToDelete?: string[]
|
||||
launchConfig?: SleepingAgentLaunchConfig
|
||||
resumeProviderSession?: AgentProviderSessionMetadata
|
||||
launchToken?: string
|
||||
launchAgent?: TuiAgent
|
||||
draftPrompt?: string
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<string, string>
|
||||
envToDelete?: string[]
|
||||
launchConfig?: SleepingAgentLaunchConfig
|
||||
resumeProviderSession?: AgentProviderSessionMetadata
|
||||
launchToken?: string
|
||||
launchAgent?: TuiAgent
|
||||
viewMode?: 'terminal' | 'chat'
|
||||
|
||||
Reference in New Issue
Block a user