fix(web): use exact turn boundaries for run time
This commit is contained in:
@@ -331,6 +331,8 @@ export interface ConversationSnapshot {
|
||||
sessionId: SessionId
|
||||
/** Human transcript plus retry notices and interrupted-turn terminal nodes in event order. */
|
||||
nodes: readonly ConversationNode[]
|
||||
/** Exact in-window `turn/start` time and optional matching `turn/end` time. */
|
||||
turnTimings: ReadonlyMap<number, { readonly startTime: number; readonly endTime?: number }>
|
||||
/** In-window completed turn number -> its `turn/end` event seq. */
|
||||
turnEnds: ReadonlyMap<number, number>
|
||||
partial: PartialAssistant | null
|
||||
|
||||
@@ -113,6 +113,11 @@ export class Session implements SessionFace {
|
||||
private pendingCache: { rev: number; value: PendingInteraction[] } | null = null
|
||||
private derivedRev = 0
|
||||
private nodesCache: { projected: readonly ConversationNode[]; derivedRev: number; value: readonly ConversationNode[] } | null = null
|
||||
/** Exact turn timing retained from the raw window so presentation never
|
||||
* infers elapsed time from transcript content. */
|
||||
private turnTimings = new Map<number, { startTime: number; endTime?: number }>()
|
||||
private turnTimingsRev = 0
|
||||
private turnTimingsCache: { rev: number; value: ConversationSnapshot['turnTimings'] } | null = null
|
||||
/** Completed turn boundaries retained from the raw window so presentation
|
||||
* actions never infer a safe fork point from transcript content alone. */
|
||||
private turnEnds = new Map<number, number>()
|
||||
@@ -799,6 +804,8 @@ export class Session implements SessionFace {
|
||||
}
|
||||
switch (event.type) {
|
||||
case 'turn/start': {
|
||||
this.turnTimings.set(event.data.turn, { startTime: event.time })
|
||||
this.turnTimingsRev++
|
||||
if (event.data.trigger.kind === 'retry') this.settleScheduledRetry('started')
|
||||
return
|
||||
}
|
||||
@@ -830,6 +837,11 @@ export class Session implements SessionFace {
|
||||
return
|
||||
}
|
||||
case 'turn/end': {
|
||||
const timing = this.turnTimings.get(event.data.turn)
|
||||
if (timing !== undefined) {
|
||||
this.turnTimings.set(event.data.turn, { ...timing, endTime: event.time })
|
||||
this.turnTimingsRev++
|
||||
}
|
||||
this.turnEnds.set(event.data.turn, event.seq)
|
||||
this.turnEndsRev++
|
||||
if (event.data.reason.kind === 'aborted' || event.data.reason.kind === 'disposed') {
|
||||
@@ -922,6 +934,8 @@ export class Session implements SessionFace {
|
||||
this.callsRev++
|
||||
this.derivedNodes = []
|
||||
this.derivedRev++
|
||||
this.turnTimings = new Map()
|
||||
this.turnTimingsRev++
|
||||
this.turnEnds = new Map()
|
||||
this.turnEndsRev++
|
||||
this.codeDispatches = new Map()
|
||||
@@ -955,6 +969,9 @@ export class Session implements SessionFace {
|
||||
if (this.callsCache === null || this.callsCache.rev !== this.callsRev) {
|
||||
this.callsCache = { rev: this.callsRev, value: [...this.openCalls.values()] }
|
||||
}
|
||||
if (this.turnTimingsCache === null || this.turnTimingsCache.rev !== this.turnTimingsRev) {
|
||||
this.turnTimingsCache = { rev: this.turnTimingsRev, value: new Map(this.turnTimings) }
|
||||
}
|
||||
if (this.turnEndsCache === null || this.turnEndsCache.rev !== this.turnEndsRev) {
|
||||
this.turnEndsCache = { rev: this.turnEndsRev, value: new Map(this.turnEnds) }
|
||||
}
|
||||
@@ -971,6 +988,7 @@ export class Session implements SessionFace {
|
||||
return {
|
||||
sessionId: this.sessionId,
|
||||
nodes,
|
||||
turnTimings: this.turnTimingsCache.value,
|
||||
turnEnds: this.turnEndsCache.value,
|
||||
partial,
|
||||
runningCalls: this.callsCache.value,
|
||||
|
||||
Reference in New Issue
Block a user