fix(terminal): preserve startup scanner output bytes

This commit is contained in:
Jinwoo-H
2026-08-12 22:21:23 -04:00
parent 79aaddd566
commit fd7567ee75
7 changed files with 203 additions and 113 deletions
+30 -7
View File
@@ -17,23 +17,27 @@
"protection": "partial",
"owner": "terminal-session-readiness",
"layer": "posix-pty-and-shell-startup",
"surfaces": ["queued terminal startup commands", "zsh and bash startup files"],
"surfaces": ["queued terminal startup commands", "zsh, bash, and Fish startup output"],
"platforms": ["macos", "linux"],
"providers": ["local", "local-daemon", "ssh-daemon", "ssh-relay", "paired-runtime"],
"coveredPlatforms": ["macos"],
"coveredProviders": ["local", "local-daemon", "ssh-relay"],
"coverageNotes": "Real macOS node-pty integration covers zsh 5.9 .zshenv/.zprofile and bash 3.2 profile replacement shells, including exec -a names, same-shell and child-process silent reads, a blocking zle-line-init hook, non-shell readline replacements, and a non-shell image renamed zsh. An Ubuntu 20.04 container confirms compatible zsh/bash termios. Deterministic owner contracts cover local, daemon, and SSH relay identity stripping plus provider- and renderer-owned delivery. Renderer-owned relay fallback publishes the existing ready marker, so mixed client/host versions need no new wire capability. Live SSH, paired-runtime, and WSL remain gaps.",
"motivatingLinks": ["https://github.com/stablyai/orca/issues/13767"],
"invariant": "Before user startup files run, the wrapper publishes its shell PID. If a later exec discards the ready marker, prompt fallback releases queued startup input only after the launched shell's actual executable image with that exact PID owns the PTY foreground, emits its post-hook line-editor enable sequence, and exposes zle/readline termios; startup reads, line-init hooks, child foreground processes, and non-shell readline replacements cannot trigger early fallback release.",
"oracle": "Run the real generated wrappers under node-pty with disposable zsh and bash startup files. Plain and exec -a replacement shells must receive one queued command at their first completed line editor without waiting for the 15-second timeout. The identical oracle must time out without the startup identity and prompt fallback. Same-shell reads, child-process reads, a blocking zle-line-init hook, sqlite3, and sqlite3 renamed zsh must not trigger prompt fallback. Relay owner coverage must strip the identity and release through the same prompt contract.",
"coverageNotes": "Real macOS node-pty integration covers zsh 5.9 .zshenv/.zprofile and bash 3.2 profile replacement shells, including exec -a names, same-shell and child-process silent reads, a blocking zle-line-init hook, non-shell readline replacements, and a non-shell image renamed zsh. A real Fish PTY proves marker stripping is byte-exact and does not render bracketed-paste control text. An Ubuntu 20.04 container confirms compatible zsh/bash termios. Deterministic owner contracts cover local, daemon, and SSH relay identity/readiness composition plus provider- and renderer-owned delivery. Renderer-owned relay fallback publishes the existing ready marker, so mixed client/host versions need no new wire capability. Live SSH, paired-runtime, and WSL remain gaps.",
"motivatingLinks": [
"https://github.com/stablyai/orca/issues/13767",
"https://linear.app/stably/issue/STA-4068"
],
"invariant": "Shell startup scanning preserves every non-Orca output byte in order across arbitrary PTY chunk boundaries. Before user startup files run, the wrapper publishes its shell PID. If a later exec discards the ready marker, prompt fallback releases queued startup input only after the launched shell's actual executable image with that exact PID owns the PTY foreground, emits its post-hook line-editor enable sequence, and exposes zle/readline termios; startup reads, line-init hooks, child foreground processes, and non-shell readline replacements cannot trigger early fallback release.",
"oracle": "Split Fish output immediately after the ready marker and after the following ESC introducer; local and daemon owners must emit the exact ESC [ ? 2004 h bytes and headless xterm must not render literal control text. Run the real generated wrappers under node-pty with disposable zsh, bash, and Fish startup files. Plain and exec -a replacement shells must receive one queued command at their first completed line editor without waiting for the 15-second timeout. The identical oracle must time out without the startup identity and prompt fallback. Same-shell reads, child-process reads, a blocking zle-line-init hook, sqlite3, and sqlite3 renamed zsh must not trigger prompt fallback. Relay owner coverage must use the same lossless scanner composition and release through the same prompt contract.",
"commands": [
"pnpm exec vitest run --config config/vitest.config.ts src/main/daemon/repro-13767-shell-ready-marker-lost-to-exec.test.ts src/main/line-editor-ready-output-scanner.test.ts src/main/shell-startup-identity-scanner.test.ts src/main/shell-prompt-readiness-probe.test.ts src/shared/pty-slave-line-discipline-echo.test.ts src/shared/shell-process-readiness.test.ts src/main/daemon/session.test.ts src/main/daemon/shell-ready.test.ts src/main/providers/local-pty-shell-ready.test.ts src/main/providers/local-pty-provider.test.ts src/relay/pty-shell-launch.test.ts src/relay/pty-handler.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/main/daemon/repro-13767-shell-ready-marker-lost-to-exec.test.ts src/main/line-editor-ready-output-scanner.test.ts src/main/shell-startup-identity-scanner.test.ts src/main/shell-startup-output-scanner.test.ts src/main/shell-prompt-readiness-probe.test.ts src/shared/pty-slave-line-discipline-echo.test.ts src/shared/shell-process-readiness.test.ts src/main/daemon/session.test.ts src/main/daemon/shell-ready.test.ts src/main/providers/local-pty-shell-ready.test.ts src/main/providers/local-pty-provider.test.ts src/relay/pty-shell-launch.test.ts src/relay/pty-handler.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/main/daemon/repro-13767-shell-ready-marker-lost-to-exec.test.ts src/relay/pty-handler.test.ts"
],
"testFiles": [
"src/main/daemon/repro-13767-shell-ready-marker-lost-to-exec.test.ts",
"src/main/line-editor-ready-output-scanner.test.ts",
"src/main/shell-startup-identity-scanner.test.ts",
"src/main/shell-startup-output-scanner.test.ts",
"src/main/shell-prompt-readiness-probe.test.ts",
"src/shared/pty-slave-line-discipline-echo.test.ts",
"src/shared/shell-process-readiness.test.ts",
@@ -45,6 +49,25 @@
"src/relay/pty-handler.test.ts"
],
"assertionRefs": [
{
"file": "src/main/shell-startup-output-scanner.test.ts",
"assertions": [
"Fish bracketed-paste output stays byte-exact across marker and ESC chunk splits",
"identity and readiness markers strip together without reordering prompt output",
"incomplete scanner prefixes drain in byte order"
]
},
{
"file": "src/main/daemon/session.test.ts",
"assertions": [
"daemon output is byte-exact for both Fish chunk splits",
"headless xterm never renders literal bracketed-paste control text"
]
},
{
"file": "src/main/providers/local-pty-provider.test.ts",
"assertions": ["local PTY output is byte-exact for both Fish chunk splits"]
},
{
"file": "src/main/daemon/repro-13767-shell-ready-marker-lost-to-exec.test.ts",
"assertions": [
@@ -90,7 +113,7 @@
},
"performanceBudget": {
"required": true,
"evidence": "The normal ready-marker path cancels before external probing. A missing marker triggers a debounced stty read only after a line-editor enable sequence, followed by narrow PID status and executable-image inspection only when termios matches. Slow ordinary startup output triggers zero probes; rejected protocol emissions are capped at four probes per startup. There is no per-command hook or steady-state polling."
"evidence": "One bounded composition pass replaces two sequential scanner passes at each owner and retains only the existing bounded prefixes. The normal ready-marker path cancels before external probing. A missing marker triggers a debounced stty read only after a line-editor enable sequence, followed by narrow PID status and executable-image inspection only when termios matches. Slow ordinary startup output triggers zero probes; rejected protocol emissions are capped at four probes per startup. There is no per-command hook or steady-state polling."
},
"promotionCriteria": [
"Collect Linux, SSH-daemon, and paired-runtime live evidence.",
+15 -38
View File
@@ -8,17 +8,11 @@ import {
import { isValidPtySize, normalizePtySize } from './daemon-pty-size'
import { PostReadyFlushGate } from './post-ready-flush-gate'
import {
createShellReadyScanState,
drainShellReadyHeldBytes,
scanForShellReady,
type ShellReadyScanState
} from '../shell-ready-marker-scanner'
import {
createShellStartupIdentityScanState,
drainShellStartupIdentityHeldBytes,
scanForShellStartupIdentity,
type ShellStartupIdentityScanState
} from '../shell-startup-identity-scanner'
createShellStartupOutputScanState,
drainShellStartupOutputScanState,
scanShellStartupOutput,
type ShellStartupOutputScanState
} from '../shell-startup-output-scanner'
import {
createShellPromptReadinessProbe,
type ShellPromptReadinessProbe
@@ -136,8 +130,7 @@ export class Session {
private preReadyStdinQueue: string[] = []
private releaseStartupDeviceAttributesResponder: (() => void) | null = null
private startupDeviceAttributesQueryFilter: StartupDeviceAttributesQueryFilter | null = null
private shellReadyScanState: ShellReadyScanState | null = null
private shellStartupIdentityScanState: ShellStartupIdentityScanState | null = null
private shellStartupOutputScanState: ShellStartupOutputScanState | null = null
private shellStartupPid: number | null = null
private shellPromptReadinessProbe: ShellPromptReadinessProbe | null = null
private shellReadyTimer: ReturnType<typeof setTimeout> | null = null
@@ -184,8 +177,7 @@ export class Session {
if (opts.shellReadySupported) {
this._shellState = 'pending'
this.shellReadyScanState = createShellReadyScanState()
this.shellStartupIdentityScanState = createShellStartupIdentityScanState()
this.shellStartupOutputScanState = createShellStartupOutputScanState()
// Why: `write` queues everything until the ready marker, including the renderer's DA1
// reply — and a shell that withholds its first prompt until DA1 is answered (fish) then
// never emits the marker that would release it. Answer from the daemon, past the queue.
@@ -636,7 +628,7 @@ export class Session {
}
this.shellPromptReadinessProbe?.dispose()
this.shellPromptReadinessProbe = null
this.shellReadyScanState = null
this.shellStartupOutputScanState = null
this.preReadyStdinQueue = []
this.postReadyFlushGate.clear()
this.disposeSubprocessHandle()
@@ -682,18 +674,13 @@ export class Session {
}
let releaseStartupDeviceAttributes = false
if (this._shellState === 'pending' && this.shellStartupIdentityScanState) {
const scanned = scanForShellStartupIdentity(this.shellStartupIdentityScanState, data)
if (this._shellState === 'pending' && this.shellStartupOutputScanState) {
const scanned = scanShellStartupOutput(this.shellStartupOutputScanState, data)
data = scanned.output
if (scanned.shellPid) {
this.shellStartupPid = scanned.shellPid
this.shellStartupIdentityScanState = null
}
}
if (this._shellState === 'pending' && this.shellReadyScanState) {
const scanned = scanForShellReady(this.shellReadyScanState, data)
data = scanned.output
if (scanned.matched) {
if (scanned.ready) {
this.transitionToReady(scanned.postMarkerBytesObserved)
releaseStartupDeviceAttributes = true
}
@@ -773,20 +760,11 @@ export class Session {
}
private releaseHeldShellReadyBytes(): string {
if (!this.shellReadyScanState && !this.shellStartupIdentityScanState) {
if (!this.shellStartupOutputScanState) {
return ''
}
let heldBytes = this.shellStartupIdentityScanState
? drainShellStartupIdentityHeldBytes(this.shellStartupIdentityScanState)
: ''
this.shellStartupIdentityScanState = null
if (this.shellReadyScanState && heldBytes) {
heldBytes = scanForShellReady(this.shellReadyScanState, heldBytes).output
}
if (this.shellReadyScanState) {
heldBytes += drainShellReadyHeldBytes(this.shellReadyScanState)
}
this.shellReadyScanState = null
const heldBytes = drainShellStartupOutputScanState(this.shellStartupOutputScanState)
this.shellStartupOutputScanState = null
// Why: scanning strips marker bytes before fan-out; if readiness never completes, release any held prefix before timeout/exit discards it.
this.startupIngress.accept(heldBytes)
return heldBytes
@@ -813,8 +791,7 @@ export class Session {
private transitionToReady(postMarkerBytesObserved = false): void {
this._shellState = 'ready'
this.shellReadyScanState = null
this.shellStartupIdentityScanState = null
this.shellStartupOutputScanState = null
this.shellPromptReadinessProbe?.dispose()
this.shellPromptReadinessProbe = null
if (this.shellReadyTimer) {
+15
View File
@@ -6,6 +6,12 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync
import type * as ShellReadyModule from './shell-ready'
import { getZshShellReadyMarkerRegistrationBlock } from '../shell-templates'
import { fishRequirementViolation, resolveFishBinary } from '../../shared/fish-binary-requirement'
import {
createShellStartupOutputScanState,
drainShellStartupOutputScanState,
scanShellStartupOutput
} from '../shell-startup-output-scanner'
import { HeadlessEmulator } from './headless-emulator'
async function importFreshShellReady(): Promise<typeof ShellReadyModule> {
vi.resetModules()
@@ -296,6 +302,8 @@ describePosix('daemon shell-ready launch config', () => {
}
})
let output = ''
let scannedOutput = ''
const startupScanState = createShellStartupOutputScanState()
let commandWritten = false
let erasureProbeWritten = false
let queryCarry = ''
@@ -321,6 +329,7 @@ describePosix('daemon shell-ready launch config', () => {
}, 50)
proc.onData((chunk) => {
output += chunk
scannedOutput += scanShellStartupOutput(startupScanState, chunk).output
// Why: fish stalls its first prompt 10s waiting on these and re-queries
// each prompt, so answer every occurrence — an unanswered query makes
// fish swallow the post-marker command as its reply.
@@ -350,9 +359,15 @@ describePosix('daemon shell-ready launch config', () => {
clearTimeout(deadline)
clearInterval(sentinelPoll)
proc.kill()
scannedOutput += drainShellStartupOutputScanState(startupScanState)
expect(output).toContain(SHELL_READY_MARKER_OUTPUT)
expect(output.split(SHELL_READY_MARKER_OUTPUT)).toHaveLength(2)
expect(scannedOutput).toBe(output.replace(SHELL_READY_MARKER_OUTPUT, ''))
const rendered = new HeadlessEmulator({ cols: 80, rows: 24 })
expect(rendered.writeSync(scannedOutput)).toBe(true)
expect(rendered.getVisibleLines().join('\n')).not.toContain('[?2004h')
rendered.dispose()
expect(existsSync(sentinel)).toBe(true)
// Why: asserts the erase directly rather than inferring it from the marker
// count, which only holds once enough prompts have been drawn to expose it.
+13 -30
View File
@@ -29,9 +29,6 @@ import { prepareMacosTccLoginShell } from './macos-tcc-login-shell'
import {
getMarkerlessShellLaunchConfig,
getShellReadyLaunchConfig,
createShellReadyScanState,
drainShellReadyHeldBytes,
scanForShellReady,
writeStartupCommandWhenShellReady,
STARTUP_COMMAND_READY_MAX_WAIT_MS
} from './local-pty-shell-ready'
@@ -76,10 +73,10 @@ import {
readPtySlavePath
} from '../../shared/pty-slave-line-discipline-echo'
import {
createShellStartupIdentityScanState,
drainShellStartupIdentityHeldBytes,
scanForShellStartupIdentity
} from '../shell-startup-identity-scanner'
createShellStartupOutputScanState,
drainShellStartupOutputScanState,
scanShellStartupOutput
} from '../shell-startup-output-scanner'
import {
createShellPromptReadinessProbe,
type ShellPromptReadinessProbe
@@ -961,11 +958,8 @@ export class LocalPtyProvider implements IPtyProvider {
let shellReadyTimeout: ReturnType<typeof setTimeout> | null = null
let shellStartupPid: number | null = null
let shellPromptReadinessProbe: ShellPromptReadinessProbe | null = null
let shellStartupIdentityScanState = shellReadyLaunch?.supportsReadyMarker
? createShellStartupIdentityScanState()
: null
const shellReadyScanState = shellReadyLaunch?.supportsReadyMarker
? createShellReadyScanState()
let shellStartupOutputScanState = shellReadyLaunch?.supportsReadyMarker
? createShellStartupOutputScanState()
: null
const shellReadyPromise = args.command
? new Promise<ShellReadySignal>((resolve) => {
@@ -987,23 +981,17 @@ export class LocalPtyProvider implements IPtyProvider {
resolve(signal)
}
const releaseHeldShellReadyBytes = (): void => {
if (!shellReadyScanState) {
if (!shellStartupOutputScanState) {
return
}
let heldBytes = shellStartupIdentityScanState
? drainShellStartupIdentityHeldBytes(shellStartupIdentityScanState)
: ''
shellStartupIdentityScanState = null
if (heldBytes) {
heldBytes = scanForShellReady(shellReadyScanState, heldBytes).output
}
heldBytes += drainShellReadyHeldBytes(shellReadyScanState)
const heldBytes = drainShellStartupOutputScanState(shellStartupOutputScanState)
shellStartupOutputScanState = null
if (heldBytes.length === 0) {
return
}
startupIngress.accept(heldBytes)
}
if (shellReadyScanState) {
if (shellStartupOutputScanState) {
shellPromptReadinessProbe = createShellPromptReadinessProbe({
slavePath: readPtySlavePath(proc),
shellPath,
@@ -1048,18 +1036,13 @@ export class LocalPtyProvider implements IPtyProvider {
const disposables: { dispose: () => void }[] = []
const onDataDisposable = proc.onData((rawData) => {
let data = rawData
if (shellStartupIdentityScanState && resolveShellReady) {
const scanned = scanForShellStartupIdentity(shellStartupIdentityScanState, data)
if (shellStartupOutputScanState && resolveShellReady) {
const scanned = scanShellStartupOutput(shellStartupOutputScanState, data)
data = scanned.output
if (scanned.shellPid) {
shellStartupPid = scanned.shellPid
shellStartupIdentityScanState = null
}
}
if (shellReadyScanState && resolveShellReady) {
const scanned = scanForShellReady(shellReadyScanState, data)
data = scanned.output
if (scanned.matched) {
if (scanned.ready) {
finishShellReady({ postMarkerBytesObserved: scanned.postMarkerBytesObserved })
}
}
@@ -0,0 +1,46 @@
import { describe, expect, it } from 'vitest'
import {
createShellStartupOutputScanState,
drainShellStartupOutputScanState,
scanShellStartupOutput
} from './shell-startup-output-scanner'
const READY_MARKER = '\x1b]777;orca-shell-ready\x07'
describe('shell startup output scanner', () => {
it.each([
['after the ready marker', [READY_MARKER, '\x1b[?2004hfish> ']],
['after the ESC introducer', [`${READY_MARKER}\x1b`, '[?2004hfish> ']]
])('preserves Fish output split %s', (_boundary, chunks) => {
const state = createShellStartupOutputScanState()
let output = ''
for (const chunk of chunks) {
output += scanShellStartupOutput(state, chunk).output
}
expect(output).toBe('\x1b[?2004hfish> ')
})
it('strips identity and readiness markers from one chunk', () => {
const state = createShellStartupOutputScanState()
const scanned = scanShellStartupOutput(
state,
`\x1b]777;orca-shell-start:12345\x07${READY_MARKER}prompt`
)
expect(scanned).toEqual({
output: 'prompt',
shellPid: 12345,
ready: true,
postMarkerBytesObserved: true
})
})
it('drains every incomplete scanner prefix in byte order', () => {
const state = createShellStartupOutputScanState()
expect(scanShellStartupOutput(state, '\x1b]777;orca-shell-st').output).toBe('')
expect(drainShellStartupOutputScanState(state)).toBe('\x1b]777;orca-shell-st')
})
})
+68
View File
@@ -0,0 +1,68 @@
import {
createShellReadyScanState,
drainShellReadyHeldBytes,
scanForShellReady,
type ShellReadyScanState
} from './shell-ready-marker-scanner'
import {
createShellStartupIdentityScanState,
drainShellStartupIdentityHeldBytes,
scanForShellStartupIdentity,
type ShellStartupIdentityScanState
} from './shell-startup-identity-scanner'
export type ShellStartupOutputScanState = {
ready: ShellReadyScanState
identity: ShellStartupIdentityScanState | null
}
export type ShellStartupOutputScanResult = {
output: string
shellPid: number | null
ready: boolean
postMarkerBytesObserved: boolean
}
export function createShellStartupOutputScanState(): ShellStartupOutputScanState {
return {
ready: createShellReadyScanState(),
identity: createShellStartupIdentityScanState()
}
}
export function scanShellStartupOutput(
state: ShellStartupOutputScanState,
data: string
): ShellStartupOutputScanResult {
let shellPid: number | null = null
if (state.identity) {
const identity = scanForShellStartupIdentity(state.identity, data)
data = identity.output
shellPid = identity.shellPid
if (shellPid) {
state.identity = null
}
}
const readiness = scanForShellReady(state.ready, data)
let output = readiness.output
if (readiness.matched && state.identity) {
output += drainShellStartupIdentityHeldBytes(state.identity)
state.identity = null
}
return {
output,
shellPid,
ready: readiness.matched,
postMarkerBytesObserved: readiness.postMarkerBytesObserved
}
}
export function drainShellStartupOutputScanState(state: ShellStartupOutputScanState): string {
let output = state.identity ? drainShellStartupIdentityHeldBytes(state.identity) : ''
state.identity = null
if (output) {
output = scanForShellReady(state.ready, output).output
}
return output + drainShellReadyHeldBytes(state.ready)
}
+16 -38
View File
@@ -27,19 +27,13 @@ import {
} from '../shared/cross-platform-path'
import { splitWorktreeId } from '../shared/worktree-id'
import { PhysicalExitTracker } from '../shared/physical-exit-tracker'
import { SHELL_READY_MARKER_PREFIX } from '../main/shell-ready-marker-scanner'
import {
createShellReadyScanState,
drainShellReadyHeldBytes,
scanForShellReady,
SHELL_READY_MARKER_PREFIX,
type ShellReadyScanState
} from '../main/shell-ready-marker-scanner'
import {
createShellStartupIdentityScanState,
drainShellStartupIdentityHeldBytes,
scanForShellStartupIdentity,
type ShellStartupIdentityScanState
} from '../main/shell-startup-identity-scanner'
createShellStartupOutputScanState,
drainShellStartupOutputScanState,
scanShellStartupOutput,
type ShellStartupOutputScanState
} from '../main/shell-startup-output-scanner'
import {
createShellPromptReadinessProbe,
type ShellPromptReadinessProbe
@@ -196,8 +190,7 @@ type ManagedStartupCommand = {
providerDelivery: boolean
delivered: boolean
waitForShellReady: boolean
scanState: ShellReadyScanState | null
identityScanState: ShellStartupIdentityScanState | null
outputScanState: ShellStartupOutputScanState | null
shellPid: number | null
promptProbe: ShellPromptReadinessProbe | null
timer: ReturnType<typeof setTimeout> | null
@@ -658,17 +651,11 @@ export class PtyHandler {
}
private drainStartupScanBytes(startup: ManagedStartupCommand): string {
let heldBytes = startup.identityScanState
? drainShellStartupIdentityHeldBytes(startup.identityScanState)
: ''
startup.identityScanState = null
if (startup.scanState && heldBytes) {
heldBytes = scanForShellReady(startup.scanState, heldBytes).output
}
if (startup.scanState) {
heldBytes += drainShellReadyHeldBytes(startup.scanState)
startup.scanState = null
if (!startup.outputScanState) {
return ''
}
const heldBytes = drainShellStartupOutputScanState(startup.outputScanState)
startup.outputScanState = null
return heldBytes
}
@@ -767,18 +754,13 @@ export class PtyHandler {
}
managed.pty.onData((data: string) => {
const startup = managed.startupCommand
if (startup?.identityScanState && !startup.delivered) {
const scanned = scanForShellStartupIdentity(startup.identityScanState, data)
if (startup?.waitForShellReady && startup.outputScanState && !startup.delivered) {
const scanned = scanShellStartupOutput(startup.outputScanState, data)
data = scanned.output
if (scanned.shellPid) {
startup.shellPid = scanned.shellPid
startup.identityScanState = null
}
}
if (startup?.waitForShellReady && startup.scanState && !startup.delivered) {
const scanned = scanForShellReady(startup.scanState, data)
data = scanned.output
if (scanned.matched) {
if (scanned.ready) {
if (startup.providerDelivery) {
this.scheduleStartupCommandResolution(managed, STARTUP_COMMAND_WRITE_DELAY_MS)
} else {
@@ -1644,13 +1626,9 @@ export class PtyHandler {
providerDelivery: shouldProviderDeliverCommand,
delivered: false,
waitForShellReady: shellLaunch.env.ORCA_SHELL_READY_MARKER === '1',
scanState:
outputScanState:
shellLaunch.env.ORCA_SHELL_READY_MARKER === '1'
? createShellReadyScanState()
: null,
identityScanState:
shellLaunch.env.ORCA_SHELL_READY_MARKER === '1'
? createShellStartupIdentityScanState()
? createShellStartupOutputScanState()
: null,
shellPid: null,
promptProbe: null,