@@ -436,6 +436,14 @@ fn ensure_thread_subscription(
436436 let thread_id_for_sub = thread_id. to_string ( ) ;
437437 mark_persistent_subscription ( thread_id. to_string ( ) ) ;
438438
439+ // Track the request_id that was active when the current turn started.
440+ // Used by the Stopped handler to flush with the correct request_id,
441+ // even if a follow-up/interrupt message has already updated the global
442+ // THREAD_REQUEST_MAP to the next turn's request_id.
443+ let turn_request_id: std:: cell:: RefCell < String > = std:: cell:: RefCell :: new (
444+ crate :: get_thread_request_id ( thread_id) . unwrap_or_default ( )
445+ ) ;
446+
439447 cx. subscribe ( thread_entity, move |thread_entity, event, cx| {
440448 match event {
441449 AcpThreadEvent :: NewEntry => {
@@ -465,6 +473,12 @@ fn ensure_thread_subscription(
465473 } ;
466474 let rid = crate :: get_thread_request_id ( & thread_id_for_sub)
467475 . unwrap_or_default ( ) ;
476+ // Snapshot the request_id when the first assistant entry of a turn appears.
477+ // This ensures the Stopped flush uses the turn's own request_id, not a
478+ // later follow-up's.
479+ if role == "assistant" {
480+ * turn_request_id. borrow_mut ( ) = rid. clone ( ) ;
481+ }
468482 let _ = crate :: send_websocket_event ( SyncEvent :: MessageAdded {
469483 acp_thread_id : thread_id_for_sub. clone ( ) ,
470484 message_id : latest_idx. to_string ( ) ,
@@ -517,9 +531,19 @@ fn ensure_thread_subscription(
517531 // content is safe: the Go accumulator uses overwrite semantics for known message_ids.
518532 let thread = thread_entity. read ( cx) ;
519533 let entries = thread. entries ( ) ;
520- let rid = crate :: get_thread_request_id ( & thread_id_for_sub)
521- . unwrap_or_default ( ) ;
522- for ( idx, entry) in entries. iter ( ) . enumerate ( ) {
534+ // Use the request_id captured when this turn started, NOT the current
535+ // global request_id. If a follow-up/interrupt message arrived before
536+ // Stopped fires, the global ID already points to the next turn.
537+ let rid = turn_request_id. borrow ( ) . clone ( ) ;
538+
539+ // Find the start of the current turn: the entry AFTER the last UserMessage.
540+ // Only flush entries from the current turn — sending old entries would cause
541+ // them to leak into the current interaction's response_entries on the Go side.
542+ let turn_start = entries. iter ( ) . enumerate ( ) . rev ( )
543+ . find_map ( |( i, e) | matches ! ( e, acp_thread:: AgentThreadEntry :: UserMessage ( _) ) . then_some ( i + 1 ) )
544+ . unwrap_or ( 0 ) ;
545+
546+ for ( idx, entry) in entries. iter ( ) . enumerate ( ) . skip ( turn_start) {
523547 match entry {
524548 acp_thread:: AgentThreadEntry :: AssistantMessage ( msg) => {
525549 let content = msg. content_only ( cx) ;
@@ -559,16 +583,16 @@ fn ensure_thread_subscription(
559583 }
560584 }
561585
562- let rid = crate :: get_thread_request_id ( & thread_id_for_sub )
563- . unwrap_or_default ( ) ;
586+ // Use the turn's captured request_id for message_completed too
587+ let completed_rid = turn_request_id . borrow ( ) . clone ( ) ;
564588 eprintln ! (
565589 "📤 [THREAD_SERVICE] Stopped event: sending message_completed for thread {} (request_id={})" ,
566- thread_id_for_sub, rid
590+ thread_id_for_sub, completed_rid
567591 ) ;
568592 let _ = crate :: send_websocket_event ( SyncEvent :: MessageCompleted {
569593 acp_thread_id : thread_id_for_sub. clone ( ) ,
570594 message_id : "0" . to_string ( ) ,
571- request_id : rid ,
595+ request_id : completed_rid ,
572596 } ) ;
573597 }
574598 _ => { }
0 commit comments