mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 16:02:29 +00:00
* fix(terminal): give plain shells and cmd.exe Codex launches --no-daemon Plain bash, zsh and fish tabs were never wrapped, so a typed codex skipped the shell function that adds --no-daemon. Wrap them (bash keeps its prompt and DEBUG trap untouched unless Orca asked for command markers), add --no-daemon host-side where no function can run (cmd.exe, path-named binaries), and move new tabs to a v38 terminal daemon so they get the new wrappers. * fix(terminal): keep plain bash a login shell and give the setup gate the codex function Plain bash and Git Bash tabs launch exactly as before again: the rcfile wrapper would have made every one a non-login shell. Plain tabs on the user's configured shell args stay unwrapped on both transports. The wait-for-setup gate's bash -lc now defines the codex function, so a sequenced Codex launch gets --no-daemon from the binary it actually runs. * fix(terminal): define the setup gate's codex function after setup finishes Setup can be what puts codex on PATH, so defining the function before the marker wait found no binary and skipped --no-daemon. * refactor(terminal): fold the SSH/WSL guard into the Codex launch planner and bound the gate test * fix(terminal): honour the pane's env deletions in the Codex opt-out check Also pin the setup-gate test's fake codex ahead of path_helper's PATH. * revert(terminal): launch plain zsh and fish tabs exactly as on main Drops the always-wrap for plain zsh and fish, the configured-args guard that only served it, and the v38 daemon bump: the daemon's launch configs and generated wrappers are byte-identical to main again. Keeps the host-side --no-daemon for cmd.exe and path-named launches and the setup gate's codex function.
100 lines
3.6 KiB
TypeScript
100 lines
3.6 KiB
TypeScript
import { isAbsolute, win32 as pathWin32 } from 'node:path'
|
|
import { runProcess } from '../../shared/child-process/run-process'
|
|
import { tokenizeStartupCommand } from '../../shared/tui-agent-startup-shell'
|
|
import { resolveLocalWindowsAgentStartupShell } from '../../shared/windows-terminal-shell'
|
|
import { resolveCommandOnLocalPath } from '../ipc/command-path-resolver'
|
|
import { CODEX_SHARED_SERVER_ARGS } from '../../shared/codex-shell-function'
|
|
|
|
const CODEX_EXECUTABLE = /^codex(\.(exe|cmd|bat|ps1))?$/i
|
|
const SHARED_SERVER_ARGS: ReadonlySet<string> = new Set(CODEX_SHARED_SERVER_ARGS)
|
|
// Why bounded: a hung binary must not hold the pane; a slow probe only costs the flag.
|
|
const HELP_PROBE_TIMEOUT_MS = 5_000
|
|
|
|
export type LocalCodexLaunch = {
|
|
command: string | undefined
|
|
/** False for SSH and WSL spawns: their shell's codex function probes on that host. */
|
|
executesOnThisHost: boolean
|
|
/** Shell the provider will launch; undefined means the platform default. */
|
|
shellOverride: string | undefined
|
|
/** Env the PTY gets on top of this process's own. */
|
|
env: Record<string, string | undefined> | undefined
|
|
/** Keys the provider removes from that merged env. */
|
|
envToDelete?: readonly string[]
|
|
cwd: string | undefined
|
|
}
|
|
|
|
/**
|
|
* The launch command with `--no-daemon` after the Codex executable, applying the
|
|
* shell codex function's rule (src/shared/codex-shell-function.ts) where that
|
|
* function never runs: cmd.exe defines none, and a path-named binary bypasses it.
|
|
* Everywhere else the function probes the binary the shell itself resolves after
|
|
* the user's startup files, which main cannot see. Null (synchronously) when
|
|
* nothing applies: an extra await tick would reorder the pane-spawn reservation
|
|
* races the spawn handlers arbitrate right after this.
|
|
*/
|
|
export function planCodexNoDaemonLaunch(launch: LocalCodexLaunch): Promise<string> | null {
|
|
const { command } = launch
|
|
if (!command || !launch.executesOnThisHost) {
|
|
return null
|
|
}
|
|
const shell =
|
|
resolveLocalWindowsAgentStartupShell({
|
|
platform: process.platform,
|
|
isRemote: false,
|
|
terminalWindowsShell: launch.shellOverride
|
|
}) ?? 'posix'
|
|
const parsed = tokenizeStartupCommand(command, shell)
|
|
const executableSpan = parsed.ok ? parsed.spans[0] : undefined
|
|
if (!parsed.ok || !executableSpan || executableSpan.divergesFromShell) {
|
|
return null
|
|
}
|
|
const [executable, ...args] = parsed.tokens
|
|
if (
|
|
!CODEX_EXECUTABLE.test(pathWin32.basename(executable)) ||
|
|
(shell !== 'cmd' && !isAbsolute(executable))
|
|
) {
|
|
return null
|
|
}
|
|
const env = { ...process.env, ...launch.env }
|
|
for (const key of launch.envToDelete ?? []) {
|
|
delete env[key]
|
|
}
|
|
if (
|
|
env.ORCA_CODEX_ISOLATE === '0' ||
|
|
args.some((arg) => SHARED_SERVER_ARGS.has(arg) || arg.startsWith('--remote='))
|
|
) {
|
|
return null
|
|
}
|
|
return supportsNoDaemon(executable, env, launch.cwd).then((supported) =>
|
|
supported
|
|
? `${command.slice(0, executableSpan.end)} --no-daemon${command.slice(executableSpan.end)}`
|
|
: command
|
|
)
|
|
}
|
|
|
|
// Why probe every launch: a cached answer goes stale across an upgrade, and 0.155 and older exit 2 on the flag.
|
|
async function supportsNoDaemon(
|
|
executable: string,
|
|
env: NodeJS.ProcessEnv,
|
|
cwd: string | undefined
|
|
): Promise<boolean> {
|
|
const program = isAbsolute(executable)
|
|
? executable
|
|
: await resolveCommandOnLocalPath(executable, { env, cwd })
|
|
if (!program) {
|
|
return false
|
|
}
|
|
try {
|
|
const help = await runProcess({
|
|
program,
|
|
args: ['--help'],
|
|
cwd,
|
|
env,
|
|
timeoutMs: HELP_PROBE_TIMEOUT_MS
|
|
})
|
|
return help.stdout.includes('--no-daemon')
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|