fix(session): close live assistant framing races

This commit is contained in:
Tianyi Cui
2026-09-02 03:49:17 +08:00
parent 30e045dfad
commit 0bb7bba015
12 changed files with 163 additions and 46 deletions
@@ -65,14 +65,7 @@ export class ClientAssistantStream {
})
}
}
const visible = entries.filter((entry) => {
if (entry.type !== 'event' || entry.event.type !== 'assistant/message'
|| entry.event.surfaceOp !== 'append') return true
const attempt = this.attemptForSettlement(entry.event)
if (attempt === undefined) return true
this.pendingMessages.set(positionKey(attempt.turn, attempt.step), entry)
return false
})
const visible = entries
this.publishedSeqs = new Set(visible.map(entry => entry.event.seq))
return visible
}
@@ -87,6 +80,7 @@ export class ClientAssistantStream {
if (event.type === 'assistant/chunk') {
const attempt = this.attemptFor(event.data.turn, event.data.step)
if (attempt === undefined) return this.publish(entry)
if (attempt.legacyChunkSeqs.has(event.seq)) return this.publish(entry)
this.pendingChunks.set(event.seq, entry)
return undefined
}
@@ -157,18 +151,6 @@ export class ClientAssistantStream {
))
}
private attemptForSettlement(
event: Extract<SessionLiveEventEntry['event'], { type: 'assistant/message' }>,
): ActiveAttempt | undefined {
const sourceEventSeqs = event.sourceEventSeqs
if (sourceEventSeqs === undefined) return undefined
return [...this.attempts.values()].find(attempt => (
attempt.turn === event.data.turn
&& attempt.step === event.data.step
&& sameSeqs([...attempt.legacyChunkSeqs], sourceEventSeqs)
))
}
private publish(entry: SessionLiveEventEntry): ClientAssistantStreamResult {
this.publishedSeqs.add(entry.event.seq)
return { type: 'publish', entry }
@@ -647,6 +647,9 @@ export class Session implements SessionFace {
projections?: ProjectionsBaseline,
assistantStream?: SessionAssistantStreamBaseline,
): void {
// A durable gap-repair page has no assistant baseline. Clearing transient
// attempts makes a held notification reopen follow once for an atomic
// page/baseline pair instead of applying it to an unrelated repair cut.
const visible = this.assistantStream.replace(entries, assistantStream)
this.baseSeq = SessionLogOffset(visible[0]?.event.seq ?? 0)
this.hasMore = hasMore