refactor(relay): delete the SSH relay server, its build assets and tests (#27215)

* wip: delete the SSH relay server and its build, assets and tests

* wip: drop main/relay plugin conformance; WSL reader lookup test

* fix: drop the emptied relay bench; prune baselines

* test: keep the native halves of tests that also covered the relay

* chore(ci): drop shard timings for the deleted relay server tests

* fix: keep the directory listing budget that darwin terminal names now import

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
This commit is contained in:
OrcaWin
2026-10-10 22:36:42 -07:00
committed by GitHub
co-authored by m4air
parent c2c63e7f9a
commit 2b4b60a87e
512 changed files with 359 additions and 85654 deletions
-10
View File
@@ -1,10 +0,0 @@
export const GIT_GREP_MAX_RECORD_BYTES = 8 * 1024 * 1024
export class GitGrepRecordCapacityError extends Error {
readonly code = 'git_grep_record_capacity'
constructor() {
super('Git search record exceeds the 8 MiB limit.')
this.name = 'GitGrepRecordCapacityError'
}
}
-5
View File
@@ -1,5 +0,0 @@
export const PTY_RETAINED_RECORD_BYTES = 128
export function chargedPtyRetainedStringBytes(value: string): number {
return Math.max(Buffer.byteLength(value, 'utf8'), 2 * value.length) + PTY_RETAINED_RECORD_BYTES
}
@@ -1,79 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const { lstatMock, opendirMock } = vi.hoisted(() => ({
lstatMock: vi.fn(),
opendirMock: vi.fn()
}))
vi.mock('node:fs/promises', () => ({
lstat: lstatMock,
opendir: opendirMock
}))
import { readQuickOpenDirectoryEntries } from './quick-open-directory-reader'
import { createQuickOpenReaddirBudget } from './quick-open-readdir-budget'
beforeEach(() => {
vi.clearAllMocks()
lstatMock.mockResolvedValue({
isDirectory: () => true,
isSymbolicLink: () => false
})
})
describe('quick-open streaming directory reader', () => {
it('orders numbered entries naturally before the recursive walk', async () => {
opendirMock.mockResolvedValue({
async *[Symbol.asyncIterator]() {
for (const name of ['100 - notes', '9 - notes', '99 - notes']) {
yield {
name,
isDirectory: () => true,
isFile: () => false,
isSymbolicLink: () => false
}
}
}
})
const entries = await readQuickOpenDirectoryEntries({
absPath: '/numbered',
allowSymlinkedRoot: false,
budget: createQuickOpenReaddirBudget()
})
expect(entries.map((entry) => entry.name)).toEqual(['9 - notes', '99 - notes', '100 - notes'])
})
it('stops a huge directory one entry beyond the exact cap and closes its iterator', async () => {
let produced = 0
let closed = false
opendirMock.mockResolvedValue({
async *[Symbol.asyncIterator]() {
try {
while (produced < 1_000_000) {
produced += 1
yield {
name: `directory-${produced}`,
isDirectory: () => true,
isFile: () => false,
isSymbolicLink: () => false
}
}
} finally {
closed = true
}
}
})
await expect(
readQuickOpenDirectoryEntries({
absPath: '/streamed',
allowSymlinkedRoot: false,
budget: createQuickOpenReaddirBudget({ maxEntries: 3 })
})
).rejects.toThrow('File listing exceeded 3 entries')
expect(produced).toBe(4)
expect(closed).toBe(true)
})
})
-69
View File
@@ -1,69 +0,0 @@
import { lstat, opendir } from 'node:fs/promises'
import { compareFileNames } from './file-name-sort'
import { isFileListingCancellation, throwIfFileListingCancelled } from './file-listing-cancellation'
import { isQuickOpenReadableDirectory } from './quick-open-directory-validation'
import {
assertQuickOpenReaddirDeadline,
consumeQuickOpenReaddirEntryBudget,
consumeQuickOpenReaddirPathBudget,
isQuickOpenReaddirBudgetError,
type QuickOpenReaddirBudget
} from './quick-open-readdir-budget'
export type QuickOpenDirectoryEntry = {
name: string
kind: 'directory' | 'file' | 'symlink' | 'other'
}
export async function readQuickOpenDirectoryEntries(opts: {
absPath: string
allowSymlinkedRoot: boolean
budget: QuickOpenReaddirBudget
signal?: AbortSignal
}): Promise<QuickOpenDirectoryEntry[]> {
try {
const stat = await lstat(opts.absPath)
if (!isQuickOpenReadableDirectory(stat, opts.allowSymlinkedRoot)) {
return []
}
const entries: QuickOpenDirectoryEntry[] = []
const directory = await opendir(opts.absPath)
try {
throwIfFileListingCancelled(opts.signal)
assertQuickOpenReaddirDeadline(opts.budget)
for await (const entry of directory) {
throwIfFileListingCancelled(opts.signal)
assertQuickOpenReaddirDeadline(opts.budget)
consumeQuickOpenReaddirEntryBudget(opts.budget)
consumeQuickOpenReaddirPathBudget(opts.budget, entry.name)
entries.push({
name: entry.name,
kind: entry.isDirectory()
? 'directory'
: entry.isFile()
? 'file'
: entry.isSymbolicLink()
? 'symlink'
: 'other'
})
}
} finally {
try {
await directory.close()
} catch {}
}
entries.sort((left, right) => compareFileNames(left.name, right.name))
// Why: discard buffered names if the path became a symlink while its
// directory handle was open; descendants must never escape the root.
const statAfterRead = await lstat(opts.absPath)
return isQuickOpenReadableDirectory(statAfterRead, opts.allowSymlinkedRoot) ? entries : []
} catch (error) {
if (isQuickOpenReaddirBudgetError(error) || isFileListingCancellation(error)) {
throw error
}
// Permission denied or a vanished subtree must not hide readable siblings.
return []
}
}
@@ -1,7 +0,0 @@
import type { Stats } from 'node:fs'
export function isQuickOpenReadableDirectory(stat: Stats, allowSymlinkedRoot = false): boolean {
// Why: an explicitly selected workspace root may be a symlink, while nested
// traversal must never follow a symlink outside that authorized root.
return stat.isDirectory() || Boolean(allowSymlinkedRoot && stat.isSymbolicLink())
}
@@ -1,26 +0,0 @@
import { describe, expect, it } from 'vitest'
import { collapseQuickOpenExpansionPaths } from './quick-open-expansion-paths'
describe('collapseQuickOpenExpansionPaths', () => {
it('collapses descendants without confusing shared-prefix siblings', () => {
const paths = new Map<string, boolean>([
['foo/bar/deep', false],
['foo-sibling', false],
['foo', false],
['foo/bar', true]
])
expect(collapseQuickOpenExpansionPaths(paths)).toEqual([
['foo', true],
['foo-sibling', false]
])
})
it('keeps many sibling placeholders distinct', () => {
const paths = new Map(
Array.from({ length: 10_001 }, (_, index) => [`generated-${index}`, true] as const)
)
expect(collapseQuickOpenExpansionPaths(paths)).toHaveLength(paths.size)
})
})
-37
View File
@@ -1,37 +0,0 @@
/**
* Remove descendant placeholders already covered by an ancestor. Sorting puts
* ancestors first; prefix lookups avoid quadratic scans across sibling paths.
*/
export function collapseQuickOpenExpansionPaths(
expansionPaths: ReadonlyMap<string, boolean>
): [string, boolean][] {
const sortedPaths = Array.from(expansionPaths).sort(([left], [right]) =>
left < right ? -1 : left > right ? 1 : 0
)
const collapsedPaths = new Map<string, boolean>()
for (const [relPath, includeSymlinks] of sortedPaths) {
let ancestorPath: string | undefined
let slashIndex = relPath.indexOf('/')
while (slashIndex !== -1) {
const candidate = relPath.substring(0, slashIndex)
if (collapsedPaths.has(candidate)) {
ancestorPath = candidate
break
}
slashIndex = relPath.indexOf('/', slashIndex + 1)
}
if (ancestorPath) {
// Why: primary and ignored passes can overlap; the ancestor walk covers
// the descendant, but must preserve either pass's symlink-leaf contract.
if (includeSymlinks) {
collapsedPaths.set(ancestorPath, true)
}
continue
}
collapsedPaths.set(relPath, includeSymlinks)
}
return Array.from(collapsedPaths)
}
@@ -1,68 +0,0 @@
import { lstat } from 'node:fs/promises'
import { join } from 'node:path'
export type QuickOpenGitEntryKind = 'keep' | 'fill-nested-repo' | 'drop-placeholder'
export type QuickOpenGitLsFilesEntry = {
path: string
isGitlink: boolean
isUntrackedDir: boolean
}
const GIT_LS_FILES_STAGE_ENTRY = /^([0-7]{6}) [0-9a-f]{40,64} [0-3]\t/
export function parseQuickOpenGitLsFilesEntry(entry: string): QuickOpenGitLsFilesEntry {
const match = GIT_LS_FILES_STAGE_ENTRY.exec(entry)
if (match) {
return {
path: entry.slice(match[0].length),
isGitlink: match[1] === '160000',
isUntrackedDir: false
}
}
return {
path: entry,
isGitlink: false,
isUntrackedDir: entry.endsWith('/')
}
}
function joinQuickOpenRootPath(rootPath: string, relPath: string): string {
return join(rootPath, ...relPath.split('/').filter(Boolean))
}
async function hasGitEntry(absPath: string): Promise<boolean> {
try {
const stat = await lstat(join(absPath, '.git'))
return stat.isDirectory() || stat.isFile()
} catch {
return false
}
}
export async function classifyQuickOpenGitEntry(
rootPath: string,
entry: string
): Promise<{ kind: QuickOpenGitEntryKind; relPath: string }> {
const parsed = parseQuickOpenGitLsFilesEntry(entry)
const relPath = parsed.path.replace(/\/+$/, '')
if (!relPath) {
return { kind: 'drop-placeholder', relPath }
}
if (!parsed.isGitlink && !parsed.isUntrackedDir) {
return { kind: 'keep', relPath }
}
let stat
try {
stat = await lstat(joinQuickOpenRootPath(rootPath, relPath))
} catch {
return { kind: 'drop-placeholder', relPath }
}
if (!stat.isDirectory()) {
return { kind: 'drop-placeholder', relPath }
}
return (await hasGitEntry(joinQuickOpenRootPath(rootPath, relPath)))
? { kind: 'fill-nested-repo', relPath }
: { kind: 'drop-placeholder', relPath }
}
-27
View File
@@ -1,27 +0,0 @@
import { describe, expect, it } from 'vitest'
import { detectLinuxInstallCommandFromOsRelease } from './quick-open-install-rg'
describe('detectLinuxInstallCommandFromOsRelease', () => {
it('uses apt for Debian and Ubuntu families', () => {
expect(detectLinuxInstallCommandFromOsRelease('ID=ubuntu')).toBe('sudo apt install ripgrep')
expect(detectLinuxInstallCommandFromOsRelease('ID=pop\nID_LIKE="ubuntu debian"')).toBe(
'sudo apt install ripgrep'
)
})
it('uses distro-specific commands for common non-Debian families', () => {
expect(detectLinuxInstallCommandFromOsRelease('ID="fedora"')).toBe('sudo dnf install ripgrep')
expect(detectLinuxInstallCommandFromOsRelease('ID=manjaro\r\nID_LIKE="arch"')).toBe(
'sudo pacman -S ripgrep'
)
expect(detectLinuxInstallCommandFromOsRelease("ID=postmarketos\nID_LIKE='alpine'")).toBe(
'sudo apk add ripgrep'
)
})
it('falls back to generic Linux package-manager guidance', () => {
expect(detectLinuxInstallCommandFromOsRelease('ID=unknown')).toBe(
'install ripgrep via your package manager (e.g. apt/dnf/pacman)'
)
})
})
-90
View File
@@ -1,90 +0,0 @@
import { readNodeFileWithinLimit } from './node-bounded-file-reader'
import { getProcessOutputFields, iterateProcessOutputLines } from './process-output-field-scanner'
const GENERIC_LINUX_RIPGREP_INSTALL =
'install ripgrep via your package manager (e.g. apt/dnf/pacman)'
const OS_RELEASE_ID_LIKE_MAX_FIELDS = 16
const MAX_OS_RELEASE_BYTES = 64 * 1024
export async function detectInstallCommand(): Promise<string> {
if (process.platform === 'darwin') {
return 'brew install ripgrep'
}
if (process.platform === 'linux') {
try {
const osRelease = (
await readNodeFileWithinLimit('/etc/os-release', MAX_OS_RELEASE_BYTES)
).buffer.toString('utf8')
return detectLinuxInstallCommandFromOsRelease(osRelease)
} catch {
/* fall through to generic guidance */
}
return GENERIC_LINUX_RIPGREP_INSTALL
}
return 'install ripgrep (https://github.com/BurntSushi/ripgrep#installation)'
}
export function detectLinuxInstallCommandFromOsRelease(osRelease: string): string {
for (const id of getOsReleasePackageFamilyIds(osRelease)) {
if (id === 'debian' || id === 'ubuntu') {
return 'sudo apt install ripgrep'
}
if (id === 'fedora' || id === 'rhel' || id === 'centos') {
return 'sudo dnf install ripgrep'
}
if (id === 'arch') {
return 'sudo pacman -S ripgrep'
}
if (id === 'alpine') {
return 'sudo apk add ripgrep'
}
}
return GENERIC_LINUX_RIPGREP_INSTALL
}
function getOsReleasePackageFamilyIds(osRelease: string): string[] {
const ids: string[] = []
for (const line of iterateProcessOutputLines(osRelease)) {
const separatorIndex = line.indexOf('=')
if (separatorIndex <= 0) {
continue
}
const key = line.slice(0, separatorIndex)
const value = readOsReleaseValue(line.slice(separatorIndex + 1))
if (key === 'ID') {
const id = getProcessOutputFields(value, 1)[0]
if (id) {
ids.push(id)
}
} else if (key === 'ID_LIKE') {
ids.push(...getProcessOutputFields(value, OS_RELEASE_ID_LIKE_MAX_FIELDS))
}
}
return ids
}
function readOsReleaseValue(rawValue: string): string {
const trimmed = rawValue.trim()
const quote = trimmed[0]
return (quote === '"' || quote === "'") && trimmed.at(-1) === quote
? trimmed.slice(1, -1)
: trimmed
}
// Why remote-only: the local side always has Orca's bundled rg, so only a remote host an upload
// never reached can still fall back to the capped git/readdir listing these messages explain.
export async function buildInstallRgMessage(cause: unknown): Promise<string> {
const reason = cause instanceof Error ? cause.message : String(cause)
return (
`Quick Open scan too large (${reason}). ` +
`Install ripgrep on the remote to enable fast, gitignore-aware listing: ${await detectInstallCommand()}`
)
}
export async function buildRipgrepRequiredMessage(): Promise<string> {
return `Quick Open search requires ripgrep on the remote to stay resource-bounded. Install it with: ${await detectInstallCommand()}`
}
-114
View File
@@ -1,114 +0,0 @@
import { QUICK_OPEN_LISTING_MAX_RESULTS } from './quick-open-listing-limits'
export const QUICK_OPEN_READDIR_MAX_FILES = QUICK_OPEN_LISTING_MAX_RESULTS
export const QUICK_OPEN_READDIR_MAX_ENTRIES = 50_000
export const QUICK_OPEN_READDIR_MAX_DIRECTORIES = 25_000
export const QUICK_OPEN_READDIR_MAX_DEPTH = 256
export const QUICK_OPEN_READDIR_MAX_PATH_CODE_UNITS = 16 * 1024 * 1024
export const QUICK_OPEN_READDIR_TIMEOUT_MS = 10_000
export type QuickOpenReaddirBudget = {
remainingFiles: number
remainingEntries: number
remainingDirectories: number
remainingPathCodeUnits: number
maxFiles: number
maxEntries: number
maxDirectories: number
maxDepth: number
maxPathCodeUnits: number
deadlineMs: number
}
export function createQuickOpenReaddirBudget(
opts: {
maxFiles?: number
maxEntries?: number
maxDirectories?: number
maxDepth?: number
maxPathCodeUnits?: number
timeoutMs?: number
nowMs?: number
} = {}
): QuickOpenReaddirBudget {
const maxFiles = opts.maxFiles ?? QUICK_OPEN_READDIR_MAX_FILES
const maxEntries = opts.maxEntries ?? QUICK_OPEN_READDIR_MAX_ENTRIES
const maxDirectories = opts.maxDirectories ?? QUICK_OPEN_READDIR_MAX_DIRECTORIES
const maxDepth = opts.maxDepth ?? QUICK_OPEN_READDIR_MAX_DEPTH
const maxPathCodeUnits = opts.maxPathCodeUnits ?? QUICK_OPEN_READDIR_MAX_PATH_CODE_UNITS
for (const [name, value] of Object.entries({
maxFiles,
maxEntries,
maxDirectories,
maxDepth,
maxPathCodeUnits
})) {
if (!Number.isSafeInteger(value) || value < 0) {
throw new RangeError(`${name} must be a non-negative safe integer`)
}
}
return {
remainingFiles: maxFiles,
remainingEntries: maxEntries,
remainingDirectories: maxDirectories,
remainingPathCodeUnits: maxPathCodeUnits,
maxFiles,
maxEntries,
maxDirectories,
maxDepth,
maxPathCodeUnits,
deadlineMs: (opts.nowMs ?? Date.now()) + (opts.timeoutMs ?? QUICK_OPEN_READDIR_TIMEOUT_MS)
}
}
const FILE_LISTING_TIMED_OUT = 'File listing timed out'
const FILE_LISTING_EXCEEDED_PREFIX = 'File listing exceeded'
/** Budget errors are the only fallback failures translated into install-rg guidance. */
export function isQuickOpenReaddirBudgetError(error: unknown): boolean {
const message = error instanceof Error ? error.message : ''
return message === FILE_LISTING_TIMED_OUT || message.startsWith(FILE_LISTING_EXCEEDED_PREFIX)
}
export function assertQuickOpenReaddirDeadline(budget: QuickOpenReaddirBudget): void {
if (Date.now() > budget.deadlineMs) {
throw new Error(FILE_LISTING_TIMED_OUT)
}
}
export function consumeQuickOpenReaddirFileBudget(budget: QuickOpenReaddirBudget): void {
if (budget.remainingFiles <= 0) {
throw new Error(`${FILE_LISTING_EXCEEDED_PREFIX} ${budget.maxFiles} files`)
}
budget.remainingFiles--
}
export function consumeQuickOpenReaddirEntryBudget(budget: QuickOpenReaddirBudget): void {
if (budget.remainingEntries <= 0) {
throw new Error(`${FILE_LISTING_EXCEEDED_PREFIX} ${budget.maxEntries} entries`)
}
budget.remainingEntries--
}
export function consumeQuickOpenReaddirDirectoryBudget(budget: QuickOpenReaddirBudget): void {
if (budget.remainingDirectories <= 0) {
throw new Error(`${FILE_LISTING_EXCEEDED_PREFIX} ${budget.maxDirectories} directories`)
}
budget.remainingDirectories--
}
export function assertQuickOpenReaddirDepth(budget: QuickOpenReaddirBudget, depth: number): void {
if (depth > budget.maxDepth) {
throw new Error(`${FILE_LISTING_EXCEEDED_PREFIX} depth ${budget.maxDepth}`)
}
}
export function consumeQuickOpenReaddirPathBudget(
budget: QuickOpenReaddirBudget,
path: string
): void {
if (path.length > budget.remainingPathCodeUnits) {
throw new Error(`${FILE_LISTING_EXCEEDED_PREFIX} ${budget.maxPathCodeUnits} path code units`)
}
budget.remainingPathCodeUnits -= path.length
}
@@ -1,92 +0,0 @@
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import {
createQuickOpenReaddirBudget,
listQuickOpenFilesWithReaddir
} from './quick-open-readdir-walk'
const tempRoots: string[] = []
async function makeRoot(): Promise<string> {
const root = await mkdtemp(join(tmpdir(), 'orca-quick-open-budget-'))
tempRoots.push(root)
return root
}
afterEach(async () => {
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })))
})
describe('quick-open readdir memory limits', () => {
it('accepts the exact entry limit and rejects the next zero-file entry', async () => {
const root = await makeRoot()
await mkdir(join(root, 'a'))
await mkdir(join(root, 'b'))
await expect(
listQuickOpenFilesWithReaddir(root, {
budget: createQuickOpenReaddirBudget({ maxEntries: 2 })
})
).resolves.toEqual([])
await mkdir(join(root, 'c'))
await expect(
listQuickOpenFilesWithReaddir(root, {
budget: createQuickOpenReaddirBudget({ maxEntries: 2 })
})
).rejects.toThrow('File listing exceeded 2 entries')
})
it('caps retained directory paths even when the tree contains no files', async () => {
const root = await makeRoot()
await mkdir(join(root, 'a'))
await mkdir(join(root, 'b'))
await expect(
listQuickOpenFilesWithReaddir(root, {
budget: createQuickOpenReaddirBudget({ maxDirectories: 3 })
})
).resolves.toEqual([])
await expect(
listQuickOpenFilesWithReaddir(root, {
budget: createQuickOpenReaddirBudget({ maxDirectories: 2 })
})
).rejects.toThrow('File listing exceeded 2 directories')
})
it('accepts the exact depth limit and rejects a deeper directory', async () => {
const root = await makeRoot()
await mkdir(join(root, 'a', 'b'), { recursive: true })
await expect(
listQuickOpenFilesWithReaddir(root, {
budget: createQuickOpenReaddirBudget({ maxDepth: 2 })
})
).resolves.toEqual([])
await expect(
listQuickOpenFilesWithReaddir(root, {
budget: createQuickOpenReaddirBudget({ maxDepth: 1 })
})
).rejects.toThrow('File listing exceeded depth 1')
})
it('bounds aggregate path storage without changing exact-boundary output', async () => {
const root = await makeRoot()
const fileName = 'a.ts'
await writeFile(join(root, fileName), 'x')
const exactPathCodeUnits = root.length + fileName.length * 2
await expect(
listQuickOpenFilesWithReaddir(root, {
budget: createQuickOpenReaddirBudget({ maxPathCodeUnits: exactPathCodeUnits })
})
).resolves.toEqual([fileName])
await expect(
listQuickOpenFilesWithReaddir(root, {
budget: createQuickOpenReaddirBudget({ maxPathCodeUnits: exactPathCodeUnits - 1 })
})
).rejects.toThrow(`File listing exceeded ${exactPathCodeUnits - 1} path code units`)
})
})
-559
View File
@@ -1,559 +0,0 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import type * as NodeFsPromises from 'node:fs/promises'
const { lstatMock, opendirMock } = vi.hoisted(() => ({
lstatMock: vi.fn(),
opendirMock: vi.fn()
}))
vi.mock('fs/promises', async () => {
const actual = await vi.importActual<typeof NodeFsPromises>('fs/promises')
lstatMock.mockImplementation(actual.lstat)
opendirMock.mockImplementation(actual.opendir)
return {
...actual,
lstat: lstatMock,
opendir: opendirMock
}
})
import { mkdtemp, mkdir, rename, rm, symlink, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import {
classifyQuickOpenGitEntry,
createQuickOpenReaddirBudget,
expandQuickOpenGitFileListing,
isQuickOpenReaddirBudgetError,
listQuickOpenFilesWithReaddir,
parseQuickOpenGitLsFilesEntry,
QUICK_OPEN_READDIR_MAX_FILES
} from './quick-open-readdir-walk'
import { isFileListingCancellation } from './file-listing-cancellation'
const tempDirs: string[] = []
const SHA1 = '0123456789abcdef0123456789abcdef01234567'
const SHA256 = `${SHA1}89abcdef0123456789abcdef`
function staged(mode: string, path: string, sha = SHA1): string {
return `${mode} ${sha} 0\t${path}`
}
async function makeTempRoot(): Promise<string> {
const root = await mkdtemp(join(tmpdir(), 'orca-quick-open-readdir-'))
tempDirs.push(root)
return root
}
async function writeRel(root: string, relPath: string, content = 'x'): Promise<void> {
const absPath = join(root, ...relPath.split('/'))
await mkdir(dirname(absPath), { recursive: true })
await writeFile(absPath, content)
}
async function mkdirRel(root: string, relPath: string): Promise<void> {
await mkdir(join(root, ...relPath.split('/')), { recursive: true })
}
async function makeNestedRepo(root: string, relPath: string, gitEntry: 'dir' | 'file' = 'dir') {
await mkdirRel(root, relPath)
const gitPath = join(root, ...relPath.split('/'), '.git')
await (gitEntry === 'dir'
? mkdir(gitPath, { recursive: true })
: writeFile(gitPath, 'gitdir: ../.git/worktrees/example'))
}
afterEach(async () => {
vi.clearAllMocks()
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })))
})
describe('quick-open readdir walk', () => {
it('parses git ls-files stage output and bare untracked entries', () => {
expect(parseQuickOpenGitLsFilesEntry(staged('100644', 'src/index.ts'))).toEqual({
path: 'src/index.ts',
isGitlink: false,
isUntrackedDir: false
})
expect(parseQuickOpenGitLsFilesEntry(staged('160000', 'packages/app'))).toEqual({
path: 'packages/app',
isGitlink: true,
isUntrackedDir: false
})
expect(parseQuickOpenGitLsFilesEntry(staged('100755', 'bin/run', SHA256))).toEqual({
path: 'bin/run',
isGitlink: false,
isUntrackedDir: false
})
expect(parseQuickOpenGitLsFilesEntry('scratch.txt')).toEqual({
path: 'scratch.txt',
isGitlink: false,
isUntrackedDir: false
})
expect(parseQuickOpenGitLsFilesEntry('packages/lib/')).toEqual({
path: 'packages/lib/',
isGitlink: false,
isUntrackedDir: true
})
})
it('keeps ordinary git entries without lstat calls', async () => {
await expect(
expandQuickOpenGitFileListing({
rootPath: '/unused/root',
gitPaths: [
staged('100644', 'README.md'),
staged('100755', 'bin/run', SHA256),
'scratch.txt'
]
})
).resolves.toEqual(['README.md', 'bin/run', 'scratch.txt'])
expect(lstatMock).not.toHaveBeenCalled()
})
it('classifies nested repo placeholders without confusing extensionless files', async () => {
const root = await makeTempRoot()
await writeRel(root, 'Makefile')
await makeNestedRepo(root, 'packages/app')
await makeNestedRepo(root, 'packages/lib', 'file')
await mkdirRel(root, 'packages/unchecked')
await expect(classifyQuickOpenGitEntry(root, staged('100644', 'Makefile'))).resolves.toEqual({
kind: 'keep',
relPath: 'Makefile'
})
await expect(
classifyQuickOpenGitEntry(root, staged('160000', 'packages/app'))
).resolves.toEqual({
kind: 'fill-nested-repo',
relPath: 'packages/app'
})
await expect(classifyQuickOpenGitEntry(root, 'packages/lib/')).resolves.toEqual({
kind: 'fill-nested-repo',
relPath: 'packages/lib'
})
await expect(classifyQuickOpenGitEntry(root, 'packages/unchecked')).resolves.toEqual({
kind: 'keep',
relPath: 'packages/unchecked'
})
await expect(classifyQuickOpenGitEntry(root, 'packages/unchecked/')).resolves.toEqual({
kind: 'drop-placeholder',
relPath: 'packages/unchecked'
})
})
it('re-prefixes nested children and filters final workspace-relative paths', async () => {
const root = await makeTempRoot()
await writeRel(root, 'README.md')
await writeRel(root, 'Makefile')
await makeNestedRepo(root, 'packages/app')
await makeNestedRepo(root, 'packages/lib', 'file')
await mkdirRel(root, 'packages/empty')
await writeRel(root, 'packages/app/src/main.ts')
await writeRel(root, 'packages/app/node_modules/pkg/index.js')
await writeRel(root, 'packages/app/.git/config')
await writeRel(root, 'packages/app/linked-worktree/file.ts')
await writeRel(root, 'packages/lib/src/lib.ts')
await expect(
expandQuickOpenGitFileListing({
rootPath: root,
gitPaths: [
staged('100644', 'README.md'),
staged('100644', 'Makefile'),
staged('160000', 'packages/app'),
'packages/lib/',
'packages/empty/'
],
excludePathPrefixes: ['packages/app/linked-worktree']
})
).resolves.toEqual([
'README.md',
'Makefile',
'packages/app/src/main.ts',
'packages/lib/src/lib.ts'
])
})
it('rejects on cap and shares one budget across nested subtrees', async () => {
const root = await makeTempRoot()
await makeNestedRepo(root, 'packages/app')
await makeNestedRepo(root, 'packages/lib')
await writeRel(root, 'packages/app/a.ts')
await writeRel(root, 'packages/app/b.ts')
await writeRel(root, 'packages/lib/c.ts')
await expect(
expandQuickOpenGitFileListing({
rootPath: root,
gitPaths: [staged('160000', 'packages/app'), staged('160000', 'packages/lib')],
budget: createQuickOpenReaddirBudget({ maxFiles: 2 })
})
).rejects.toThrow('File listing exceeded')
})
it('prunes excluded nested subtrees during traversal without consuming the budget', async () => {
const root = await makeTempRoot()
await makeNestedRepo(root, 'packages/app')
await writeRel(root, 'packages/app/keep.ts')
// A large excluded subtree inside the nested repo: if it were walked before
// being filtered, it would exhaust the tiny budget and reject.
for (let i = 0; i < 20; i += 1) {
await writeRel(root, `packages/app/excluded/file-${i}.ts`)
}
await expect(
expandQuickOpenGitFileListing({
rootPath: root,
gitPaths: [staged('160000', 'packages/app')],
excludePathPrefixes: ['packages/app/excluded'],
budget: createQuickOpenReaddirBudget({ maxFiles: 5 })
})
).resolves.toEqual(['packages/app/keep.ts'])
})
it('expands allowed ignored directories without walking blocked or excluded directories', async () => {
const root = await makeTempRoot()
await writeRel(root, 'dist/generated.js')
await writeRel(root, 'node_modules/pkg/index.js')
await writeRel(root, '.cache/state.json')
await writeRel(root, '.local/share/state.json')
await writeRel(root, '.local/config.toml')
await writeRel(root, 'excluded/other.js')
await expect(
expandQuickOpenGitFileListing({
rootPath: root,
gitPaths: [],
directoryPaths: [
'dist/',
'.local/',
'node_modules/',
'.cache/',
'.local/share/',
'excluded/'
],
excludePathPrefixes: ['excluded'],
budget: createQuickOpenReaddirBudget({ maxFiles: 2 })
})
).resolves.toEqual(['.local/config.toml', 'dist/generated.js'])
const walkedPaths = opendirMock.mock.calls.map(([path]) => path)
expect(walkedPaths).toContain(join(root, 'dist'))
expect(walkedPaths).toContain(join(root, '.local'))
expect(walkedPaths).not.toContain(join(root, '.local', 'share'))
})
it('batches many allowed directory placeholders with bounded concurrency', async () => {
const root = await makeTempRoot()
const directoryPaths = Array.from({ length: 40 }, (_, index) => `generated-${index}/`)
await Promise.all(
directoryPaths.map((directoryPath, index) =>
writeRel(root, `${directoryPath}file-${index}.ts`)
)
)
const actual = await vi.importActual<typeof import('node:fs/promises')>('node:fs/promises') // eslint-disable-line @typescript-eslint/consistent-type-imports -- vi.importActual requires inline import()
let activeReads = 0
let maxActiveReads = 0
opendirMock.mockImplementation(async (...args: Parameters<typeof actual.opendir>) => {
activeReads++
maxActiveReads = Math.max(maxActiveReads, activeReads)
await new Promise((resolve) => setTimeout(resolve, 5))
try {
return await actual.opendir(...args)
} finally {
activeReads--
}
})
try {
const files = await expandQuickOpenGitFileListing({
rootPath: root,
gitPaths: [],
directoryPaths
})
expect(files).toHaveLength(directoryPaths.length)
expect(maxActiveReads).toBeGreaterThan(1)
expect(maxActiveReads).toBeLessThanOrEqual(32)
} finally {
opendirMock.mockImplementation(actual.opendir)
}
})
it('preserves symlink leaves from collapsed Git directories without following them', async () => {
const root = await makeTempRoot()
await mkdirRel(root, 'scratch')
await writeRel(root, 'target/file.ts')
try {
await symlink(join(root, 'target', 'file.ts'), join(root, 'scratch', 'link.ts'))
await symlink(join(root, 'target'), join(root, 'scratch', 'linked-dir'), 'dir')
} catch (err) {
if ((err as NodeJS.ErrnoException).code === 'EPERM') {
return
}
throw err
}
await expect(
expandQuickOpenGitFileListing({
rootPath: root,
gitPaths: [],
directoryPaths: ['scratch/']
})
).resolves.toEqual(['scratch/link.ts', 'scratch/linked-dir'])
})
it('does not follow a collapsed directory replaced by a symlink', async () => {
const root = await makeTempRoot()
const outsideRoot = await makeTempRoot()
await writeRel(outsideRoot, 'secret.ts')
try {
await symlink(outsideRoot, join(root, 'dist'), 'dir')
} catch (err) {
if ((err as NodeJS.ErrnoException).code === 'EPERM') {
return
}
throw err
}
await expect(
expandQuickOpenGitFileListing({
rootPath: root,
gitPaths: [],
directoryPaths: ['dist/']
})
).resolves.toEqual([])
})
it('discards entries when a collapsed directory changes during opendir', async () => {
const root = await makeTempRoot()
const outsideRoot = await makeTempRoot()
await mkdirRel(root, 'dist')
await writeRel(outsideRoot, 'secret.ts')
const actual = await vi.importActual<typeof import('node:fs/promises')>('node:fs/promises') // eslint-disable-line @typescript-eslint/consistent-type-imports -- vi.importActual requires inline import()
const distPath = join(root, 'dist')
let swapped = false
opendirMock.mockImplementation(async (...args: Parameters<typeof actual.opendir>) => {
if (!swapped && args[0] === distPath) {
swapped = true
await rename(distPath, join(root, 'old-dist'))
await symlink(outsideRoot, distPath, 'dir')
}
return actual.opendir(...args)
})
try {
await expect(
expandQuickOpenGitFileListing({
rootPath: root,
gitPaths: [],
directoryPaths: ['dist/']
})
).resolves.toEqual([])
} finally {
opendirMock.mockImplementation(actual.opendir)
}
})
it('walks overlapping primary and ignored placeholders only once', async () => {
const root = await makeTempRoot()
await writeRel(root, 'foo/a.ts')
await writeRel(root, 'foo/bar/b.ts')
await expect(
expandQuickOpenGitFileListing({
rootPath: root,
gitPaths: [],
directoryPaths: ['foo/', 'foo/bar/'],
budget: createQuickOpenReaddirBudget({ maxFiles: 2 })
})
).resolves.toEqual(['foo/a.ts', 'foo/bar/b.ts'])
expect(
opendirMock.mock.calls.filter(([path]) => path === join(root, 'foo', 'bar'))
).toHaveLength(1)
})
it('rejects instead of returning a partial ignored-directory expansion', async () => {
const root = await makeTempRoot()
await writeRel(root, 'dist/a.js')
await writeRel(root, 'dist/b.js')
await expect(
expandQuickOpenGitFileListing({
rootPath: root,
gitPaths: [],
directoryPaths: ['dist/'],
budget: createQuickOpenReaddirBudget({ maxFiles: 1 })
})
).rejects.toThrow('File listing exceeded')
})
it('supports more than 10,000 files while keeping the default safety cap', async () => {
expect(QUICK_OPEN_READDIR_MAX_FILES).toBeGreaterThan(10_000)
const root = await makeTempRoot()
await mkdirRel(root, 'dist')
opendirMock.mockResolvedValueOnce({
async *[Symbol.asyncIterator]() {
for (let index = 0; index <= QUICK_OPEN_READDIR_MAX_FILES; index += 1) {
yield {
name: `file-${index}.ts`,
isDirectory: () => false,
isFile: () => true,
isSymbolicLink: () => false
}
}
}
})
// Why: directory collapse prevents generated trees from flooding the relay;
// the Git fallback must reject rather than silently return a partial list.
await expect(
expandQuickOpenGitFileListing({
rootPath: root,
gitPaths: [],
directoryPaths: ['dist/']
})
).rejects.toThrow(`File listing exceeded ${QUICK_OPEN_READDIR_MAX_FILES} files`)
})
it('identifies budget errors so callers can translate only those to install-rg guidance', () => {
expect(isQuickOpenReaddirBudgetError(new Error('File listing timed out'))).toBe(true)
expect(isQuickOpenReaddirBudgetError(new Error('File listing exceeded 10000 files'))).toBe(true)
// Genuine git failures must keep their own message, not the install-rg toast.
expect(isQuickOpenReaddirBudgetError(new Error('git ls-files killed by SIGTERM'))).toBe(false)
expect(isQuickOpenReaddirBudgetError('File listing timed out')).toBe(false)
})
it('rejects on deadline instead of returning a partial list', async () => {
const root = await makeTempRoot()
await writeRel(root, 'src/index.ts')
await expect(
listQuickOpenFilesWithReaddir(root, {
budget: createQuickOpenReaddirBudget({ nowMs: Date.now() - 2_000, timeoutMs: 1_000 })
})
).rejects.toThrow('File listing timed out')
})
it('returns a successful bounded prefix without consuming the safety cap', async () => {
const root = await makeTempRoot()
await writeRel(root, 'a.ts')
await writeRel(root, 'b.ts')
const files = await listQuickOpenFilesWithReaddir(root, { maxResults: 1 })
expect(files).toHaveLength(1)
expect(['a.ts', 'b.ts']).toContain(files[0])
})
it('does not list symlinked files or follow symlinked directories', async () => {
const root = await makeTempRoot()
await writeRel(root, 'src/index.ts')
await writeRel(root, 'target/file.ts')
try {
await symlink(join(root, 'src/index.ts'), join(root, 'src/link.ts'))
await symlink(join(root, 'target'), join(root, 'linked-dir'), 'dir')
} catch (err) {
if ((err as NodeJS.ErrnoException).code === 'EPERM') {
return
}
throw err
}
const files = await listQuickOpenFilesWithReaddir(root)
expect(files).toEqual(expect.arrayContaining(['src/index.ts', 'target/file.ts']))
expect(files).not.toContain('src/link.ts')
expect(files).not.toContain('linked-dir/file.ts')
})
it('walks an explicitly selected symlinked workspace root', async () => {
const targetRoot = await makeTempRoot()
const linkContainer = await makeTempRoot()
await writeRel(targetRoot, 'src/index.ts')
const linkedRoot = join(linkContainer, 'linked-workspace')
try {
await symlink(targetRoot, linkedRoot, 'dir')
} catch (err) {
if ((err as NodeJS.ErrnoException).code === 'EPERM') {
return
}
throw err
}
await expect(listQuickOpenFilesWithReaddir(linkedRoot)).resolves.toEqual(['src/index.ts'])
})
it('fills nested repo paths containing spaces and glob metacharacters', async () => {
const root = await makeTempRoot()
await makeNestedRepo(root, 'packages/app [one] space')
await writeRel(root, 'packages/app [one] space/src/main.ts')
await expect(
expandQuickOpenGitFileListing({
rootPath: root,
gitPaths: ['packages/app [one] space/']
})
).resolves.toEqual(['packages/app [one] space/src/main.ts'])
})
it('stops the walk with a cancellation error when the signal aborts (#7721)', async () => {
const root = await makeTempRoot()
await writeRel(root, 'src/a.ts')
await writeRel(root, 'src/b.ts')
const controller = new AbortController()
controller.abort()
const rejection = listQuickOpenFilesWithReaddir(root, { signal: controller.signal })
await expect(rejection).rejects.toSatisfy(isFileListingCancellation)
// Cancellation must never be mistaken for a budget error, which callers
// translate into "install rg" guidance.
await rejection.catch((err) => expect(isQuickOpenReaddirBudgetError(err)).toBe(false))
})
it('stops ignored-directory expansion when the signal aborts (#7721)', async () => {
const root = await makeTempRoot()
await writeRel(root, 'src/kept.ts')
const controller = new AbortController()
controller.abort()
await expect(
expandQuickOpenGitFileListing({
rootPath: root,
gitPaths: [],
directoryPaths: ['src/'],
signal: controller.signal
})
).rejects.toSatisfy(isFileListingCancellation)
})
it('closes the directory when cancellation lands after opendir', async () => {
const root = await makeTempRoot()
const controller = new AbortController()
const actual = await vi.importActual<typeof NodeFsPromises>('node:fs/promises')
let closeCalls = 0
opendirMock.mockImplementationOnce(async (...args: Parameters<typeof actual.opendir>) => {
const directory = await actual.opendir(...args)
const close = directory.close.bind(directory)
directory.close = async () => {
closeCalls += 1
await close()
}
controller.abort()
return directory
})
await expect(
listQuickOpenFilesWithReaddir(root, { signal: controller.signal })
).rejects.toSatisfy(isFileListingCancellation)
expect(closeCalls).toBe(1)
})
})
-317
View File
@@ -1,317 +0,0 @@
import { join, relative } from 'node:path'
import { throwIfFileListingCancelled } from './file-listing-cancellation'
import { readQuickOpenDirectoryEntries } from './quick-open-directory-reader'
import { collapseQuickOpenExpansionPaths } from './quick-open-expansion-paths'
import { classifyQuickOpenGitEntry } from './quick-open-git-entry-classification'
import {
HIDDEN_DIR_BLOCKLIST,
shouldExcludeQuickOpenRelPath,
shouldIncludeQuickOpenPath
} from './quick-open-filter'
import {
assertQuickOpenReaddirDepth,
assertQuickOpenReaddirDeadline,
consumeQuickOpenReaddirDirectoryBudget,
consumeQuickOpenReaddirEntryBudget,
consumeQuickOpenReaddirFileBudget,
consumeQuickOpenReaddirPathBudget,
createQuickOpenReaddirBudget,
type QuickOpenReaddirBudget
} from './quick-open-readdir-budget'
export {
classifyQuickOpenGitEntry,
parseQuickOpenGitLsFilesEntry,
type QuickOpenGitEntryKind,
type QuickOpenGitLsFilesEntry
} from './quick-open-git-entry-classification'
export {
createQuickOpenReaddirBudget,
isQuickOpenReaddirBudgetError,
QUICK_OPEN_READDIR_MAX_DEPTH,
QUICK_OPEN_READDIR_MAX_DIRECTORIES,
QUICK_OPEN_READDIR_MAX_ENTRIES,
QUICK_OPEN_READDIR_MAX_FILES,
QUICK_OPEN_READDIR_MAX_PATH_CODE_UNITS,
QUICK_OPEN_READDIR_TIMEOUT_MS
} from './quick-open-readdir-budget'
const QUICK_OPEN_READDIR_CONCURRENCY = 32
function shouldDescend(name: string): boolean {
return name !== 'node_modules' && !HIDDEN_DIR_BLOCKLIST.has(name)
}
function toRelPath(rootPath: string, absPath: string): string {
// Why: path.relative returns backslashes on Windows, while Quick Open paths
// are always stored and matched with POSIX separators.
return relative(rootPath, absPath).replace(/\\/g, '/')
}
function joinRootRel(rootPath: string, relPath: string): string {
return join(rootPath, ...relPath.split('/').filter(Boolean))
}
function normalizeGitEntry(entry: string): string {
return entry.replace(/\/+$/, '')
}
// Translate workspace-root-relative exclude prefixes into prefixes relative to
// one expanded subtree, so its walk prunes them during traversal. Prefixes
// outside that subtree are dropped because they cannot match.
function rebaseExcludePrefixesForSubtree(
excludePathPrefixes: readonly string[],
subtreeRelPath: string
): string[] {
const base = `${subtreeRelPath}/`
const rebased: string[] = []
for (const prefix of excludePathPrefixes) {
if (prefix.startsWith(base)) {
rebased.push(prefix.slice(base.length))
}
}
return rebased
}
export async function listQuickOpenFilesWithReaddir(
rootPath: string,
opts: {
excludePathPrefixes?: readonly string[]
workspaceRelPathPrefix?: string
budget?: QuickOpenReaddirBudget
maxResults?: number
signal?: AbortSignal
} = {}
): Promise<string[]> {
return listQuickOpenFilesFromRoots(
[
{
rootPath,
excludePathPrefixes: opts.excludePathPrefixes ?? [],
workspaceRelPathPrefix: opts.workspaceRelPathPrefix,
allowRootSymlink: true
}
],
opts.budget ?? createQuickOpenReaddirBudget(),
opts.signal,
opts.maxResults
)
}
type QuickOpenReaddirRoot = {
rootPath: string
excludePathPrefixes: readonly string[]
workspaceRelPathPrefix?: string
outputPathPrefix?: string
includeSymlinks?: boolean
allowRootSymlink?: boolean
}
async function listQuickOpenFilesFromRoots(
roots: readonly QuickOpenReaddirRoot[],
budget: QuickOpenReaddirBudget,
signal?: AbortSignal,
maxResults?: number,
knownFiles?: ReadonlySet<string>
): Promise<string[]> {
const files: string[] = []
if (maxResults !== undefined && maxResults <= 0) {
return files
}
let pendingDirectories: {
root: QuickOpenReaddirRoot
absPath: string
depth: number
isRoot: boolean
}[] = []
for (const root of roots) {
assertQuickOpenReaddirDepth(budget, 0)
consumeQuickOpenReaddirDirectoryBudget(budget)
consumeQuickOpenReaddirPathBudget(budget, root.rootPath)
pendingDirectories.push({ root, absPath: root.rootPath, depth: 0, isRoot: true })
}
while (pendingDirectories.length > 0) {
const nextDirectories: typeof pendingDirectories = []
for (
let offset = 0;
offset < pendingDirectories.length;
offset += QUICK_OPEN_READDIR_CONCURRENCY
) {
// Why: batch only the filesystem calls. Result processing stays serial,
// so the shared cap remains exact while shallow placeholder-heavy repos
// do not pay one relay event-loop turn per directory.
throwIfFileListingCancelled(signal)
assertQuickOpenReaddirDeadline(budget)
const batch = pendingDirectories.slice(offset, offset + QUICK_OPEN_READDIR_CONCURRENCY)
const readResults = await Promise.allSettled(
batch.map(async (pending) => {
const entries = await readQuickOpenDirectoryEntries({
absPath: pending.absPath,
allowSymlinkedRoot: Boolean(pending.isRoot && pending.root.allowRootSymlink),
budget,
signal
})
return { pending, entries }
})
)
const entryGroups: {
pending: (typeof pendingDirectories)[number]
entries: Awaited<ReturnType<typeof readQuickOpenDirectoryEntries>>
}[] = []
for (const result of readResults) {
if (result.status === 'rejected') {
throw result.reason
}
entryGroups.push(result.value)
}
// Why: an empty directory has no per-entry checkpoint below. Cancellation
// or timeout that lands during opendir must still reject, never resolve [].
throwIfFileListingCancelled(signal)
assertQuickOpenReaddirDeadline(budget)
for (const { pending, entries } of entryGroups) {
for (const entry of entries) {
throwIfFileListingCancelled(signal)
assertQuickOpenReaddirDeadline(budget)
const name = entry.name
const absPath = join(pending.absPath, name)
const relPath = toRelPath(pending.root.rootPath, absPath)
const workspaceRelPath = pending.root.workspaceRelPathPrefix
? `${pending.root.workspaceRelPathPrefix}/${relPath}`
: relPath
if (shouldExcludeQuickOpenRelPath(relPath, pending.root.excludePathPrefixes)) {
continue
}
if (entry.kind === 'directory') {
if (shouldDescend(name) && shouldIncludeQuickOpenPath(workspaceRelPath)) {
const depth = pending.depth + 1
assertQuickOpenReaddirDepth(budget, depth)
consumeQuickOpenReaddirDirectoryBudget(budget)
consumeQuickOpenReaddirPathBudget(budget, absPath)
nextDirectories.push({ root: pending.root, absPath, depth, isRoot: false })
}
continue
}
if (
(entry.kind === 'file' || (pending.root.includeSymlinks && entry.kind === 'symlink')) &&
shouldIncludeQuickOpenPath(workspaceRelPath)
) {
const outputPath = pending.root.outputPathPrefix
? `${pending.root.outputPathPrefix}/${relPath}`
: relPath
if (knownFiles?.has(outputPath)) {
continue
}
consumeQuickOpenReaddirFileBudget(budget)
consumeQuickOpenReaddirPathBudget(budget, outputPath)
files.push(outputPath)
// Why: a caller result limit is a successful bounded prefix, while
// the separate traversal budget still rejects incomplete scans.
if (maxResults !== undefined && files.length >= maxResults) {
return files
}
}
}
}
}
pendingDirectories = nextDirectories
}
return files
}
export async function expandQuickOpenGitFileListing(opts: {
rootPath: string
gitPaths: Iterable<string>
directoryPaths?: Iterable<string>
excludePathPrefixes?: readonly string[]
budget?: QuickOpenReaddirBudget
maxResults?: number
signal?: AbortSignal
}): Promise<string[]> {
if (opts.maxResults !== undefined && opts.maxResults <= 0) {
return []
}
const files = new Set<string>()
const excludePathPrefixes = opts.excludePathPrefixes ?? []
const budget = opts.budget ?? createQuickOpenReaddirBudget()
const expansionPaths = new Map<string, boolean>()
const addFinalPath = (relPath: string): void => {
if (!relPath) {
return
}
if (shouldExcludeQuickOpenRelPath(relPath, excludePathPrefixes)) {
return
}
if (shouldIncludeQuickOpenPath(relPath)) {
files.add(relPath)
}
}
for (const rawPath of opts.gitPaths) {
throwIfFileListingCancelled(opts.signal)
assertQuickOpenReaddirDeadline(budget)
const { kind, relPath } = await classifyQuickOpenGitEntry(opts.rootPath, rawPath)
if (kind === 'keep') {
addFinalPath(relPath)
continue
}
if (kind === 'drop-placeholder') {
continue
}
consumeQuickOpenReaddirEntryBudget(budget)
consumeQuickOpenReaddirPathBudget(budget, relPath)
expansionPaths.set(relPath, expansionPaths.get(relPath) ?? false)
}
for (const rawPath of opts.directoryPaths ?? []) {
throwIfFileListingCancelled(opts.signal)
assertQuickOpenReaddirDeadline(budget)
const relPath = normalizeGitEntry(rawPath)
// Why: Git intentionally leaves collapsed directories unexpanded; reject
// blocked and nested-worktree placeholders before any filesystem IO.
if (
!relPath ||
shouldExcludeQuickOpenRelPath(relPath, excludePathPrefixes) ||
!shouldIncludeQuickOpenPath(relPath)
) {
continue
}
consumeQuickOpenReaddirEntryBudget(budget)
consumeQuickOpenReaddirPathBudget(budget, relPath)
// Why: before directory collapse, Git returned untracked symlink entries
// without following them. Preserve those paths when expanding placeholders.
expansionPaths.set(relPath, true)
}
const expandedFiles = await listQuickOpenFilesFromRoots(
collapseQuickOpenExpansionPaths(expansionPaths).map(([relPath, includeSymlinks]) => ({
rootPath: joinRootRel(opts.rootPath, relPath),
// Why: exclude prefixes are workspace-root-relative; rebase them onto
// each expanded subtree so blocked work is pruned before consuming cap.
excludePathPrefixes: rebaseExcludePrefixesForSubtree(excludePathPrefixes, relPath),
// Why: Git can collapse `.local/share/` to `.local/`; keep workspace
// context so the walker still prunes the multi-segment blocklist.
workspaceRelPathPrefix: relPath,
outputPathPrefix: relPath,
includeSymlinks
})),
budget,
opts.signal,
opts.maxResults === undefined ? undefined : Math.max(0, opts.maxResults - files.size),
files
)
for (const expandedFile of expandedFiles) {
addFinalPath(expandedFile)
}
return Array.from(files).slice(0, opts.maxResults)
}
-23
View File
@@ -1,23 +0,0 @@
export const SSH_AI_VAULT_LIST_SESSIONS_METHOD = 'aiVault.listSessions' as const
export const SSH_AI_VAULT_RESOLVE_SESSION_TITLES_METHOD = 'aiVault.resolveSessionTitles' as const
export const SSH_AI_VAULT_LIST_SESSIONS_TIMEOUT_MS = 130_000
export const SSH_AI_VAULT_RESOLVE_SESSION_TITLES_TIMEOUT_MS = 15_000
export const SSH_AI_VAULT_LIST_LIMIT_MAX = 1000
export const SSH_AI_VAULT_SCOPE_PATH_MAX_LENGTH = 4096
export type SshAiVaultRelayListParams = {
includeAntigravityIdeSessions?: boolean
limit?: number
unlimited?: boolean
force?: boolean
scopePaths?: string[]
scopePathsTruncated?: boolean
}
export type SshAiVaultRelayTitleParams = {
requests: {
agent: 'claude' | 'codex'
sessionId: string
transcriptPath?: string
}[]
}
+1 -3
View File
@@ -419,9 +419,7 @@ export type PortForwardEntry = {
advertisedProtocol?: 'http' | 'https'
}
/** A listening port detected on the remote host by the relay.
* Keep in sync with src/relay/port-scan-handler.ts — DetectedPort.
* The relay is deployed as a standalone bundle and cannot import from shared. */
/** A listening port detected on a remote host. */
export type DetectedPort = {
port: number
host: string