fix(desktop): prefer the live backlog over the last snapshot fence when appending a catch-up delta
syncAgentTerminalSnapshot checked the stale lastSnapshots prefix-delta before the authoritative backlog (body) prefix-delta. Live agent.terminal.output chunks advance the backlog but never lastSnapshots, so once any chunk has streamed since the last snapshot, body is strictly longer than previous and both are prefixes of a catch-up process.list snapshot. The fence-first branch then emitted output.slice(previous.length), re-appending bytes the chunks had already written and duplicating them on screen and in the replay backlog. body is the authoritative on-screen state and is always >= previous, so its delta is the correct, smaller one; check it first. Regression case added to agent-terminal-stream.bound.test.ts (red without the swap). Co-authored-by: briandevans <252620095+briandevans@users.noreply.github.com>
This commit is contained in:
committed by
brooklyn!
parent
63ee01c78f
commit
4b2682e9fa
@@ -133,6 +133,26 @@ describe('agent terminal retention is bounded', () => {
|
||||
expect(replay('reset-11')).toContain(tail.slice(0, 1000))
|
||||
})
|
||||
|
||||
it('a catch-up snapshot does not re-print bytes already streamed by live chunks', () => {
|
||||
// Live `agent.terminal.output` chunks advance the backlog but never the
|
||||
// lastSnapshots fence. A catch-up snapshot that contains everything shown so
|
||||
// far matches BOTH prefix checks, and the fence-first order emitted
|
||||
// output.slice(previous.length) — re-appending bytes the chunks had already
|
||||
// written, so the screen showed `AAABBBAAABBBCCC` for `AAABBB` + `CCC`.
|
||||
const proc = 'dup-proc'
|
||||
let screen = ''
|
||||
|
||||
const stop = registerAgentTerminalWriter(proc, chunk => {
|
||||
screen = chunk.startsWith('\x1bc') ? chunk.slice(2) : screen + chunk
|
||||
})
|
||||
|
||||
writeAgentTerminalChunk(proc, 'AAA')
|
||||
writeAgentTerminalChunk(proc, 'BBB')
|
||||
syncAgentTerminalSnapshot(proc, 'AAABBBCCC')
|
||||
stop()
|
||||
expect(screen).toBe('AAABBBCCC')
|
||||
})
|
||||
|
||||
it('an evicted process does not diff against a tail it no longer has', () => {
|
||||
// `lastSnapshots` is the delta fence for `backlog`. If eviction dropped the
|
||||
// backlog but KEPT the fence, the next snapshot takes the `startsWith(previous)`
|
||||
|
||||
@@ -137,18 +137,22 @@ export function syncAgentTerminalSnapshot(procId: string, output: string): void
|
||||
return
|
||||
}
|
||||
|
||||
// The fence is updated BEFORE the write so the write's eviction pass counts it:
|
||||
// after, it would re-create a fence for a process that pass just forgot.
|
||||
if (output.startsWith(previous)) {
|
||||
// The live backlog (body) is the authoritative on-screen state and is always at
|
||||
// least as long as the last snapshot fence, so check its prefix-delta FIRST: the
|
||||
// fence-first order would re-append bytes already streamed by live chunks and
|
||||
// print them twice. The fence is updated BEFORE the write so the write's eviction
|
||||
// pass counts it: after, it would re-create a fence for a process that pass just
|
||||
// forgot.
|
||||
if (output.startsWith(body)) {
|
||||
lastSnapshots.set(procId, output)
|
||||
writeAgentTerminalChunk(procId, output.slice(previous.length))
|
||||
writeAgentTerminalChunk(procId, output.slice(body.length))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (output.startsWith(body)) {
|
||||
if (output.startsWith(previous)) {
|
||||
lastSnapshots.set(procId, output)
|
||||
writeAgentTerminalChunk(procId, output.slice(body.length))
|
||||
writeAgentTerminalChunk(procId, output.slice(previous.length))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user