mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 08:03:20 +00:00
perf(native-chat): stop the transcript lookup walking the whole projects tree
Naming reads the persisted conversation name on every Claude acquisition, which resolves the transcript path through walkSessionFiles. That collected EVERY match across ~/.claude/projects before taking files[0]: measured here at 84-103ms warm over 2665 directories and 5911 transcripts, competing with the attach it runs alongside. Return at the first match, which is the only one either caller reads and leaves traversal order untouched — 86ms to 0.75ms when the transcript is found early, unchanged when it is found late or not at all.
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import type { Dirent } from 'node:fs'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { walkSessionFiles } from './session-scanner-discovery'
|
||||
|
||||
function dirent(name: string, directory: boolean): Dirent {
|
||||
return { name, isDirectory: () => directory, isFile: () => !directory } as Dirent
|
||||
}
|
||||
|
||||
/** Two project directories, each holding the transcript the caller asked for. */
|
||||
function tree(): Record<string, Dirent[]> {
|
||||
return {
|
||||
'/projects': [dirent('a', true), dirent('b', true)],
|
||||
'/projects/a': [dirent('other.jsonl', false), dirent('target.jsonl', false)],
|
||||
'/projects/b': [dirent('target.jsonl', false)]
|
||||
}
|
||||
}
|
||||
|
||||
describe('walkSessionFiles stopAfterFirstMatch', () => {
|
||||
const options = (readDirectory: (path: string) => Promise<Dirent[]>) => ({
|
||||
extensions: new Set(['.jsonl']),
|
||||
filePredicate: (path: string) => path.endsWith('target.jsonl'),
|
||||
readDirectory
|
||||
})
|
||||
|
||||
it('stops walking once one file matches', async () => {
|
||||
const entries = tree()
|
||||
const readDirectory = vi.fn(async (path: string) => entries[path] ?? [])
|
||||
|
||||
const files = await walkSessionFiles('/projects', 'claude', [], {
|
||||
...options(readDirectory),
|
||||
stopAfterFirstMatch: true
|
||||
})
|
||||
|
||||
expect(files).toEqual(['/projects/a/target.jsonl'])
|
||||
// The whole point: the rest of the tree is never read. On a home with
|
||||
// thousands of transcripts that traversal is most of the call.
|
||||
expect(readDirectory.mock.calls.map((call) => call[0])).toEqual(['/projects', '/projects/a'])
|
||||
})
|
||||
|
||||
it('returns the same file the exhaustive walk would have returned first', async () => {
|
||||
const entries = tree()
|
||||
const readDirectory = vi.fn(async (path: string) => entries[path] ?? [])
|
||||
|
||||
const all = await walkSessionFiles('/projects', 'claude', [], options(readDirectory))
|
||||
|
||||
expect(all[0]).toBe('/projects/a/target.jsonl')
|
||||
expect(all).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
@@ -96,6 +96,11 @@ export async function walkSessionFiles(
|
||||
// rootDir, so pruned subtrees are never stat'd or parsed.
|
||||
directoryPredicate?: (name: string, depth: number) => boolean
|
||||
readDirectory?: (dirPath: string) => Promise<Dirent[]>
|
||||
/** Return as soon as one file matches. For a caller that only wants
|
||||
* `files[0]`, walking the rest of the tree is pure cost — on a home with
|
||||
* thousands of transcripts it is most of the call. Traversal order is
|
||||
* unchanged, so the file returned is the same one. */
|
||||
stopAfterFirstMatch?: boolean
|
||||
signal?: AbortSignal
|
||||
},
|
||||
depth = 0
|
||||
@@ -125,6 +130,9 @@ export async function walkSessionFiles(
|
||||
// avoiding the readdir cost of descending into them.
|
||||
if (options.directoryPredicate?.(entry.name, depth) ?? true) {
|
||||
files.push(...(await walkSessionFiles(fullPath, agent, issues, options, depth + 1)))
|
||||
if (options.stopAfterFirstMatch && files.length > 0) {
|
||||
return files
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -134,6 +142,9 @@ export async function walkSessionFiles(
|
||||
(options.filePredicate?.(fullPath) ?? true)
|
||||
) {
|
||||
files.push(fullPath)
|
||||
if (options.stopAfterFirstMatch) {
|
||||
return files
|
||||
}
|
||||
}
|
||||
}
|
||||
return files
|
||||
|
||||
@@ -234,6 +234,8 @@ async function resolveClaudeSessionFile(
|
||||
const files = await walkSessionFiles(projectsDir, 'claude', [], {
|
||||
extensions: new Set(['.jsonl']),
|
||||
filePredicate: (path) => basename(path) === targetName,
|
||||
// Only `files[0]` is ever used, and this runs on every acquisition.
|
||||
stopAfterFirstMatch: true,
|
||||
signal
|
||||
})
|
||||
if (files[0]) {
|
||||
@@ -291,6 +293,7 @@ async function findCodexRolloutInDirs(
|
||||
: (
|
||||
await walkSessionFiles(sessionsDir, 'codex', [], {
|
||||
...scanOptions,
|
||||
stopAfterFirstMatch: true,
|
||||
signal
|
||||
})
|
||||
)[0]
|
||||
|
||||
Reference in New Issue
Block a user