refactor(pty): one host-env step for both terminal spawn lanes (#29297)

* test(pty): pin where the two spawn lanes build a daemon spawn's host env differently

Characterization on main's code before the host-env step merges: which failed env builds sweep
per-PTY state (minted ids only, both lanes), the empty-id split, the host-only WSL Pi/OMP guest
readiness wait, and that a host create operation still waits on a managed Codex login.

* refactor(pty): one host-env step for both terminal spawn lanes

The window (pty:spawn) and host (runtime controller) lanes each carried a copy of Codex resume,
Codex home selection, the managed-auth wait, backfill recovery and the daemon host-env build.
Both now call resolvePtySpawnHostEnv. Lane differences are explicit inputs: two TEMPORARY ones
(the host-only WSL Pi/OMP guest readiness wait; the host skipping host env for an empty id) and
three that mean the same in both lanes. The resume step's launchEnv was never read and is gone.

* test(pty): pin that both lanes hand Codex resume prep the request's CODEX_HOME

orca serve (#29209) skips resume prep for a launch whose env names a client-sent CODEX_HOME.

* test(pty): type the new lane-pin requests instead of asserting them

* test: pin selected Codex home on terminal restart
This commit is contained in:
Brennan Benson
2026-10-16 10:32:03 -07:00
committed by GitHub
parent 85254f9ecd
commit 2fa848395b
7 changed files with 498 additions and 373 deletions
@@ -1,5 +1,6 @@
import { describe, expect, it, vi } from 'vitest'
import { openCodeClearPtyMock, piBuildPtyEnvMock, piClearPtyMock } from './pty-ipc-mock-registry'
import { awaitExplicitPiOmpGuestReadiness } from '../agent-hooks/wsl-pi-omp-guest-readiness'
import { setupPtyIpcSuite } from './pty-ipc-test-harness'
import { createDaemonActiveProviderFixtures } from './pty-ipc-daemon-provider-fixtures'
import { registerPtyHandlers } from './pty/register-handlers'
@@ -48,6 +49,9 @@ vi.mock('../codex/codex-pane-account-registry', () =>
vi.mock('../codex/codex-state-db-backfill-recovery', () =>
import('./pty-ipc-mock-registry').then((m) => m.codexBackfillRecoveryModuleMock())
)
vi.mock('../agent-hooks/wsl-pi-omp-guest-readiness', () => ({
awaitExplicitPiOmpGuestReadiness: vi.fn(async () => true)
}))
describe('registerPtyHandlers', () => {
const { handlers, mainWindow, recoveredAgentClaim, recoveredAgentSurface } = setupPtyIpcSuite()
@@ -322,4 +326,82 @@ describe('registerPtyHandlers', () => {
})
})
})
// Pins main's host-env step (row 4, C5c-1): where the window (pty:spawn) and host (runtime
// controller) lanes build a daemon spawn's env differently today.
describe('daemon host env: where the lanes differ today', () => {
const { setupDaemonAdapter } = createDaemonActiveProviderFixtures({ handlers, mainWindow })
function startLanes() {
const daemonSpawn = setupDaemonAdapter()
let controller: { spawn: (args: Record<string, unknown>) => Promise<unknown> } | undefined
const runtime = {
setPtyController: vi.fn((next) => {
controller = next
}),
createPreAllocatedTerminalHandle: vi.fn(() => null),
registerPreAllocatedHandleForPty: vi.fn(),
registerPty: vi.fn()
}
handlers.clear()
registerPtyHandlers(mainWindow as never, runtime as never)
return {
daemonSpawn,
spawn: (lane: 'window' | 'host', request: Record<string, unknown>) =>
lane === 'window'
? handlers.get('pty:spawn')!(null, { cols: 80, rows: 24, ...request })
: controller!.spawn({ cols: 80, rows: 24, ...request })
}
}
// Only the id this request minted is swept; an operation id or caller id may name a live PTY.
it.each([
{ lane: 'window', name: 'a minted id', request: {}, swept: true },
{ lane: 'window', name: 'a caller id', request: { sessionId: 'caller-1' }, swept: false },
{ lane: 'host', name: 'a minted id', request: {}, swept: true },
{
lane: 'host',
name: 'an operation id',
request: { worktreeId: 'wt-alpha', agentSessionCreateOperationId: 'a'.repeat(43) },
swept: false
},
{
lane: 'host',
name: 'a new caller id',
request: { sessionId: 'caller-1', isNewSession: true },
swept: false
}
] as const)('$lane lane, $name: a failed env build sweeps per-PTY state: $swept', async (c) => {
const lanes = startLanes()
piBuildPtyEnvMock.mockImplementationOnce(() => {
throw new Error('env boom')
})
await expect(lanes.spawn(c.lane, { env: {}, ...c.request })).rejects.toThrow('env boom')
expect(lanes.daemonSpawn).not.toHaveBeenCalled()
expect(piClearPtyMock).toHaveBeenCalledTimes(c.swept ? 1 : 0)
})
// TEMPORARY (row 7): an empty id (the host trims a blank one to it) skips the host's host env; the
// window refuses it.
it('an empty session id: the window refuses it; the host spawns without host env', async () => {
const lanes = startLanes()
await expect(lanes.spawn('window', { env: {}, sessionId: '' })).rejects.toThrow(
/Invalid PTY session id/
)
await lanes.spawn('host', { env: {}, sessionId: '' })
expect(lanes.daemonSpawn.mock.calls.at(-1)![0].env.ORCA_AGENT_HOOK_PORT).toBeUndefined()
expect(piBuildPtyEnvMock).not.toHaveBeenCalled()
})
// TEMPORARY: only host starts wait for a WSL guest's Pi/OMP relay.
it('only the host lane runs the WSL Pi/OMP guest readiness wait', async () => {
const lanes = startLanes()
vi.mocked(awaitExplicitPiOmpGuestReadiness).mockClear()
await lanes.spawn('window', { env: {}, command: 'pi' })
expect(vi.mocked(awaitExplicitPiOmpGuestReadiness)).not.toHaveBeenCalled()
await lanes.spawn('host', { env: {}, command: 'pi' })
expect(vi.mocked(awaitExplicitPiOmpGuestReadiness)).toHaveBeenCalledOnce()
})
})
})
@@ -5,6 +5,7 @@ import { setupPtyIpcSuite } from './pty-ipc-test-harness'
import { createDaemonActiveProviderFixtures } from './pty-ipc-daemon-provider-fixtures'
import { ManagedCodexHomeTemporarilyUnavailableError } from '../codex-accounts/host-codex-managed-home-ownership'
import { registerPtyHandlers } from './pty/register-handlers'
import type { PrepareCodexSessionResume } from './pty/host-env/types'
vi.mock('electron', () => import('./pty-ipc-mock-registry').then((m) => m.electronModuleMock()))
vi.mock('fs', () => import('./pty-ipc-mock-registry').then((m) => m.fsModuleMock()))
@@ -63,8 +64,12 @@ const MANAGED_ACCOUNT_SETTINGS = (() =>
]
}) as never) as () => never
type RuntimeSpawnRequest = { cols: number; rows: number; launchAgent?: 'codex' } & Record<
string,
unknown
>
type RuntimeSpawnController = {
spawn(args: { cols: number; rows: number; launchAgent?: 'codex' }): Promise<{ id: string }>
spawn(args: RuntimeSpawnRequest): Promise<{ id: string }>
}
/**
@@ -82,6 +87,7 @@ describe('registerPtyHandlers Codex launch refusal on an unreadable managed home
function makeRuntime() {
return {
setPtyController: vi.fn(),
createPreAllocatedTerminalHandle: vi.fn(() => null),
registerPty: vi.fn(),
noteTerminalSpawnCommand: vi.fn(),
onPtySpawned: vi.fn(),
@@ -97,7 +103,7 @@ describe('registerPtyHandlers Codex launch refusal on an unreadable managed home
context?: { unavailableManagedHomePath?: string; launchesCodex?: boolean }
) => string | null,
runtime?: ReturnType<typeof makeRuntime>,
prepareCodexSessionResume?: () => Promise<never>
prepareCodexSessionResume?: PrepareCodexSessionResume
): void {
handlers.clear()
registerPtyHandlers(
@@ -357,6 +363,92 @@ describe('registerPtyHandlers Codex launch refusal on an unreadable managed home
})
})
// Both lanes: only a launch whose caller named no session waits on a managed login. A host
// create operation's derived id is not caller-named, so it waits too.
it.each([
{
lane: 'host',
name: 'a create operation',
request: { worktreeId: 'wt-alpha', agentSessionCreateOperationId: 'a'.repeat(43) },
waits: true
},
{
lane: 'host',
name: 'a caller-named session',
request: { sessionId: 'caller-1' },
waits: false
},
{
lane: 'window',
name: 'a caller-named session',
request: { sessionId: 'caller-1' },
waits: false
}
] as const)('$lane lane, $name: waits on the managed login: $waits', async (c) => {
vi.useFakeTimers()
makeAuthUnreadable()
const daemonSpawn = setupDaemonAdapter()
const runtime = makeRuntime()
register((_target, _env, context) => {
if (context?.unavailableManagedHomePath) {
throw new ManagedCodexHomeTemporarilyUnavailableError()
}
return TEST_CODEX_HOME
}, runtime)
const controller: RuntimeSpawnController = runtime.setPtyController.mock.calls[0]?.[0]
const request: RuntimeSpawnRequest = { cols: 80, rows: 24, launchAgent: 'codex', ...c.request }
const spawn = Promise.resolve(
c.lane === 'host' ? controller.spawn(request) : handlers.get('pty:spawn')!(null, request)
).then(
() => 'spawned',
(error: Error) => error.message
)
await vi.advanceTimersByTimeAsync(2_000)
await expect(spawn).resolves.toBe(c.waits ? UNAVAILABLE_MESSAGE : 'spawned')
expect(daemonSpawn).toHaveBeenCalledTimes(c.waits ? 0 : 1)
})
// #29209: a host runtime skips resume prep when the launch env names a client-sent CODEX_HOME.
it.each(['window', 'host', 'window restart'] as const)(
'%s: resume preserves the request home and selected-account restart',
async (entry) => {
const restart = entry === 'window restart'
readFileSyncMock.mockReturnValue(TEST_CODEX_AUTH_JSON)
const daemonSpawn = setupDaemonAdapter()
const runtime = makeRuntime()
const sourceHome = `${TEST_CODEX_HOME}-source`
const prepareResume = vi.fn<PrepareCodexSessionResume>(async (args) => ({
outcome: 'resume',
codexHomePath: args.useSelectedAccount ? TEST_CODEX_HOME : sourceHome
}))
register(() => TEST_CODEX_HOME, runtime, prepareResume)
const controller: RuntimeSpawnController = runtime.setPtyController.mock.calls[0]?.[0]
const request: RuntimeSpawnRequest = {
cols: 80,
rows: 24,
launchAgent: 'codex',
command: `codex resume ${RESUME_SESSION.id}`,
resumeProviderSession: RESUME_SESSION,
...(restart ? { replacesPtyId: 'old-pty' } : {}),
env: { CODEX_HOME: '/client/codex-home' }
}
await (entry === 'host'
? controller.spawn(request)
: handlers.get('pty:spawn')!(null, request))
expect(prepareResume.mock.calls[0]?.[0].launchEnv).toMatchObject({
CODEX_HOME: '/client/codex-home'
})
expect(prepareResume.mock.calls[0]?.[0].useSelectedAccount).toBe(restart ? true : undefined)
expect(daemonSpawn.mock.calls[0]?.[0].env.CODEX_HOME).toBe(
restart ? TEST_CODEX_HOME : sourceHome
)
}
)
// Why: only an Orca-launched Codex may wait for Codex's hook hashes; a plain terminal never does.
describe('which spawns launch prep may make wait', () => {
it('marks a Codex launch, and not a plain terminal, on the local spawn path', async () => {
+31 -161
View File
@@ -1,23 +1,5 @@
import { inheritOmpLaunchEnvironment } from '../host-env/omp-launch-environment'
import { getAppEnvironment } from '../../../../shared/app-environment'
import { isTuiAgent } from '../../../../shared/tui-agent-config'
import { isAgentStatusHooksEnabled } from '../../../agent-hooks/managed-agent-hook-controls'
import { isSafePtySessionId } from '../../../daemon/pty-session-id'
import { isNativeWindowsLocalPtySpawn } from '../../../runtime/terminal-model-query-authority'
import { stampWslOrchestrationCompatibilityHost } from '../../../pty/wsl-orca-env'
import { stampRuntimeSourceEnv } from '../../../../shared/runtime-source-env'
import { getRuntimeSourceStamp } from '../../../runtime/host-descriptor'
import { ensureCodexStateDbBackfillRecoveryStarted } from '../../../codex/codex-state-db-backfill-recovery'
import { buildPtyHostEnv } from '../host-env/assembly'
import {
getCompatibleSelectedCodexHomePath,
resolveCodexHomeAfterManagedAuthReadiness,
shouldSkipCodexHomeEnvForWindowsShell,
shouldStripInheritedOrcaCodexHome,
codexHomePathsEqual
} from '../host-env/codex-home'
import { promoteAgentTeamsShimPath } from '../host-env/path'
import { clearProviderPtyState } from '../provider/state-cleanup'
import { resolvePtySpawnHostEnv } from '../spawn-host-env'
import type { PtyIpcSpawnState } from './spawn-state'
export async function assemblePtyIpcSpawnCodexEnv(ctx: PtyIpcSpawnState): Promise<void> {
@@ -28,147 +10,35 @@ export async function assemblePtyIpcSpawnCodexEnv(ctx: PtyIpcSpawnState): Promis
cwd: args.cwd,
shellOverride: ctx.effectiveShellOverride
})
const codexResumePreparation = ctx.preAdoptedStablePane
? null
: ctx.deps.prepareCodexResumeHome({
connectionId: args.connectionId,
launchAgent: args.launchAgent,
providerSession: args.resumeProviderSession,
target: ctx.codexSelectionTarget,
launchEnv: ctx.baseEnv,
...(args.replacesPtyId ? { useSelectedAccount: true } : {})
})
ctx.codexResumeLaunch = codexResumePreparation
? await ctx.deps.resolveCodexResumeLaunch(args.command, codexResumePreparation)
: ctx.deps.noCodexResumeLaunch(ctx.preAdoptedStablePane ? undefined : args.command)
// Why: these three phases have unrelated costs (session provenance + hook
// repair, account/auth resolution, then the synchronous env build). One
// `host_env` label hid all of them behind the name of the cheapest.
ctx.spawnTiming.mark('codex_resume')
const codexResumeHome = ctx.codexResumeLaunch.codexResumeHome
ctx.launchCommand = ctx.codexResumeLaunch.command
ctx.baseEnv = ctx.deps.stripSequencedStartupResumeArgv(ctx.baseEnv, ctx.codexResumeLaunch)
// Why: declared after the strip so a local-provider spawn cannot capture the
// pre-strip env — only the daemon branch below re-derives this from baseEnv.
ctx.env = ctx.baseEnv
const launchesCodex = args.launchAgent === 'codex'
const selectLaunchCodexHome = async (): Promise<string | null> =>
(await ctx.deps.getSelectedCodexHomePath?.(ctx.codexSelectionTarget, ctx.baseEnv, {
launchesCodex
})) ?? null
ctx.selectedCodexHomePath =
!ctx.preAdoptedStablePane && !args.connectionId
? getCompatibleSelectedCodexHomePath(
ctx.codexSelectionTarget,
codexResumeHome
? await ctx.deps.reconcileSharedRuntimeResumeHome(codexResumeHome, async () =>
getCompatibleSelectedCodexHomePath(
ctx.codexSelectionTarget,
await selectLaunchCodexHome()
)
)
: await selectLaunchCodexHome()
)
: null
if (!ctx.preAdoptedStablePane && args.launchAgent === 'codex' && args.sessionId === undefined) {
const resolution = resolveCodexHomeAfterManagedAuthReadiness({
selectedCodexHomePath: ctx.selectedCodexHomePath,
getSettings: () => ctx.deps.getSettings?.(),
requiredCodexHomePath: codexResumeHome?.codexHomePath,
target: ctx.codexSelectionTarget,
resolveCurrent: async () =>
getCompatibleSelectedCodexHomePath(
ctx.codexSelectionTarget,
(await ctx.deps.getSelectedCodexHomePath?.(ctx.codexSelectionTarget, ctx.baseEnv, {
launchesCodex
})) ?? null
),
resolveAfterUnavailable: async (unavailableManagedHomePath) =>
getCompatibleSelectedCodexHomePath(
ctx.codexSelectionTarget,
(await ctx.deps.getSelectedCodexHomePath?.(ctx.codexSelectionTarget, ctx.baseEnv, {
unavailableManagedHomePath,
launchesCodex
})) ?? null
)
})
ctx.selectedCodexHomePath = resolution instanceof Promise ? await resolution : resolution
}
if (args.launchAgent === 'codex' && ctx.selectedCodexHomePath) {
await ensureCodexStateDbBackfillRecoveryStarted(ctx.selectedCodexHomePath)
}
ctx.spawnTiming.mark('codex_home')
ctx.codexResumeHomeSelected = Boolean(
codexResumeHome && codexHomePathsEqual(ctx.selectedCodexHomePath, codexResumeHome.codexHomePath)
)
ctx.skipCodexHomeEnv =
ctx.isDaemonHostSpawn &&
shouldSkipCodexHomeEnvForWindowsShell(ctx.effectiveShellOverride, ctx.cwd) &&
!ctx.selectedCodexHomePath
const ptySettings = ctx.isDaemonHostSpawn ? ctx.deps.getSettings?.() : undefined
ctx.stripInheritedOrcaCodexHome =
ctx.isDaemonHostSpawn &&
shouldStripInheritedOrcaCodexHome({
target: ctx.codexSelectionTarget,
selectedCodexHomePath: ctx.selectedCodexHomePath,
skipCodexHomeEnv: ctx.skipCodexHomeEnv,
settings: ptySettings
})
if (ctx.isDaemonHostSpawn && !ctx.preAdoptedStablePane) {
if (ctx.effectiveSessionId === undefined) {
// Should be unreachable: effectiveSessionId is a string when isDaemonHostSpawn; defense-in-depth.
throw new Error('Invariant violation: daemon spawn without sessionId')
}
const sessionIdForEnv = ctx.effectiveSessionId
// Why: this id reaches filesystem paths; reject traversal/separators so a crafted IPC payload can't escape the expected roots.
if (!isSafePtySessionId(sessionIdForEnv, getAppEnvironment().getPath('userData'))) {
throw new Error('Invalid PTY session id')
}
// Why: clone before mutating so injections don't leak back into args.env (renderer may reuse it).
ctx.env = { ...ctx.baseEnv }
try {
await inheritOmpLaunchEnvironment(ctx.env, {
isWsl: shouldSkipCodexHomeEnvForWindowsShell(ctx.effectiveShellOverride, ctx.cwd),
launchAgent: args.launchAgent,
launchCommand: ctx.launchCommand
})
buildPtyHostEnv(sessionIdForEnv, ctx.env, {
isPackaged: getAppEnvironment().isPackaged(),
resourcesPath: process.resourcesPath,
userDataPath: getAppEnvironment().getPath('userData'),
selectedCodexHomePath: ctx.selectedCodexHomePath,
skipCodexHomeEnv: ctx.skipCodexHomeEnv,
stripInheritedOrcaCodexHome: ctx.stripInheritedOrcaCodexHome,
launchCommand: ctx.launchCommand,
launchAgent: isTuiAgent(args.launchAgent) ? args.launchAgent : undefined,
shellPath: ctx.effectiveShellOverride,
isWsl: shouldSkipCodexHomeEnvForWindowsShell(ctx.effectiveShellOverride, ctx.cwd),
wslDistro: ctx.codexSelectionTarget.runtime === 'wsl' ? ctx.expectedWslDistro : null,
agentStatusHooksEnabled: isAgentStatusHooksEnabled(ptySettings),
terminalInlineImages: ptySettings?.terminalInlineImages,
disabledTuiAgents: ptySettings?.disabledTuiAgents,
networkProxySettings: ptySettings,
routeBrowserOpensToClient: ctx.deps.runtime?.shouldRelayTerminalBrowserOpens?.(),
deferGitConfigGuardToDaemon:
ctx.provider.supportsGitCredentialGuardHost?.(ctx.effectiveSessionId) === true
})
stampWslOrchestrationCompatibilityHost(
ctx.env,
ctx.deps.runtime?.getOrchestrationCompatibilityHostId?.(),
ctx.codexSelectionTarget.runtime === 'wsl' ? ctx.expectedWslDistro : null
)
stampRuntimeSourceEnv(
ctx.env,
getRuntimeSourceStamp(ctx.deps.runtime, getAppEnvironment().getPath('userData'))
)
promoteAgentTeamsShimPath(ctx.env, ctx.requestedAgentTeamsPath)
} catch (err) {
// Why: buildPtyHostEnv has fs side-effects (Pi/OMP install); clear per-PTY state on throw, but only minted ids — caller ids may name existing PTYs.
if (ctx.isMintedSessionId) {
clearProviderPtyState(sessionIdForEnv)
}
throw err
}
}
const hostEnv = await resolvePtySpawnHostEnv({
launchAgent: args.launchAgent,
command: args.command,
resumeProviderSession: args.resumeProviderSession,
env: ctx.baseEnv,
cwd: ctx.cwd,
shellOverride: ctx.effectiveShellOverride,
expectedWslDistro: ctx.expectedWslDistro,
codexSelectionTarget: ctx.codexSelectionTarget,
preAdopted: Boolean(ctx.preAdoptedStablePane),
isDaemonHostSpawn: ctx.isDaemonHostSpawn,
sessionId: ctx.effectiveSessionId,
callerNamedSession: args.sessionId !== undefined,
mintedSessionId: ctx.isMintedSessionId,
resumeOnSelectedAccount: Boolean(args.replacesPtyId),
awaitsWslPiOmpGuestReadiness: false,
emptySessionIdSkipsHostEnv: false,
requestedAgentTeamsPath: ctx.requestedAgentTeamsPath,
provider: ctx.provider,
deps: ctx.deps,
mark: (phase) => ctx.spawnTiming.mark(phase)
})
ctx.codexResumeLaunch = hostEnv.codexResumeLaunch
ctx.launchCommand = hostEnv.launchCommand
ctx.baseEnv = hostEnv.baseEnv
ctx.env = hostEnv.env
ctx.selectedCodexHomePath = hostEnv.selectedCodexHomePath
ctx.codexResumeHomeSelected = hostEnv.codexResumeHomeSelected
ctx.skipCodexHomeEnv = hostEnv.skipCodexHomeEnv
ctx.stripInheritedOrcaCodexHome = hostEnv.stripInheritedOrcaCodexHome
ctx.spawnTiming.mark('host_env')
}
+2 -25
View File
@@ -10,17 +10,15 @@ import type { TerminalStartupCwdMissingDirFallback } from '../../../../shared/te
import type { OrcaRuntimeService } from '../../../runtime/orca-runtime'
import type { Store } from '../../../persistence'
import type { PtySpawnResult } from '../../../providers/pty-provider-contract'
import type { CodexAccountSelectionTarget } from '../../../codex-accounts/runtime-selection'
import type { CodexSessionResumePreparation } from '../../../codex/codex-session-resume-home'
import type {
CodexHomePtySpawnedLifecycleArgs,
GetSelectedCodexHomePath,
PrepareClaudeAuth,
PrepareCodexSessionResume
} from '../host-env/types'
import type { CodexResumeLaunch, PreparedCodexResumeHome } from '../host-env/codex-resume'
import type { StablePaneOwner } from '../pane/stable-owner'
import type { ResolvedPtyHost } from '../provider/registry'
import type { PtySpawnCodexResumeDeps } from '../spawn-host-env'
export type PtySpawnIpcArgs = {
cols: number
@@ -86,7 +84,7 @@ export type AdoptStablePaneResult = {
materialized?: true
}
export type PtySpawnIpcDeps = {
export type PtySpawnIpcDeps = PtySpawnCodexResumeDeps & {
runtime?: OrcaRuntimeService
store?: Store
getSettings?: () => GlobalSettings
@@ -108,27 +106,6 @@ export type PtySpawnIpcDeps = {
missingDirFallback?: TerminalStartupCwdMissingDirFallback
) => string | undefined
localStartupCwdDirectoryExists: (path: string) => boolean
prepareCodexResumeHome: (args: {
connectionId?: string | null
launchAgent?: TuiAgent
providerSession?: AgentProviderSessionMetadata
target: CodexAccountSelectionTarget
launchEnv?: NodeJS.ProcessEnv
useSelectedAccount?: boolean
}) => PreparedCodexResumeHome | null
noCodexResumeLaunch: (command: string | undefined) => CodexResumeLaunch
resolveCodexResumeLaunch: (
command: string | undefined,
prepared: PreparedCodexResumeHome
) => Promise<CodexResumeLaunch>
reconcileSharedRuntimeResumeHome: (
resumeHome: Extract<CodexSessionResumePreparation, { outcome: 'resume' }>,
resolveCurrent: () => string | null | Promise<string | null>
) => Promise<string>
stripSequencedStartupResumeArgv: <T extends Record<string, string> | undefined>(
env: T,
launch: CodexResumeLaunch
) => T
transitionSpawnHiddenRendererPtyDeliveryState: (id: string, hidden: boolean) => void
trustedTerminalHandleEnv: Set<string>
sendPtySpawnedToRenderer: (id: string) => void
+2 -26
View File
@@ -3,49 +3,25 @@ import type { OrcaRuntimeService } from '../../../runtime/orca-runtime'
import type { Store } from '../../../persistence'
import type { IPtyProvider } from '../../../providers/pty-provider-contract'
import type { GlobalSettings } from '../../../../shared/global-settings-types'
import type { TuiAgent } from '../../../../shared/tui-agent'
import type { AgentProviderSessionMetadata } from '../../../../shared/agent-session-resume'
import type { TerminalStartupCwdMissingDirFallback } from '../../../../shared/terminal-startup-cwd'
import type { CodexAccountSelectionTarget } from '../../../codex-accounts/runtime-selection'
import type { CodexSessionResumePreparation } from '../../../codex/codex-session-resume-home'
import type {
CodexHomePtySpawnedLifecycleArgs,
GetSelectedCodexHomePath,
PrepareClaudeAuth,
PrepareCodexSessionResume
} from '../host-env/types'
import type { CodexResumeLaunch, PreparedCodexResumeHome } from '../host-env/codex-resume'
import type { StablePaneOwner } from '../pane/stable-owner'
import type { AdoptStablePaneArgs, AdoptStablePaneResult } from '../ipc/spawn-types'
import type { finishPtyShutdown } from '../provider/liveness'
import type { ResolvedPtyHost } from '../provider/registry'
import type { PtySpawnCodexResumeDeps } from '../spawn-host-env'
export type PtyRuntimeControllerDeps = {
export type PtyRuntimeControllerDeps = PtySpawnCodexResumeDeps & {
runtime?: OrcaRuntimeService
store?: Store
adoptStablePane: (args: AdoptStablePaneArgs) => Promise<AdoptStablePaneResult | null>
getLocalPtyStartupPromise: (hostId?: ResolvedPtyHost) => Promise<void> | undefined
getLocalPtyProviderStartupPromise: (hostId?: ResolvedPtyHost) => Promise<void> | undefined
prepareCodexResumeHome: (args: {
connectionId?: string | null
launchAgent?: TuiAgent
providerSession?: AgentProviderSessionMetadata
target: CodexAccountSelectionTarget
launchEnv?: NodeJS.ProcessEnv
}) => PreparedCodexResumeHome | null
resolveCodexResumeLaunch: (
command: string | undefined,
prepared: PreparedCodexResumeHome
) => Promise<CodexResumeLaunch>
noCodexResumeLaunch: (command: string | undefined) => CodexResumeLaunch
reconcileSharedRuntimeResumeHome: (
resumeHome: Extract<CodexSessionResumePreparation, { outcome: 'resume' }>,
resolveCurrent: () => string | null | Promise<string | null>
) => Promise<string>
stripSequencedStartupResumeArgv: <T extends Record<string, string> | undefined>(
env: T,
launch: CodexResumeLaunch
) => T
assertWorkspaceAcceptsNewPty: (
worktreeId: string | undefined,
sessionId?: string
+31 -159
View File
@@ -1,41 +1,22 @@
import { withClaudeProfileTerminalEnv } from '../../../claude-accounts/claude-profile-installed-router'
import { inheritOmpLaunchEnvironment } from '../host-env/omp-launch-environment'
import { getAppEnvironment } from '../../../../shared/app-environment'
import type { PtySpawnResult } from '../../../providers/pty-provider-contract'
import { LocalPtyProvider } from '../../../providers/local-pty-provider'
import { isValidTerminalTabId } from '../../../../shared/terminal-tab-id'
import { isTerminalLeafId } from '../../../../shared/stable-pane-id'
import { getProvider } from '../provider/registry'
import { buildPtyHostEnv } from '../host-env/assembly'
import {
getCompatibleSelectedCodexHomePath,
resolveCodexHomeAfterManagedAuthReadiness,
shouldSkipCodexHomeEnvForWindowsShell,
shouldStripInheritedOrcaCodexHome,
codexHomePathsEqual
} from '../host-env/codex-home'
import { promoteAgentTeamsShimPath } from '../host-env/path'
import {
isClaudeLaunchCommand,
recoverFreshSpawnProviderRouting,
routesFreshSpawnsToLocalProvider
} from '../host-env/fresh-spawn-routing'
import { isTuiAgent } from '../../../../shared/tui-agent-config'
import {
isSafePtySessionId,
mintPtySessionId,
ptySessionIdForAgentCreateOperation
} from '../../../daemon/pty-session-id'
import { isAgentStatusHooksEnabled } from '../../../agent-hooks/managed-agent-hook-controls'
import { resolvePathEnvKey } from '../../../pty/windows-environment-path'
import { stampWslOrchestrationCompatibilityHost } from '../../../pty/wsl-orca-env'
import { stampRuntimeSourceEnv } from '../../../../shared/runtime-source-env'
import { getRuntimeSourceStamp } from '../../../runtime/host-descriptor'
import { ensureCodexStateDbBackfillRecoveryStarted } from '../../../codex/codex-state-db-backfill-recovery'
import { clearProviderPtyState } from '../provider/state-cleanup'
import { awaitExplicitPiOmpGuestReadiness } from '../../../agent-hooks/wsl-pi-omp-guest-readiness'
import type { RuntimePtySpawnState } from './spawn-state'
import { resolvePtySpawnStartupTarget } from '../spawn-startup-target'
import { resolvePtySpawnHostEnv } from '../spawn-host-env'
import { getConnectionExecutionHostId } from '../../../../shared/execution-host'
export async function prepareRuntimePtySpawn(
@@ -90,23 +71,6 @@ export async function prepareRuntimePtySpawn(
ctx.daemonShellOverride = ctx.terminalRuntimeOptions.shellOverride
ctx.expectedWslDistro = target.expectedWslDistro
ctx.codexSelectionTarget = target.codexSelectionTarget
const codexResumePreparation = ctx.preAdoptedStablePane
? null
: ctx.deps.prepareCodexResumeHome({
connectionId: args.connectionId,
launchAgent: args.launchAgent,
providerSession: args.resumeProviderSession,
target: ctx.codexSelectionTarget,
launchEnv: args.env
})
const codexResumeLaunch = codexResumePreparation
? await ctx.deps.resolveCodexResumeLaunch(args.command, codexResumePreparation)
: ctx.deps.noCodexResumeLaunch(ctx.preAdoptedStablePane ? undefined : args.command)
const codexResumeHome = codexResumeLaunch.codexResumeHome
// Why: the drop still applies here, but this controller's result has no field for
// notifyResumeUnavailable — runtime/relay panes start fresh without the notice.
ctx.launchCommand = codexResumeLaunch.command
ctx.codexResumeArgvDropped = codexResumeLaunch.droppedResumeArgv
args.env = withClaudeProfileTerminalEnv(args.env, ctx.codexSelectionTarget)
ctx.claudeAuth =
ctx.isClaudeLaunch && ctx.deps.prepareClaudeAuth
@@ -138,131 +102,39 @@ export async function prepareRuntimePtySpawn(
ctx.requestedAgentTeamsPath = ctx.env?.ORCA_AGENT_TEAMS_TEAM_ID
? ctx.env[resolvePathEnvKey(ctx.env, process.platform)]
: undefined
ctx.env = ctx.deps.stripSequencedStartupResumeArgv(ctx.env, codexResumeLaunch)
if (args.preAllocatedHandle) {
ctx.env = { ...ctx.env, ORCA_TERMINAL_HANDLE: args.preAllocatedHandle }
}
const launchesCodex = args.launchAgent === 'codex'
const selectLaunchCodexHome = async (): Promise<string | null> =>
(await ctx.deps.getSelectedCodexHomePath?.(ctx.codexSelectionTarget, ctx.env, {
launchesCodex
})) ?? null
ctx.selectedCodexHomePath = !ctx.preAdoptedStablePane
? getCompatibleSelectedCodexHomePath(
ctx.codexSelectionTarget,
codexResumeHome
? await ctx.deps.reconcileSharedRuntimeResumeHome(codexResumeHome, async () =>
getCompatibleSelectedCodexHomePath(
ctx.codexSelectionTarget,
await selectLaunchCodexHome()
)
)
: await selectLaunchCodexHome()
)
: null
if (
!ctx.preAdoptedStablePane &&
args.launchAgent === 'codex' &&
ctx.callerRequestedSessionId === undefined
) {
const resolution = resolveCodexHomeAfterManagedAuthReadiness({
selectedCodexHomePath: ctx.selectedCodexHomePath,
getSettings: () => ctx.deps.getSettings?.(),
requiredCodexHomePath: codexResumeHome?.codexHomePath,
target: ctx.codexSelectionTarget,
resolveCurrent: async () =>
getCompatibleSelectedCodexHomePath(
ctx.codexSelectionTarget,
(await ctx.deps.getSelectedCodexHomePath?.(ctx.codexSelectionTarget, ctx.env, {
launchesCodex
})) ?? null
),
resolveAfterUnavailable: async (unavailableManagedHomePath) =>
getCompatibleSelectedCodexHomePath(
ctx.codexSelectionTarget,
(await ctx.deps.getSelectedCodexHomePath?.(ctx.codexSelectionTarget, ctx.env, {
unavailableManagedHomePath,
launchesCodex
})) ?? null
)
})
ctx.selectedCodexHomePath = resolution instanceof Promise ? await resolution : resolution
}
if (args.launchAgent === 'codex' && ctx.selectedCodexHomePath) {
await ensureCodexStateDbBackfillRecoveryStarted(ctx.selectedCodexHomePath)
}
ctx.codexResumeHomeSelected = Boolean(
codexResumeHome && codexHomePathsEqual(ctx.selectedCodexHomePath, codexResumeHome.codexHomePath)
)
ctx.skipCodexHomeEnv =
ctx.isDaemonHostSpawn &&
shouldSkipCodexHomeEnvForWindowsShell(ctx.daemonShellOverride, ctx.cwd) &&
!ctx.selectedCodexHomePath
const ptySettings = ctx.isDaemonHostSpawn ? ctx.deps.getSettings?.() : undefined
ctx.stripInheritedOrcaCodexHome =
ctx.isDaemonHostSpawn &&
shouldStripInheritedOrcaCodexHome({
target: ctx.codexSelectionTarget,
selectedCodexHomePath: ctx.selectedCodexHomePath,
skipCodexHomeEnv: ctx.skipCodexHomeEnv,
settings: ptySettings
})
if (ctx.isDaemonHostSpawn && ctx.sessionId && !ctx.preAdoptedStablePane) {
if (!isSafePtySessionId(ctx.sessionId, getAppEnvironment().getPath('userData'))) {
throw new Error('Invalid PTY session id')
}
try {
ctx.env ??= {}
await inheritOmpLaunchEnvironment(ctx.env, {
isWsl: shouldSkipCodexHomeEnvForWindowsShell(ctx.daemonShellOverride, ctx.cwd),
launchAgent: args.launchAgent,
launchCommand: ctx.launchCommand
})
await awaitExplicitPiOmpGuestReadiness({
isWsl: shouldSkipCodexHomeEnvForWindowsShell(ctx.daemonShellOverride, ctx.cwd),
distro: ctx.codexSelectionTarget.runtime === 'wsl' ? ctx.expectedWslDistro : null,
codexHomePath: ctx.selectedCodexHomePath,
launchAgent: isTuiAgent(args.launchAgent) ? args.launchAgent : undefined,
launchCommand: ctx.launchCommand
})
ctx.env = buildPtyHostEnv(ctx.sessionId, ctx.env ?? {}, {
isPackaged: getAppEnvironment().isPackaged(),
resourcesPath: process.resourcesPath,
userDataPath: getAppEnvironment().getPath('userData'),
selectedCodexHomePath: ctx.selectedCodexHomePath,
skipCodexHomeEnv: ctx.skipCodexHomeEnv,
stripInheritedOrcaCodexHome: ctx.stripInheritedOrcaCodexHome,
launchCommand: ctx.launchCommand,
launchAgent: isTuiAgent(args.launchAgent) ? args.launchAgent : undefined,
shellPath: ctx.daemonShellOverride,
isWsl: shouldSkipCodexHomeEnvForWindowsShell(ctx.daemonShellOverride, ctx.cwd),
wslDistro: ctx.codexSelectionTarget.runtime === 'wsl' ? ctx.expectedWslDistro : null,
agentStatusHooksEnabled: isAgentStatusHooksEnabled(ptySettings),
terminalInlineImages: ptySettings?.terminalInlineImages,
disabledTuiAgents: ptySettings?.disabledTuiAgents,
networkProxySettings: ptySettings,
routeBrowserOpensToClient: ctx.deps.runtime?.shouldRelayTerminalBrowserOpens?.(),
deferGitConfigGuardToDaemon:
ctx.provider.supportsGitCredentialGuardHost?.(ctx.sessionId) === true
})
stampWslOrchestrationCompatibilityHost(
ctx.env,
ctx.deps.runtime?.getOrchestrationCompatibilityHostId?.(),
ctx.codexSelectionTarget.runtime === 'wsl' ? ctx.expectedWslDistro : null
)
stampRuntimeSourceEnv(
ctx.env,
getRuntimeSourceStamp(ctx.deps.runtime, getAppEnvironment().getPath('userData'))
)
promoteAgentTeamsShimPath(ctx.env, ctx.requestedAgentTeamsPath)
} catch (error) {
// Why: host-env setup can materialize agent hooks/extensions before failing.
if (ctx.requestedSessionId === undefined) {
clearProviderPtyState(ctx.sessionId)
}
throw error
}
}
// Why: a dropped resume still applies here, but this controller's result has no field for
// notifyResumeUnavailable — runtime/relay panes start fresh without the notice.
const hostEnv = await resolvePtySpawnHostEnv({
launchAgent: args.launchAgent,
command: args.command,
resumeProviderSession: args.resumeProviderSession,
env: ctx.env,
cwd: ctx.cwd,
shellOverride: ctx.daemonShellOverride,
expectedWslDistro: ctx.expectedWslDistro,
codexSelectionTarget: ctx.codexSelectionTarget,
preAdopted: Boolean(ctx.preAdoptedStablePane),
isDaemonHostSpawn: ctx.isDaemonHostSpawn,
sessionId: ctx.sessionId,
callerNamedSession: ctx.callerRequestedSessionId !== undefined,
mintedSessionId: ctx.requestedSessionId === undefined,
resumeOnSelectedAccount: false,
awaitsWslPiOmpGuestReadiness: true,
emptySessionIdSkipsHostEnv: true,
requestedAgentTeamsPath: ctx.requestedAgentTeamsPath,
provider: ctx.provider,
deps: ctx.deps
})
ctx.launchCommand = hostEnv.launchCommand
ctx.codexResumeArgvDropped = hostEnv.codexResumeLaunch.droppedResumeArgv
ctx.env = hostEnv.env
ctx.selectedCodexHomePath = hostEnv.selectedCodexHomePath
ctx.codexResumeHomeSelected = hostEnv.codexResumeHomeSelected
ctx.skipCodexHomeEnv = hostEnv.skipCodexHomeEnv
ctx.stripInheritedOrcaCodexHome = hostEnv.stripInheritedOrcaCodexHome
return null
}
+256
View File
@@ -0,0 +1,256 @@
// The env a new PTY starts with and the Codex home it runs, for both the window and the host spawn lane.
import type { AgentProviderSessionMetadata } from '../../../shared/agent-session-resume'
import { getAppEnvironment } from '../../../shared/app-environment'
import type { GlobalSettings } from '../../../shared/global-settings-types'
import { stampRuntimeSourceEnv } from '../../../shared/runtime-source-env'
import type { TuiAgent } from '../../../shared/tui-agent'
import { isTuiAgent } from '../../../shared/tui-agent-config'
import { isAgentStatusHooksEnabled } from '../../agent-hooks/managed-agent-hook-controls'
import { awaitExplicitPiOmpGuestReadiness } from '../../agent-hooks/wsl-pi-omp-guest-readiness'
import { ensureCodexStateDbBackfillRecoveryStarted } from '../../codex/codex-state-db-backfill-recovery'
import type { CodexSessionResumePreparation } from '../../codex/codex-session-resume-home'
import type { CodexAccountSelectionTarget } from '../../codex-accounts/runtime-selection'
import { isSafePtySessionId } from '../../daemon/pty-session-id'
import type { IPtyProvider } from '../../providers/pty-provider-contract'
import { stampWslOrchestrationCompatibilityHost } from '../../pty/wsl-orca-env'
import { getRuntimeSourceStamp } from '../../runtime/host-descriptor'
import type { OrcaRuntimeService } from '../../runtime/orca-runtime'
import { buildPtyHostEnv } from './host-env/assembly'
import {
codexHomePathsEqual,
getCompatibleSelectedCodexHomePath,
resolveCodexHomeAfterManagedAuthReadiness,
shouldSkipCodexHomeEnvForWindowsShell,
shouldStripInheritedOrcaCodexHome
} from './host-env/codex-home'
import type {
CodexResumeLaunch,
PrepareCodexResumeHomeArgs,
PreparedCodexResumeHome
} from './host-env/codex-resume'
import { inheritOmpLaunchEnvironment } from './host-env/omp-launch-environment'
import { promoteAgentTeamsShimPath } from './host-env/path'
import type { GetSelectedCodexHomePath } from './host-env/types'
import { clearProviderPtyState } from './provider/state-cleanup'
/** The Codex resume steps both lanes are handed (bound in register-handlers). */
export type PtySpawnCodexResumeDeps = {
prepareCodexResumeHome: (args: PrepareCodexResumeHomeArgs) => PreparedCodexResumeHome | null
resolveCodexResumeLaunch: (
command: string | undefined,
prepared: PreparedCodexResumeHome
) => Promise<CodexResumeLaunch>
noCodexResumeLaunch: (command: string | undefined) => CodexResumeLaunch
reconcileSharedRuntimeResumeHome: (
resumeHome: Extract<CodexSessionResumePreparation, { outcome: 'resume' }>,
resolveCurrent: () => string | null | Promise<string | null>
) => Promise<string>
stripSequencedStartupResumeArgv: <T extends Record<string, string> | undefined>(
env: T,
launch: CodexResumeLaunch
) => T
}
export type PtySpawnHostEnv = {
codexResumeLaunch: CodexResumeLaunch
launchCommand: string | undefined
/** Before Orca's host env; TEMPORARY (row 6 removes it): the window reports this one at commit. */
baseEnv: Record<string, string> | undefined
env: Record<string, string> | undefined
selectedCodexHomePath: string | null
codexResumeHomeSelected: boolean
skipCodexHomeEnv: boolean
stripInheritedOrcaCodexHome: boolean
}
type PtySpawnHostEnvArgs = {
launchAgent: TuiAgent | undefined
command: string | undefined
resumeProviderSession: AgentProviderSessionMetadata | undefined
/** The lane's spawn env before Orca's host env. */
env: Record<string, string> | undefined
cwd: string | undefined
shellOverride: string | undefined
expectedWslDistro: string | null
codexSelectionTarget: CodexAccountSelectionTarget
/** An adopted stable pane launches nothing, so it gets no launch prep. */
preAdopted: boolean
isDaemonHostSpawn: boolean
sessionId: string | undefined
/** Only a launch whose caller named no session waits on a managed Codex login. */
callerNamedSession: boolean
/** A failed env build sweeps per-PTY state only for an id this request minted. */
mintedSessionId: boolean
/** An account-switch restart resumes onto the selected account; only the window has one. */
resumeOnSelectedAccount: boolean
/** TEMPORARY (its own stacked PR removes it): only host starts wait for a WSL Pi/OMP relay. */
awaitsWslPiOmpGuestReadiness: boolean
/** TEMPORARY (row 7 removes it): the host builds no host env for an empty id; the window refuses it. */
emptySessionIdSkipsHostEnv: boolean
requestedAgentTeamsPath: string | undefined
provider: IPtyProvider
deps: PtySpawnCodexResumeDeps & {
runtime?: OrcaRuntimeService
getSettings?: () => GlobalSettings | undefined
getSelectedCodexHomePath?: GetSelectedCodexHomePath
}
mark?: (phase: string) => void
}
export async function resolvePtySpawnHostEnv(args: PtySpawnHostEnvArgs): Promise<PtySpawnHostEnv> {
const { deps, codexSelectionTarget: target } = args
const codexResumePreparation = args.preAdopted
? null
: deps.prepareCodexResumeHome({
launchAgent: args.launchAgent,
providerSession: args.resumeProviderSession,
target,
// Why: a host runtime skips resume prep for a launch whose env names a client-sent CODEX_HOME.
launchEnv: args.env,
...(args.resumeOnSelectedAccount ? { useSelectedAccount: true } : {})
})
const codexResumeLaunch = codexResumePreparation
? await deps.resolveCodexResumeLaunch(args.command, codexResumePreparation)
: deps.noCodexResumeLaunch(args.preAdopted ? undefined : args.command)
// Why: these three phases have unrelated costs (session provenance + hook
// repair, account/auth resolution, then the synchronous env build). One
// `host_env` label hid all of them behind the name of the cheapest.
args.mark?.('codex_resume')
const codexResumeHome = codexResumeLaunch.codexResumeHome
const launchCommand = codexResumeLaunch.command
const baseEnv = deps.stripSequencedStartupResumeArgv(args.env, codexResumeLaunch)
const launchesCodex = args.launchAgent === 'codex'
const selectLaunchCodexHome = async (): Promise<string | null> =>
(await deps.getSelectedCodexHomePath?.(target, baseEnv, { launchesCodex })) ?? null
let selectedCodexHomePath = !args.preAdopted
? getCompatibleSelectedCodexHomePath(
target,
codexResumeHome
? await deps.reconcileSharedRuntimeResumeHome(codexResumeHome, async () =>
getCompatibleSelectedCodexHomePath(target, await selectLaunchCodexHome())
)
: await selectLaunchCodexHome()
)
: null
if (!args.preAdopted && launchesCodex && !args.callerNamedSession) {
const resolution = resolveCodexHomeAfterManagedAuthReadiness({
selectedCodexHomePath,
getSettings: () => deps.getSettings?.(),
requiredCodexHomePath: codexResumeHome?.codexHomePath,
target,
resolveCurrent: async () =>
getCompatibleSelectedCodexHomePath(
target,
(await deps.getSelectedCodexHomePath?.(target, baseEnv, { launchesCodex })) ?? null
),
resolveAfterUnavailable: async (unavailableManagedHomePath) =>
getCompatibleSelectedCodexHomePath(
target,
(await deps.getSelectedCodexHomePath?.(target, baseEnv, {
unavailableManagedHomePath,
launchesCodex
})) ?? null
)
})
selectedCodexHomePath = resolution instanceof Promise ? await resolution : resolution
}
if (launchesCodex && selectedCodexHomePath) {
await ensureCodexStateDbBackfillRecoveryStarted(selectedCodexHomePath)
}
args.mark?.('codex_home')
const isWsl = shouldSkipCodexHomeEnvForWindowsShell(args.shellOverride, args.cwd)
const skipCodexHomeEnv = args.isDaemonHostSpawn && isWsl && !selectedCodexHomePath
const ptySettings = args.isDaemonHostSpawn ? deps.getSettings?.() : undefined
const hostEnv: PtySpawnHostEnv = {
codexResumeLaunch,
launchCommand,
baseEnv,
env: baseEnv,
selectedCodexHomePath,
codexResumeHomeSelected: Boolean(
codexResumeHome && codexHomePathsEqual(selectedCodexHomePath, codexResumeHome.codexHomePath)
),
skipCodexHomeEnv,
stripInheritedOrcaCodexHome:
args.isDaemonHostSpawn &&
shouldStripInheritedOrcaCodexHome({
target,
selectedCodexHomePath,
skipCodexHomeEnv,
settings: ptySettings
})
}
const sessionId = args.sessionId
if (
!args.isDaemonHostSpawn ||
args.preAdopted ||
(args.emptySessionIdSkipsHostEnv && !sessionId)
) {
return hostEnv
}
if (sessionId === undefined) {
// Should be unreachable: a daemon spawn always has an id; defense-in-depth.
throw new Error('Invariant violation: daemon spawn without sessionId')
}
// Why: this id reaches filesystem paths; reject traversal/separators so a crafted payload can't escape the expected roots.
if (!isSafePtySessionId(sessionId, getAppEnvironment().getPath('userData'))) {
throw new Error('Invalid PTY session id')
}
const wslDistro = target.runtime === 'wsl' ? args.expectedWslDistro : null
// Why: clone before mutating so injections don't leak back into the caller's env (renderer may reuse it).
const env = { ...baseEnv }
try {
await inheritOmpLaunchEnvironment(env, {
isWsl,
launchAgent: args.launchAgent,
launchCommand
})
if (args.awaitsWslPiOmpGuestReadiness) {
await awaitExplicitPiOmpGuestReadiness({
isWsl,
distro: wslDistro,
codexHomePath: selectedCodexHomePath,
launchAgent: isTuiAgent(args.launchAgent) ? args.launchAgent : undefined,
launchCommand
})
}
buildPtyHostEnv(sessionId, env, {
isPackaged: getAppEnvironment().isPackaged(),
resourcesPath: process.resourcesPath,
userDataPath: getAppEnvironment().getPath('userData'),
selectedCodexHomePath,
skipCodexHomeEnv,
stripInheritedOrcaCodexHome: hostEnv.stripInheritedOrcaCodexHome,
launchCommand,
launchAgent: isTuiAgent(args.launchAgent) ? args.launchAgent : undefined,
shellPath: args.shellOverride,
isWsl,
wslDistro,
agentStatusHooksEnabled: isAgentStatusHooksEnabled(ptySettings),
terminalInlineImages: ptySettings?.terminalInlineImages,
disabledTuiAgents: ptySettings?.disabledTuiAgents,
networkProxySettings: ptySettings,
routeBrowserOpensToClient: deps.runtime?.shouldRelayTerminalBrowserOpens?.(),
deferGitConfigGuardToDaemon:
args.provider.supportsGitCredentialGuardHost?.(sessionId) === true
})
stampWslOrchestrationCompatibilityHost(
env,
deps.runtime?.getOrchestrationCompatibilityHostId?.(),
wslDistro
)
stampRuntimeSourceEnv(
env,
getRuntimeSourceStamp(deps.runtime, getAppEnvironment().getPath('userData'))
)
promoteAgentTeamsShimPath(env, args.requestedAgentTeamsPath)
} catch (err) {
// Why: host-env setup has fs side-effects (hooks, Pi/OMP install); only a minted id's state is ours to clear.
if (args.mintedSessionId) {
clearProviderPtyState(sessionId)
}
throw err
}
return { ...hostEnv, env }
}