From 1e9075e1edc421fff9c075db88fa1303ed141e33 Mon Sep 17 00:00:00 2001 From: oudecheng <13802883547@139.com> Date: Wed, 5 Aug 2026 09:05:35 +0800 Subject: [PATCH] =?UTF-8?q?fix(subagent):=20=E5=A4=B1=E8=B4=A5/=E8=B6=85?= =?UTF-8?q?=E6=97=B6=E5=AD=90=E6=99=BA=E8=83=BD=E4=BD=93=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E5=8C=96=E7=BB=93=E6=9E=9C=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=89=8D=E7=AB=AF=E7=82=B9=E5=87=BB=E6=9F=A5=E7=9C=8B?= =?UTF-8?q?=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spawn()/resume() 在会话创建后的失败(含超时、执行错误、AgentCreationFailed) 现返回 Ok(TaskToolResult { status: "failed"/"timeout", task_id }) 而非 Err(e), 使 tool_result 内容始终为结构化 JSON。前端已有的 taskStatusConfig、StatusIcon、 "查看完整会话"按钮自动生效,同时修复服务重启后点击入口丢失的问题。 - runtime.rs: 提取 handle_task_failure 辅助方法统一处理失败;将 model_resolver/ create_subagent 的 ? 纳入 result 块,确保所有会话后失败都返回结构化结果; 修复 resume() 未区分 timeout 的 bug - tool.rs: 根据 task_result.status 判断 success,失败时 output 仍为 JSON - agent_loop.rs: 失败时 result.output 非空则透传(仅影响 task 工具),其他工具回退 到 "Error: {error}" 纯文本 --- src/agent/agent_loop.rs | 9 +- src/tools/task/runtime.rs | 183 +++++++++++++++++++++----------------- src/tools/task/tool.rs | 19 ++-- 3 files changed, 122 insertions(+), 89 deletions(-) diff --git a/src/agent/agent_loop.rs b/src/agent/agent_loop.rs index 1dfffeb..9f47d01 100644 --- a/src/agent/agent_loop.rs +++ b/src/agent/agent_loop.rs @@ -1752,7 +1752,14 @@ impl AgentLoop { output = %result.output, "Tool returned an error result" ); - ToolExecutionOutcome::failure(format!("Error: {}", error), Some(error)) + // task 工具在失败时提供结构化 JSON output(含 task_id),直接透传; + // 其他工具 output 为空,走纯文本 "Error: {error}" 回退。 + let failure_output = if !result.output.is_empty() { + result.output + } else { + format!("Error: {}", error) + }; + ToolExecutionOutcome::failure(failure_output, Some(error)) } } Err(e) => { diff --git a/src/tools/task/runtime.rs b/src/tools/task/runtime.rs index 2c20113..3f9c6e5 100644 --- a/src/tools/task/runtime.rs +++ b/src/tools/task/runtime.rs @@ -648,6 +648,39 @@ impl DefaultSubAgentRuntime { Err(_) => Err(TaskError::Timeout), } } + + /// 会话创建后的失败处理:标记状态、持久化、发布错误事件、返回结构化失败结果。 + /// + /// 返回 `Ok(TaskToolResult)` 而非 `Err`,确保 tool_result 携带 `task_id` 供前端导航。 + /// `save_task_session` 失败是基础设施故障,仍通过 `?` 返回 `Err`。 + async fn handle_task_failure( + &self, + session: TaskSession, + error: TaskError, + ) -> Result { + let status = error.as_status(); + tracing::warn!( + task_id = %session.id, + session_id = %session.session_id, + status = %status, + error = %error, + "Task failed, updating session" + ); + let mut session = session; + if status == "timeout" { + session.mark_timeout(); + } else { + session.mark_failed(error.to_string()); + } + self.task_repository.save_task_session(&session).await?; + publish_subagent_error(&self.bus, &session, &error.to_string()).await; + Ok(TaskToolResult { + status: status.to_string(), + summary: error.to_string(), + output: String::new(), + task_id: session.id.clone(), + }) + } } #[async_trait] @@ -763,54 +796,58 @@ impl SubAgentRuntime for DefaultSubAgentRuntime { } } - // 6. 构建子代理系统提示词 - // 实时按 def.capability 过滤技能索引(替代冻结快照,反映运行时技能增删) - let skills_index = if def.capability.has_skill_policy() { - self.skills.system_index_prompt_filtered( - def.capability.allowed_skills.as_deref(), - &def.capability.denied_skills, - ) - } else { - self.skills.system_index_prompt() - }; - // 同步解析 def 中的 provider/model 覆盖,保证环境提示中的模型名与实际使用的模型一致 - let effective_provider_config = match (def.provider.is_some(), def.model.is_some()) { - (true, _) | (_, true) => self - .model_resolver - .resolve( - def.provider.as_deref(), - def.model.as_deref(), - &self.provider_config, + // 6-8. 构建提示词、创建子代理、执行任务 + // 统一为单个 Result 表达式:model_resolver / create_subagent / execute_task + // 的任何失败都流入下方 match 的 Err 分支,经 handle_task_failure 返回结构化结果。 + let result: Result = { + // 6. 构建子代理系统提示词 + // 实时按 def.capability 过滤技能索引(替代冻结快照,反映运行时技能增删) + let skills_index = if def.capability.has_skill_policy() { + self.skills.system_index_prompt_filtered( + def.capability.allowed_skills.as_deref(), + &def.capability.denied_skills, ) - .map_err(|e| { - TaskError::AgentCreationFailed(format!( - "subagent '{}' model resolution failed: {}", - def.name, e - )) - })?, - _ => self.provider_config.clone(), + } else { + self.skills.system_index_prompt() + }; + // 同步解析 def 中的 provider/model 覆盖,保证环境提示中的模型名与实际使用的模型一致 + let effective_provider_config = match (def.provider.is_some(), def.model.is_some()) { + (true, _) | (_, true) => self + .model_resolver + .resolve( + def.provider.as_deref(), + def.model.as_deref(), + &self.provider_config, + ) + .map_err(|e| { + TaskError::AgentCreationFailed(format!( + "subagent '{}' model resolution failed: {}", + def.name, e + )) + })?, + _ => self.provider_config.clone(), + }; + let system_prompt = SubagentPromptBuilder::build( + &def, + &task.description, + &task.prompt, + &effective_provider_config, + skills_index.as_deref(), + ); + + // 7. 创建子代理 + let agent = self.create_subagent( + &session, + system_prompt, + Some(&def), + parent_context.nesting_depth, + parent_context.task_id.clone(), + )?; + + // 8. 执行任务 + self.execute_task(agent, &session, &def, task.prompt.clone()) + .await }; - let system_prompt = SubagentPromptBuilder::build( - &def, - &task.description, - &task.prompt, - &effective_provider_config, - skills_index.as_deref(), - ); - - // 7. 创建子代理 - let agent = self.create_subagent( - &session, - system_prompt, - Some(&def), - parent_context.nesting_depth, - parent_context.task_id.clone(), - )?; - - // 8. 执行任务 - let result = self - .execute_task(agent, &session, &def, task.prompt.clone()) - .await; // 9. 更新会话状态并保存 match result { @@ -828,24 +865,9 @@ impl SubAgentRuntime for DefaultSubAgentRuntime { Ok(tool_result) } Err(e) => { - let mut session = session; - let status = e.as_status(); - tracing::warn!( - task_id = %session.id, - session_id = %session.session_id, - status = %status, - error = %e, - "Task failed, updating session" - ); - if status == "timeout" { - session.mark_timeout(); - } else { - session.mark_failed(e.to_string()); - } - self.task_repository.save_task_session(&session).await?; - // 发布子智能体 ErrorNotification,metadata 注入 subagent_task_id 供前端路由到对应子智能体层 - publish_subagent_error(&self.bus, &session, &e.to_string()).await; - Err(e) + // 会话创建后的任何失败(含 AgentCreationFailed、Timeout、ExecutionFailed) + // 统一返回结构化结果,携带 task_id 供前端导航 + self.handle_task_failure(session, e).await } } } @@ -907,19 +929,18 @@ impl SubAgentRuntime for DefaultSubAgentRuntime { .find_subagent_def(&session.subagent_type) .map_err(TaskError::InvalidArguments)?; - // 5. 创建子代理 - let agent = self.create_subagent( - &session, - system_prompt, - Some(&def), - parent_context.nesting_depth, - parent_context.task_id.clone(), - )?; - - // 6. 使用历史继续执行 - let result = self - .execute_task_with_history(agent, &session, additional_prompt) - .await; + // 5-6. 创建子代理 + 执行(统一为 Result,失败走 handle_task_failure) + let result: Result = { + let agent = self.create_subagent( + &session, + system_prompt, + Some(&def), + parent_context.nesting_depth, + parent_context.task_id.clone(), + )?; + self.execute_task_with_history(agent, &session, additional_prompt) + .await + }; // 7. 更新会话状态 match result { @@ -932,12 +953,8 @@ impl SubAgentRuntime for DefaultSubAgentRuntime { Ok(tool_result) } Err(e) => { - let mut session = session; - session.mark_failed(e.to_string()); - self.task_repository.save_task_session(&session).await?; - // 发布子智能体 ErrorNotification,metadata 注入 subagent_task_id 供前端路由到对应子智能体层 - publish_subagent_error(&self.bus, &session, &e.to_string()).await; - Err(e) + // 修复:原代码一律 mark_failed,未处理 timeout;现统一走 handle_task_failure + self.handle_task_failure(session, e).await } } } diff --git a/src/tools/task/tool.rs b/src/tools/task/tool.rs index f48111c..ef4f3cf 100644 --- a/src/tools/task/tool.rs +++ b/src/tools/task/tool.rs @@ -162,12 +162,21 @@ impl Tool for TaskTool { }; // 5. 构建返回结果 + // spawn()/resume() 在会话创建后的失败也返回 Ok(TaskToolResult { status: "failed"/"timeout" }), + // 携带 task_id 供前端导航。仅会话创建前的失败(参数校验、def 未找到等)返回 Err。 match result { - Ok(task_result) => Ok(ToolResult { - success: true, - output: serde_json::to_string(&task_result)?, - error: None, - }), + Ok(task_result) => { + let success = task_result.status == "success"; + Ok(ToolResult { + success, + output: serde_json::to_string(&task_result)?, + error: if success { + None + } else { + Some(task_result.summary.clone()) + }, + }) + } Err(e) => Ok(ToolResult { success: false, output: String::new(),