From 4fc072697f4477b39fee7ab457a82a7b016b64e9 Mon Sep 17 00:00:00 2001 From: viettranx Date: Wed, 18 Mar 2026 11:05:43 +0700 Subject: [PATCH] fix(streaming): prevent mid-run stream content loss on Telegram MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Skip FinalizeStream on tool.call — keep streamed message visible - Gate tool_status placeholder_update to non-streaming runs only - Prevents streamed text from being overwritten by tool status emoji --- internal/channels/events.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/internal/channels/events.go b/internal/channels/events.go index e96b722d..25ef84be 100644 --- a/internal/channels/events.go +++ b/internal/channels/events.go @@ -70,8 +70,6 @@ func (m *Manager) HandleAgentEvent(eventType, runID string, payload any) { // Stop the current stream (reasoning or answer) and finalize only // the answer stream (reasoning messages stay visible). rc.mu.Lock() - // Capture current state before resetting for next iteration. - wasReasoningStream := rc.hasThinking && !rc.thinkingDone currentStream := rc.stream rc.stream = nil rc.inToolPhase = true @@ -84,17 +82,18 @@ func (m *Manager) HandleAgentEvent(eventType, runID string, payload any) { if err := currentStream.Stop(ctx); err != nil { slog.Debug("stream tool-phase stop failed", "channel", rc.ChannelName, "error", err) } - // Only finalize answer streams (hand off messageID to Send()). - // Reasoning streams stay as visible messages — don't put their - // messageID into placeholders or it would confuse Send(). - if !wasReasoningStream { - sc.FinalizeStream(ctx, rc.ChatID, currentStream) - } + // Don't finalize mid-run streams — their messageID must NOT go + // into placeholders. Otherwise tool_status placeholder_update + // overwrites streamed content, and subsequent FinalizeStream + // calls overwrite the placeholder key, leaving earlier messages + // stuck at tool status text. Only run.completed finalizes. } - // Show tool status in streaming preview (edit placeholder with tool name). + // Show tool status by editing placeholder message (non-streaming only). + // Streaming channels show tool status via reaction emoji instead — + // editing the placeholder would overwrite streamed content. toolName := extractPayloadString(payload, "name") - if toolName != "" && rc.ToolStatusEnabled { + if toolName != "" && rc.ToolStatusEnabled && !rc.Streaming { statusText := formatToolStatus(toolName) outMeta := copyRoutingMeta(rc.Metadata) outMeta["placeholder_update"] = "true"