mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 16:03:17 +00:00
fix(codex): resume compressed rollout representations
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
writeFileSync
|
||||
} from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { basename, join } from 'node:path'
|
||||
import { prepareLegacySharedCodexSessionResume } from './codex-legacy-session-resume'
|
||||
|
||||
describe('prepareLegacySharedCodexSessionResume', () => {
|
||||
@@ -47,6 +47,16 @@ describe('prepareLegacySharedCodexSessionResume', () => {
|
||||
expect(statSync(targetPath).ino).toBe(statSync(rolloutPath).ino)
|
||||
})
|
||||
|
||||
it('materializes a compressed legacy rollout without changing its representation', async () => {
|
||||
const compressedPath = `${rolloutPath}.zst`
|
||||
rmSync(rolloutPath)
|
||||
rolloutPath = compressedPath
|
||||
writeFileSync(rolloutPath, 'compressed-rollout', 'utf-8')
|
||||
|
||||
await expect(prepare()).resolves.toEqual({ useRealCodexHome: true })
|
||||
expect(readFileSync(targetRolloutPath(), 'utf-8')).toBe('compressed-rollout')
|
||||
})
|
||||
|
||||
it('coalesces concurrent resumes of the same legacy rollout', async () => {
|
||||
const [first, second] = await Promise.all([prepare(), prepare()])
|
||||
|
||||
@@ -129,14 +139,7 @@ describe('prepareLegacySharedCodexSessionResume', () => {
|
||||
}
|
||||
|
||||
function targetRolloutPath(): string {
|
||||
return join(
|
||||
systemHome,
|
||||
'sessions',
|
||||
'2026',
|
||||
'07',
|
||||
'20',
|
||||
'rollout-2026-07-20T12-00-00-session.jsonl'
|
||||
)
|
||||
return join(systemHome, 'sessions', '2026', '07', '20', basename(rolloutPath))
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ function isLegacyRolloutRelativePath(relativePath: string): boolean {
|
||||
/^\d{4}$/.test(parts[0] ?? '') &&
|
||||
/^\d{2}$/.test(parts[1] ?? '') &&
|
||||
/^\d{2}$/.test(parts[2] ?? '') &&
|
||||
/^rollout-.+\.jsonl$/.test(parts[3] ?? '')
|
||||
/^rollout-.+\.jsonl(?:\.zst)?$/.test(parts[3] ?? '')
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,34 @@ export async function* listCodexSessionJsonlFilesIncrementally(
|
||||
rootPath: string,
|
||||
options: CodexSessionBridgeIncrementalOptions,
|
||||
onDirectoryError?: (directoryPath: string, error: unknown) => void | Promise<void>
|
||||
): AsyncGenerator<string> {
|
||||
yield* listCodexSessionFilesIncrementally(
|
||||
rootPath,
|
||||
options,
|
||||
(fileName) => fileName.endsWith('.jsonl'),
|
||||
onDirectoryError
|
||||
)
|
||||
}
|
||||
|
||||
/** Yields both physical representations that current Codex can resume. */
|
||||
export async function* listCodexSessionRolloutFilesIncrementally(
|
||||
rootPath: string,
|
||||
options: CodexSessionBridgeIncrementalOptions,
|
||||
onDirectoryError?: (directoryPath: string, error: unknown) => void | Promise<void>
|
||||
): AsyncGenerator<string> {
|
||||
yield* listCodexSessionFilesIncrementally(
|
||||
rootPath,
|
||||
options,
|
||||
(fileName) => fileName.endsWith('.jsonl') || fileName.endsWith('.jsonl.zst'),
|
||||
onDirectoryError
|
||||
)
|
||||
}
|
||||
|
||||
async function* listCodexSessionFilesIncrementally(
|
||||
rootPath: string,
|
||||
options: CodexSessionBridgeIncrementalOptions,
|
||||
isSessionFile: (fileName: string) => boolean,
|
||||
onDirectoryError?: (directoryPath: string, error: unknown) => void | Promise<void>
|
||||
): AsyncGenerator<string> {
|
||||
const batchSize = Math.max(1, options.batchSize ?? INCREMENTAL_BRIDGE_BATCH_SIZE)
|
||||
const yieldMs = Math.max(0, options.yieldMs ?? INCREMENTAL_BRIDGE_YIELD_MS)
|
||||
@@ -75,7 +103,7 @@ export async function* listCodexSessionJsonlFilesIncrementally(
|
||||
const childPath = join(currentDirectory, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
pendingDirectories.push(childPath)
|
||||
} else if (entry.isFile() && entry.name.endsWith('.jsonl')) {
|
||||
} else if (entry.isFile() && isSessionFile(entry.name)) {
|
||||
yield childPath
|
||||
}
|
||||
entriesSinceYield += 1
|
||||
|
||||
@@ -95,11 +95,69 @@ describe('resolveTrustedCodexSessionResumeHome', () => {
|
||||
).toBe(homePath)
|
||||
})
|
||||
|
||||
it('follows Codex when a persisted plain rollout was compressed in place', async () => {
|
||||
const homePath = mkdtempSync(join(tmpdir(), 'orca-codex-resume-home-'))
|
||||
tempRoots.push(homePath)
|
||||
const plainPath = join(
|
||||
homePath,
|
||||
'sessions',
|
||||
'2026',
|
||||
'07',
|
||||
'20',
|
||||
'rollout-2026-07-20T12-00-00-session.jsonl'
|
||||
)
|
||||
const compressedPath = `${plainPath}.zst`
|
||||
mkdirSync(join(plainPath, '..'), { recursive: true })
|
||||
writeFileSync(compressedPath, 'compressed-rollout')
|
||||
|
||||
await expect(
|
||||
findTrustedCodexSessionResume({
|
||||
sessionId: 'session-a',
|
||||
transcriptPath: plainPath,
|
||||
trustedCodexHomes: [homePath]
|
||||
})
|
||||
).resolves.toEqual({ homePath, transcriptPath: compressedPath })
|
||||
|
||||
writeFileSync(plainPath, 'active-rollout')
|
||||
await expect(
|
||||
findTrustedCodexSessionResume({
|
||||
sessionId: 'session-a',
|
||||
transcriptPath: compressedPath,
|
||||
trustedCodexHomes: [homePath]
|
||||
})
|
||||
).resolves.toEqual({ homePath, transcriptPath: plainPath })
|
||||
})
|
||||
|
||||
it('finds compressed rollouts for legacy records without transcript provenance', async () => {
|
||||
const homePath = mkdtempSync(join(tmpdir(), 'orca-codex-resume-home-'))
|
||||
tempRoots.push(homePath)
|
||||
const sessionId = '019f81b9-19a9-7651-a8d1-352d9420bd11'
|
||||
const compressedPath = join(
|
||||
homePath,
|
||||
'sessions',
|
||||
'2026',
|
||||
'07',
|
||||
'20',
|
||||
`rollout-2026-07-20T12-00-00-${sessionId}.jsonl.zst`
|
||||
)
|
||||
mkdirSync(join(compressedPath, '..'), { recursive: true })
|
||||
writeFileSync(compressedPath, 'compressed-rollout')
|
||||
|
||||
await expect(
|
||||
findTrustedCodexSessionResume({
|
||||
sessionId,
|
||||
transcriptPath: undefined,
|
||||
trustedCodexHomes: [homePath]
|
||||
})
|
||||
).resolves.toEqual({ homePath, transcriptPath: compressedPath })
|
||||
})
|
||||
|
||||
it('finds older saved sessions by id when transcript provenance is absent', async () => {
|
||||
const sessionId = '019f81b9-19a9-7651-a8d1-352d9420bd11'
|
||||
const rolloutPath = `/managed/account/home/sessions/2026/07/20/rollout-2026-07-20T15-50-19-${sessionId}.jsonl`
|
||||
const listSessionFiles = async function* (sessionsRoot: string): AsyncIterable<string> {
|
||||
if (sessionsRoot === '/managed/account/home/sessions') {
|
||||
yield `/managed/account/home/sessions/misplaced-${sessionId}.jsonl`
|
||||
yield rolloutPath
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,15 @@ import {
|
||||
normalizeRuntimePathForComparison,
|
||||
relativePathInsideRoot
|
||||
} from '../../shared/cross-platform-path'
|
||||
import { listCodexSessionJsonlFilesIncrementally } from './codex-session-file-listing'
|
||||
import { listCodexSessionRolloutFilesIncrementally } from './codex-session-file-listing'
|
||||
|
||||
const ROLLOUT_RELATIVE_PATH = /^\d{4}\/\d{2}\/\d{2}\/rollout-[^/]+\.jsonl$/
|
||||
// Why: only Codex's dated rollout layout may establish account-home provenance; nested/misplaced JSONL must not select credentials.
|
||||
const ROLLOUT_RELATIVE_PATH = /^\d{4}\/\d{2}\/\d{2}\/rollout-[^/]+\.jsonl(?:\.zst)?$/
|
||||
|
||||
function isCodexRolloutInsideSessionsRoot(sessionsRoot: string, filePath: string): boolean {
|
||||
const relativePath = relativePathInsideRoot(sessionsRoot, filePath)
|
||||
return Boolean(relativePath && ROLLOUT_RELATIVE_PATH.test(relativePath.replace(/\\/g, '/')))
|
||||
}
|
||||
|
||||
function isRegularFile(filePath: string): boolean {
|
||||
try {
|
||||
@@ -17,23 +23,52 @@ function isRegularFile(filePath: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveExistingRolloutPath(
|
||||
transcriptPath: string,
|
||||
fileIsRegular: (filePath: string) => boolean
|
||||
): string | null {
|
||||
const plainPath = transcriptPath.endsWith('.jsonl.zst')
|
||||
? transcriptPath.slice(0, -'.zst'.length)
|
||||
: transcriptPath.endsWith('.jsonl')
|
||||
? transcriptPath
|
||||
: null
|
||||
if (!plainPath) {
|
||||
return fileIsRegular(transcriptPath) ? transcriptPath : null
|
||||
}
|
||||
if (fileIsRegular(plainPath)) {
|
||||
return plainPath
|
||||
}
|
||||
const compressedPath = `${plainPath}.zst`
|
||||
return fileIsRegular(compressedPath) ? compressedPath : null
|
||||
}
|
||||
|
||||
function resolveTrustedCodexSessionResume(args: {
|
||||
transcriptPath: string | undefined
|
||||
trustedCodexHomes: readonly string[]
|
||||
fileIsRegular?: (filePath: string) => boolean
|
||||
}): { homePath: string; transcriptPath: string } | null {
|
||||
const persistedPath = args.transcriptPath?.trim()
|
||||
const transcriptPath = persistedPath
|
||||
? resolveExistingRolloutPath(persistedPath, args.fileIsRegular ?? isRegularFile)
|
||||
: null
|
||||
if (!transcriptPath) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (const homePath of args.trustedCodexHomes) {
|
||||
if (isCodexRolloutInsideSessionsRoot(join(homePath, 'sessions'), transcriptPath)) {
|
||||
return { homePath, transcriptPath }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function resolveTrustedCodexSessionResumeHome(args: {
|
||||
transcriptPath: string | undefined
|
||||
trustedCodexHomes: readonly string[]
|
||||
fileIsRegular?: (filePath: string) => boolean
|
||||
}): string | null {
|
||||
const transcriptPath = args.transcriptPath?.trim()
|
||||
if (!transcriptPath || !(args.fileIsRegular ?? isRegularFile)(transcriptPath)) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (const homePath of args.trustedCodexHomes) {
|
||||
const relativePath = relativePathInsideRoot(join(homePath, 'sessions'), transcriptPath)
|
||||
if (relativePath && ROLLOUT_RELATIVE_PATH.test(relativePath.replace(/\\/g, '/'))) {
|
||||
return homePath
|
||||
}
|
||||
}
|
||||
return null
|
||||
return resolveTrustedCodexSessionResume(args)?.homePath ?? null
|
||||
}
|
||||
|
||||
export async function findTrustedCodexSessionResume(args: {
|
||||
@@ -43,9 +78,9 @@ export async function findTrustedCodexSessionResume(args: {
|
||||
fileIsRegular?: (filePath: string) => boolean
|
||||
listSessionFiles?: (sessionsRoot: string) => AsyncIterable<string>
|
||||
}): Promise<{ homePath: string; transcriptPath: string } | null> {
|
||||
const directHome = resolveTrustedCodexSessionResumeHome(args)
|
||||
if (directHome && args.transcriptPath) {
|
||||
return { homePath: directHome, transcriptPath: args.transcriptPath.trim() }
|
||||
const directSession = resolveTrustedCodexSessionResume(args)
|
||||
if (directSession) {
|
||||
return directSession
|
||||
}
|
||||
if (!/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(args.sessionId)) {
|
||||
return null
|
||||
@@ -54,7 +89,7 @@ export async function findTrustedCodexSessionResume(args: {
|
||||
const listSessionFiles =
|
||||
args.listSessionFiles ??
|
||||
((sessionsRoot: string) =>
|
||||
listCodexSessionJsonlFilesIncrementally(sessionsRoot, { batchSize: 64, yieldMs: 0 }))
|
||||
listCodexSessionRolloutFilesIncrementally(sessionsRoot, { batchSize: 64, yieldMs: 0 }))
|
||||
const expectedSuffix = `-${args.sessionId}.jsonl`.toLowerCase()
|
||||
const seenHomes = new Set<string>()
|
||||
for (const homePath of args.trustedCodexHomes) {
|
||||
@@ -68,8 +103,22 @@ export async function findTrustedCodexSessionResume(args: {
|
||||
continue
|
||||
}
|
||||
for await (const filePath of listSessionFiles(sessionsRoot)) {
|
||||
if (getRuntimePathBasename(filePath).toLowerCase().endsWith(expectedSuffix)) {
|
||||
return { homePath, transcriptPath: filePath }
|
||||
const plainFilePath = filePath.endsWith('.jsonl.zst')
|
||||
? filePath.slice(0, -'.zst'.length)
|
||||
: filePath
|
||||
// Why: the directory entry already proves the compressed file exists; only probe its preferred plain sibling.
|
||||
const preferredFilePath =
|
||||
plainFilePath !== filePath && (args.fileIsRegular ?? isRegularFile)(plainFilePath)
|
||||
? plainFilePath
|
||||
: filePath
|
||||
const plainFileName = getRuntimePathBasename(preferredFilePath)
|
||||
.toLowerCase()
|
||||
.replace(/\.zst$/, '')
|
||||
if (
|
||||
isCodexRolloutInsideSessionsRoot(sessionsRoot, preferredFilePath) &&
|
||||
plainFileName.endsWith(expectedSuffix)
|
||||
) {
|
||||
return { homePath, transcriptPath: preferredFilePath }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user