fix(sync): deduplicate overlapping delta after coalesced part.updated (#916)
* fix: hide archived section and empty folders when no sessions remain - Only push archived group in useSessionGrouping when there are archived sessions, preventing an empty archived section from rendering - Hide empty folders in archived bucket via shouldKeepFolder check in SessionGroupSection (folders with no sessions and no content in children are filtered out) - Always filter folders through shouldKeepFolder, not just during search * perf: memoize archived folder filtering * fix(sync): deduplicate overlapping delta after coalesced part.updated When message.part.updated coalesces in the event pipeline and a message.part.delta for the same part arrives in the same flush window, the reducer appends the delta verbatim to the already-complete field value, producing duplicated text in tool output and assistant messages. Add targeted overlap reconciliation: only when a part.updated replaces an existing part with overlapping string content, mark the next delta for that field as dedupe-eligible. Normal streaming deltas remain untouched (pure append). Covers four cases: - Full overlap: delta already present -> no-op - Partial overlap: only non-overlapping suffix appended - No overlap: unchanged append behavior - Legitimate repeated output (ha + ha -> haha): preserved --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
9908f2dc9c
commit
ea5c19e934
@@ -0,0 +1,154 @@
|
||||
import { describe, expect, it } from 'bun:test'
|
||||
import { applyDirectoryEvent } from '../event-reducer'
|
||||
import { INITIAL_STATE } from '../types'
|
||||
|
||||
describe('applyDirectoryEvent', () => {
|
||||
it('does not duplicate overlapping delta text after a newer part.updated replaces an older one', () => {
|
||||
const state = structuredClone(INITIAL_STATE)
|
||||
const messageID = 'msg-1'
|
||||
const partID = 'part-1'
|
||||
|
||||
applyDirectoryEvent(state, {
|
||||
type: 'message.part.updated',
|
||||
properties: {
|
||||
part: {
|
||||
id: partID,
|
||||
type: 'text',
|
||||
messageID,
|
||||
text: 'Fix typo in ToolOutputDialog — ',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
applyDirectoryEvent(state, {
|
||||
type: 'message.part.updated',
|
||||
properties: {
|
||||
part: {
|
||||
id: partID,
|
||||
type: 'text',
|
||||
messageID,
|
||||
text: 'Fix typo in ToolOutputDialog — toolFailedToReadDiagram vs toolFailedReadDiagram • Let me fix it.',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
applyDirectoryEvent(state, {
|
||||
type: 'message.part.delta',
|
||||
properties: {
|
||||
messageID,
|
||||
partID,
|
||||
field: 'text',
|
||||
delta: 'toolFailedToReadDiagram vs toolFailedReadDiagram • Let me fix it.',
|
||||
},
|
||||
})
|
||||
|
||||
expect(state.part[messageID]).toHaveLength(1)
|
||||
expect(state.part[messageID]?.[0]?.text).toBe(
|
||||
'Fix typo in ToolOutputDialog — toolFailedToReadDiagram vs toolFailedReadDiagram • Let me fix it.',
|
||||
)
|
||||
})
|
||||
|
||||
it('appends only the non-overlapping suffix of a streaming delta', () => {
|
||||
const state = structuredClone(INITIAL_STATE)
|
||||
const messageID = 'msg-2'
|
||||
const partID = 'part-2'
|
||||
|
||||
applyDirectoryEvent(state, {
|
||||
type: 'message.part.updated',
|
||||
properties: {
|
||||
part: {
|
||||
id: partID,
|
||||
type: 'text',
|
||||
messageID,
|
||||
text: 'toolFailedToReadDiagram vs toolFailedRead',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
applyDirectoryEvent(state, {
|
||||
type: 'message.part.updated',
|
||||
properties: {
|
||||
part: {
|
||||
id: partID,
|
||||
type: 'text',
|
||||
messageID,
|
||||
text: 'toolFailedToReadDiagram vs toolFailedReadDiagra',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
applyDirectoryEvent(state, {
|
||||
type: 'message.part.delta',
|
||||
properties: {
|
||||
messageID,
|
||||
partID,
|
||||
field: 'text',
|
||||
delta: 'Diagram • Let me fix it.',
|
||||
},
|
||||
})
|
||||
|
||||
expect(state.part[messageID]?.[0]?.text).toBe(
|
||||
'toolFailedToReadDiagram vs toolFailedReadDiagram • Let me fix it.',
|
||||
)
|
||||
})
|
||||
|
||||
it('appends a non-overlapping delta unchanged', () => {
|
||||
const state = structuredClone(INITIAL_STATE)
|
||||
const messageID = 'msg-3'
|
||||
const partID = 'part-3'
|
||||
|
||||
applyDirectoryEvent(state, {
|
||||
type: 'message.part.updated',
|
||||
properties: {
|
||||
part: {
|
||||
id: partID,
|
||||
type: 'text',
|
||||
messageID,
|
||||
text: 'PR comment done — ',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
applyDirectoryEvent(state, {
|
||||
type: 'message.part.delta',
|
||||
properties: {
|
||||
messageID,
|
||||
partID,
|
||||
field: 'text',
|
||||
delta: 'Let me fix it.',
|
||||
},
|
||||
})
|
||||
|
||||
expect(state.part[messageID]?.[0]?.text).toBe('PR comment done — Let me fix it.')
|
||||
})
|
||||
|
||||
it('preserves legitimate repeated output when no updated-to-delta dedupe window is active', () => {
|
||||
const state = structuredClone(INITIAL_STATE)
|
||||
const messageID = 'msg-4'
|
||||
const partID = 'part-4'
|
||||
|
||||
applyDirectoryEvent(state, {
|
||||
type: 'message.part.updated',
|
||||
properties: {
|
||||
part: {
|
||||
id: partID,
|
||||
type: 'text',
|
||||
messageID,
|
||||
text: 'ha',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
applyDirectoryEvent(state, {
|
||||
type: 'message.part.delta',
|
||||
properties: {
|
||||
messageID,
|
||||
partID,
|
||||
field: 'text',
|
||||
delta: 'ha',
|
||||
},
|
||||
})
|
||||
|
||||
expect(state.part[messageID]?.[0]?.text).toBe('haha')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user