mirror of
https://github.com/stablyai/orca.git
synced 2026-10-03 00:02:19 +00:00
fix(terminal): run the Codex launch preflight through the CLI the terminal names
Packaged Linux names the userData shim in ORCA_CLI_COMMAND, while the preflight ran the bundled launcher behind it. The CLI saw a different launcher and handed the preflight off to the shim, booting Electron twice before every codex launch.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
import { piBuildPtyEnvMock } from './pty-ipc-mock-registry'
|
import { piBuildPtyEnvMock, statSyncMock } from './pty-ipc-mock-registry'
|
||||||
import { setupPtyIpcSuite } from './pty-ipc-test-harness'
|
import { setupPtyIpcSuite } from './pty-ipc-test-harness'
|
||||||
import {
|
import {
|
||||||
type DaemonSpawnCall,
|
type DaemonSpawnCall,
|
||||||
@@ -463,6 +463,37 @@ describe('registerPtyHandlers', () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
it('runs the Codex launch preflight through the CLI the packaged Linux terminal names', async () => {
|
||||||
|
// Why: the bundled launcher behind the shim is a different file, so running it directly
|
||||||
|
// made the CLI hand the preflight off to the shim and boot Electron twice per launch.
|
||||||
|
const originalPlatform = process.platform
|
||||||
|
Object.defineProperty(process, 'platform', {
|
||||||
|
configurable: true,
|
||||||
|
value: 'linux'
|
||||||
|
})
|
||||||
|
const shimPath = join('/tmp/orca-user-data', 'linux-orca-cli-shim', 'orca')
|
||||||
|
statSyncMock.mockImplementation((target: string) => ({
|
||||||
|
isDirectory: () => target !== shimPath,
|
||||||
|
isFile: () => target === shimPath,
|
||||||
|
mode: 0o755,
|
||||||
|
size: 1
|
||||||
|
}))
|
||||||
|
try {
|
||||||
|
const env = await daemonSpawnAndGetEnv(
|
||||||
|
{ PATH: ['/usr/local/bin', '/usr/bin'].join(delimiter) },
|
||||||
|
() => '/tmp/orca-codex-home'
|
||||||
|
)
|
||||||
|
expect(env.ORCA_CLI_COMMAND).toBe(
|
||||||
|
join('/tmp/orca-user-data', 'linux-orca-cli-shim', 'orca')
|
||||||
|
)
|
||||||
|
expect(env.ORCA_CODEX_LAUNCH_PREFLIGHT).toBe(env.ORCA_CLI_COMMAND)
|
||||||
|
} finally {
|
||||||
|
Object.defineProperty(process, 'platform', {
|
||||||
|
configurable: true,
|
||||||
|
value: originalPlatform
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
it('prepends the bundled CLI dir to PATH for packaged macOS spawns', async () => {
|
it('prepends the bundled CLI dir to PATH for packaged macOS spawns', async () => {
|
||||||
const resourcesPathDescriptor = Object.getOwnPropertyDescriptor(process, 'resourcesPath')
|
const resourcesPathDescriptor = Object.getOwnPropertyDescriptor(process, 'resourcesPath')
|
||||||
Object.defineProperty(process, 'resourcesPath', {
|
Object.defineProperty(process, 'resourcesPath', {
|
||||||
|
|||||||
@@ -242,35 +242,6 @@ export function buildPtyHostEnv(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Why: keep the Codex home override PTY-scoped so dev/prod Orcas don't share hooks through ~/.codex.
|
|
||||||
if (opts.skipCodexHomeEnv) {
|
|
||||||
delete baseEnv.CODEX_HOME
|
|
||||||
delete baseEnv.ORCA_CODEX_HOME
|
|
||||||
delete baseEnv.ORCA_CODEX_LAUNCH_PREFLIGHT
|
|
||||||
} else if (opts.selectedCodexHomePath) {
|
|
||||||
baseEnv.CODEX_HOME = opts.selectedCodexHomePath
|
|
||||||
// Why: user startup files may re-export CODEX_HOME; shell-ready wrappers restore this runtime home before Codex launches.
|
|
||||||
baseEnv.ORCA_CODEX_HOME = opts.selectedCodexHomePath
|
|
||||||
const preflightCommand = resolveCodexShellLaunchPreflightCommand({
|
|
||||||
hooksEnabled: opts.codexStatusHooksEnabled ?? opts.agentStatusHooksEnabled,
|
|
||||||
isPackaged: opts.isPackaged,
|
|
||||||
isWsl: opts.isWsl,
|
|
||||||
managedHomePath: opts.selectedCodexHomePath,
|
|
||||||
userDataPath: opts.userDataPath,
|
|
||||||
resourcesPath: opts.resourcesPath
|
|
||||||
})
|
|
||||||
if (preflightCommand) {
|
|
||||||
baseEnv.ORCA_CODEX_LAUNCH_PREFLIGHT = preflightCommand
|
|
||||||
} else {
|
|
||||||
delete baseEnv.ORCA_CODEX_LAUNCH_PREFLIGHT
|
|
||||||
}
|
|
||||||
} else if (opts.stripInheritedOrcaCodexHome) {
|
|
||||||
stripInheritedOrcaCodexHomeOverride(baseEnv)
|
|
||||||
delete baseEnv.ORCA_CODEX_LAUNCH_PREFLIGHT
|
|
||||||
} else {
|
|
||||||
delete baseEnv.ORCA_CODEX_LAUNCH_PREFLIGHT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Why: WSL shells need the managed userData root for shell-ready wrappers; dev-mode terminals need the same export so `orca` targets the live dev instance.
|
// Why: WSL shells need the managed userData root for shell-ready wrappers; dev-mode terminals need the same export so `orca` targets the live dev instance.
|
||||||
if (opts.isWsl) {
|
if (opts.isWsl) {
|
||||||
baseEnv.ORCA_USER_DATA_PATH = opts.userDataPath
|
baseEnv.ORCA_USER_DATA_PATH = opts.userDataPath
|
||||||
@@ -293,6 +264,36 @@ export function buildPtyHostEnv(
|
|||||||
delete baseEnv.ORCA_CLI_COMMAND
|
delete baseEnv.ORCA_CLI_COMMAND
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Why: keep the Codex home override PTY-scoped so dev/prod Orcas don't share hooks through ~/.codex.
|
||||||
|
if (opts.skipCodexHomeEnv) {
|
||||||
|
delete baseEnv.CODEX_HOME
|
||||||
|
delete baseEnv.ORCA_CODEX_HOME
|
||||||
|
delete baseEnv.ORCA_CODEX_LAUNCH_PREFLIGHT
|
||||||
|
} else if (opts.selectedCodexHomePath) {
|
||||||
|
baseEnv.CODEX_HOME = opts.selectedCodexHomePath
|
||||||
|
// Why: user startup files may re-export CODEX_HOME; shell-ready wrappers restore this runtime home before Codex launches.
|
||||||
|
baseEnv.ORCA_CODEX_HOME = opts.selectedCodexHomePath
|
||||||
|
const preflightCommand = resolveCodexShellLaunchPreflightCommand({
|
||||||
|
hooksEnabled: opts.codexStatusHooksEnabled ?? opts.agentStatusHooksEnabled,
|
||||||
|
isPackaged: opts.isPackaged,
|
||||||
|
isWsl: opts.isWsl,
|
||||||
|
managedHomePath: opts.selectedCodexHomePath,
|
||||||
|
userDataPath: opts.userDataPath,
|
||||||
|
resourcesPath: opts.resourcesPath,
|
||||||
|
cliLauncher: launcher
|
||||||
|
})
|
||||||
|
if (preflightCommand) {
|
||||||
|
baseEnv.ORCA_CODEX_LAUNCH_PREFLIGHT = preflightCommand
|
||||||
|
} else {
|
||||||
|
delete baseEnv.ORCA_CODEX_LAUNCH_PREFLIGHT
|
||||||
|
}
|
||||||
|
} else if (opts.stripInheritedOrcaCodexHome) {
|
||||||
|
stripInheritedOrcaCodexHomeOverride(baseEnv)
|
||||||
|
delete baseEnv.ORCA_CODEX_LAUNCH_PREFLIGHT
|
||||||
|
} else {
|
||||||
|
delete baseEnv.ORCA_CODEX_LAUNCH_PREFLIGHT
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
opts.routeBrowserOpensToClient === true &&
|
opts.routeBrowserOpensToClient === true &&
|
||||||
baseEnv.BROWSER === undefined &&
|
baseEnv.BROWSER === undefined &&
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
writeFileSync
|
writeFileSync
|
||||||
} from 'node:fs'
|
} from 'node:fs'
|
||||||
import { tmpdir } from 'node:os'
|
import { tmpdir } from 'node:os'
|
||||||
import { delimiter, isAbsolute, join } from 'node:path'
|
import { delimiter, dirname, isAbsolute, join } from 'node:path'
|
||||||
import { execFileSync, spawnSync } from 'node:child_process'
|
import { execFileSync, spawnSync } from 'node:child_process'
|
||||||
import { afterEach, describe, expect, it } from 'vitest'
|
import { afterEach, describe, expect, it } from 'vitest'
|
||||||
import {
|
import {
|
||||||
@@ -479,6 +479,47 @@ describe('Codex shell launch preflight command', () => {
|
|||||||
).toBe(launcherPath)
|
).toBe(launcherPath)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('runs the launcher the terminal names as its CLI, so the CLI never hands off to it', () => {
|
||||||
|
// Why: packaged Linux names the userData shim, not the bundled launcher behind it; running the
|
||||||
|
// launcher directly would re-run the preflight through the shim and boot Electron twice.
|
||||||
|
const { userDataPath, resourcesPath } = makeCliRoot()
|
||||||
|
writeExecutable(join(resourcesPath, 'bin', 'orca-ide'), '#!/bin/sh\nexit 0\n')
|
||||||
|
const shimPath = join(userDataPath, 'linux-orca-cli-shim', 'orca')
|
||||||
|
mkdirSync(dirname(shimPath), { recursive: true })
|
||||||
|
writeExecutable(shimPath, '#!/bin/sh\nexit 0\n')
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveCodexShellLaunchPreflightCommand({
|
||||||
|
hooksEnabled: true,
|
||||||
|
isPackaged: true,
|
||||||
|
managedHomePath: '/managed/home',
|
||||||
|
userDataPath,
|
||||||
|
resourcesPath,
|
||||||
|
cliLauncher: shimPath,
|
||||||
|
platform: 'linux'
|
||||||
|
})
|
||||||
|
).toBe(shimPath)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps the Windows launcher for WSL even when the host terminal names a CLI', () => {
|
||||||
|
const { userDataPath, resourcesPath } = makeCliRoot()
|
||||||
|
const launcherPath = join(resourcesPath, 'bin', 'orca.exe')
|
||||||
|
writeExecutable(launcherPath, '#!/bin/sh\nexit 0\n')
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveCodexShellLaunchPreflightCommand({
|
||||||
|
hooksEnabled: true,
|
||||||
|
isPackaged: true,
|
||||||
|
isWsl: true,
|
||||||
|
managedHomePath: '/home/jin/.local/share/orca/codex-runtime-home/home',
|
||||||
|
userDataPath,
|
||||||
|
resourcesPath,
|
||||||
|
cliLauncher: join(userDataPath, 'elsewhere', 'orca.exe'),
|
||||||
|
platform: 'win32'
|
||||||
|
})
|
||||||
|
).toBe(launcherPath)
|
||||||
|
})
|
||||||
|
|
||||||
it('never returns an unqualified command name that a profile-rewritten PATH could hijack', () => {
|
it('never returns an unqualified command name that a profile-rewritten PATH could hijack', () => {
|
||||||
const { userDataPath, resourcesPath } = makeCliRoot()
|
const { userDataPath, resourcesPath } = makeCliRoot()
|
||||||
writeExecutable(join(resourcesPath, 'bin', 'orca'), '#!/bin/sh\nexit 0\n')
|
writeExecutable(join(resourcesPath, 'bin', 'orca'), '#!/bin/sh\nexit 0\n')
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ export type CodexShellLaunchPreflightCommandOptions = {
|
|||||||
userDataPath: string
|
userDataPath: string
|
||||||
/** Packaged app resources root; the bundled launcher lives under it. */
|
/** Packaged app resources root; the bundled launcher lives under it. */
|
||||||
resourcesPath?: string | null
|
resourcesPath?: string | null
|
||||||
|
/** The launcher this terminal names in `ORCA_CLI_COMMAND`; the preflight runs that same one so it never hands off to it (Linux names a shim in front of the bundled launcher). */
|
||||||
|
cliLauncher?: string | null
|
||||||
/** Test seam. */
|
/** Test seam. */
|
||||||
platform?: NodeJS.Platform
|
platform?: NodeJS.Platform
|
||||||
}
|
}
|
||||||
@@ -33,15 +35,18 @@ export function resolveCodexShellLaunchPreflightCommand(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const platform = options.platform ?? process.platform
|
const platform = options.platform ?? process.platform
|
||||||
const candidate = options.isPackaged
|
const candidate =
|
||||||
? options.resourcesPath
|
options.cliLauncher && !options.isWsl
|
||||||
? getBundledLauncherPath(platform, options.resourcesPath)
|
? options.cliLauncher
|
||||||
: null
|
: options.isPackaged
|
||||||
: join(
|
? options.resourcesPath
|
||||||
options.userDataPath,
|
? getBundledLauncherPath(platform, options.resourcesPath)
|
||||||
...DEV_LAUNCHER_DIR,
|
: null
|
||||||
platform === 'win32' ? `${DEV_COMMAND_NAME}.cmd` : DEV_COMMAND_NAME
|
: join(
|
||||||
)
|
options.userDataPath,
|
||||||
|
...DEV_LAUNCHER_DIR,
|
||||||
|
platform === 'win32' ? `${DEV_COMMAND_NAME}.cmd` : DEV_COMMAND_NAME
|
||||||
|
)
|
||||||
if (!candidate || !isExecutableFileOnDisk(candidate, platform)) {
|
if (!candidate || !isExecutableFileOnDisk(candidate, platform)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user