mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
perf: skip WSL discovery when filtering native-only paths (#20266)
* perf: skip WSL discovery when filtering native-only paths * fix: skip the AI Vault running-distro probe on WSL-less hosts - getAiVaultWslHomeDirs, the sibling in the same Promise.all as the native-path filter, still spawned wsl.exe unconditionally on win32; gate it on the cached installed-distro list so a host with no distro performs no probe when only native Codex homes are configured. - Hosts with a distro installed keep probing from that sibling, so the running-distro last-known-good cache is still warmed by the listing and a later probe outage falls back to the observed list, not []. - Add a test against the real wsl module asserting zero wsl.exe spawns across the whole listing Promise.all, plus the warmed-cache fallback. * fix(ai-vault): gate WSL home discovery on the cached distro list, not a probe `listWslDistrosAsync()` resolves `[]` when the `wsl.exe` probe is rejected, so a transient failure made `getAiVaultWslHomeDirs()` conclude "no WSL distros" and skip discovery. That narrowed the allowed-roots set `ai-vault-delete` and `ai-vault-subagent-list` validate against, wrongly rejecting WSL-hosted paths. Gate on `hasCachedWslDistros()` / `getCachedWslDistros()` instead: a pure cache read that only skips discovery once a successful probe has reported zero user distros. It also never probes, so the AI Vault listing cannot be the first to cache `[]` and flip a configured distro to "missing" in runtime resolution. * test(ai-vault): drop the type assertion tripping the casting gate check-changed-code-quality runs config/oxlint-code-quality-casting.json with assertionStyle:'never' over changed lines, and `args as string[]` in the new wsl-probe spy failed it. Narrow through Array.isArray instead, which is also honest about execFile's argv being optional. cached-session-list-wsl-probe + cached-session-list: 9/9 pass; tc:node clean; changed-code quality gate passes. --------- Co-authored-by: Orca Worker <orca-worker@localhost> Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
co-authored by
Orca Worker
Neil
parent
81c3d188a4
commit
392583caba
@@ -0,0 +1,110 @@
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as childProcess from 'node:child_process'
|
||||
|
||||
const { execFileMock, scanAiVaultSessionsInWorker } = vi.hoisted(() => ({
|
||||
execFileMock: vi.fn(),
|
||||
scanAiVaultSessionsInWorker: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('child_process', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof childProcess>()),
|
||||
execFile: execFileMock
|
||||
}))
|
||||
vi.mock('./session-scanner-worker-spawn', () => ({
|
||||
scanAiVaultSessionsInWorker,
|
||||
resetAiVaultScannerWorkerForTests: vi.fn()
|
||||
}))
|
||||
|
||||
import { _resetWslCachesForTests, _setWslCachesForTests, listWslDistrosAsync } from '../wsl'
|
||||
import { filterPathsToRunningWslDistrosAsync } from '../wsl-running-path-filter'
|
||||
import {
|
||||
configureAiVaultSessionSources,
|
||||
getAiVaultWslHomeDirs,
|
||||
listAiVaultSessions,
|
||||
resetAiVaultSessionListCacheForTests
|
||||
} from './cached-session-list'
|
||||
|
||||
const NATIVE_CODEX_HOME = 'C:\\Users\\ada\\.codex'
|
||||
const WSL_HOME = '\\\\wsl.localhost\\Ubuntu\\home\\ada'
|
||||
|
||||
function wslSpawns(): string[][] {
|
||||
return execFileMock.mock.calls
|
||||
.filter(([command]) => command === 'wsl.exe')
|
||||
.flatMap(([, args]) => (Array.isArray(args) ? [args.map(String)] : []))
|
||||
}
|
||||
|
||||
// Why the real wsl module: the point is the wsl.exe spawn count across the WHOLE
|
||||
// listing Promise.all, which a per-function mock cannot observe.
|
||||
describe('AI Vault listing wsl.exe probes', () => {
|
||||
beforeEach(() => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32')
|
||||
resetAiVaultSessionListCacheForTests()
|
||||
configureAiVaultSessionSources({ getAdditionalCodexHomePaths: () => [NATIVE_CODEX_HOME] })
|
||||
scanAiVaultSessionsInWorker.mockResolvedValue({ sessions: [], issues: [], scannedAt: 'scan' })
|
||||
})
|
||||
afterEach(() => {
|
||||
execFileMock.mockReset()
|
||||
_resetWslCachesForTests()
|
||||
resetAiVaultSessionListCacheForTests()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('spawns no wsl.exe for native-only codex homes when no distro is installed', async () => {
|
||||
_setWslCachesForTests({ distros: [] })
|
||||
|
||||
await listAiVaultSessions()
|
||||
|
||||
expect(wslSpawns()).toEqual([])
|
||||
expect(scanAiVaultSessionsInWorker).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
additionalCodexSessionsDirs: [join(NATIVE_CODEX_HOME, 'sessions')],
|
||||
wslHomeDirs: []
|
||||
}),
|
||||
expect.anything()
|
||||
)
|
||||
})
|
||||
|
||||
it('still probes running distros when one is installed, so a later outage keeps the last-known-good list', async () => {
|
||||
_setWslCachesForTests({ distros: ['Ubuntu'] })
|
||||
execFileMock.mockImplementation((_command, args, _options, callback) => {
|
||||
callback(null, args.includes('--running') ? 'Ubuntu\n' : '/home/ada\n')
|
||||
})
|
||||
|
||||
await listAiVaultSessions()
|
||||
|
||||
expect(wslSpawns()).toEqual([
|
||||
['--list', '--running', '--quiet'],
|
||||
['-d', 'Ubuntu', '--exec', 'bash', '-c', 'echo $HOME']
|
||||
])
|
||||
expect(scanAiVaultSessionsInWorker).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ wslHomeDirs: [WSL_HOME] }),
|
||||
expect.anything()
|
||||
)
|
||||
|
||||
execFileMock.mockImplementation((_command, _args, _options, callback) => {
|
||||
callback(new Error('wsl unavailable'), '')
|
||||
})
|
||||
await expect(filterPathsToRunningWslDistrosAsync([`${WSL_HOME}\\.codex`])).resolves.toEqual([
|
||||
`${WSL_HOME}\\.codex`
|
||||
])
|
||||
})
|
||||
|
||||
// Why: a rejected `--list --quiet` yields [] without caching. Treating that as "no distro
|
||||
// installed" would narrow the allowed roots delete/subagent validation trusts.
|
||||
it('still discovers WSL homes after the installed-distro probe was rejected', async () => {
|
||||
execFileMock.mockImplementation((_command, args, _options, callback) => {
|
||||
if (args.includes('--running')) {
|
||||
callback(null, 'Ubuntu\n')
|
||||
} else if (args.includes('--list')) {
|
||||
callback(new Error('wsl.exe transient failure'), '')
|
||||
} else {
|
||||
callback(null, '/home/ada\n')
|
||||
}
|
||||
})
|
||||
await expect(listWslDistrosAsync()).resolves.toEqual([])
|
||||
|
||||
await expect(getAiVaultWslHomeDirs()).resolves.toEqual([WSL_HOME])
|
||||
expect(wslSpawns()).toContainEqual(['--list', '--running', '--quiet'])
|
||||
})
|
||||
})
|
||||
@@ -3,10 +3,14 @@ import type { AiVaultListResult } from '../../shared/ai-vault-types'
|
||||
|
||||
const {
|
||||
filterPathsToRunningWslDistrosAsync,
|
||||
getCachedWslDistros,
|
||||
hasCachedWslDistros,
|
||||
listRunningWslHomeDirsAsync,
|
||||
scanAiVaultSessionsInWorker
|
||||
} = vi.hoisted(() => ({
|
||||
filterPathsToRunningWslDistrosAsync: vi.fn(async (paths: readonly string[]) => [...paths]),
|
||||
getCachedWslDistros: vi.fn((): string[] | null => null),
|
||||
hasCachedWslDistros: vi.fn(() => false),
|
||||
listRunningWslHomeDirsAsync: vi.fn().mockResolvedValue([]),
|
||||
scanAiVaultSessionsInWorker: vi.fn()
|
||||
}))
|
||||
@@ -16,6 +20,8 @@ vi.mock('./session-scanner-worker-spawn', () => ({
|
||||
resetAiVaultScannerWorkerForTests: vi.fn()
|
||||
}))
|
||||
vi.mock('../wsl', () => ({
|
||||
getCachedWslDistros,
|
||||
hasCachedWslDistros,
|
||||
listRunningWslHomeDirsAsync
|
||||
}))
|
||||
vi.mock('../wsl-running-path-filter', () => ({ filterPathsToRunningWslDistrosAsync }))
|
||||
@@ -51,6 +57,8 @@ describe('invalidateAiVaultSessionListCache generation guard', () => {
|
||||
vi.spyOn(process, 'platform', 'get').mockImplementation(() => platform)
|
||||
resetAiVaultSessionListCacheForTests()
|
||||
filterPathsToRunningWslDistrosAsync.mockClear()
|
||||
getCachedWslDistros.mockReset().mockReturnValue(null)
|
||||
hasCachedWslDistros.mockReset().mockReturnValue(false)
|
||||
listRunningWslHomeDirsAsync.mockReset().mockResolvedValue([])
|
||||
scanAiVaultSessionsInWorker.mockReset()
|
||||
})
|
||||
@@ -99,6 +107,22 @@ describe('invalidateAiVaultSessionListCache generation guard', () => {
|
||||
expect(listRunningWslHomeDirsAsync).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('skips running-distro discovery once a probe has reported no installed WSL distro', async () => {
|
||||
hasCachedWslDistros.mockReturnValue(true)
|
||||
getCachedWslDistros.mockReturnValue([])
|
||||
|
||||
await expect(getAiVaultWslHomeDirs()).resolves.toEqual([])
|
||||
expect(listRunningWslHomeDirsAsync).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('still discovers running distros before any distro probe has succeeded', async () => {
|
||||
hasCachedWslDistros.mockReturnValue(false)
|
||||
listRunningWslHomeDirsAsync.mockResolvedValue(['\\\\wsl.localhost\\Ubuntu\\home\\ada'])
|
||||
|
||||
await expect(getAiVaultWslHomeDirs()).resolves.toEqual(['\\\\wsl.localhost\\Ubuntu\\home\\ada'])
|
||||
expect(listRunningWslHomeDirsAsync).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('skips WSL home discovery off Windows', async () => {
|
||||
platform = 'linux'
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
resetAiVaultScannerBackgroundForTests,
|
||||
scanAiVaultSessionsInBackground
|
||||
} from './session-scanner-background'
|
||||
import { listRunningWslHomeDirsAsync } from '../wsl'
|
||||
import { getCachedWslDistros, hasCachedWslDistros, listRunningWslHomeDirsAsync } from '../wsl'
|
||||
import { filterPathsToRunningWslDistrosAsync } from '../wsl-running-path-filter'
|
||||
import type { AiVaultListArgs, AiVaultListResult } from '../../shared/ai-vault-types'
|
||||
import { LOCAL_EXECUTION_HOST_ID } from '../../shared/execution-host'
|
||||
@@ -137,6 +137,13 @@ export async function getAiVaultWslHomeDirs(): Promise<string[]> {
|
||||
if (process.platform !== 'win32') {
|
||||
return []
|
||||
}
|
||||
// No installed distro can be running: spares WSL-less hosts the running-distro probe.
|
||||
// Cache read only: a rejected wsl.exe probe yields [] without caching, so it must not
|
||||
// narrow the WSL roots delete/subagent validation trusts; and probing here would let this
|
||||
// listing be the first to cache [] and flip a configured distro to "missing".
|
||||
if (hasCachedWslDistros() && getCachedWslDistros()?.length === 0) {
|
||||
return []
|
||||
}
|
||||
return listRunningWslHomeDirsAsync()
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ vi.mock('../ai-vault/remote-session-scanner', () => ({
|
||||
scanRemoteAiVaultSessions: mocks.scanRemoteAiVaultSessions
|
||||
}))
|
||||
vi.mock('../wsl', () => ({
|
||||
listRunningWslHomeDirsAsync: vi.fn().mockResolvedValue([])
|
||||
listRunningWslHomeDirsAsync: vi.fn().mockResolvedValue([]),
|
||||
hasCachedWslDistros: vi.fn(() => false)
|
||||
}))
|
||||
vi.mock('../wsl-running-path-filter', () => ({
|
||||
filterPathsToRunningWslDistrosAsync: vi.fn(async (paths: readonly string[]) => [...paths])
|
||||
|
||||
@@ -75,7 +75,8 @@ vi.mock('../ai-vault/session-scanner-parse-cache', async (importOriginal) => {
|
||||
|
||||
vi.mock('../wsl', () => ({
|
||||
listRunningWslDistrosAsync: vi.fn().mockResolvedValue([]),
|
||||
listRunningWslHomeDirsAsync: vi.fn().mockResolvedValue([])
|
||||
listRunningWslHomeDirsAsync: vi.fn().mockResolvedValue([]),
|
||||
hasCachedWslDistros: vi.fn(() => false)
|
||||
}))
|
||||
|
||||
vi.mock('../providers/ssh-filesystem-dispatch', () => ({
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { filterPathsToRunningWslDistrosAsync } from './wsl-running-path-filter'
|
||||
import { listRunningWslDistrosAsync } from './wsl'
|
||||
|
||||
vi.mock('./wsl', () => ({ listRunningWslDistrosAsync: vi.fn() }))
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
vi.resetAllMocks()
|
||||
})
|
||||
|
||||
describe('filterPathsToRunningWslDistrosAsync', () => {
|
||||
it.each([[], ['C:\\Users\\user\\.codex'], ['\\\\server\\share', '/local/path']])(
|
||||
'does not query WSL for native paths %j',
|
||||
async (...paths: string[]) => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32')
|
||||
const result = await filterPathsToRunningWslDistrosAsync(paths)
|
||||
expect(result).toEqual(paths)
|
||||
expect(result).not.toBe(paths)
|
||||
expect(listRunningWslDistrosAsync).not.toHaveBeenCalled()
|
||||
}
|
||||
)
|
||||
|
||||
it('still queries running distros for a mixed list and preserves path order', async () => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32')
|
||||
vi.mocked(listRunningWslDistrosAsync).mockResolvedValue(['Ubuntu'])
|
||||
const paths = [
|
||||
'C:\\local',
|
||||
'\\\\wsl$\\ubuntu\\home',
|
||||
'//wsl.localhost/Debian/home',
|
||||
'D:\\local'
|
||||
]
|
||||
expect(await filterPathsToRunningWslDistrosAsync(paths)).toEqual([paths[0], paths[1], paths[3]])
|
||||
expect(listRunningWslDistrosAsync).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not interpret WSL-shaped paths on a non-Windows host', async () => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue('linux')
|
||||
const paths = ['//wsl.localhost/Ubuntu/home']
|
||||
expect(await filterPathsToRunningWslDistrosAsync(paths)).toEqual(paths)
|
||||
expect(listRunningWslDistrosAsync).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import { parseWslUncPath } from '../shared/wsl-paths'
|
||||
import { isWslUncPath, parseWslUncPath } from '../shared/wsl-paths'
|
||||
import { listRunningWslDistrosAsync } from './wsl'
|
||||
|
||||
export function filterPathsToWslDistros(
|
||||
@@ -19,5 +19,6 @@ export async function filterPathsToRunningWslDistrosAsync(
|
||||
if (process.platform !== 'win32') {
|
||||
return [...paths]
|
||||
}
|
||||
return filterPathsToWslDistros(paths, await listRunningWslDistrosAsync())
|
||||
const runningDistros = paths.some(isWslUncPath) ? await listRunningWslDistrosAsync() : []
|
||||
return filterPathsToWslDistros(paths, runningDistros)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user