mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-12 04:01:20 +00:00
fix(session): harden embedded assistant streams
This commit is contained in:
@@ -163,6 +163,20 @@ export class MutableSessionEventSource implements SessionEventSource {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace one attempt's transient rows with its committed durable settlement.
|
||||
* @param attemptId - process-local attempt whose live rows are now redundant.
|
||||
* @param entry - durable settlement committed for that attempt.
|
||||
*/
|
||||
settleAssistant(attemptId: string, entry: SessionLiveEventEntry): void {
|
||||
const entries = materialize(this.window).filter(candidate => (
|
||||
candidate.type !== 'transient' || String(candidate.event.data.attemptId) !== attemptId
|
||||
))
|
||||
entries.push(entry)
|
||||
this.window = leaf(entries)
|
||||
this.publish(this.snapshot.hasMore, { kind: 'replace', entries })
|
||||
}
|
||||
|
||||
private publish(
|
||||
hasMore: boolean,
|
||||
change: SessionEventChange,
|
||||
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
|
||||
interface ActiveAttempt {
|
||||
readonly attemptId: string
|
||||
readonly startedTime: number
|
||||
readonly startedAfterSeq: number
|
||||
readonly turn: number
|
||||
readonly step: number
|
||||
nextIndex: number
|
||||
@@ -23,6 +23,11 @@ interface ActiveAttempt {
|
||||
/** One Web publication decision from the assistant stream fold. */
|
||||
export type ClientAssistantStreamResult =
|
||||
| { readonly type: 'publish'; readonly entry: SessionLiveEventEntry }
|
||||
| {
|
||||
readonly type: 'settlement'
|
||||
readonly attemptId: string
|
||||
readonly entry: SessionLiveEventEntry
|
||||
}
|
||||
| { readonly type: 'transient'; readonly entry: SessionTransientEventEntry }
|
||||
| { readonly type: 'rebaseline' }
|
||||
| undefined
|
||||
@@ -52,7 +57,7 @@ export class ClientAssistantStream {
|
||||
if (opening !== undefined) {
|
||||
this.activeAttempt = {
|
||||
attemptId: String(opening.attemptId),
|
||||
startedTime: opening.startedTime,
|
||||
startedAfterSeq: opening.startedAfterSeq,
|
||||
turn: opening.turn,
|
||||
step: opening.step,
|
||||
nextIndex: opening.nextIndex,
|
||||
@@ -120,7 +125,7 @@ export class ClientAssistantStream {
|
||||
this.pending.clear()
|
||||
this.activeAttempt = {
|
||||
attemptId: String(frame.attemptId),
|
||||
startedTime: frame.startedTime,
|
||||
startedAfterSeq: frame.startedAfterSeq,
|
||||
turn: frame.turn,
|
||||
step: frame.step,
|
||||
nextIndex: 0,
|
||||
@@ -170,7 +175,8 @@ export class ClientAssistantStream {
|
||||
return { type: 'rebaseline' }
|
||||
}
|
||||
this.pending.delete(frame.outcome.seq)
|
||||
return this.publish(entry)
|
||||
this.publishedSeqs.add(entry.event.seq)
|
||||
return { type: 'settlement', attemptId: attempt.attemptId, entry }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,7 +188,7 @@ export class ClientAssistantStream {
|
||||
if (attempt === undefined
|
||||
|| (event.type !== 'assistant/message' && event.type !== 'assistant/attempt')
|
||||
|| (event.type === 'assistant/message' && event.surfaceOp !== 'append')
|
||||
|| event.time < attempt.startedTime
|
||||
|| event.seq <= attempt.startedAfterSeq
|
||||
|| attempt.turn !== event.data.turn
|
||||
|| attempt.step !== event.data.step) return undefined
|
||||
return attempt
|
||||
|
||||
@@ -668,6 +668,12 @@ export class Session implements SessionFace {
|
||||
})
|
||||
return
|
||||
}
|
||||
if (result?.type === 'settlement') {
|
||||
this.eventSource.settleAssistant(result.attemptId, result.entry)
|
||||
this.observeSubmissionEvent(result.entry.event)
|
||||
this.notifier.markDirty()
|
||||
return
|
||||
}
|
||||
if (result?.type === 'publish' && this.appendLive(result.entry)) {
|
||||
this.notifier.markDirty()
|
||||
} else if (result?.type === 'transient') {
|
||||
|
||||
Reference in New Issue
Block a user