Files
orca/src/main/codex/codex-shared-server-fix.ts
T
Jinwoo HongandOrca Worker bd6d7b92b1 feat(codex): run Windows Codex on the real ~/.codex (#24356)
Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-02 19:01:12 -07:00

95 lines
3.4 KiB
TypeScript

import { stat } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { runProcess } from '../../shared/child-process/run-process'
import {
CODEX_DISABLE_SHARED_SERVER_ARGS,
CODEX_SHARED_SERVER_FEATURE_KEY,
CODEX_STOP_SHARED_SERVER_ARGS
} from '../../shared/codex-shared-server-command'
import { probeCodexSharedServer } from './codex-shared-server-probe'
const COMMAND_TIMEOUT_MS = 15_000
// Why longer: Codex lets running turns drain for up to 60 s by default, then forces after 10 s.
const STOP_TIMEOUT_MS = 75_000
const MAX_OUTPUT_BYTES = 64 * 1024
/**
* The Codex CLI Codex installed under this home's `packages/` (the server's
* package, else the legacy standalone one), so its lifecycle commands match
* the server's version rather than whatever `codex` is on PATH.
*/
export async function resolveCodexSharedServerBinary(codexHome: string): Promise<string | null> {
const fileName = process.platform === 'win32' ? 'codex.exe' : 'codex'
for (const packageName of ['app-server-daemon', 'standalone']) {
const current = join(codexHome, 'packages', packageName, 'current')
for (const candidate of [join(current, 'bin', fileName), join(current, fileName)]) {
try {
if ((await stat(candidate)).isFile()) {
return candidate
}
} catch {
// Not installed in this layout.
}
}
}
return null
}
/** The command's stdout when it exited 0 in time; otherwise null. */
async function runCodex(
codexHome: string,
args: readonly string[],
timeoutMs: number
): Promise<string | null> {
const program = await resolveCodexSharedServerBinary(codexHome)
if (!program) {
return null
}
try {
const result = await runProcess({
program,
args,
// Why: the program's own folder exists whenever it resolved.
cwd: dirname(program),
env: { ...process.env, CODEX_HOME: codexHome },
timeoutMs,
maxOutputBytes: MAX_OUTPUT_BYTES
})
return result.code === 0 && !result.timedOut ? result.stdout : null
} catch {
return null
}
}
/** Codex's `features list` row for `key`: `name stage true|false`. */
export function readFeatureEnabled(stdout: string, key: string): boolean | null {
for (const line of stdout.split(/\r?\n/)) {
const columns = line.trim().split(/\s+/)
if (columns[0] === key) {
const enabled = columns.at(-1)
return enabled === 'true' ? true : enabled === 'false' ? false : null
}
}
return null
}
/**
* Turns off server sharing in the pane's own home; true only once Codex reads
* it back as off.
*/
export async function disableCodexSharedServerAutoStart(codexHome: string): Promise<boolean> {
if ((await runCodex(codexHome, CODEX_DISABLE_SHARED_SERVER_ARGS, COMMAND_TIMEOUT_MS)) === null) {
return false
}
// Why read back: managed config can pin the feature on even when the write exits 0.
const list = await runCodex(codexHome, ['features', 'list'], COMMAND_TIMEOUT_MS)
return list !== null && readFeatureEnabled(list, CODEX_SHARED_SERVER_FEATURE_KEY) === false
}
/** Stops this home's shared server; true only once it is proven gone. */
export async function stopCodexSharedServer(codexHome: string): Promise<boolean> {
// Why the probe decides: only it shows whether this home's server is actually gone.
await runCodex(codexHome, CODEX_STOP_SHARED_SERVER_ARGS, STOP_TIMEOUT_MS)
return (await probeCodexSharedServer(codexHome)) === 'absent'
}