@@ -117,13 +117,19 @@ impl SessionTranslationState {
117117 pub ( crate ) fn start_turn ( & mut self , session_id : String , turn_id : String ) {
118118 self . session_id = session_id. clone ( ) ;
119119 let turn_state = self . session_turns . entry ( session_id) . or_default ( ) ;
120+ // User prompt parts can arrive before `session.status=active` (common for
121+ // subagent sessions). Preserve the in-progress user message so later chunks
122+ // keep merging into the same frontend item instead of creating a duplicate.
123+ let preserved_user_message_item_id = turn_state. user_message_item_id . clone ( ) ;
124+ let preserved_user_message_text = turn_state. user_message_text . clone ( ) ;
120125 turn_state. turn_id = turn_id;
121126 turn_state. tool_call_items . clear ( ) ;
122127 turn_state. agent_message_item_id = None ;
128+ turn_state. agent_message_part_id = None ;
123129 turn_state. reasoning_item_id = None ;
124130 turn_state. reasoning_part_id = None ;
125- turn_state. user_message_item_id = None ;
126- turn_state. user_message_text . clear ( ) ;
131+ turn_state. user_message_item_id = preserved_user_message_item_id ;
132+ turn_state. user_message_text = preserved_user_message_text ;
127133 }
128134
129135 /// Prepare translation state for replaying historical messages.
@@ -2392,6 +2398,77 @@ mod tests {
23922398 ) ;
23932399 }
23942400
2401+ #[ test]
2402+ fn session_active_preserves_in_progress_user_message_stream_state ( ) {
2403+ let mut state = make_state ( ) ;
2404+
2405+ let _ = translate_sse_event (
2406+ & json ! ( {
2407+ "type" : "message.updated" ,
2408+ "properties" : {
2409+ "info" : {
2410+ "id" : "msg_user_1" ,
2411+ "sessionID" : "ses_test123" ,
2412+ "role" : "user"
2413+ }
2414+ }
2415+ } ) ,
2416+ & mut state,
2417+ ) ;
2418+
2419+ let first = translate_sse_event (
2420+ & json ! ( {
2421+ "type" : "message.part.updated" ,
2422+ "properties" : {
2423+ "part" : {
2424+ "type" : "text" ,
2425+ "id" : "prt_user_text_1" ,
2426+ "sessionID" : "ses_test123" ,
2427+ "messageID" : "msg_user_1" ,
2428+ "text" : "Give me a quick summary"
2429+ }
2430+ }
2431+ } ) ,
2432+ & mut state,
2433+ ) ;
2434+ let first_item_id = first[ 0 ] [ "params" ] [ "item" ] [ "id" ] . clone ( ) ;
2435+
2436+ let active_events = translate_sse_event (
2437+ & json ! ( {
2438+ "type" : "session.status" ,
2439+ "properties" : {
2440+ "sessionID" : "ses_test123" ,
2441+ "status" : { "type" : "active" }
2442+ }
2443+ } ) ,
2444+ & mut state,
2445+ ) ;
2446+ assert_eq ! ( active_events. len( ) , 1 ) ;
2447+ assert_eq ! ( active_events[ 0 ] [ "method" ] , "turn/started" ) ;
2448+
2449+ let second = translate_sse_event (
2450+ & json ! ( {
2451+ "type" : "message.part.delta" ,
2452+ "properties" : {
2453+ "sessionID" : "ses_test123" ,
2454+ "messageID" : "msg_user_1" ,
2455+ "partID" : "prt_user_text_1" ,
2456+ "field" : "text" ,
2457+ "delta" : " of the docs folder"
2458+ }
2459+ } ) ,
2460+ & mut state,
2461+ ) ;
2462+
2463+ assert_eq ! ( second. len( ) , 1 ) ;
2464+ assert_eq ! ( second[ 0 ] [ "method" ] , "item/completed" ) ;
2465+ assert_eq ! ( second[ 0 ] [ "params" ] [ "item" ] [ "id" ] , first_item_id) ;
2466+ assert_eq ! (
2467+ second[ 0 ] [ "params" ] [ "item" ] [ "content" ] [ 0 ] [ "text" ] ,
2468+ "Give me a quick summary of the docs folder"
2469+ ) ;
2470+ }
2471+
23952472 #[ test]
23962473 fn user_text_part_updated_does_not_produce_agent_message_delta ( ) {
23972474 let mut state = make_state ( ) ;
0 commit comments