mirror of
https://github.com/stablyai/orca.git
synced 2026-09-26 16:02:43 +00:00
Merge remote-tracking branch 'origin/main' into brennanb2025/ua-fallback-impl
This commit is contained in:
@@ -9,6 +9,8 @@ const MAX_CACHE_ENTRIES = 4096
|
||||
|
||||
export type SessionParseResumePoint = {
|
||||
state: ResumableSessionParseState
|
||||
mtimeMs: number
|
||||
sizeBytes: number | undefined
|
||||
// Byte offset just past the last complete ('\n'-terminated) line consumed;
|
||||
// a trailing unterminated line is deliberately left before this point.
|
||||
byteOffset: number
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { appendFile, mkdir, mkdtemp, rm, stat, writeFile } from 'node:fs/promises'
|
||||
import { appendFile, mkdir, mkdtemp, rm, stat, truncate, utimes, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, expect, it, vi } from 'vitest'
|
||||
@@ -170,6 +170,8 @@ it('replays only the appended lines on a resumed read', async () => {
|
||||
expect(firstRead?.outcome?.incomplete).toBe(false)
|
||||
|
||||
await appendFile(transcript, `${jsonLines(claudeTurns(5, 5))}\n`)
|
||||
const changedAt = new Date(firstRead!.start.candidate.file.mtimeMs + 2000)
|
||||
await utimes(transcript, changedAt, changedAt)
|
||||
consumer.reads.length = 0
|
||||
await scanAiVaultSessions({ ...roots, platform: 'darwin', limit: 20 })
|
||||
|
||||
@@ -183,6 +185,38 @@ it('replays only the appended lines on a resumed read', async () => {
|
||||
])
|
||||
})
|
||||
|
||||
it.each(['rewrite', 'truncate then regrow'])(
|
||||
're-reads a same-size %s from zero',
|
||||
async (operation) => {
|
||||
const { transcript } = await writeClaudeFixture()
|
||||
const before = await claudeCandidate(transcript)
|
||||
await parseAgentSessionFileCached(before, 'darwin')
|
||||
const consumer = recordingConsumer()
|
||||
const rewritten = `${jsonLines(claudeTurns(1, 4))}\n`.replace('reply 4', 'fresh 4')
|
||||
expect(Buffer.byteLength(rewritten)).toBe(before.file.sizeBytes)
|
||||
|
||||
if (operation === 'truncate then regrow') {
|
||||
await truncate(transcript, 0)
|
||||
await appendFile(transcript, rewritten)
|
||||
} else {
|
||||
await writeFile(transcript, rewritten)
|
||||
}
|
||||
const changedAt = new Date(before.file.mtimeMs + 2000)
|
||||
await utimes(transcript, changedAt, changedAt)
|
||||
const session = await parseAgentSessionFileCached(await claudeCandidate(transcript), 'darwin')
|
||||
|
||||
expect(consumer.reads).toHaveLength(1)
|
||||
expect(consumer.reads[0].start.mode).toBe('replace')
|
||||
expect(consumer.reads[0].start.previousByteOffset).toBe(0)
|
||||
expect(textsFor(consumer.reads, 'claude')).toContain('assistant:fresh 4')
|
||||
expect(textsFor(consumer.reads, 'claude')).not.toContain('assistant:reply 4')
|
||||
resetSessionParseCacheForTests()
|
||||
expect(session).toEqual(
|
||||
await parseAgentSessionFileCached(await claudeCandidate(transcript), 'darwin')
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
it('publishes a trailing unterminated line once, when it is complete', async () => {
|
||||
const { roots, transcript } = await writeClaudeFixture()
|
||||
const consumer = recordingConsumer()
|
||||
|
||||
@@ -68,6 +68,9 @@ export async function readResumableTranscript(args: {
|
||||
resume !== null &&
|
||||
typeof file.sizeBytes === 'number' &&
|
||||
file.sizeBytes >= resume.byteOffset &&
|
||||
file.mtimeMs >= resume.mtimeMs &&
|
||||
// A changed timestamp without growth signals a rewrite, even at a valid line boundary.
|
||||
(file.mtimeMs === resume.mtimeMs || file.sizeBytes > (resume.sizeBytes ?? resume.byteOffset)) &&
|
||||
(resume.byteOffset === 0 || (await endsWithNewlineAt(file.path, resume.byteOffset)))
|
||||
|
||||
// Clone before consuming: a failed read must not corrupt the cached state,
|
||||
@@ -127,7 +130,13 @@ export async function readResumableTranscript(args: {
|
||||
channel.finishRead({ session, byteOffset: readResult.consumedThrough, incomplete: false })
|
||||
return {
|
||||
session,
|
||||
resume: { state, byteOffset: readResult.consumedThrough, channel }
|
||||
resume: {
|
||||
state,
|
||||
byteOffset: readResult.consumedThrough,
|
||||
mtimeMs: file.mtimeMs,
|
||||
sizeBytes: file.sizeBytes,
|
||||
channel
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
channel.finishRead({ session: null, byteOffset: startOffset, incomplete: true })
|
||||
|
||||
Reference in New Issue
Block a user