From 74f6dd0236e99c6538d3a383b334f8bb1f1d1624 Mon Sep 17 00:00:00 2001 From: Haze <709547807@qq.com> Date: Mon, 20 Apr 2026 17:00:00 +0800 Subject: [PATCH] fix(chat): exclude tool-result user messages from run segmentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gateway history contains `role: 'user'` messages that are actually tool-result wrappers (Anthropic API format). These were incorrectly treated as run boundaries in nextUserMessageIndexes, causing: - isLatestOpenRun=false during tool execution → graph collapses - Run split into multiple segments → incorrect step attribution Add isRealUserMessage() that detects tool-result wrappers by checking if all content blocks are type 'tool_result', and use it in both nextUserMessageIndexes computation and userRunCards filtering. Also remove debug logging from previous iterations. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/pages/Chat/index.tsx | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/pages/Chat/index.tsx b/src/pages/Chat/index.tsx index 4ce94bd..ffd1ef1 100644 --- a/src/pages/Chat/index.tsx +++ b/src/pages/Chat/index.tsx @@ -187,11 +187,25 @@ export function Chat() { const isEmpty = messages.length === 0 && !sending; const subagentCompletionInfos = messages.map((message) => parseSubagentCompletionInfo(message)); + // Build an index of the *next* real user message after each position. + // Gateway history may contain `role: 'user'` messages that are actually + // tool-result wrappers (Anthropic API format). These must NOT split + // the run into multiple segments — only genuine user-authored messages + // should act as run boundaries. + const isRealUserMessage = (msg: RawMessage): boolean => { + if (msg.role !== 'user') return false; + const content = msg.content; + if (!Array.isArray(content)) return true; + // If every block in the content is a tool_result, this is a Gateway + // tool-result wrapper, not a real user message. + const blocks = content as Array<{ type?: string }>; + return blocks.length === 0 || !blocks.every((b) => b.type === 'tool_result'); + }; const nextUserMessageIndexes = new Array(messages.length).fill(-1); let nextUserMessageIndex = -1; for (let idx = messages.length - 1; idx >= 0; idx -= 1) { nextUserMessageIndexes[idx] = nextUserMessageIndex; - if (messages[idx].role === 'user' && !subagentCompletionInfos[idx]) { + if (isRealUserMessage(messages[idx]) && !subagentCompletionInfos[idx]) { nextUserMessageIndex = idx; } } @@ -202,7 +216,7 @@ export function Chat() { const foldedNarrationIndices = new Set(); const userRunCards: UserRunCard[] = messages.flatMap((message, idx) => { - if (message.role !== 'user' || subagentCompletionInfos[idx]) return []; + if (!isRealUserMessage(message) || subagentCompletionInfos[idx]) return []; const runKey = message.id ? `msg-${message.id}` @@ -345,10 +359,12 @@ export function Chat() { foldedNarrationIndices.add(idx + 1 + offset); } + const cardActive = isLatestOpenRun && streamingReplyText == null; + return [{ triggerIndex: idx, replyIndex, - active: isLatestOpenRun && streamingReplyText == null, + active: cardActive, agentLabel: segmentAgentLabel, sessionLabel: segmentSessionLabel, segmentEnd: nextUserIndex === -1 ? messages.length - 1 : nextUserIndex - 1,