mirror of
https://github.com/stablyai/orca.git
synced 2026-09-25 08:02:31 +00:00
85 lines
3.2 KiB
TypeScript
85 lines
3.2 KiB
TypeScript
import * as pty from 'node-pty'
|
|
import {
|
|
hostReportsChildExitStatus,
|
|
wrapShellSpawnForMacosTccAttribution
|
|
} from '../../providers/macos-tcc-login-shell'
|
|
import type { WindowsShellSpawnAttempt } from '../../providers/windows-shell-fallback-chain'
|
|
import { assignHostProcessToKillOnCloseJob } from '../../windows/windows-pty-job'
|
|
import { scrubCodexDefaultHomeMarkerForLaunch } from '../../pty/codex-default-home-shell-startup'
|
|
|
|
export type SpawnedDaemonPty = {
|
|
process: pty.IPty
|
|
shellPath: string
|
|
spawnCwd: string
|
|
startupCommandDeliveredInShellArgs?: boolean
|
|
/** False when a wrapper owns the reported status, so no exit code may be read from it. */
|
|
reportsChildExitStatus: boolean
|
|
}
|
|
|
|
/** Walks the Windows PowerShell -> cmd.exe fallback chain when ConPTY rejects the primary shell. */
|
|
export function spawnNativeDaemonPty(args: {
|
|
shellPath: string
|
|
shellArgs: string[]
|
|
spawnCwd: string
|
|
env: Record<string, string>
|
|
cols: number
|
|
rows: number
|
|
windowsFallbackAttempts: WindowsShellSpawnAttempt[]
|
|
onMacosTccSpawnStrategy?: (strategy: 'wrapped' | 'direct') => void
|
|
}): SpawnedDaemonPty {
|
|
let reportsChildExitStatus = true
|
|
const spawnAt = (shellPath: string, shellArgs: string[], cwd: string): pty.IPty => {
|
|
const wrapped = wrapShellSpawnForMacosTccAttribution(shellPath, shellArgs, args.env)
|
|
// Why: children inherit job membership, so the host job must exist before the first Windows PTY.
|
|
if (process.platform === 'win32') {
|
|
assignHostProcessToKillOnCloseJob()
|
|
}
|
|
const proc = pty.spawn(wrapped.file, wrapped.args, {
|
|
name: args.env.TERM ?? 'xterm-256color',
|
|
cols: args.cols,
|
|
rows: args.rows,
|
|
cwd,
|
|
env: args.env,
|
|
// Why: bundled ConPTY has the wrap-marker behavior xterm expects.
|
|
...(process.platform === 'win32' ? { useConptyDll: true } : {})
|
|
})
|
|
reportsChildExitStatus = hostReportsChildExitStatus(wrapped.file)
|
|
args.onMacosTccSpawnStrategy?.(wrapped.file === shellPath ? 'direct' : 'wrapped')
|
|
return proc
|
|
}
|
|
|
|
try {
|
|
const process_ = spawnAt(args.shellPath, args.shellArgs, args.spawnCwd)
|
|
return {
|
|
process: process_,
|
|
shellPath: args.shellPath,
|
|
spawnCwd: args.spawnCwd,
|
|
reportsChildExitStatus
|
|
}
|
|
} catch (primaryErr) {
|
|
if (process.platform !== 'win32') {
|
|
throw primaryErr
|
|
}
|
|
for (const attempt of args.windowsFallbackAttempts.slice(1)) {
|
|
try {
|
|
scrubCodexDefaultHomeMarkerForLaunch(args.env, attempt.supportsCodexDefaultHomeAfterProfile)
|
|
const process = spawnAt(attempt.shellPath, attempt.shellArgs, attempt.effectiveCwd)
|
|
const message = primaryErr instanceof Error ? primaryErr.message : String(primaryErr)
|
|
console.warn(
|
|
`[daemon/pty] Primary shell "${args.shellPath}" failed (${message}), fell back to "${attempt.shellPath}"`
|
|
)
|
|
return {
|
|
process,
|
|
shellPath: attempt.shellPath,
|
|
spawnCwd: attempt.effectiveCwd,
|
|
startupCommandDeliveredInShellArgs: attempt.startupCommandDeliveredInShellArgs,
|
|
reportsChildExitStatus
|
|
}
|
|
} catch {
|
|
// This fallback shell also failed -- try the next link in the chain.
|
|
}
|
|
}
|
|
throw primaryErr
|
|
}
|
|
}
|