diff --git a/src/gateway/agent_factory.rs b/src/gateway/agent_factory.rs index d7abd4b..3a4fb2c 100644 --- a/src/gateway/agent_factory.rs +++ b/src/gateway/agent_factory.rs @@ -80,6 +80,7 @@ impl AgentFactory { message_seq: None, subagent_description: None, nesting_depth: 0, + parent_task_id: None, }); // 如果有取消信号接收端,注入 Agent if let Some(token) = request.cancel_token { diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 3ac298e..a640957 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -212,6 +212,8 @@ pub enum WsOutbound { subagent_type: String, #[serde(default, skip_serializing_if = "Option::is_none")] topic_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + parent_task_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 231bb04..043c9d4 100644 --- a/src/protocol/ws_adapter.rs +++ b/src/protocol/ws_adapter.rs @@ -173,6 +173,7 @@ pub(crate) fn ws_outbound_from_outbound_message(message: &OutboundMessage) -> Ve 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(), + parent_task_id: message.metadata.get("parent_task_id").cloned(), }], OutboundEventKind::StreamDelta => vec![WsOutbound::StreamDelta { id: message.tool_call_id.clone().unwrap_or_default(), diff --git a/src/tools/task/runtime.rs b/src/tools/task/runtime.rs index 8e3192f..bdf1dda 100644 --- a/src/tools/task/runtime.rs +++ b/src/tools/task/runtime.rs @@ -262,6 +262,14 @@ impl SystemPromptProvider for StaticSystemPromptProvider { } } +/// 从 session_id 中提取 task_id 部分 +/// session_id 格式: "sub:{...}:task:{uuid}",提取 "task:{uuid}" 部分 +fn extract_task_id_from_session_id(session_id: &str) -> Option { + session_id + .rfind(":task:") + .map(|pos| session_id[pos + 1..].to_string()) +} + /// 默认子代理运行时实现 pub struct DefaultSubAgentRuntime { config: SubAgentRuntimeConfig, @@ -337,6 +345,15 @@ impl DefaultSubAgentRuntime { None, // 子代理不需要 skill provider ) .map(|agent| { + // 确定 parent_task_id: + // - 主 agent (depth=0) 创建子 agent → parent_task_id = None + // - 子 agent (depth>0) 创建孙 agent → parent_task_id = 父 agent 自身的 task_id + let parent_task_id = if parent_nesting_depth > 0 { + extract_task_id_from_session_id(&session.parent_session_id) + } else { + None + }; + let agent = agent.with_tool_context(ToolContext { channel_name: Some(session.parent_channel_name.clone()), sender_id: None, @@ -347,6 +364,7 @@ impl DefaultSubAgentRuntime { message_seq: None, subagent_description: Some(session.description.clone()), nesting_depth: parent_nesting_depth + 1, + parent_task_id, }); // 如果有 MessageBus,附加实时广播 emitter @@ -534,6 +552,17 @@ impl SubAgentRuntime for DefaultSubAgentRuntime { 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()); + // 如果是子智能体创建的孙智能体,传递父 task_id + if parent_context.nesting_depth > 0 { + if let Some(ref ptid) = parent_context.parent_task_id { + metadata.insert("parent_task_id".to_string(), ptid.clone()); + } else if let Some(ptid) = extract_task_id_from_session_id( + parent_context.session_id.as_deref().unwrap_or(""), + ) { + metadata.insert("parent_task_id".to_string(), ptid); + } + } + let event = OutboundMessage { channel: session.parent_channel_name.clone(), chat_id: session.parent_chat_id.clone(), diff --git a/src/tools/todo_read.rs b/src/tools/todo_read.rs index d9cca0f..d0e8d8d 100644 --- a/src/tools/todo_read.rs +++ b/src/tools/todo_read.rs @@ -183,6 +183,7 @@ mod tests { message_seq: Some(1), subagent_description: None, nesting_depth: 0, + parent_task_id: None, } } diff --git a/src/tools/todo_write.rs b/src/tools/todo_write.rs index 6195e48..17c4616 100644 --- a/src/tools/todo_write.rs +++ b/src/tools/todo_write.rs @@ -409,6 +409,7 @@ mod tests { message_seq: Some(1), subagent_description: None, nesting_depth: 0, + parent_task_id: None, } } diff --git a/src/tools/traits.rs b/src/tools/traits.rs index 02960aa..52d6f2b 100644 --- a/src/tools/traits.rs +++ b/src/tools/traits.rs @@ -20,6 +20,8 @@ pub struct ToolContext { pub subagent_description: Option, /// 当前嵌套深度(0 = 主 agent,1 = 子 agent,2 = 孙 agent...) pub nesting_depth: u32, + /// 父任务 ID(仅子/孙智能体有值,用于构建任务层级) + pub parent_task_id: Option, } #[async_trait] diff --git a/web/src/hooks/useChat.ts b/web/src/hooks/useChat.ts index d7321d1..92d5075 100644 --- a/web/src/hooks/useChat.ts +++ b/web/src/hooks/useChat.ts @@ -431,6 +431,8 @@ export function useChat(): UseChatReturn { const msg = message as TaskStarted // 只 backfill 当前话题的 task tool_call,避免跨话题串扰 if (msg.topic_id && msg.topic_id !== selectedTopicRef.current) break + // 孙智能体的 TaskStarted 不应 backfill 到主视图 + if (msg.parent_task_id) break // 立即更新对应的 task tool_call,让用户可以点击查看实时进度 setMessages((prev) => { diff --git a/web/src/types/protocol.ts b/web/src/types/protocol.ts index c34d5d0..1701900 100644 --- a/web/src/types/protocol.ts +++ b/web/src/types/protocol.ts @@ -101,6 +101,7 @@ export interface TaskStarted { description: string subagent_type: string topic_id?: string + parent_task_id?: string } export interface SessionEstablished {