mirror of
https://github.com/stablyai/orca.git
synced 2026-10-26 00:03:55 +00:00
fix(terminal): retain live history after restore loss
This commit is contained in:
@@ -2217,9 +2217,12 @@ export class DaemonPtyAdapter implements IPtyProvider {
|
||||
sessionId,
|
||||
take.snapshot,
|
||||
[...take.drainedRecords, ...take.records],
|
||||
opts.requireContinuityProof === true
|
||||
? { requiredPreviousPendingOutputSeq: take.seq - 1 }
|
||||
: undefined
|
||||
{
|
||||
pendingRecordsAreComplete: take.seq === 1,
|
||||
...(opts.requireContinuityProof === true
|
||||
? { requiredPreviousPendingOutputSeq: take.seq - 1 }
|
||||
: {})
|
||||
}
|
||||
)
|
||||
const checkpoint = await this.historyManager.checkpoint(sessionId, snapshot, {
|
||||
pendingOutputSeq: take.seq
|
||||
@@ -2259,7 +2262,10 @@ export class DaemonPtyAdapter implements IPtyProvider {
|
||||
sessionId: string,
|
||||
liveSnapshot: NonNullable<TakePendingOutputResult['snapshot']>,
|
||||
pendingRecords: TakePendingOutputResult['records'],
|
||||
opts?: { requiredPreviousPendingOutputSeq?: number }
|
||||
opts: {
|
||||
pendingRecordsAreComplete: boolean
|
||||
requiredPreviousPendingOutputSeq?: number
|
||||
}
|
||||
): Promise<NonNullable<TakePendingOutputResult['snapshot']>> {
|
||||
if (!this.historyReader) {
|
||||
return liveSnapshot
|
||||
@@ -2270,8 +2276,9 @@ export class DaemonPtyAdapter implements IPtyProvider {
|
||||
wslDistro: this.wslDistrosBySessionId.get(sessionId)
|
||||
})
|
||||
if (
|
||||
opts?.requiredPreviousPendingOutputSeq !== undefined &&
|
||||
restoreInfo?.pendingOutputSeq !== opts.requiredPreviousPendingOutputSeq
|
||||
(!restoreInfo && !opts.pendingRecordsAreComplete) ||
|
||||
(opts.requiredPreviousPendingOutputSeq !== undefined &&
|
||||
restoreInfo?.pendingOutputSeq !== opts.requiredPreviousPendingOutputSeq)
|
||||
) {
|
||||
console.warn('[history] durable continuity unproven; using live snapshot:', sessionId)
|
||||
return liveSnapshot
|
||||
|
||||
@@ -8,6 +8,7 @@ import { DaemonServer } from './daemon-server'
|
||||
import { buildDurableCheckpointSnapshot } from './daemon-durable-history-snapshot'
|
||||
import { DAEMON_RESTORE_SCROLLBACK_ROWS } from './daemon-restore-scrollback-depth'
|
||||
import { DAEMON_SESSION_SCROLLBACK_ROWS } from './daemon-session-scrollback-window'
|
||||
import { getHistorySessionDirName } from './history-paths'
|
||||
import { getDaemonSocketPath } from './daemon-spawner'
|
||||
import { HeadlessEmulator } from './headless-emulator'
|
||||
import { HistoryManager } from './history-manager'
|
||||
@@ -372,6 +373,44 @@ describe('STA-4091 previously recoverable restore depth', () => {
|
||||
expect(snapshotText(restore ?? {})).toContain(OLDEST_WRITTEN_LINE)
|
||||
})
|
||||
|
||||
it('uses the live window when durable history disappears after a prior drain', async () => {
|
||||
const { id } = await adapter.spawn({
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
sessionId: 'missing-history-after-drain',
|
||||
cwd: '/tmp'
|
||||
})
|
||||
lastSubprocess.emitData(numberedOutput(DESKTOP_TERMINAL_SCROLLBACK_ROWS_DEFAULT))
|
||||
await adapter.getBufferSnapshot(id)
|
||||
lastSubprocess.emitData(`${FRESH_AFTER_CHECKPOINT}\r\n`)
|
||||
|
||||
const internals = adapter as unknown as { checkpointDirtySessions: () => Promise<void> }
|
||||
await internals.checkpointDirtySessions()
|
||||
rmSync(join(historyDir, getHistorySessionDirName(id), 'checkpoint.json'))
|
||||
lastSubprocess.emitData('TAIL_AFTER_HISTORY_LOSS\r\n')
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
|
||||
try {
|
||||
const snapshot = await adapter.getBufferSnapshot(id)
|
||||
const text = `${snapshot?.scrollbackAnsi ?? ''}${snapshot?.data ?? ''}`
|
||||
expect(text).toContain(NEWEST_WRITTEN_LINE)
|
||||
expect(text).toContain('TAIL_AFTER_HISTORY_LOSS')
|
||||
expect(text).not.toContain(OLDEST_WRITTEN_LINE)
|
||||
const restored = await new HistoryReader(historyDir).detectColdRestore(id, {
|
||||
ignoreCleanEnd: true
|
||||
})
|
||||
expect(snapshotText(restored ?? {})).toContain(NEWEST_WRITTEN_LINE)
|
||||
expect(snapshotText(restored ?? {})).toContain('TAIL_AFTER_HISTORY_LOSS')
|
||||
expect(restored?.scrollbackLines).toBe(DAEMON_SESSION_SCROLLBACK_ROWS)
|
||||
expect(warn).toHaveBeenCalledWith(
|
||||
'[history] durable continuity unproven; using live snapshot:',
|
||||
id
|
||||
)
|
||||
} finally {
|
||||
warn.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('preserves durable depth after an incremental append and adapter crash', async () => {
|
||||
const { id } = await adapter.spawn({
|
||||
cols: 80,
|
||||
|
||||
Reference in New Issue
Block a user