fix(codex): heal WSL hooks before typed launches

This commit is contained in:
Brennan Benson
2026-08-27 11:36:12 -07:00
parent fecdf0bde8
commit b635937bb9
17 changed files with 447 additions and 15 deletions
+28
View File
@@ -92,6 +92,7 @@ describe('agent hooks CLI handler', () => {
})
afterEach(() => {
vi.unstubAllEnvs()
vi.restoreAllMocks()
rmSync(userDataPath, { recursive: true, force: true })
})
@@ -139,6 +140,33 @@ describe('agent hooks CLI handler', () => {
})
})
it('forwards WSL pane routing to the runtime exactly once without using the host installer', async () => {
const home = '/home/jin/.local/share/orca/codex-runtime-home/home'
vi.stubEnv('CODEX_HOME', home)
vi.stubEnv('ORCA_CODEX_HOME', home)
vi.stubEnv('WSL_DISTRO_NAME', 'Ubuntu-24.04')
callMock.mockResolvedValue({ result: { state: 'installed' } })
await main(['agent', 'hooks', 'prepare-codex'], userDataPath)
expect(callMock).toHaveBeenCalledExactlyOnceWith(
'agentHooks.prepareCodexForWslPane',
{ codexHome: home, orcaCodexHome: home, wslDistro: 'Ubuntu-24.04' },
{ timeoutMs: 10_000 }
)
expect(prepareManagedCodexHomeBeforeShellLaunchMock).not.toHaveBeenCalled()
})
it('fails open when WSL runtime preparation is unavailable', async () => {
vi.stubEnv('CODEX_HOME', '/home/jin/.local/share/orca/codex-runtime-home/home')
vi.stubEnv('ORCA_CODEX_HOME', '/home/jin/.local/share/orca/codex-runtime-home/home')
vi.stubEnv('WSL_DISTRO_NAME', 'Ubuntu')
callMock.mockRejectedValue(new Error('method_not_found'))
await expect(main(['agent', 'hooks', 'prepare-codex'], userDataPath)).resolves.toBeUndefined()
expect(prepareManagedCodexHomeBeforeShellLaunchMock).not.toHaveBeenCalled()
})
it('honors Codex-specific disablement when the runtime is unavailable', async () => {
const state = getDefaultPersistedState(userDataPath)
state.settings.disabledTuiAgents = ['codex']
+16
View File
@@ -207,6 +207,22 @@ async function setAgentHooksEnabled(
export const AGENT_HOOK_HANDLERS: Record<string, CommandHandler> = {
'agent hooks prepare-codex': async ({ client }) => {
if (process.env.WSL_DISTRO_NAME?.trim()) {
try {
await client.call(
'agentHooks.prepareCodexForWslPane',
{
codexHome: process.env.CODEX_HOME ?? '',
orcaCodexHome: process.env.ORCA_CODEX_HOME ?? '',
wslDistro: process.env.WSL_DISTRO_NAME
},
{ timeoutMs: 10_000 }
)
} catch {
// Best effort: old or unavailable runtimes must not block Codex launch.
}
return
}
const settings = await readHookSettings(client)
await prepareManagedCodexHomeBeforeShellLaunch({
userDataPath: getDefaultUserDataPath(),
@@ -72,7 +72,7 @@ fi
# Why || : twice — zsh alone aborts inside the substitution, but every shell's
# assignment adopts its exit status, so an absent codex trips set -e in bash too.
__orca_codex_binary="$(unalias codex 2>/dev/null || :; command -v codex 2>/dev/null || :)"
if [[ -n "${ORCA_CODEX_LAUNCH_PREFLIGHT:-}" && -n "${__orca_codex_binary:-}" && -x "${__orca_codex_binary}" ]]; then
if [[ -n "${ORCA_CODEX_LAUNCH_PREFLIGHT:-}" && -x "${ORCA_CODEX_LAUNCH_PREFLIGHT}" && -n "${__orca_codex_binary:-}" && -x "${__orca_codex_binary}" ]]; then
# Why the function reserved word: it suppresses alias expansion of the name,
# which otherwise rewrites this header at parse time and aborts the whole file.
function codex {
@@ -2,7 +2,7 @@ function __orca_shell_ready_marker --on-event fish_prompt
builtin printf "\033]777;orca-shell-ready\007"
functions -e __orca_shell_ready_marker
end
if test -n "$ORCA_CODEX_LAUNCH_PREFLIGHT"; and test (type -t codex 2>/dev/null) = file
if test -x "$ORCA_CODEX_LAUNCH_PREFLIGHT"; and test (type -t codex 2>/dev/null) = file
function codex
command "$ORCA_CODEX_LAUNCH_PREFLIGHT" agent hooks prepare-codex >/dev/null 2>&1; or true
command codex $argv
@@ -110,7 +110,7 @@ __orca_deferred_init() {
# Why || : twice — zsh alone aborts inside the substitution, but every shell's
# assignment adopts its exit status, so an absent codex trips set -e in bash too.
__orca_codex_binary="$(unalias codex 2>/dev/null || :; command -v codex 2>/dev/null || :)"
if [[ -n "${ORCA_CODEX_LAUNCH_PREFLIGHT:-}" && -n "${__orca_codex_binary:-}" && -x "${__orca_codex_binary}" ]]; then
if [[ -n "${ORCA_CODEX_LAUNCH_PREFLIGHT:-}" && -x "${ORCA_CODEX_LAUNCH_PREFLIGHT}" && -n "${__orca_codex_binary:-}" && -x "${__orca_codex_binary}" ]]; then
# Why the function reserved word: it suppresses alias expansion of the name,
# which otherwise rewrites this header at parse time and aborts the whole file.
function codex {
@@ -75,7 +75,7 @@ fi
# Why || : twice — zsh alone aborts inside the substitution, but every shell's
# assignment adopts its exit status, so an absent codex trips set -e in bash too.
__orca_codex_binary="$(unalias codex 2>/dev/null || :; command -v codex 2>/dev/null || :)"
if [[ -n "${ORCA_CODEX_LAUNCH_PREFLIGHT:-}" && -n "${__orca_codex_binary:-}" && -x "${__orca_codex_binary}" ]]; then
if [[ -n "${ORCA_CODEX_LAUNCH_PREFLIGHT:-}" && -x "${ORCA_CODEX_LAUNCH_PREFLIGHT}" && -n "${__orca_codex_binary:-}" && -x "${__orca_codex_binary}" ]]; then
# Why the function reserved word: it suppresses alias expansion of the name,
# which otherwise rewrites this header at parse time and aborts the whole file.
function codex {
@@ -2,7 +2,7 @@ function __orca_shell_ready_marker --on-event fish_prompt
builtin printf "\033]777;orca-shell-ready\007"
functions -e __orca_shell_ready_marker
end
if test -n "$ORCA_CODEX_LAUNCH_PREFLIGHT"; and test (type -t codex 2>/dev/null) = file
if test -x "$ORCA_CODEX_LAUNCH_PREFLIGHT"; and test (type -t codex 2>/dev/null) = file
function codex
command "$ORCA_CODEX_LAUNCH_PREFLIGHT" agent hooks prepare-codex >/dev/null 2>&1; or true
command codex $argv
@@ -110,7 +110,7 @@ __orca_deferred_init() {
# Why || : twice — zsh alone aborts inside the substitution, but every shell's
# assignment adopts its exit status, so an absent codex trips set -e in bash too.
__orca_codex_binary="$(unalias codex 2>/dev/null || :; command -v codex 2>/dev/null || :)"
if [[ -n "${ORCA_CODEX_LAUNCH_PREFLIGHT:-}" && -n "${__orca_codex_binary:-}" && -x "${__orca_codex_binary}" ]]; then
if [[ -n "${ORCA_CODEX_LAUNCH_PREFLIGHT:-}" && -x "${ORCA_CODEX_LAUNCH_PREFLIGHT}" && -n "${__orca_codex_binary:-}" && -x "${__orca_codex_binary}" ]]; then
# Why the function reserved word: it suppresses alias expansion of the name,
# which otherwise rewrites this header at parse time and aborts the whole file.
function codex {
@@ -4,7 +4,9 @@ import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
prepareManagedCodexHomeBeforeShellLaunch,
resolveManagedCodexShellPreflightHome
prepareManagedWslCodexHomeBeforeShellLaunch,
resolveManagedCodexShellPreflightHome,
resolveManagedWslCodexShellPreflightTarget
} from './managed-home-shell-preflight'
const roots: string[] = []
@@ -160,3 +162,84 @@ describe('managed Codex shell preflight', () => {
).toBeNull()
})
})
describe('managed WSL Codex shell preflight', () => {
const home = '/home/jin/.local/share/orca/codex-runtime-home/home'
const env = {
CODEX_HOME: home,
ORCA_CODEX_HOME: home,
WSL_DISTRO_NAME: 'Ubuntu-24.04'
}
it('targets the pane-selected managed home through its UNC twin', () => {
expect(resolveManagedWslCodexShellPreflightTarget(env)).toEqual({
runtimeHomePath:
'\\\\wsl.localhost\\Ubuntu-24.04\\home\\jin\\.local\\share\\orca\\codex-runtime-home\\home',
wslDistro: 'Ubuntu-24.04'
})
})
it('installs once through the WSL runtime-home lane while hooks are enabled', () => {
const status = {
agent: 'codex' as const,
state: 'installed' as const,
configPath: 'config.toml',
managedHooksPresent: true,
detail: null
}
const install = vi.fn(() => status)
expect(prepareManagedWslCodexHomeBeforeShellLaunch({ env, hooksEnabled: true, install })).toBe(
status
)
expect(install).toHaveBeenCalledExactlyOnceWith(
'\\\\wsl.localhost\\Ubuntu-24.04\\home\\jin\\.local\\share\\orca\\codex-runtime-home\\home',
{ runtime: 'wsl', wslDistro: 'Ubuntu-24.04' }
)
expect(
prepareManagedWslCodexHomeBeforeShellLaunch({ env, hooksEnabled: false, install })
).toBeNull()
expect(install).toHaveBeenCalledTimes(1)
})
it.each([
[
'a user home',
{ ...env, CODEX_HOME: '/home/jin/.codex', ORCA_CODEX_HOME: '/home/jin/.codex' }
],
['unequal routing markers', { ...env, ORCA_CODEX_HOME: `${home}-other` }],
[
'a parent traversal',
{
...env,
CODEX_HOME: `/home/jin/../jin${home.slice('/home/jin'.length)}`,
ORCA_CODEX_HOME: `/home/jin/../jin${home.slice('/home/jin'.length)}`
}
],
[
'a dot segment',
{
...env,
CODEX_HOME: `/home/./jin${home.slice('/home/jin'.length)}`,
ORCA_CODEX_HOME: `/home/./jin${home.slice('/home/jin'.length)}`
}
],
[
'a host path',
{ ...env, CODEX_HOME: 'C:\\Users\\jin\\.codex', ORCA_CODEX_HOME: 'C:\\Users\\jin\\.codex' }
],
['a missing distro', { ...env, WSL_DISTRO_NAME: '' }],
['a distro path escape', { ...env, WSL_DISTRO_NAME: 'Ubuntu\\..\\host' }],
[
'a repeated separator',
{
...env,
CODEX_HOME: home.replace('/jin/', '/jin//'),
ORCA_CODEX_HOME: home.replace('/jin/', '/jin//')
}
]
])('rejects %s', (_label, candidate) => {
expect(resolveManagedWslCodexShellPreflightTarget(candidate)).toBeNull()
})
})
@@ -2,11 +2,20 @@ import { existsSync, readFileSync, realpathSync } from 'node:fs'
import { join, relative, resolve, sep } from 'node:path'
import type { AgentHookInstallStatus } from '../../shared/agent-hook-types'
import { normalizeRuntimePathForComparison } from '../../shared/cross-platform-path'
import { toWindowsWslPath } from '../../shared/wsl-paths'
import { wslCodexRuntimeHomeForGuestHome } from '../pty/codex-home-wsl-env'
import type { CodexWslRuntimeHookTarget } from './codex-wsl-hook-install-plan'
import { codexHookService } from './hook-service'
type ShellPreflightEnvironment = {
CODEX_HOME?: string
ORCA_CODEX_HOME?: string
WSL_DISTRO_NAME?: string
}
export type ManagedWslCodexShellPreflightTarget = {
runtimeHomePath: string
wslDistro: string
}
function pathsEqual(left: string, right: string): boolean {
@@ -104,3 +113,67 @@ export async function prepareManagedCodexHomeBeforeShellLaunch(args: {
}
return (args.install ?? ((home) => codexHookService.install(home)))(runtimeHomePath)
}
function isAbsolutePosixPathWithoutDotSegments(path: string): boolean {
const segments = path.split('/').slice(1)
return (
path.startsWith('/') &&
!path.startsWith('//') &&
!path.includes('\\') &&
segments.every((segment) => segment && segment !== '.' && segment !== '..')
)
}
export function resolveManagedWslCodexShellPreflightTarget(
env: ShellPreflightEnvironment
): ManagedWslCodexShellPreflightTarget | null {
const codexHome = env.CODEX_HOME?.trim()
const orcaCodexHome = env.ORCA_CODEX_HOME?.trim()
const wslDistro = env.WSL_DISTRO_NAME?.trim()
if (
!codexHome ||
codexHome !== orcaCodexHome ||
!wslDistro ||
/[\\/\r\n]/.test(wslDistro) ||
!isAbsolutePosixPathWithoutDotSegments(codexHome)
) {
return null
}
const runtimeHomeSuffix = wslCodexRuntimeHomeForGuestHome('')
if (!codexHome.endsWith(runtimeHomeSuffix)) {
return null
}
const guestHome = codexHome.slice(0, -runtimeHomeSuffix.length)
if (
!guestHome ||
!isAbsolutePosixPathWithoutDotSegments(guestHome) ||
wslCodexRuntimeHomeForGuestHome(guestHome) !== codexHome
) {
return null
}
return { runtimeHomePath: toWindowsWslPath(codexHome, wslDistro), wslDistro }
}
export function prepareManagedWslCodexHomeBeforeShellLaunch(args: {
env: ShellPreflightEnvironment
hooksEnabled: boolean
install?: (
runtimeHomePath: string,
target: CodexWslRuntimeHookTarget
) => AgentHookInstallStatus | null
}): AgentHookInstallStatus | null {
if (!args.hooksEnabled) {
return null
}
const target = resolveManagedWslCodexShellPreflightTarget(args.env)
if (!target) {
return null
}
const install =
args.install ??
((home: string, hookTarget: CodexWslRuntimeHookTarget) =>
codexHookService.installForRuntimeHome(home, hookTarget))
return install(target.runtimeHomePath, { runtime: 'wsl', wslDistro: target.wslDistro })
}
@@ -170,6 +170,30 @@ describe.skipIf(process.platform === 'win32')('Codex shell launch preflight', ()
)
})
it('does not trigger a preflight outside an Orca terminal', () => {
const root = mkdtempSync(join(tmpdir(), 'orca-codex-plain-shell-'))
roots.push(root)
const bin = join(root, 'bin')
const marker = join(root, 'preflight-ran')
mkdirSync(bin)
writeExecutable(join(bin, 'codex'), '#!/bin/sh\nprintf launched\n')
writeExecutable(join(bin, 'orca-test'), `#!/bin/sh\nprintf ran > ${JSON.stringify(marker)}\n`)
const output = execFileSync(
'/bin/bash',
['--noprofile', '--norc', '-c', `${getPosixCodexShellLaunchPreflight()}\ncodex`],
{
encoding: 'utf-8',
env: {
PATH: `${bin}${delimiter}${process.env.PATH ?? ''}`
}
}
)
expect(output.trim()).toBe('launched')
expect(existsSync(marker)).toBe(false)
})
// Why a loop over it.each: an early `return` reported green on hosts without the
// shell, so the zsh half silently never ran on Linux. skipIf reports it as a skip.
for (const [shell, strict] of [
@@ -319,6 +343,27 @@ describe('Codex shell launch preflight command', () => {
).toBe(launcherPath)
})
it.skipIf(process.platform !== 'win32')(
'carries the packaged Windows launcher as a guest-absolute WSL path',
() => {
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,
platform: 'win32'
})
).toMatch(/^\/mnt\/[a-z]\//)
}
)
it('never returns an unqualified command name that a profile-rewritten PATH could hijack', () => {
const { userDataPath, resourcesPath } = makeCliRoot()
writeExecutable(join(resourcesPath, 'bin', 'orca'), '#!/bin/sh\nexit 0\n')
@@ -340,7 +385,6 @@ describe('Codex shell launch preflight command', () => {
it.each([
{ label: 'the launcher file is missing', create: null },
{ label: 'the launcher is not executable', create: 0o644 },
{ label: 'the launcher path is a directory', create: 'directory' as const }
])('skips the preflight when $label', (config) => {
const { userDataPath, resourcesPath } = makeCliRoot()
@@ -364,6 +408,27 @@ describe('Codex shell launch preflight command', () => {
).toBeNull()
})
it.skipIf(process.platform === 'win32')(
'skips the preflight when the launcher is not executable',
() => {
const { userDataPath, resourcesPath } = makeCliRoot()
const launcherPath = join(resourcesPath, 'bin', 'orca')
writeFileSync(launcherPath, '#!/bin/sh\nexit 0\n')
chmodSync(launcherPath, 0o644)
expect(
resolveCodexShellLaunchPreflightCommand({
hooksEnabled: true,
isPackaged: true,
managedHomePath: '/managed/home',
userDataPath,
resourcesPath,
platform: 'darwin'
})
).toBeNull()
}
)
it('skips the preflight when the packaged build exposes no resources root', () => {
const { userDataPath } = makeCliRoot()
@@ -381,7 +446,7 @@ describe('Codex shell launch preflight command', () => {
it.each([
{ hooksEnabled: false, isWsl: false, managedHomePath: '/managed/home' },
{ hooksEnabled: true, isWsl: true, managedHomePath: '/managed/home' },
{ hooksEnabled: true, isWsl: true, managedHomePath: '/managed/home', isPackaged: false },
{ hooksEnabled: true, isWsl: false, managedHomePath: null }
])('does not enable an unsupported preflight for %o', (options) => {
const { userDataPath, resourcesPath } = makeCliRoot()
@@ -390,7 +455,7 @@ describe('Codex shell launch preflight command', () => {
expect(
resolveCodexShellLaunchPreflightCommand({
...options,
isPackaged: true,
isPackaged: options.isPackaged ?? true,
userDataPath,
resourcesPath,
platform: 'darwin'
+12 -4
View File
@@ -1,5 +1,6 @@
import { accessSync, constants, statSync } from 'node:fs'
import { join } from 'node:path'
import { toLinuxPath } from '../../shared/wsl-paths'
import { getBundledLauncherPath } from '../cli/bundled-cli-launcher-path'
const DEV_LAUNCHER_DIR = ['cli', 'bin']
@@ -29,7 +30,7 @@ export type CodexShellLaunchPreflightCommandOptions = {
export function resolveCodexShellLaunchPreflightCommand(
options: CodexShellLaunchPreflightCommandOptions
): string | null {
if (!options.hooksEnabled || options.isWsl || !options.managedHomePath) {
if (!options.hooksEnabled || !options.managedHomePath) {
return null
}
const platform = options.platform ?? process.platform
@@ -42,7 +43,14 @@ export function resolveCodexShellLaunchPreflightCommand(
...DEV_LAUNCHER_DIR,
platform === 'win32' ? `${DEV_COMMAND_NAME}.cmd` : DEV_COMMAND_NAME
)
return candidate && isExecutableFileOnDisk(candidate, platform) ? candidate : null
if (!candidate || !isExecutableFileOnDisk(candidate, platform)) {
return null
}
if (!options.isWsl) {
return candidate
}
// Why: WSL executes the verified Windows launcher through interop; its guest path crosses WSLENV untranslated.
return platform === 'win32' && options.isPackaged ? toLinuxPath(candidate) : null
}
function isExecutableFileOnDisk(path: string, platform: NodeJS.Platform): boolean {
@@ -65,7 +73,7 @@ export function getPosixCodexShellLaunchPreflight(): string {
# Why || : twice — zsh alone aborts inside the substitution, but every shell's
# assignment adopts its exit status, so an absent codex trips set -e in bash too.
__orca_codex_binary="$(unalias codex 2>/dev/null || :; command -v codex 2>/dev/null || :)"
if [[ -n "\${ORCA_CODEX_LAUNCH_PREFLIGHT:-}" && -n "\${__orca_codex_binary:-}" && -x "\${__orca_codex_binary}" ]]; then
if [[ -n "\${ORCA_CODEX_LAUNCH_PREFLIGHT:-}" && -x "\${ORCA_CODEX_LAUNCH_PREFLIGHT}" && -n "\${__orca_codex_binary:-}" && -x "\${__orca_codex_binary}" ]]; then
# Why the function reserved word: it suppresses alias expansion of the name,
# which otherwise rewrites this header at parse time and aborts the whole file.
function codex {
@@ -78,7 +86,7 @@ unset __orca_codex_binary
}
export function getFishCodexShellLaunchPreflight(): string {
return `if test -n "$ORCA_CODEX_LAUNCH_PREFLIGHT"; and test (type -t codex 2>/dev/null) = file
return `if test -x "$ORCA_CODEX_LAUNCH_PREFLIGHT"; and test (type -t codex 2>/dev/null) = file
function codex
command "$ORCA_CODEX_LAUNCH_PREFLIGHT" agent hooks prepare-codex >/dev/null 2>&1; or true
command codex $argv
+1 -1
View File
@@ -84,7 +84,7 @@ describe('addOrcaWslInteropEnv', () => {
expect(env.WSLENV).toContain('ORCA_TERMINAL_HANDLE/u')
expect(env.WSLENV).toContain('ORCA_USER_DATA_PATH/p')
expect(env.WSLENV).toContain('ORCA_CLI_COMMAND/u')
expect(env.WSLENV).not.toContain('ORCA_CODEX_LAUNCH_PREFLIGHT')
expect(env.WSLENV).toContain('ORCA_CODEX_LAUNCH_PREFLIGHT/u')
expect(env.WSLENV).toContain('ORCA_OMP_STATUS_EXTENSION/p')
expect(env.WSLENV).not.toContain('ORCA_PRIME_AGENT_STATUS_EXTENSION')
expect(env.WSLENV).toContain('ORCA_PANE_KEY/u')
+1
View File
@@ -77,6 +77,7 @@ export function addOrcaWslInteropEnv(env: Record<string, string>): void {
// and it cannot derive the hash segment from ORCA_USER_DATA_PATH alone.
'ORCA_SHELL_READY_ROOT/p',
'ORCA_CLI_COMMAND/u',
'ORCA_CODEX_LAUNCH_PREFLIGHT/u',
'ORCA_PANE_KEY/u',
'ORCA_TAB_ID/u',
'ORCA_WORKTREE_ID/u',
@@ -0,0 +1,118 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { OrcaRuntimeService } from '../../orca-runtime'
import { isStreamingMethod, type RpcContext } from '../core'
const { installForRuntimeHomeMock } = vi.hoisted(() => ({
installForRuntimeHomeMock: vi.fn()
}))
vi.mock('../../../codex/hook-service', () => ({
codexHookService: { installForRuntimeHome: installForRuntimeHomeMock }
}))
import { AGENT_HOOK_METHODS } from './agent-hooks'
function prepareMethod() {
const method = AGENT_HOOK_METHODS.find(
(candidate) => candidate.name === 'agentHooks.prepareCodexForWslPane'
)
if (!method || isStreamingMethod(method)) {
throw new Error('Missing agentHooks.prepareCodexForWslPane request method')
}
return method
}
function runtimeWithSettings(enabled = true, disabledTuiAgents: string[] = []): OrcaRuntimeService {
return {
getClientSettings: vi.fn(() => ({
agentStatusHooksEnabled: enabled,
disabledTuiAgents
}))
} as unknown as OrcaRuntimeService
}
describe('agent hook RPC methods', () => {
beforeEach(() => {
installForRuntimeHomeMock.mockReset()
})
it('installs the pane-selected WSL home once and returns its status', async () => {
const status = { agent: 'codex', state: 'installed' }
installForRuntimeHomeMock.mockReturnValue(status)
const method = prepareMethod()
const params = method.params!.parse({
codexHome: '/home/jin/.local/share/orca/codex-runtime-home/home',
orcaCodexHome: '/home/jin/.local/share/orca/codex-runtime-home/home',
wslDistro: 'Ubuntu-24.04'
})
expect(method.handler(params, { runtime: runtimeWithSettings() })).toBe(status)
expect(installForRuntimeHomeMock).toHaveBeenCalledExactlyOnceWith(
'\\\\wsl.localhost\\Ubuntu-24.04\\home\\jin\\.local\\share\\orca\\codex-runtime-home\\home',
{ runtime: 'wsl', wslDistro: 'Ubuntu-24.04' }
)
})
it.each([
[false, []],
[true, ['codex']]
])('does not install when hooks are disabled (%s, %j)', async (enabled, disabledTuiAgents) => {
const method = prepareMethod()
const params = method.params!.parse({
codexHome: '/home/jin/.local/share/orca/codex-runtime-home/home',
orcaCodexHome: '/home/jin/.local/share/orca/codex-runtime-home/home',
wslDistro: 'Ubuntu'
})
expect(
method.handler(params, { runtime: runtimeWithSettings(enabled, disabledTuiAgents) })
).toBeNull()
expect(installForRuntimeHomeMock).not.toHaveBeenCalled()
})
it('rejects non-local callers', async () => {
const method = prepareMethod()
const params = method.params!.parse({
codexHome: '/home/jin/.local/share/orca/codex-runtime-home/home',
orcaCodexHome: '/home/jin/.local/share/orca/codex-runtime-home/home',
wslDistro: 'Ubuntu'
})
expect(() =>
method.handler(params, {
runtime: runtimeWithSettings(),
clientKind: 'runtime'
} as RpcContext)
).toThrow(/only available to the local Orca CLI/)
expect(installForRuntimeHomeMock).not.toHaveBeenCalled()
})
it('propagates an attempted installer failure', () => {
installForRuntimeHomeMock.mockImplementation(() => {
throw new Error('install failed')
})
const method = prepareMethod()
const params = method.params!.parse({
codexHome: '/home/jin/.local/share/orca/codex-runtime-home/home',
orcaCodexHome: '/home/jin/.local/share/orca/codex-runtime-home/home',
wslDistro: 'Ubuntu'
})
expect(() => method.handler(params, { runtime: runtimeWithSettings() })).toThrow(
'install failed'
)
expect(installForRuntimeHomeMock).toHaveBeenCalledOnce()
})
it('rejects malformed distro names at the RPC schema', () => {
const method = prepareMethod()
expect(() =>
method.params!.parse({
codexHome: '/home/jin/.local/share/orca/codex-runtime-home/home',
orcaCodexHome: '/home/jin/.local/share/orca/codex-runtime-home/home',
wslDistro: 'Ubuntu\\..\\host'
})
).toThrow()
})
})
@@ -0,0 +1,38 @@
import { z } from 'zod'
import { prepareManagedWslCodexHomeBeforeShellLaunch } from '../../../codex/managed-home-shell-preflight'
import { defineMethod, type RpcMethod } from '../core'
const PrepareCodexForWslPaneParams = z
.object({
codexHome: z.string().max(4_096),
orcaCodexHome: z.string().max(4_096),
wslDistro: z
.string()
.trim()
.min(1)
.max(255)
.regex(/^[^\\/\r\n]+$/)
})
.strict()
export const AGENT_HOOK_METHODS: readonly RpcMethod[] = [
defineMethod({
name: 'agentHooks.prepareCodexForWslPane',
params: PrepareCodexForWslPaneParams,
handler: (params, { runtime, clientKind }) => {
if (clientKind !== undefined) {
throw new Error('Codex hook preparation is only available to the local Orca CLI.')
}
const settings = runtime.getClientSettings()
return prepareManagedWslCodexHomeBeforeShellLaunch({
env: {
CODEX_HOME: params.codexHome,
ORCA_CODEX_HOME: params.orcaCodexHome,
WSL_DISTRO_NAME: params.wslDistro
},
hooksEnabled:
settings.agentStatusHooksEnabled && !settings.disabledTuiAgents.includes('codex')
})
}
})
]
+2
View File
@@ -43,12 +43,14 @@ import { PAIRING_METHODS } from './pairing'
import { UPDATER_METHODS } from './updater'
import { AGENT_SESSION_METHODS } from './agent-session'
import { ARTIFACT_METHODS } from './artifacts'
import { AGENT_HOOK_METHODS } from './agent-hooks'
// Why: a flat manifest keeps registration order explicit and provides one
// grep-point for "what methods does the RPC server expose?" — useful when
// auditing the security boundary or wiring new CLI commands.
export const ALL_RPC_METHODS: readonly RpcAnyMethod[] = [
...STATUS_METHODS,
...AGENT_HOOK_METHODS,
...AI_VAULT_METHODS,
...ARTIFACT_METHODS,
...AUTOMATION_METHODS,