From 4caca36859cea9652069257c046af7b4ae2005c9 Mon Sep 17 00:00:00 2001 From: Fimeg Date: Thu, 30 Jul 2026 15:22:12 -0400 Subject: [PATCH] fix: preserve the full reply (not the streamed partial) on surface-drop Revises the prior fix: the model's reply is already complete in final_content before the paced stream loop, so truncating to needlessly dropped the unstreamed tail. On SSE receiver-drop, stop streaming but commit the FULL reply (interrupt flag skips the pointless N+1 and marks the turn); the surface recovers the whole reply on resume. --- src/server/turn.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/server/turn.rs b/src/server/turn.rs index ab67bf8..d9084b8 100644 --- a/src/server/turn.rs +++ b/src/server/turn.rs @@ -473,17 +473,21 @@ pub(crate) async fn run_turn( send_res = tx.send(Ok(BackendEvent::Token(s.clone()))) => { if send_res.is_err() { // The SSE receiver is gone (the surface - // disconnected mid-reply). Do NOT return early: - // that would skip the session commit below and - // orphan this turn, leaving a user→user gap so - // the next turn cannot link to the past — the - // model reads the follow-up with no preceding - // reply and answers as if from nowhere. Commit - // the partial text like an interrupt instead, so - // the conversation thread stays intact even when - // the surface vanishes. + // disconnected mid-stream). The model's reply is + // already complete in `final_content` — only the + // paced live-stream was cut, so do NOT truncate + // to `streamed`. Stop streaming to the dead + // receiver and let the turn commit the FULL + // reply below; treating it as an interrupt also + // skips the now-pointless N+1 pass and lands a + // small marker so the next turn knows the live + // tail was missed. Returning early here used to + // orphan the turn entirely — no commit — leaving + // a user→user gap so the next turn could not link + // to the past, and the model answered as if from + // nowhere ("whatever it was…"). The surface + // recovers the whole reply on its next resume. interrupted = true; - final_content = streamed; break; } dispatcher.emit_segment(&s);