mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 00:04:01 +00:00
fix(terminal): preserve unverifiable exit evidence
This commit is contained in:
@@ -196,12 +196,41 @@ describe('LocalPtyProvider', () => {
|
||||
'powershell.exe',
|
||||
expect.any(Object)
|
||||
)
|
||||
await expect(provider.inspectProcess(id)).resolves.toEqual({
|
||||
foregroundProcess: 'powershell.exe',
|
||||
hasChildProcesses: false
|
||||
})
|
||||
})
|
||||
|
||||
it('returns null for unknown PTY ids', async () => {
|
||||
expect(await provider.getForegroundProcess('nonexistent')).toBeNull()
|
||||
})
|
||||
|
||||
it('marks a degraded local scan unavailable for completion-sensitive inspection', async () => {
|
||||
resolveAgentForegroundProcessMock.mockResolvedValue({
|
||||
available: false,
|
||||
processName: 'zsh'
|
||||
})
|
||||
const { id } = await provider.spawn({ cols: 80, rows: 24 })
|
||||
|
||||
await expect(provider.inspectProcess(id)).resolves.toEqual({
|
||||
foregroundProcess: 'zsh',
|
||||
hasChildProcesses: false,
|
||||
unavailable: true
|
||||
})
|
||||
})
|
||||
|
||||
it('reports live child evidence in completion-sensitive inspection', async () => {
|
||||
const { id } = await provider.spawn({ cols: 80, rows: 24 })
|
||||
mockProc.process = 'node'
|
||||
resolveAgentForegroundProcessMock.mockResolvedValue({ available: true, processName: null })
|
||||
|
||||
await expect(provider.inspectProcess(id)).resolves.toEqual({
|
||||
foregroundProcess: null,
|
||||
hasChildProcesses: true
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps a recognized agent across an unavailable scan without adding probes', async () => {
|
||||
resolveAgentForegroundProcessMock
|
||||
.mockResolvedValueOnce({ available: true, processName: 'claude' })
|
||||
|
||||
@@ -99,6 +99,7 @@ import {
|
||||
expandWindowsPathEnvironmentVariables
|
||||
} from '../../shared/windows-environment-expansion'
|
||||
import { resolveProcessExitCause, type TerminalExitCause } from '../../shared/terminal-exit-cause'
|
||||
import type { PtyProcessInspection } from './pty-process-inspection'
|
||||
|
||||
const PANE_IDENTITY_ENV_KEYS = [
|
||||
'ORCA_PANE_KEY',
|
||||
@@ -1419,11 +1420,13 @@ export class LocalPtyProvider implements IPtyProvider {
|
||||
}
|
||||
}
|
||||
|
||||
async getForegroundProcess(id: string): Promise<string | null> {
|
||||
private async inspectForegroundProcessResolution(
|
||||
id: string
|
||||
): Promise<{ processName: string | null; available: boolean }> {
|
||||
const proc = ptyProcesses.get(id)
|
||||
if (!proc) {
|
||||
ptyLastRecognizedForeground.delete(id)
|
||||
return null
|
||||
return { processName: null, available: false }
|
||||
}
|
||||
const fallbackProcess = resolveForegroundFallbackProcess(
|
||||
proc.process || null,
|
||||
@@ -1444,7 +1447,7 @@ export class LocalPtyProvider implements IPtyProvider {
|
||||
try {
|
||||
const paneProcessIds = readWindowsPtyJobProcessIds(proc)
|
||||
if (ptyProcesses.get(id) !== proc) {
|
||||
return null
|
||||
return { processName: null, available: false }
|
||||
}
|
||||
const verdict = judgeCachedAgentJobEvidence({
|
||||
jobProcessIds: paneProcessIds,
|
||||
@@ -1454,7 +1457,7 @@ export class LocalPtyProvider implements IPtyProvider {
|
||||
identityAgeMs: Date.now() - (cachedEntry?.at ?? 0)
|
||||
})
|
||||
if (verdict === 'confirmed' || verdict === 'unproven') {
|
||||
return cachedAgent
|
||||
return { processName: cachedAgent, available: verdict === 'confirmed' }
|
||||
}
|
||||
if (verdict === 'exited') {
|
||||
// The shell stands alone in a complete, inescapable job list: no
|
||||
@@ -1486,7 +1489,7 @@ export class LocalPtyProvider implements IPtyProvider {
|
||||
)
|
||||
// Why: the scan can outlive PTY teardown/id reuse; stale results must not resurrect cache for a foreign id.
|
||||
if (ptyProcesses.get(id) !== proc) {
|
||||
return null
|
||||
return { processName: null, available: false }
|
||||
}
|
||||
// Why: a degraded scan reporting shell-as-foreground fires a false "agent done"; keep last recognized agent instead.
|
||||
const lastRecognizedAgent = ptyLastRecognizedForeground.get(id)?.name ?? null
|
||||
@@ -1525,13 +1528,47 @@ export class LocalPtyProvider implements IPtyProvider {
|
||||
} else if (!stable.lastRecognizedAgent) {
|
||||
ptyLastRecognizedForeground.delete(id)
|
||||
}
|
||||
return stable.processName
|
||||
return { processName: stable.processName, available: stableResolution.available }
|
||||
} catch {
|
||||
if (ptyProcesses.get(id) !== proc) {
|
||||
return null
|
||||
return { processName: null, available: false }
|
||||
}
|
||||
// Why: an inspection error is a degraded read; fall back to last recognized agent (null reads as an exit).
|
||||
return ptyLastRecognizedForeground.get(id)?.name ?? null
|
||||
return {
|
||||
processName: ptyLastRecognizedForeground.get(id)?.name ?? null,
|
||||
available: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getForegroundProcess(id: string): Promise<string | null> {
|
||||
return (await this.inspectForegroundProcessResolution(id)).processName
|
||||
}
|
||||
|
||||
async inspectProcess(id: string): Promise<PtyProcessInspection> {
|
||||
const proc = ptyProcesses.get(id)
|
||||
if (!proc) {
|
||||
return { foregroundProcess: null, hasChildProcesses: false, unavailable: true }
|
||||
}
|
||||
const foreground = await this.inspectForegroundProcessResolution(id)
|
||||
if (ptyProcesses.get(id) !== proc) {
|
||||
return { foregroundProcess: null, hasChildProcesses: false, unavailable: true }
|
||||
}
|
||||
let hasChildProcesses: boolean
|
||||
try {
|
||||
const shell = ptyShellName.get(id)
|
||||
const currentProcess = resolveForegroundFallbackProcess(proc.process || null, shell)
|
||||
hasChildProcesses = !shell || (currentProcess !== null && currentProcess !== shell)
|
||||
} catch {
|
||||
return {
|
||||
foregroundProcess: foreground.processName,
|
||||
hasChildProcesses: false,
|
||||
unavailable: true
|
||||
}
|
||||
}
|
||||
return {
|
||||
foregroundProcess: foreground.processName,
|
||||
hasChildProcesses,
|
||||
...(foreground.available ? {} : { unavailable: true as const })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,6 +114,11 @@ import {
|
||||
} from '../memory/pty-registry'
|
||||
import { inspectPtyProviderProcess } from '../providers/pty-process-inspection'
|
||||
import type { IPtyProvider } from '../providers/types'
|
||||
import {
|
||||
getForegroundProcessFromRuntimeController,
|
||||
inspectProcessFromRuntimeController
|
||||
} from '../ipc/pty/runtime/operations'
|
||||
import { getLocalPtyProvider, setLocalPtyProvider } from '../ipc/pty/provider/registry'
|
||||
import * as worktreePathComparison from '../ipc/worktree-path-comparison'
|
||||
import * as localWorktreeFilesystem from '../local-worktree-filesystem'
|
||||
import {
|
||||
@@ -984,6 +989,15 @@ function syncSinglePty(
|
||||
})
|
||||
}
|
||||
|
||||
function inspectForegroundProcessWith(
|
||||
readForegroundProcess: (ptyId: string) => string | null | Promise<string | null>
|
||||
): (ptyId: string) => Promise<{ foregroundProcess: string | null; hasChildProcesses: boolean }> {
|
||||
return async (ptyId) => ({
|
||||
foregroundProcess: await readForegroundProcess(ptyId),
|
||||
hasChildProcesses: false
|
||||
})
|
||||
}
|
||||
|
||||
function makeDeferred(): { promise: Promise<void>; resolve: () => void } {
|
||||
let resolve!: () => void
|
||||
const promise = new Promise<void>((next) => {
|
||||
@@ -10365,11 +10379,12 @@ describe('OrcaRuntimeService', () => {
|
||||
runtime.ingestSyntheticTitleFrame('pty-1', '\x1b]0;Codex ready\x07')
|
||||
batches.length = 0
|
||||
|
||||
const getForegroundProcess = vi.fn().mockResolvedValueOnce('codex')
|
||||
const getForegroundProcess = vi.fn().mockResolvedValueOnce('codex').mockResolvedValue('zsh')
|
||||
runtime.setPtyController({
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
getForegroundProcess
|
||||
getForegroundProcess,
|
||||
inspectProcess: inspectForegroundProcessWith(getForegroundProcess)
|
||||
})
|
||||
runtime.onPtyData('pty-1', '\x1b]0;bichir\x07', 101)
|
||||
|
||||
@@ -10400,7 +10415,7 @@ describe('OrcaRuntimeService', () => {
|
||||
incarnationId: 'inc-agent-exit'
|
||||
})
|
||||
)
|
||||
expect(getForegroundProcess).toHaveBeenCalledTimes(2)
|
||||
expect(getForegroundProcess).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('settles a pending title exit when command completion proves the agent returned to shell', async () => {
|
||||
@@ -10416,7 +10431,8 @@ describe('OrcaRuntimeService', () => {
|
||||
runtime.setPtyController({
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
getForegroundProcess
|
||||
getForegroundProcess,
|
||||
inspectProcess: inspectForegroundProcessWith(getForegroundProcess)
|
||||
})
|
||||
runtime.onPtyData('pty-1', '\x1b]0;bichir\x07', 101)
|
||||
|
||||
@@ -10543,7 +10559,8 @@ describe('OrcaRuntimeService', () => {
|
||||
runtime.setPtyController({
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
getForegroundProcess
|
||||
getForegroundProcess,
|
||||
inspectProcess: inspectForegroundProcessWith(getForegroundProcess)
|
||||
})
|
||||
|
||||
runtime.ingestSyntheticTitleFrame('pty-1', '\x1b]0;Codex ready\x07')
|
||||
@@ -10568,7 +10585,8 @@ describe('OrcaRuntimeService', () => {
|
||||
runtime.setPtyController({
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
getForegroundProcess
|
||||
getForegroundProcess,
|
||||
inspectProcess: inspectForegroundProcessWith(getForegroundProcess)
|
||||
})
|
||||
|
||||
runtime.onPtyData('pty-1', '\x1b]0;bichir\x07', 100)
|
||||
@@ -10579,6 +10597,108 @@ describe('OrcaRuntimeService', () => {
|
||||
expect(getForegroundProcess).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('does not certify an adapter-swallowed foreground query failure as an agent exit', async () => {
|
||||
const { runtime, batches } = createSideEffectRuntime()
|
||||
syncSinglePty(runtime)
|
||||
runtime.onPtySpawned('pty-1', 'inc-unverifiable-exit', { awaitsRegistration: false })
|
||||
runtime.ingestSyntheticTitleFrame('pty-1', '\x1b]0;Codex ready\x07')
|
||||
batches.length = 0
|
||||
|
||||
const originalProvider = getLocalPtyProvider()
|
||||
const providerQuery = vi.fn(async () => {
|
||||
throw new Error('socket_closed')
|
||||
})
|
||||
setLocalPtyProvider({ getForegroundProcess: providerQuery } as unknown as IPtyProvider)
|
||||
onTestFinished(() => setLocalPtyProvider(originalProvider))
|
||||
runtime.setPtyController({
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
// Production's legacy read swallows this failure while strong inspection preserves it.
|
||||
getForegroundProcess: getForegroundProcessFromRuntimeController,
|
||||
inspectProcess: inspectProcessFromRuntimeController
|
||||
})
|
||||
|
||||
runtime.onPtyData('pty-1', '\x1b]0;bichir\x07', 100)
|
||||
|
||||
await vi.waitFor(() => expect(providerQuery).toHaveBeenCalled())
|
||||
await vi.waitFor(() =>
|
||||
expect(
|
||||
(
|
||||
runtime as unknown as {
|
||||
ptyForegroundProcessReads: Map<string, unknown>
|
||||
}
|
||||
).ptyForegroundProcessReads.size
|
||||
).toBe(0)
|
||||
)
|
||||
expect(batches.flatMap((batch) => batch.facts)).toEqual([
|
||||
{ kind: 'title', normalizedTitle: 'bichir', rawTitle: 'bichir' },
|
||||
{ kind: 'agent-exited' }
|
||||
])
|
||||
})
|
||||
|
||||
it('still certifies an agent exit when process inspection confirms no foreground agent', async () => {
|
||||
const { runtime, batches } = createSideEffectRuntime()
|
||||
syncSinglePty(runtime)
|
||||
runtime.onPtySpawned('pty-1', 'inc-confirmed-exit', { awaitsRegistration: false })
|
||||
runtime.ingestSyntheticTitleFrame('pty-1', '\x1b]0;Codex ready\x07')
|
||||
batches.length = 0
|
||||
|
||||
const originalProvider = getLocalPtyProvider()
|
||||
const providerQuery = vi.fn(async () => null)
|
||||
setLocalPtyProvider({
|
||||
getForegroundProcess: providerQuery,
|
||||
hasChildProcesses: async () => false
|
||||
} as unknown as IPtyProvider)
|
||||
onTestFinished(() => setLocalPtyProvider(originalProvider))
|
||||
runtime.setPtyController({
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
getForegroundProcess: getForegroundProcessFromRuntimeController,
|
||||
inspectProcess: inspectProcessFromRuntimeController
|
||||
})
|
||||
|
||||
runtime.onPtyData('pty-1', '\x1b]0;bichir\x07', 100)
|
||||
|
||||
await vi.waitFor(() =>
|
||||
expect(batches.flatMap((batch) => batch.facts)).toContainEqual({
|
||||
kind: 'agent-exited',
|
||||
executionHostConfirmed: true,
|
||||
incarnationId: 'inc-confirmed-exit'
|
||||
})
|
||||
)
|
||||
expect(providerQuery).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('does not certify an agent exit while process inspection still sees children', async () => {
|
||||
const { runtime, batches } = createSideEffectRuntime()
|
||||
syncSinglePty(runtime)
|
||||
runtime.onPtySpawned('pty-1', 'inc-live-child', { awaitsRegistration: false })
|
||||
runtime.ingestSyntheticTitleFrame('pty-1', '\x1b]0;Codex ready\x07')
|
||||
batches.length = 0
|
||||
runtime.setPtyController({
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
getForegroundProcess: async () => null,
|
||||
inspectProcess: async () => ({ foregroundProcess: null, hasChildProcesses: true })
|
||||
})
|
||||
|
||||
runtime.onPtyData('pty-1', '\x1b]0;bichir\x07', 100)
|
||||
|
||||
await vi.waitFor(() =>
|
||||
expect(
|
||||
(
|
||||
runtime as unknown as {
|
||||
ptyForegroundProcessReads: Map<string, unknown>
|
||||
}
|
||||
).ptyForegroundProcessReads.size
|
||||
).toBe(0)
|
||||
)
|
||||
expect(batches.flatMap((batch) => batch.facts)).toEqual([
|
||||
{ kind: 'title', normalizedTitle: 'bichir', rawTitle: 'bichir' },
|
||||
{ kind: 'agent-exited' }
|
||||
])
|
||||
})
|
||||
|
||||
it('does not confirm an agent exit when the SSH host is unverifiable', async () => {
|
||||
const { runtime, batches } = createSideEffectRuntime()
|
||||
runtime.registerPty('pty-1', TEST_WORKTREE_ID, 'conn-1')
|
||||
@@ -10590,7 +10710,8 @@ describe('OrcaRuntimeService', () => {
|
||||
runtime.setPtyController({
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
getForegroundProcess
|
||||
getForegroundProcess,
|
||||
inspectProcess: inspectForegroundProcessWith(getForegroundProcess)
|
||||
})
|
||||
|
||||
runtime.onPtyData('pty-1', '\x1b]0;bichir\x07', 100)
|
||||
@@ -10613,7 +10734,8 @@ describe('OrcaRuntimeService', () => {
|
||||
runtime.setPtyController({
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
getForegroundProcess
|
||||
getForegroundProcess,
|
||||
inspectProcess: inspectForegroundProcessWith(getForegroundProcess)
|
||||
})
|
||||
|
||||
runtime.onPtyData('pty-1', '\x1b]0;bichir\x07', 100)
|
||||
@@ -10639,7 +10761,8 @@ describe('OrcaRuntimeService', () => {
|
||||
runtime.setPtyController({
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
getForegroundProcess
|
||||
getForegroundProcess,
|
||||
inspectProcess: inspectForegroundProcessWith(getForegroundProcess)
|
||||
})
|
||||
|
||||
runtime.onPtyData('pty-1', '\x1b]0;bichir\x07', 100)
|
||||
@@ -36907,11 +37030,13 @@ describe('OrcaRuntimeService', () => {
|
||||
const runtime = new OrcaRuntimeService(store)
|
||||
const db = new InMemoryOrchestrationMessages()
|
||||
const write = vi.fn().mockReturnValue(true)
|
||||
const getForegroundProcess = vi.fn(async () => 'codex')
|
||||
setInMemoryOrchestrationMessages(runtime, db)
|
||||
runtime.setPtyController({
|
||||
write,
|
||||
kill: vi.fn(),
|
||||
getForegroundProcess: async () => 'codex'
|
||||
getForegroundProcess,
|
||||
inspectProcess: inspectForegroundProcessWith(getForegroundProcess)
|
||||
})
|
||||
syncSinglePty(runtime)
|
||||
|
||||
|
||||
@@ -1703,12 +1703,14 @@ type PtyForegroundAgentRefresh = {
|
||||
type PtyForegroundProcessRead = {
|
||||
controller: RuntimePtyController
|
||||
process: string | null
|
||||
hasChildProcesses: boolean | null
|
||||
available: boolean
|
||||
}
|
||||
|
||||
type PtyForegroundProcessReadEntry = {
|
||||
controller: RuntimePtyController
|
||||
startedAfterTitleObservation: number
|
||||
verifiedAbsence: boolean
|
||||
promise: Promise<PtyForegroundProcessRead>
|
||||
}
|
||||
|
||||
@@ -19634,7 +19636,8 @@ export class OrcaRuntimeService {
|
||||
|
||||
private readPtyForegroundProcessFromController(
|
||||
ptyId: string,
|
||||
afterTitleObservation = 0
|
||||
afterTitleObservation = 0,
|
||||
verifiedAbsence = false
|
||||
): Promise<PtyForegroundProcessRead> | null {
|
||||
const controller = this.ptyController
|
||||
if (!controller) {
|
||||
@@ -19643,32 +19646,61 @@ export class OrcaRuntimeService {
|
||||
const pending = this.ptyForegroundProcessReads.get(ptyId)
|
||||
if (
|
||||
pending?.controller === controller &&
|
||||
pending.startedAfterTitleObservation >= afterTitleObservation
|
||||
pending.startedAfterTitleObservation >= afterTitleObservation &&
|
||||
(!verifiedAbsence || pending.verifiedAbsence)
|
||||
) {
|
||||
return pending.promise
|
||||
}
|
||||
if (pending?.controller === controller) {
|
||||
return pending.promise.then(
|
||||
() =>
|
||||
this.readPtyForegroundProcessFromController(ptyId, afterTitleObservation) ?? {
|
||||
controller,
|
||||
process: null,
|
||||
available: false
|
||||
}
|
||||
)
|
||||
return pending.promise.then((result) => {
|
||||
// A recognized agent disproves exit; every absence claim needs strong inspection.
|
||||
if (
|
||||
(!verifiedAbsence ||
|
||||
pending.verifiedAbsence ||
|
||||
recognizeAgentProcess(result.process) !== null) &&
|
||||
pending.startedAfterTitleObservation >= afterTitleObservation
|
||||
) {
|
||||
return result
|
||||
}
|
||||
return (
|
||||
this.readPtyForegroundProcessFromController(
|
||||
ptyId,
|
||||
afterTitleObservation,
|
||||
verifiedAbsence
|
||||
) ?? { controller, process: null, hasChildProcesses: null, available: false }
|
||||
)
|
||||
})
|
||||
}
|
||||
const unavailable: PtyForegroundProcessRead = {
|
||||
controller,
|
||||
process: null,
|
||||
hasChildProcesses: null,
|
||||
available: false
|
||||
}
|
||||
let processRead: Promise<string | null>
|
||||
let processRead: Promise<{
|
||||
process: string | null
|
||||
hasChildProcesses: boolean | null
|
||||
available: boolean
|
||||
}>
|
||||
try {
|
||||
processRead = Promise.resolve(controller.getForegroundProcess(ptyId))
|
||||
processRead = verifiedAbsence
|
||||
? controller.inspectProcess
|
||||
? Promise.resolve(controller.inspectProcess(ptyId)).then((inspection) => ({
|
||||
process: inspection.foregroundProcess,
|
||||
hasChildProcesses: inspection.hasChildProcesses,
|
||||
available: inspection.unavailable !== true
|
||||
}))
|
||||
: Promise.resolve({ process: null, hasChildProcesses: null, available: false })
|
||||
: Promise.resolve(controller.getForegroundProcess(ptyId)).then((process) => ({
|
||||
process,
|
||||
hasChildProcesses: null,
|
||||
available: true
|
||||
}))
|
||||
} catch {
|
||||
const entry: PtyForegroundProcessReadEntry = {
|
||||
controller,
|
||||
startedAfterTitleObservation: afterTitleObservation,
|
||||
verifiedAbsence,
|
||||
promise: Promise.resolve(unavailable)
|
||||
}
|
||||
entry.promise = entry.promise.finally(() => {
|
||||
@@ -19681,7 +19713,7 @@ export class OrcaRuntimeService {
|
||||
}
|
||||
let entry: PtyForegroundProcessReadEntry
|
||||
const promise = processRead
|
||||
.then((process) => ({ controller, process, available: true }))
|
||||
.then((result) => ({ controller, ...result }))
|
||||
.catch(() => unavailable)
|
||||
.finally(() => {
|
||||
if (this.ptyForegroundProcessReads.get(ptyId) === entry) {
|
||||
@@ -19691,6 +19723,7 @@ export class OrcaRuntimeService {
|
||||
entry = {
|
||||
controller,
|
||||
startedAfterTitleObservation: afterTitleObservation,
|
||||
verifiedAbsence,
|
||||
promise
|
||||
}
|
||||
this.ptyForegroundProcessReads.set(ptyId, entry)
|
||||
@@ -19797,7 +19830,11 @@ export class OrcaRuntimeService {
|
||||
: { kind: 'agent-exited' }
|
||||
)
|
||||
}
|
||||
const foregroundRead = this.readPtyForegroundProcessFromController(ptyId, titleObservedAt ?? 0)
|
||||
const foregroundRead = this.readPtyForegroundProcessFromController(
|
||||
ptyId,
|
||||
titleObservedAt ?? 0,
|
||||
true
|
||||
)
|
||||
if (!pty?.connected || !foregroundRead) {
|
||||
// Why: a local PTY that is already gone after a shell-title exit is
|
||||
// `exited`. A remote PTY that we cannot observe is `unverifiable`.
|
||||
@@ -19831,7 +19868,6 @@ export class OrcaRuntimeService {
|
||||
}
|
||||
if (
|
||||
result.controller === this.ptyController &&
|
||||
result.available &&
|
||||
recognizeAgentProcess(result.process) !== null
|
||||
) {
|
||||
const restoredStatus = this.ptyTitleTrackersByPtyId
|
||||
@@ -19852,10 +19888,10 @@ export class OrcaRuntimeService {
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!result.available && current.connectionId) {
|
||||
if ((!result.available || result.hasChildProcesses !== false) && current.connectionId) {
|
||||
return
|
||||
}
|
||||
recordExit(result.available)
|
||||
recordExit(result.available && result.hasChildProcesses === false)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user