Files
orca/src/relay/preflight-handler.test.ts
T
c3bf22b9a8 [P2] perf(windows): stop the capability poll respawning blocking wsl.exe probes (#11698)
* perf(windows): stop the capability poll respawning blocking wsl.exe probes

#11295 added a 30s renderer interval to `useWindowsTerminalCapabilities` whose
early-return only fires when WSL is available with at least one distro, so on the
common Windows host (no WSL) it re-ran a full capability read forever. Each read
IPCs four probes whose main-process handlers were synchronous `execFileSync` calls
to wsl.exe/pwsh.exe, blocking the Electron main event loop for up to 5s a time.

The un-latching intent is kept: a host that answers "no WSL" is still re-checked,
now on an exponential backoff (30s, +60s, +120s) that parks once the answer stops
moving, re-arms on window focus, is shared by all consumers of an owner key, and
stops entirely when the last consumer unmounts. The wsl/pwsh IPC handlers now use
async twins that share the existing caches and back off identically.

* fix(windows): classify async wsl/pwsh probe failures with the execFile error shape

The async twins feed `execFile` callback errors into classifiers written for
`execFileSync`: a non-zero exit lands on `error.code` as a number rather than
`error.status`, and a timeout is a SIGTERM kill rather than ETIMEDOUT.

So a Windows host without WSL (wsl.exe ships in System32, so it exits non-zero
instead of ENOENT) was cached as retryable, shrinking the shared window from
10min to 45s and making the still-sync callers re-pay their blocking spawn ~13x
more often; and a pwsh cold start past 5s cached "pwsh missing" for 30s,
demoting the user's PowerShell 7 preference — the exact case the ETIMEDOUT
branch exists to prevent.

Also drops a literal NUL byte from the new re-probe module's signature
separator, which made the file binary to git, and seeds `lastProbeAt` at
registration so focus churn right after mount cannot defer the first re-probe
indefinitely.

Co-authored-by: Orca <help@stably.ai>

* perf(windows): route relay host-capability probes through the async wsl/pwsh twins

A paired web/mobile client resolves `useWindowsTerminalCapabilities` to a local
target (TabBar's `isWebClient` gate, and `useSettingsNavigationMetadata` forces
`{kind:'local'}`), so the new re-probe arms there too. But `window.api.wsl/pwsh`
on a web client is not the ipc/app.ts channel — it is `host.wsl.*`/`host.pwsh.*`
over the runtime RPC, which still ran the sync probes and blocked the desktop
main event loop on `execFileSync('wsl.exe' | 'pwsh.exe')` for up to 5s per call.

Switch those handlers and the relay preflight capability probe to the async
twins added here; they share the same caches, dedupe and backoff, so remote
callers see no behavior change.

* fix(windows): harden async capability reprobes

* fix(windows): dedupe PowerShell shell probes

---------

Co-authored-by: Orca <help@stably.ai>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-07 21:59:24 -07:00

360 lines
11 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
import { buildPosixCommandPathLookupScript } from '../shared/posix-command-path-lookup'
const { execFileAsyncMock } = vi.hoisted(() => ({
execFileAsyncMock: vi.fn()
}))
const {
isPwshAvailableAsyncMock,
isWslAvailableAsyncMock,
listWslDistrosAsyncMock,
isGitBashAvailableMock
} = vi.hoisted(() => ({
isPwshAvailableAsyncMock: vi.fn(),
isWslAvailableAsyncMock: vi.fn(),
listWslDistrosAsyncMock: vi.fn(),
isGitBashAvailableMock: vi.fn()
}))
vi.mock('child_process', () => {
const execFileWithPromisify = Object.assign(vi.fn(), {
[Symbol.for('nodejs.util.promisify.custom')]: execFileAsyncMock
})
return { execFile: execFileWithPromisify }
})
vi.mock('../main/pwsh', () => ({ isPwshAvailableAsync: isPwshAvailableAsyncMock }))
vi.mock('../main/wsl', () => ({
isWslAvailableAsync: isWslAvailableAsyncMock,
listWslDistrosAsync: listWslDistrosAsyncMock
}))
vi.mock('../main/git-bash', () => ({ isGitBashAvailable: isGitBashAvailableMock }))
import {
buildCommandLookupSpec,
buildCommandLookupSpecs,
hasAbsoluteCommandPath,
isCommandOnPathForRelay,
PreflightHandler
} from './preflight-handler'
function lookupArgs(command: string, mode: '-lc' | '-ilc' = '-lc'): string[] {
return [
mode,
[
buildPosixCommandPathLookupScript({ kind: 'literal', value: command }),
'if [ -n "$resolved" ]; then',
'printf \'__ORCA_AGENT_PATH__%s\\n\' "$resolved"',
'fi'
].join('\n')
]
}
function fishLookupArgs(command: string): string[] {
return [
'-ilc',
[
`set -l resolved (command -v ${command} 2>/dev/null)`,
'if test -n "$resolved"',
'printf \'__ORCA_AGENT_PATH__%s\\n\' "$resolved"',
'end'
].join('\n')
]
}
beforeEach(() => {
execFileAsyncMock.mockReset()
isPwshAvailableAsyncMock.mockReset()
isWslAvailableAsyncMock.mockReset()
listWslDistrosAsyncMock.mockReset()
isGitBashAvailableMock.mockReset()
})
describe('buildCommandLookupSpec', () => {
it('uses where.exe on native Windows SSH hosts', () => {
expect(buildCommandLookupSpec('codex', 'win32')).toEqual({
file: 'where.exe',
args: ['codex'],
windowsHide: true
})
})
it('falls back to sh for POSIX probes without a configured shell', () => {
expect(buildCommandLookupSpec('codex', 'linux', {}, null)).toEqual({
file: '/bin/sh',
args: lookupArgs('codex')
})
})
it('uses the configured remote shell for POSIX probes', () => {
expect(buildCommandLookupSpec('codex', 'linux', { SHELL: '/bin/zsh' }, '/bin/zsh')).toEqual({
file: '/bin/zsh',
args: lookupArgs('codex', '-ilc')
})
})
it('quotes command names in shell probes', () => {
expect(
buildCommandLookupSpec("agent'cli", 'linux', { SHELL: '/bin/bash' }, '/bin/bash')
).toEqual({
file: '/bin/bash',
args: lookupArgs("agent'cli", '-ilc')
})
})
})
describe('buildCommandLookupSpecs', () => {
it('falls back to inherited PATH after a trusted configured POSIX shell', () => {
expect(buildCommandLookupSpecs('codex', 'linux', { SHELL: '/bin/zsh' }, '/bin/zsh')).toEqual([
{ file: '/bin/zsh', args: lookupArgs('codex', '-ilc') },
{ file: '/bin/sh', args: lookupArgs('codex') }
])
})
it('allows a custom shell path only when the account login shell matches', () => {
expect(
buildCommandLookupSpecs(
'codex',
'darwin',
{ SHELL: '/opt/homebrew/bin/zsh' },
'/opt/homebrew/bin/zsh'
)
).toEqual([
{ file: '/opt/homebrew/bin/zsh', args: lookupArgs('codex', '-ilc') },
{ file: '/bin/sh', args: lookupArgs('codex') }
])
})
it('allows conservative system shell paths when account lookup is unavailable', () => {
expect(buildCommandLookupSpecs('codex', 'linux', { SHELL: '/usr/bin/bash' }, null)[0]).toEqual({
file: '/usr/bin/bash',
args: lookupArgs('codex', '-ilc')
})
})
it('uses fish syntax for trusted fish shells', () => {
expect(buildCommandLookupSpecs('codex', 'linux', { SHELL: '/usr/bin/fish' }, null)[0]).toEqual({
file: '/usr/bin/fish',
args: fishLookupArgs("'codex'")
})
})
it('ignores untrusted temp shell paths even when the basename is supported', () => {
expect(buildCommandLookupSpecs('codex', 'linux', { SHELL: '/tmp/zsh' }, '/bin/bash')).toEqual([
{ file: '/bin/sh', args: lookupArgs('codex') }
])
})
it('ignores untrusted home-bin shell paths even when the basename is supported', () => {
expect(
buildCommandLookupSpecs('codex', 'linux', { SHELL: '/home/test/bin/bash' }, '/bin/bash')
).toEqual([{ file: '/bin/sh', args: lookupArgs('codex') }])
})
})
describe('isCommandOnPathForRelay', () => {
it('falls back to inherited PATH when shell startup returns no absolute command path', async () => {
execFileAsyncMock
.mockResolvedValueOnce({ stdout: 'welcome\ncodex is a function\n' })
.mockResolvedValueOnce({ stdout: '__ORCA_AGENT_PATH__/relay/path/codex\n' })
await expect(
isCommandOnPathForRelay('codex', {
platform: 'linux',
env: { SHELL: '/bin/zsh', PATH: '/usr/bin' },
accountLoginShell: '/bin/zsh'
})
).resolves.toBe(true)
expect(execFileAsyncMock).toHaveBeenNthCalledWith(1, '/bin/zsh', lookupArgs('codex', '-ilc'), {
encoding: 'utf-8',
env: expect.objectContaining({ SHELL: '/bin/zsh' }),
timeout: 5000
})
expect(execFileAsyncMock).toHaveBeenNthCalledWith(2, '/bin/sh', lookupArgs('codex'), {
encoding: 'utf-8',
env: expect.objectContaining({ SHELL: '/bin/zsh' }),
timeout: 5000
})
})
it('falls back to inherited PATH when shell startup fails', async () => {
execFileAsyncMock
.mockRejectedValueOnce(new Error('startup failed'))
.mockResolvedValueOnce({ stdout: '__ORCA_AGENT_PATH__/relay/path/codex\n' })
await expect(
isCommandOnPathForRelay('codex', {
platform: 'linux',
env: { SHELL: '/bin/bash', PATH: '/usr/bin' },
accountLoginShell: '/bin/bash'
})
).resolves.toBe(true)
expect(execFileAsyncMock).toHaveBeenCalledTimes(2)
})
it('does not execute an untrusted configured shell before inherited PATH lookup', async () => {
execFileAsyncMock.mockResolvedValueOnce({ stdout: '__ORCA_AGENT_PATH__/relay/path/codex\n' })
await expect(
isCommandOnPathForRelay('codex', {
platform: 'linux',
env: { SHELL: '/tmp/zsh', PATH: '/usr/bin' },
accountLoginShell: '/bin/bash'
})
).resolves.toBe(true)
expect(execFileAsyncMock).toHaveBeenCalledTimes(1)
expect(execFileAsyncMock).toHaveBeenCalledWith('/bin/sh', lookupArgs('codex'), {
encoding: 'utf-8',
env: expect.objectContaining({ SHELL: '/tmp/zsh' }),
timeout: 5000
})
})
})
describe('hasAbsoluteCommandPath', () => {
it('ignores banners and shell function output', () => {
expect(hasAbsoluteCommandPath('/tmp/not-the-agent\ncodex is a shell function\n', 'linux')).toBe(
false
)
})
it('ignores unmarked POSIX absolute paths from shell startup output', () => {
expect(hasAbsoluteCommandPath('/tmp/not-the-agent\n', 'linux')).toBe(false)
})
it('recognizes a sentinel-marked command path amid shell startup and exit output', () => {
expect(
hasAbsoluteCommandPath('welcome\n__ORCA_AGENT_PATH__/opt/bin/codex\nlogout-banner\n', 'linux')
).toBe(true)
})
it('recognizes Windows absolute command paths', () => {
expect(
hasAbsoluteCommandPath('C:\\Users\\alice\\AppData\\Roaming\\npm\\codex.cmd\r\n', 'win32')
).toBe(true)
})
})
describe('PreflightHandler', () => {
it('honors required commands when reporting detected agents', async () => {
execFileAsyncMock.mockImplementation(async (_file, args) => {
const script = String(args[1])
if (script.includes("'orca'")) {
return { stdout: '__ORCA_AGENT_PATH__/relay/path/orca\n' }
}
throw new Error('not found')
})
const requestHandlers = new Map<string, (params: Record<string, unknown>) => Promise<unknown>>()
const dispatcher = {
onRequest: vi.fn(
(method: string, handler: (params: Record<string, unknown>) => Promise<unknown>) => {
requestHandlers.set(method, handler)
}
)
}
new PreflightHandler(dispatcher as never)
const handler = requestHandlers.get('preflight.detectAgents')
expect(handler).toBeDefined()
await expect(
handler!({
commands: [
{ id: 'claude-agent-teams', cmd: 'orca', requiredCommands: ['claude'] },
{ id: 'claude', cmd: 'claude' }
]
})
).resolves.toEqual({ agents: [] })
})
it('does not report platform-unsupported agents on native Windows SSH hosts', async () => {
const originalPlatform = process.platform
Object.defineProperty(process, 'platform', {
configurable: true,
value: 'win32'
})
execFileAsyncMock.mockImplementation(async (_file, args) => {
if (String(args[0]) === 'claude') {
return { stdout: 'C:\\Users\\test\\AppData\\Roaming\\npm\\claude.cmd\r\n' }
}
if (String(args[0]) === 'orca') {
return { stdout: 'C:\\Program Files\\Orca\\orca.cmd\r\n' }
}
throw new Error('not found')
})
const requestHandlers = new Map<string, (params: Record<string, unknown>) => Promise<unknown>>()
const dispatcher = {
onRequest: vi.fn(
(method: string, handler: (params: Record<string, unknown>) => Promise<unknown>) => {
requestHandlers.set(method, handler)
}
)
}
try {
new PreflightHandler(dispatcher as never)
const handler = requestHandlers.get('preflight.detectAgents')
expect(handler).toBeDefined()
await expect(
handler!({
commands: [
{
id: 'claude-agent-teams',
cmd: 'orca',
requiredCommands: ['claude'],
unsupportedRuntimes: ['win32']
},
{ id: 'claude', cmd: 'claude' }
]
})
).resolves.toEqual({ agents: ['claude'] })
} finally {
Object.defineProperty(process, 'platform', {
configurable: true,
value: originalPlatform
})
}
})
it('reports remote Windows shell capabilities through the SSH preflight path', async () => {
const originalPlatform = process.platform
Object.defineProperty(process, 'platform', {
configurable: true,
value: 'win32'
})
isWslAvailableAsyncMock.mockResolvedValue(true)
listWslDistrosAsyncMock.mockResolvedValue(['Ubuntu'])
isPwshAvailableAsyncMock.mockResolvedValue(true)
isGitBashAvailableMock.mockReturnValue(true)
const requestHandlers = new Map<string, (params: Record<string, unknown>) => Promise<unknown>>()
const dispatcher = {
onRequest: vi.fn(
(method: string, handler: (params: Record<string, unknown>) => Promise<unknown>) => {
requestHandlers.set(method, handler)
}
)
}
new PreflightHandler(dispatcher as never)
try {
const handler = requestHandlers.get('preflight.detectWindowsTerminalCapabilities')
expect(handler).toBeDefined()
await expect(handler!({})).resolves.toEqual({
wslAvailable: true,
wslDistros: ['Ubuntu'],
pwshAvailable: true,
gitBashAvailable: true,
hostPlatform: 'win32'
})
} finally {
Object.defineProperty(process, 'platform', {
configurable: true,
value: originalPlatform
})
}
})
})