mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(preflight): preserve network path detection
This commit is contained in:
@@ -16,6 +16,10 @@ export async function readDetectionDirectory(dir: string): Promise<Map<string, s
|
||||
// Large directories cost less to probe by candidate than to enumerate in full.
|
||||
return null
|
||||
}
|
||||
if (/[^\p{ASCII}]/u.test(entry.name)) {
|
||||
// Filesystem Unicode equivalence can exceed JavaScript normalization and case folding.
|
||||
return null
|
||||
}
|
||||
names.set(directoryNameKey(entry.name), entry.name)
|
||||
}
|
||||
return names
|
||||
|
||||
@@ -120,6 +120,32 @@ describe('bounded PATH detection', () => {
|
||||
expect(statFile).toHaveBeenCalledTimes(inventory.length)
|
||||
})
|
||||
|
||||
it('closes a Unicode listing early and lets candidate stats decide filename equivalence', async () => {
|
||||
let scanned = 0
|
||||
let closed = false
|
||||
openDirectory.mockResolvedValue({
|
||||
async *[Symbol.asyncIterator]() {
|
||||
try {
|
||||
for (const name of ['cruſh', ...inventory]) {
|
||||
scanned++
|
||||
yield { name }
|
||||
}
|
||||
} finally {
|
||||
closed = true
|
||||
}
|
||||
}
|
||||
})
|
||||
executableFiles(new Set(['/front/crush']))
|
||||
await expect(
|
||||
findCommandsOnLocalPath(['crush', ...inventory], {
|
||||
...options,
|
||||
env: { PATH: '/front' }
|
||||
})
|
||||
).resolves.toEqual(new Set(['crush']))
|
||||
expect(scanned).toBe(1)
|
||||
expect(closed).toBe(true)
|
||||
})
|
||||
|
||||
it.each([true, false])(
|
||||
'delegates folded POSIX matches to volume case semantics (%s)',
|
||||
async (caseSensitive) => {
|
||||
|
||||
@@ -207,6 +207,8 @@ export async function findCommandsOnLocalPath(
|
||||
|
||||
const env = options.env ?? process.env
|
||||
const isAbsolute = isWin ? path.win32.isAbsolute : path.posix.isAbsolute
|
||||
// Native Windows joining preserves UNC roots; POSIX hosts retain synthetic lookups.
|
||||
const joinPath = isWin ? path.join : path.posix.join
|
||||
const pathDirs = (readEnvCaseInsensitive(env, 'PATH') ?? '')
|
||||
.split(isWin ? ';' : ':')
|
||||
// The install-dir fallback also searches trimmed PATH entries.
|
||||
@@ -239,7 +241,7 @@ export async function findCommandsOnLocalPath(
|
||||
continue
|
||||
}
|
||||
// POSIX stat must decide volume case sensitivity, even for a folded-name match.
|
||||
const candidate = path.posix.join(dir, isWin ? (listed ?? wanted) : wanted)
|
||||
const candidate = joinPath(dir, isWin ? (listed ?? wanted) : wanted)
|
||||
const candidateKey = isWin ? candidate.toLowerCase() : candidate
|
||||
if (seenCandidates.has(candidateKey)) {
|
||||
continue
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import type * as FileSystemPromises from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import type * as NodePath from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { openDirectory, statFile, detectInstallDirs } = vi.hoisted(() => ({
|
||||
openDirectory: vi.fn(),
|
||||
statFile: vi.fn(),
|
||||
detectInstallDirs: vi.fn(() => new Set<string>())
|
||||
}))
|
||||
|
||||
// Model native Windows path semantics even when this suite runs on a POSIX host.
|
||||
vi.mock('node:path', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof NodePath>()
|
||||
return { ...actual, ...actual.win32, default: actual.win32 }
|
||||
})
|
||||
vi.mock('node:fs/promises', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof FileSystemPromises>()),
|
||||
opendir: openDirectory,
|
||||
stat: statFile
|
||||
}))
|
||||
vi.mock('../../shared/local-agent-install-dir-detection', () => ({
|
||||
detectCommandsInInstallDirs: detectInstallDirs
|
||||
}))
|
||||
vi.mock('../pty/windows-environment-path', () => ({
|
||||
mergePersistedWindowsPath: vi.fn()
|
||||
}))
|
||||
vi.mock('../ipc/wsl-guest-windows-drive-path', () => ({
|
||||
readGuestWindowsDriveMounts: () => []
|
||||
}))
|
||||
|
||||
import { detectAgentCommandsOnHost } from './agent-command-detection'
|
||||
|
||||
describe('Windows host detection with PATH fallback skipped', () => {
|
||||
const originalPlatform = process.platform
|
||||
const shareDirectory = String.raw`\\review-server\tools\bin`
|
||||
const executable = String.raw`\\review-server\tools\bin\codex.cmd`
|
||||
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' })
|
||||
detectInstallDirs.mockClear()
|
||||
openDirectory.mockReset().mockImplementation(async (dir: string) => ({
|
||||
async *[Symbol.asyncIterator]() {
|
||||
if (path.normalize(dir).toLowerCase() === shareDirectory) {
|
||||
yield { name: 'codex.cmd' }
|
||||
}
|
||||
}
|
||||
}))
|
||||
statFile.mockReset().mockImplementation(async (candidate: string) => {
|
||||
if (path.normalize(candidate).toLowerCase() !== executable) {
|
||||
throw Object.assign(new Error('not found'), { code: 'ENOENT' })
|
||||
}
|
||||
return { isFile: () => true, isDirectory: () => false }
|
||||
})
|
||||
vi.stubEnv('PATHEXT', '.COM')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs()
|
||||
Object.defineProperty(process, 'platform', { configurable: true, value: originalPlatform })
|
||||
})
|
||||
|
||||
it.each(['//review-server/tools/bin', shareDirectory])(
|
||||
'detects a shim installed only in the UNC PATH entry %s',
|
||||
async (directory) => {
|
||||
vi.stubEnv('PATH', directory)
|
||||
|
||||
await expect(detectAgentCommandsOnHost(['codex'])).resolves.toEqual(new Set(['codex']))
|
||||
expect(detectInstallDirs).toHaveBeenCalledWith([], undefined, { skipPathSearch: true })
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -63,18 +63,21 @@ describe('detectAgentCommandsOnHost install-dir fallback', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it.skipIf(process.platform === 'win32')(
|
||||
'preserves actual volume case behavior when PATH fallback is skipped',
|
||||
async () => {
|
||||
it.skipIf(process.platform === 'win32').each([
|
||||
['AgEnT-PrObE', 'agent-probe'],
|
||||
['cruſh', 'crush']
|
||||
])(
|
||||
'preserves filesystem equivalence for %s when looking up %s with PATH fallback skipped',
|
||||
async (filename, command) => {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), 'agent-detection-case-'))
|
||||
try {
|
||||
await writeFile(path.join(dir, 'AgEnT-PrObE'), '#!/bin/sh\n')
|
||||
await chmod(path.join(dir, 'AgEnT-PrObE'), 0o755)
|
||||
await writeFile(path.join(dir, filename), '#!/bin/sh\n')
|
||||
await chmod(path.join(dir, filename), 0o755)
|
||||
vi.stubEnv('PATH', dir)
|
||||
const commands = ['agent-probe', 'missing-a', 'missing-b', 'missing-c', 'missing-d']
|
||||
const expected = await isCommandOnLocalPath('agent-probe')
|
||||
const commands = [command, 'missing-a', 'missing-b', 'missing-c', 'missing-d']
|
||||
const expected = await isCommandOnLocalPath(command)
|
||||
await expect(detectAgentCommandsOnHost(commands)).resolves.toEqual(
|
||||
new Set(expected ? ['agent-probe'] : [])
|
||||
new Set(expected ? [command] : [])
|
||||
)
|
||||
expect(detectInstallDirs).toHaveBeenCalledWith(
|
||||
expected ? commands.slice(1) : commands,
|
||||
|
||||
Reference in New Issue
Block a user