Skip to content

Commit ad49f08

Browse files
committed
Fix duplicate subagent thread items
1 parent c1f2a67 commit ad49f08

1 file changed

Lines changed: 79 additions & 2 deletions

File tree

src-tauri/src/backend/event_translator.rs

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)