Fix Windows agent launch readiness matching (#2404)

This commit is contained in:
Jinwoo Hong 2026-05-20 02:20:58 -04:00 committed by GitHub
parent db4199e91a
commit 23871f40e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 75 additions and 11 deletions

View File

@ -0,0 +1,43 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { waitForAgentReady } from './agent-ready-wait'
import { useAppStore } from '@/store'
import { inspectRuntimeTerminalProcess } from '@/runtime/runtime-terminal-inspection'
vi.mock('@/store', () => ({
useAppStore: {
getState: vi.fn()
}
}))
vi.mock('@/runtime/runtime-terminal-inspection', () => ({
inspectRuntimeTerminalProcess: vi.fn()
}))
vi.mock('@/lib/tui-agent-startup', () => ({
isShellProcess: vi.fn(() => false)
}))
describe('waitForAgentReady', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.stubGlobal('window', { setTimeout })
vi.mocked(useAppStore.getState).mockReturnValue({
ptyIdsByTabId: { 'tab-1': ['pty-1'] },
runtimePaneTitlesByTabId: {},
tabsByWorktree: {},
settings: {}
} as never)
})
it('recognizes a Windows foreground process reported as a full executable path', async () => {
vi.mocked(inspectRuntimeTerminalProcess).mockResolvedValue({
foregroundProcess: String.raw`C:\Users\dev\AppData\Roaming\npm\claude.exe`,
hasChildProcesses: false
})
await expect(waitForAgentReady('tab-1', 'claude', { timeoutMs: 1 })).resolves.toEqual({
ready: true,
reason: 'foreground-match'
})
})
})

View File

@ -1,4 +1,5 @@
import { detectAgentStatusFromTitle } from '../../../shared/agent-detection'
import { isExpectedAgentProcess } from '../../../shared/agent-process-recognition'
import { isShellProcess } from '@/lib/tui-agent-startup'
import { useAppStore } from '@/store'
import { inspectRuntimeTerminalProcess } from '@/runtime/runtime-terminal-inspection'
@ -92,11 +93,7 @@ export async function waitForAgentReady(
try {
const process = await inspectRuntimeTerminalProcess(useAppStore.getState().settings, ptyId)
const foreground = process.foregroundProcess?.toLowerCase() ?? ''
if (
foreground === expectedProcess ||
foreground.startsWith(`${expectedProcess}.`) ||
foreground.endsWith(`/${expectedProcess}`)
) {
if (isExpectedAgentProcess(foreground, expectedProcess)) {
return { ready: true, reason: 'foreground-match' }
}

View File

@ -8,6 +8,7 @@ import type { AgentStartupPlan } from '@/lib/tui-agent-startup'
import { isShellProcess } from '@/lib/tui-agent-startup'
import type { OrcaHooks, TaskViewPresetId } from '../../../shared/types'
import { normalizeHookCommandSourcePolicy } from '../../../shared/hook-command-source-policy'
import { isExpectedAgentProcess } from '../../../shared/agent-process-recognition'
/**
* Why: the TaskPage's preset buttons and the openTaskPage prefetcher both need
@ -294,11 +295,7 @@ async function waitForAgentForeground(ptyId: string, expectedProcess: string): P
try {
const process = await inspectRuntimeTerminalProcess(useAppStore.getState().settings, ptyId)
const foreground = process.foregroundProcess?.toLowerCase() ?? ''
const owns =
foreground === expectedProcess ||
foreground.startsWith(`${expectedProcess}.`) ||
foreground.endsWith(`/${expectedProcess}`)
if (owns) {
if (isExpectedAgentProcess(foreground, expectedProcess)) {
return
}
if (attempt >= 4 && !isShellProcess(foreground)) {

View File

@ -1,5 +1,9 @@
import { describe, expect, it } from 'vitest'
import { isRecognizedAgentType, recognizeAgentProcess } from './agent-process-recognition'
import {
isExpectedAgentProcess,
isRecognizedAgentType,
recognizeAgentProcess
} from './agent-process-recognition'
describe('agent process recognition', () => {
it('recognizes packaged Codex foreground process names', () => {
@ -9,4 +13,12 @@ describe('agent process recognition', () => {
})
expect(isRecognizedAgentType('codex-aarch64-ap')).toBe(true)
})
it('matches expected agents from platform-specific foreground process paths', () => {
expect(
isExpectedAgentProcess(String.raw`C:\Users\dev\AppData\Roaming\npm\claude.exe`, 'claude')
).toBe(true)
expect(isExpectedAgentProcess('/usr/local/bin/claude', 'claude')).toBe(true)
expect(isExpectedAgentProcess('powershell.exe', 'claude')).toBe(false)
})
})

View File

@ -55,6 +55,21 @@ function agentForNormalizedProcess(normalized: string): TuiAgent | undefined {
return undefined
}
export function isExpectedAgentProcess(
processName: string | null | undefined,
expectedProcess: string
): boolean {
const normalizedProcess = normalizeProcessName(processName)
const normalizedExpected = normalizeProcessName(expectedProcess)
if (!normalizedProcess || !normalizedExpected) {
return false
}
return (
normalizedProcess === normalizedExpected ||
normalizedProcess.startsWith(`${normalizedExpected}.`)
)
}
export function recognizeAgentProcess(
processName: string | null | undefined
): RecognizedAgentProcess | null {