Speed up Codex terminal startup (#5664)

* Speed up Codex terminal startup

Co-authored-by: Orca <help@stably.ai>

* Address Codex startup review comments

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Brennan Benson 2026-06-17 20:49:47 -07:00 committed by GitHub
parent 08ba730d8e
commit 7f640ca904
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 123 additions and 31 deletions

View File

@ -9,6 +9,7 @@ import { HistoryManager } from './history-manager'
import { HistoryReader } from './history-reader'
import { mintPtySessionId, parsePtySessionId } from './pty-session-id'
import { supportsPtyStartupBarrier } from './shell-ready'
import { CODEX_SHELL_READY_TIMEOUT_MS } from './session'
import {
PROTOCOL_VERSION,
type CreateOrAttachResult,
@ -20,6 +21,7 @@ import {
} from './types'
import type { IPtyProvider, PtySpawnOptions, PtySpawnResult } from '../providers/types'
import { isShellProcess } from '../../shared/agent-detection'
import { recognizeAgentProcessFromCommandLine } from '../../shared/agent-process-recognition'
export type DaemonPtyAdapterOptions = {
socketPath: string
@ -133,6 +135,12 @@ export class DaemonPtyAdapter implements IPtyProvider {
await this.ensureConnected()
const shellReadySupported = opts.command ? supportsPtyStartupBarrier(opts.env ?? {}) : false
const shellReadyTimeoutMs =
shellReadySupported && recognizeAgentProcessFromCommandLine(opts.command)?.agent === 'codex'
? CODEX_SHELL_READY_TIMEOUT_MS
: undefined
const result = await this.client.request<CreateOrAttachResult>('createOrAttach', {
sessionId,
cols: effectiveCols,
@ -149,7 +157,8 @@ export class DaemonPtyAdapter implements IPtyProvider {
shellOverride: opts.shellOverride,
terminalWindowsWslDistro: opts.terminalWindowsWslDistro,
terminalWindowsPowerShellImplementation: opts.terminalWindowsPowerShellImplementation,
shellReadySupported: opts.command ? supportsPtyStartupBarrier(opts.env ?? {}) : false
shellReadySupported,
...(shellReadyTimeoutMs !== undefined ? { shellReadyTimeoutMs } : {})
})
if (effectiveCwd) {

View File

@ -278,6 +278,9 @@ export class DaemonServer {
terminalWindowsWslDistro: p.terminalWindowsWslDistro,
terminalWindowsPowerShellImplementation: p.terminalWindowsPowerShellImplementation,
shellReadySupported: p.shellReadySupported,
...(p.shellReadyTimeoutMs !== undefined
? { shellReadyTimeoutMs: p.shellReadyTimeoutMs }
: {}),
streamClient: {
onData: (data) => {
const lastInputAt = this.lastInputAtBySessionId.get(p.sessionId)

View File

@ -387,6 +387,8 @@ export function createPtySubprocess(opts: PtySubprocessOptions): SubprocessHandl
let shellPath =
cwdWslInfo || sessionWslContext ? 'wsl.exe' : opts.shellOverride || resolvePtyShellPath(env)
let shellArgs: string[]
const startupAgentRecognition = recognizeAgentProcessFromCommandLine(opts.command)
const isCodexStartupCommand = startupAgentRecognition?.agent === 'codex'
const requestedCwd = opts.cwd || getDefaultCwd()
let spawnCwd = requestedCwd
let validationCwd = spawnCwd
@ -500,16 +502,24 @@ export function createPtySubprocess(opts: PtySubprocessOptions): SubprocessHandl
}
// Why: any Orca-injected overlay env that user rc files can clobber
// needs the wrapper so the post-rc restore line runs.
const shellLaunch = opts.command
? getShellReadyLaunchConfig(shellPath)
: env.ORCA_ATTRIBUTION_SHIM_DIR ||
env.ORCA_OPENCODE_CONFIG_DIR ||
env.ORCA_PI_CODING_AGENT_DIR ||
env.ORCA_OMP_CODING_AGENT_DIR ||
env.ORCA_CODEX_HOME ||
env.ORCA_AGENT_TEAMS_SHIM_DIR
? getAttributionShellLaunchConfig(shellPath)
: null
let shellLaunch: ReturnType<typeof getShellReadyLaunchConfig> | null = null
if (opts.command && isCodexStartupCommand) {
// Why: Codex needs the env-restoring wrapper, but waiting for a shell
// marker delays the first useful TUI frame.
shellLaunch = getAttributionShellLaunchConfig(shellPath)
} else if (opts.command) {
shellLaunch = getShellReadyLaunchConfig(shellPath)
} else {
shellLaunch =
env.ORCA_ATTRIBUTION_SHIM_DIR ||
env.ORCA_OPENCODE_CONFIG_DIR ||
env.ORCA_PI_CODING_AGENT_DIR ||
env.ORCA_OMP_CODING_AGENT_DIR ||
env.ORCA_CODEX_HOME ||
env.ORCA_AGENT_TEAMS_SHIM_DIR
? getAttributionShellLaunchConfig(shellPath)
: null
}
if (shellLaunch) {
Object.assign(env, shellLaunch.env)
}
@ -562,7 +572,6 @@ export function createPtySubprocess(opts: PtySubprocessOptions): SubprocessHandl
cwd: opts.cwd,
worktreeId: parsePtySessionId(opts.sessionId).worktreeId
})
const startupAgentRecognition = recognizeAgentProcessFromCommandLine(opts.command)
let startupAgentForeground: { processName: string; expiresAt: number } | null =
startupAgentRecognition
? {

View File

@ -73,6 +73,7 @@ describe('Session', () => {
function createSession(opts?: {
shellReadySupported?: boolean
shellReadyTimeoutMs?: number
cols?: number
rows?: number
}): Session {
@ -81,7 +82,10 @@ describe('Session', () => {
cols: opts?.cols ?? 80,
rows: opts?.rows ?? 24,
subprocess,
shellReadySupported: opts?.shellReadySupported ?? false
shellReadySupported: opts?.shellReadySupported ?? false,
...(opts?.shellReadyTimeoutMs !== undefined
? { shellReadyTimeoutMs: opts.shellReadyTimeoutMs }
: {})
})
return session
}
@ -213,6 +217,18 @@ describe('Session', () => {
expect(subprocess.written).toEqual(['waiting input'])
})
it('honors a shorter shell-ready timeout for Codex startup sessions', () => {
createSession({ shellReadySupported: true, shellReadyTimeoutMs: 300 })
session.write('codex\n')
vi.advanceTimersByTime(299)
expect(subprocess.written).toEqual([])
vi.advanceTimersByTime(1)
expect(session.shellState).toBe('timed_out' satisfies ShellReadyState)
expect(subprocess.written).toEqual(['codex\n'])
})
it('detects marker split across data chunks', () => {
createSession({ shellReadySupported: true })

View File

@ -11,6 +11,9 @@ import type {
} from './types'
const SHELL_READY_TIMEOUT_MS = 15_000
// Why: Codex startup skips marker-gated command delivery; this only bounds
// older daemon/local paths that still report shell-ready support for Codex.
export const CODEX_SHELL_READY_TIMEOUT_MS = 300
const KILL_TIMEOUT_MS = 5_000
const SHELL_READY_MARKER = '\x1b]777;orca-shell-ready\x07'
// Why: pending records exist so the 5s checkpoint can persist increments
@ -47,6 +50,7 @@ export type SessionOptions = {
rows: number
subprocess: SubprocessHandle
shellReadySupported: boolean
shellReadyTimeoutMs?: number
scrollback?: number
}
@ -94,7 +98,7 @@ export class Session {
this._shellState = 'pending'
this.shellReadyTimer = setTimeout(() => {
this.onShellReadyTimeout()
}, SHELL_READY_TIMEOUT_MS)
}, opts.shellReadyTimeoutMs ?? SHELL_READY_TIMEOUT_MS)
} else {
this._shellState = 'unsupported'
}

View File

@ -27,6 +27,7 @@ export type CreateOrAttachOptions = {
terminalWindowsWslDistro?: string | null
terminalWindowsPowerShellImplementation?: 'auto' | 'powershell.exe' | 'pwsh.exe'
shellReadySupported?: boolean
shellReadyTimeoutMs?: number
streamClient: { onData: (data: string) => void; onExit: (code: number) => void }
}
@ -122,7 +123,10 @@ export class TerminalHost {
cols: size.cols,
rows: size.rows,
subprocess,
shellReadySupported: opts.shellReadySupported ?? false
shellReadySupported: opts.shellReadySupported ?? false,
...(opts.shellReadyTimeoutMs !== undefined
? { shellReadyTimeoutMs: opts.shellReadyTimeoutMs }
: {})
})
this.sessions.set(opts.sessionId, session)

View File

@ -3,9 +3,9 @@
// when daemon-baked behavior cannot be delivered by on-disk wrapper refresh.
// Why: bump when adding daemon wire behavior so same-version old daemons do
// not silently accept the handshake and then reject new RPCs.
export const PROTOCOL_VERSION = 14
export const PROTOCOL_VERSION = 15
export const PREVIOUS_DAEMON_PROTOCOL_VERSIONS = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
] as const
// ─── Session State Machine ──────────────────────────────────────────
@ -101,6 +101,7 @@ export type CreateOrAttachRequest = {
* PTY path resolves the same effective executable as LocalPtyProvider. */
terminalWindowsPowerShellImplementation?: 'auto' | 'powershell.exe' | 'pwsh.exe'
shellReadySupported?: boolean
shellReadyTimeoutMs?: number
}
}

View File

@ -4780,7 +4780,41 @@ describe('registerPtyHandlers', () => {
}
)
posixOnlyIt('falls back to a max wait when the shell emits no readiness output', async () => {
posixOnlyIt(
'uses the no-marker wrapper and writes quickly for Codex startup commands',
async () => {
vi.useFakeTimers()
const mockProc = createMockProc()
spawnMock.mockReturnValue(mockProc.proc)
try {
registerPtyHandlers(mainWindow as never)
await handlers.get('pty:spawn')!(null, {
cols: 80,
rows: 24,
cwd: '/tmp',
command: 'codex'
})
const [, , options] = spawnMock.mock.calls[0]!
expect(options.env.ORCA_SHELL_READY_MARKER).toBe('0')
await Promise.resolve()
vi.advanceTimersByTime(49)
await Promise.resolve()
expect(mockProc.proc.write).not.toHaveBeenCalled()
vi.advanceTimersByTime(1)
await Promise.resolve()
vi.runAllTimers()
expect(mockProc.proc.write).toHaveBeenCalledWith('codex\n')
} finally {
vi.useRealTimers()
}
}
)
posixOnlyIt('keeps the conservative max wait for non-agent startup commands', async () => {
vi.useFakeTimers()
const mockProc = createMockProc()
spawnMock.mockReturnValue(mockProc.proc)
@ -4791,13 +4825,17 @@ describe('registerPtyHandlers', () => {
cols: 80,
rows: 24,
cwd: '/tmp',
command: 'codex'
command: 'printf "hello"'
})
vi.advanceTimersByTime(1500)
vi.advanceTimersByTime(1499)
await Promise.resolve()
expect(mockProc.proc.write).not.toHaveBeenCalled()
vi.advanceTimersByTime(1)
await Promise.resolve()
vi.runAllTimers()
expect(mockProc.proc.write).toHaveBeenCalledWith('codex\n')
expect(mockProc.proc.write).toHaveBeenCalledWith('printf "hello"\n')
} finally {
vi.useRealTimers()
}

View File

@ -41,6 +41,7 @@ import {
import { WINDOWS_GIT_BASH_SHELL } from '../../shared/windows-terminal-shell'
import { resolveAgentForegroundProcess } from './agent-foreground-process'
import { getAgentForegroundContextPaths } from './agent-foreground-context-paths'
import { recognizeAgentProcessFromCommandLine } from '../../shared/agent-process-recognition'
const PANE_IDENTITY_ENV_KEYS = ['ORCA_PANE_KEY', 'ORCA_TAB_ID', 'ORCA_WORKTREE_ID'] as const
@ -472,16 +473,23 @@ export class LocalPtyProvider implements IPtyProvider {
finalEnv.ORCA_OMP_CODING_AGENT_DIR ||
finalEnv.ORCA_CODEX_HOME ||
finalEnv.ORCA_AGENT_TEAMS_SHIM_DIR
getFallbackShellReadyConfig = args.command
? (shell) => getShellReadyLaunchConfig(shell)
: needsNoMarkerWrapper
? (shell) => getAttributionShellLaunchConfig(shell)
: undefined
const shellLaunch = args.command
? getShellReadyLaunchConfig(shellPath)
: needsNoMarkerWrapper
? getAttributionShellLaunchConfig(shellPath)
: null
const isCodexStartupCommand =
recognizeAgentProcessFromCommandLine(args.command)?.agent === 'codex'
let shellLaunch: ReturnType<typeof getShellReadyLaunchConfig> | null = null
if (args.command && isCodexStartupCommand) {
// Why: Codex needs the env-restoring wrapper, but waiting for a shell
// marker delays the first useful TUI frame.
getFallbackShellReadyConfig = (shell) => getAttributionShellLaunchConfig(shell)
shellLaunch = getAttributionShellLaunchConfig(shellPath)
} else if (args.command) {
getFallbackShellReadyConfig = (shell) => getShellReadyLaunchConfig(shell)
shellLaunch = getShellReadyLaunchConfig(shellPath)
} else if (needsNoMarkerWrapper) {
getFallbackShellReadyConfig = (shell) => getAttributionShellLaunchConfig(shell)
shellLaunch = getAttributionShellLaunchConfig(shellPath)
} else {
getFallbackShellReadyConfig = undefined
}
if (shellLaunch) {
Object.assign(finalEnv, shellLaunch.env)
shellArgs = shellLaunch.args ?? shellArgs