From cedd8b2a693ca49fa43d171ca552815c737bd9bf Mon Sep 17 00:00:00 2001 From: oudecheng <13802883547@139.com> Date: Fri, 12 Jun 2026 12:22:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20topic=5Fid=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=88=B0=E6=B6=88=E6=81=AF=E7=BB=93=E6=9E=84?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/protocol/mod.rs | 2 ++ src/protocol/ws_adapter.rs | 1 + src/tools/task/runtime.rs | 2 ++ web/src/hooks/useChat.ts | 23 +++++++++++++++++++++++ web/src/types/protocol.ts | 1 + 5 files changed, 29 insertions(+) diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index e46984c..87cd453 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -202,6 +202,8 @@ pub enum WsOutbound { task_id: String, description: String, subagent_type: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + topic_id: Option, }, #[serde(rename = "session_established")] SessionEstablished { session_id: String }, diff --git a/src/protocol/ws_adapter.rs b/src/protocol/ws_adapter.rs index 6e08e79..47a4e1f 100644 --- a/src/protocol/ws_adapter.rs +++ b/src/protocol/ws_adapter.rs @@ -172,6 +172,7 @@ pub(crate) fn ws_outbound_from_outbound_message(message: &OutboundMessage) -> Ve task_id: message.metadata.get("task_id").cloned().unwrap_or_default(), description: message.metadata.get("task_description").cloned().unwrap_or_default(), subagent_type: message.metadata.get("task_subagent_type").cloned().unwrap_or_default(), + topic_id: message.metadata.get("topic_id").cloned(), }], } } diff --git a/src/tools/task/runtime.rs b/src/tools/task/runtime.rs index c984aea..6b5bfa4 100644 --- a/src/tools/task/runtime.rs +++ b/src/tools/task/runtime.rs @@ -250,6 +250,7 @@ impl DefaultSubAgentRuntime { let mut metadata = HashMap::new(); metadata.insert("subagent_task_id".to_string(), session.id.clone()); metadata.insert("is_subagent_event".to_string(), "true".to_string()); + metadata.insert("topic_id".to_string(), session.parent_topic_id.clone().unwrap_or_default()); let emitter = Arc::new(PersistingEmittedMessageHandler::new( SubAgentEmitter { @@ -424,6 +425,7 @@ impl SubAgentRuntime for DefaultSubAgentRuntime { metadata.insert("task_id".to_string(), session.id.clone()); metadata.insert("task_description".to_string(), session.description.clone()); metadata.insert("task_subagent_type".to_string(), session.subagent_type.clone()); + metadata.insert("topic_id".to_string(), session.parent_topic_id.clone().unwrap_or_default()); let event = OutboundMessage { channel: session.parent_channel_name.clone(), diff --git a/web/src/hooks/useChat.ts b/web/src/hooks/useChat.ts index 940b424..9fc3033 100644 --- a/web/src/hooks/useChat.ts +++ b/web/src/hooks/useChat.ts @@ -181,6 +181,15 @@ export function useChat(): UseChatReturn { return undefined } + // Extract topic_id from a message if present + const getTopicId = (message: WsOutbound): string | undefined => { + if (message.type === 'tool_call' || message.type === 'tool_result' + || message.type === 'tool_pending' || message.type === 'assistant_response') { + return (message as ToolCall | ToolResult | ToolPending | AssistantResponse).topic_id + } + return undefined + } + // Convert a server message to ChatMessage (extracted from handleServerMessage logic) const serverMessageToChatMessage = (message: WsOutbound): ChatMessage | null => { switch (message.type) { @@ -311,12 +320,20 @@ export function useChat(): UseChatReturn { appendToSubAgentViewMessage(message) return } + // 丢弃其他子智能体的消息,避免 fall through 到主消息处理 + if (msgSubagentTaskId) { + return + } } // In main view, skip sub-agent messages (they belong to sub-agent view). // But use the task_id to associate with the running task tool card. const msgSubagentTaskId = getSubagentTaskId(message) if (msgSubagentTaskId) { + // 只 backfill 当前话题的 task tool_call,避免跨话题串扰 + const msgTopicId = getTopicId(message) + if (msgTopicId && msgTopicId !== selectedTopicRef.current) return + setMessages((prev) => { for (let i = prev.length - 1; i >= 0; i--) { if (prev[i].type === 'tool_call' && prev[i].toolName === 'task' && !prev[i].subagentTaskId) { @@ -340,6 +357,9 @@ export function useChat(): UseChatReturn { case 'task_started': { const msg = message as TaskStarted + // 只 backfill 当前话题的 task tool_call,避免跨话题串扰 + if (msg.topic_id && msg.topic_id !== selectedTopicRef.current) break + // 立即更新对应的 task tool_call,让用户可以点击查看实时进度 setMessages((prev) => { for (let i = prev.length - 1; i >= 0; i--) { @@ -602,6 +622,7 @@ export function useChat(): UseChatReturn { const selectTopic = useCallback((topicId: string) => { setSelectedTopic(topicId) setMessages([]) + setSubAgentView(null) }, []) const createTopic = useCallback((title?: string): Command => { @@ -647,6 +668,7 @@ export function useChat(): UseChatReturn { setTopics([]) setSelectedTopic(null) setMessages([]) + setSubAgentView(null) setIsLoading(true) }, [selectedChannel]) @@ -656,6 +678,7 @@ export function useChat(): UseChatReturn { setTopics([]) setSelectedTopic(null) setMessages([]) + setSubAgentView(null) setIsLoading(true) }, [selectedSessionId]) diff --git a/web/src/types/protocol.ts b/web/src/types/protocol.ts index af9264c..3540f6d 100644 --- a/web/src/types/protocol.ts +++ b/web/src/types/protocol.ts @@ -100,6 +100,7 @@ export interface TaskStarted { task_id: string description: string subagent_type: string + topic_id?: string } export interface SessionEstablished {