Files
orca/src/main/daemon/pty-subprocess.ts
T
Neil 087b895524 refactor(daemon): split oversized PTY services (#16160)
* refactor(daemon): split oversized PTY services

* revert(daemon): restore merge-base session listing and canceled-spawn behavior

Two behavior changes rode along with the file-splitting refactor:

- listLiveTerminalHostSessions dropped sessions with isTerminating, not just
  dead ones, hiding sessions the merge base still advertised.
- spawnAndPublishSession called session.beginTermination() before publishing a
  canceled spawn into the host map.

Both hunks are reverted to the merge base; the refactor is untouched.
2026-08-24 21:18:11 -07:00

115 lines
4.0 KiB
TypeScript

import type { SubprocessHandle } from './session-subprocess-handle'
import { normalizePtySize } from './daemon-pty-size'
import { TerminalAttachCanceledError } from './daemon-errors'
import { createDaemonPtyEnvironment } from './pty-subprocess/spawn-environment'
import { createPtyShellLaunchPlan } from './pty-subprocess/shell-launch-plan'
import { spawnNativeDaemonPty, type SpawnedDaemonPty } from './pty-subprocess/native-pty-spawn'
import {
formatPtySpawnError,
preflightPtySpawn,
preflightPtySpawnHealth,
runPtySpawnHealthProbe
} from './pty-subprocess/spawn-preflight'
import { createDaemonPtySubprocessHandle } from './pty-subprocess/subprocess-handle'
import type { StartupCommandDelivery } from '../../shared/codex-startup-delivery'
import type { TuiAgent } from '../../shared/tui-agent'
const PTY_SPAWN_HEALTH_RETRY_ATTEMPTS = 2
export type PtySubprocessOptions = {
sessionId: string
cols: number
rows: number
cwd?: string
env?: Record<string, string>
envToDelete?: string[]
command?: string
startupCommandDelivery?: StartupCommandDelivery
launchAgent?: TuiAgent
/** Explicit shell executable path/basename requested by the renderer. */
shellOverride?: string
terminalWindowsWslDistro?: string | null
terminalWindowsPowerShellImplementation?: 'auto' | 'powershell.exe' | 'pwsh.exe'
isCanceled?: () => boolean
/** Aborts in-progress cwd validation; `isCanceled` is only polled between steps. */
cancelSignal?: AbortSignal
onMacosTccSpawnStrategy?: (strategy: 'wrapped' | 'direct') => void
}
export async function checkPtySpawnHealth(): Promise<void> {
if (!preflightPtySpawnHealth()) {
return
}
let lastError: unknown
for (let attempt = 1; attempt <= PTY_SPAWN_HEALTH_RETRY_ATTEMPTS; attempt++) {
try {
await runPtySpawnHealthProbe()
return
} catch (error) {
lastError = error
if (attempt < PTY_SPAWN_HEALTH_RETRY_ATTEMPTS) {
console.warn(
`[daemon] PTY spawn health probe attempt ${attempt} failed; retrying`,
error instanceof Error ? error.message : error
)
}
}
}
throw lastError instanceof Error ? lastError : new Error(String(lastError))
}
/**
* Spawns the daemon-owned PTY subprocess for a terminal session.
*
* Launch planning stays ahead of validation so Windows validates the effective
* host or WSL cwd selected by the same fallback chain that native spawn uses.
* The handle then owns all event buffering, foreground identity, and teardown.
*/
export async function createPtySubprocess(opts: PtySubprocessOptions): Promise<SubprocessHandle> {
const size = normalizePtySize(opts.cols, opts.rows)
const env = createDaemonPtyEnvironment(opts)
const launch = createPtyShellLaunchPlan(opts, env)
await preflightPtySpawn({
validationCwd: launch.validationCwd,
cwdWasExplicit: opts.cwd !== undefined,
sessionId: opts.sessionId,
...(opts.cancelSignal ? { signal: opts.cancelSignal } : {})
})
if (opts.isCanceled?.()) {
throw new TerminalAttachCanceledError(opts.sessionId)
}
let spawned: SpawnedDaemonPty
try {
spawned = spawnNativeDaemonPty({
shellPath: launch.shellPath,
shellArgs: launch.shellArgs,
spawnCwd: launch.spawnCwd,
env,
cols: size.cols,
rows: size.rows,
windowsFallbackAttempts: launch.windowsFallbackAttempts,
onMacosTccSpawnStrategy: opts.onMacosTccSpawnStrategy
})
} catch (error) {
if (process.platform === 'win32') {
throw formatPtySpawnError(error, launch.shellPath, launch.spawnCwd)
}
throw error
}
return createDaemonPtySubprocessHandle({
process: spawned.process,
shellPath: spawned.shellPath,
spawnCwd: spawned.spawnCwd,
env,
startupCommandDeliveredInShellArgs:
spawned.startupCommandDeliveredInShellArgs ?? launch.startupCommandDeliveredInShellArgs,
reportsChildExitStatus: spawned.reportsChildExitStatus,
requestedCwd: opts.cwd,
sessionId: opts.sessionId,
startupAgentRecognition: launch.startupAgentRecognition
})
}