@@ -981,10 +981,10 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
981981 // useChat resume / reconnectToStream path doesn't open a
982982 // second `session.out` subscription on top of our stitched
983983 // response.
984- // - On `trigger:session-state`, hydrate `state.lastEventId `
985- // with the agent's final S2 event id. Without this, turn 2's
986- // `session.out` subscribe reads from the start and replays
987- // turn 1's chunks back into the UI .
984+ // - On `trigger:session-state`, hydrate `state.activeInputSeq `
985+ // as soon as the handover append commits and `state.lastEventId`
986+ // when the agent stream finishes. These keep reloads correlated
987+ // to the active turn and keep turn 2 from replaying turn 1 .
988988 // - On stream end (handover-skip case — no
989989 // `trigger:turn-complete` arrives, customer's stream just
990990 // ends), also clear `isStreaming` for the same reason.
@@ -993,9 +993,17 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
993993 this . notifySessionChange ( id , state ) ;
994994 const TRIGGER_TURN_COMPLETE = "trigger:turn-complete" ;
995995 const TRIGGER_SESSION_STATE = "trigger:session-state" ;
996+ const clearActiveTurn = ( ) => {
997+ const state = sessions . get ( chatId ) ;
998+ if ( state && ( state . isStreaming || state . activeInputSeq !== undefined ) ) {
999+ state . activeInputSeq = undefined ;
1000+ state . isStreaming = false ;
1001+ notifyChange ( chatId , state ) ;
1002+ }
1003+ } ;
9961004 const clearStreaming = ( ) => {
9971005 const state = sessions . get ( chatId ) ;
998- if ( state && state . isStreaming ) {
1006+ if ( state ? .isStreaming ) {
9991007 state . isStreaming = false ;
10001008 notifyChange ( chatId , state ) ;
10011009 }
@@ -1007,6 +1015,13 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
10071015 notifyChange ( chatId , state ) ;
10081016 }
10091017 } ;
1018+ const setActiveInputSeq = ( activeInputSeq : number ) => {
1019+ const state = sessions . get ( chatId ) ;
1020+ if ( state ) {
1021+ state . activeInputSeq = activeInputSeq ;
1022+ notifyChange ( chatId , state ) ;
1023+ }
1024+ } ;
10101025 const emit = ( event : ChatTransportEvent ) => this . emitEvent ( event ) ;
10111026 const attribution = ( ) => this . turnAttribution ( chatId ) ;
10121027 let sawFirstChunk = false ;
@@ -1028,7 +1043,7 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
10281043 if ( chunk && typeof chunk === "object" ) {
10291044 const type = ( chunk as { type ?: unknown } ) . type ;
10301045 if ( type === TRIGGER_TURN_COMPLETE ) {
1031- clearStreaming ( ) ;
1046+ clearActiveTurn ( ) ;
10321047 emit ( {
10331048 type : "turn-completed" ,
10341049 chatId,
@@ -1039,10 +1054,17 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
10391054 return ; // drop — not a real UIMessageChunk
10401055 }
10411056 if ( type === TRIGGER_SESSION_STATE ) {
1042- const lastEventId = ( chunk as { lastEventId ?: unknown } ) . lastEventId ;
1057+ const sessionState = chunk as {
1058+ lastEventId ?: unknown ;
1059+ activeInputSeq ?: unknown ;
1060+ } ;
1061+ const lastEventId = sessionState . lastEventId ;
10431062 if ( typeof lastEventId === "string" ) {
10441063 setLastEventId ( lastEventId ) ;
10451064 }
1065+ if ( typeof sessionState . activeInputSeq === "number" ) {
1066+ setActiveInputSeq ( sessionState . activeInputSeq ) ;
1067+ }
10461068 return ; // drop
10471069 }
10481070 }
@@ -1760,6 +1782,7 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
17601782 } ) as typeof fetch )
17611783 : undefined ;
17621784 let sawFirstChunk = false ;
1785+ let sinceInSeq = options ?. sinceInSeq ;
17631786
17641787 const connectSseOnce = async ( token : string ) => {
17651788 const subscription = new SSEStreamSubscription ( streamUrl , {
@@ -1993,10 +2016,10 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
19932016 if ( controlValue === TRIGGER_CONTROL_SUBTYPE . TURN_COMPLETE ) {
19942017 // Skip a turn-complete from an earlier turn (committed `.in` cursor
19952018 // below this send's seq), e.g. an undo action that raced this send.
1996- if ( options ?. sinceInSeq !== undefined ) {
2019+ if ( sinceInSeq !== undefined ) {
19972020 const cursorRaw = headerValue ( value . headers , SESSION_IN_EVENT_ID_HEADER ) ;
19982021 const cursor = cursorRaw !== undefined ? Number . parseInt ( cursorRaw , 10 ) : NaN ;
1999- if ( ! Number . isNaN ( cursor ) && cursor < options . sinceInSeq ) {
2022+ if ( ! Number . isNaN ( cursor ) && cursor < sinceInSeq ) {
20002023 continue ;
20012024 }
20022025 }
@@ -2014,6 +2037,8 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
20142037 sessionInEventId : headerValue ( value . headers , SESSION_IN_EVENT_ID_HEADER ) ,
20152038 ...this . turnAttribution ( chatId ) ,
20162039 } ) ;
2040+ state . activeInputSeq = undefined ;
2041+ sinceInSeq = undefined ;
20172042 state . isStreaming = false ;
20182043 this . notifySessionChange ( chatId , state ) ;
20192044 this . coordinator ?. release ( chatId ) ;
0 commit comments