From cd3f07bcc841098ee376516bba6e8c3a2d055abb Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Wed, 11 Mar 2026 18:05:01 +0800 Subject: [PATCH] fix: enrich sub-agent results --- modules/sub_agent_manager.py | 34 +++++++++++++++++++ server/chat_flow_task_main.py | 7 ++++ server/chat_flow_task_support.py | 11 ++++++ .../overlay/SubAgentActivityDialog.vue | 20 +++++------ utils/tool_result_formatter.py | 21 +++++++++++- 5 files changed, 82 insertions(+), 11 deletions(-) diff --git a/modules/sub_agent_manager.py b/modules/sub_agent_manager.py index 19b03cf..e21bcd2 100644 --- a/modules/sub_agent_manager.py +++ b/modules/sub_agent_manager.py @@ -328,6 +328,10 @@ class SubAgentManager: task["status"] = status task["updated_at"] = time.time() + elapsed_seconds = self._compute_elapsed_seconds(task) + if status == "completed" and elapsed_seconds is not None: + task["elapsed_seconds"] = elapsed_seconds + task["runtime_seconds"] = elapsed_seconds # 构建系统消息 agent_id = task.get("agent_id") @@ -340,6 +344,7 @@ class SubAgentManager: stats_summary=stats_summary, summary=summary, deliverables_dir=deliverables_dir, + duration_seconds=elapsed_seconds, ) elif status == "timeout": system_message = self._compose_sub_agent_message( @@ -365,6 +370,9 @@ class SubAgentManager: "stats_summary": stats_summary, "system_message": system_message, } + if status == "completed" and elapsed_seconds is not None: + result["elapsed_seconds"] = elapsed_seconds + result["runtime_seconds"] = elapsed_seconds task["final_result"] = result return result @@ -827,6 +835,11 @@ class SubAgentManager: copied_path = self._copy_deliverables_to_project(task, deliverables_dir) task["copied_path"] = str(copied_path) + elapsed_seconds = self._compute_elapsed_seconds(task) + if elapsed_seconds is not None: + task["elapsed_seconds"] = elapsed_seconds + task["runtime_seconds"] = elapsed_seconds + system_message = self._build_system_message(task, status, copied_path, message) result = { "success": True, @@ -840,6 +853,9 @@ class SubAgentManager: "system_message": system_message, "details": service_payload, } + if elapsed_seconds is not None: + result["elapsed_seconds"] = elapsed_seconds + result["runtime_seconds"] = elapsed_seconds task["final_result"] = result return result @@ -896,7 +912,10 @@ class SubAgentManager: extra = (extra_message or "").strip() if status == "completed" and copied_path: + elapsed_seconds = self._compute_elapsed_seconds(task) msg = f"{prefix} 已完成,成果已复制到 {copied_path}。" + if elapsed_seconds is not None: + msg += f" 运行了{elapsed_seconds}秒。" if extra: msg += f" ({extra})" return msg @@ -916,6 +935,18 @@ class SubAgentManager: except (TypeError, ValueError): return 0 + @staticmethod + def _compute_elapsed_seconds(task: Dict) -> Optional[int]: + try: + created_at = float(task.get("created_at") or 0) + updated_at = float(task.get("updated_at") or time.time()) + except (TypeError, ValueError): + return None + if created_at <= 0: + return None + elapsed = max(0.0, updated_at - created_at) + return int(round(elapsed)) + def _build_stats_summary(self, stats: Optional[Dict[str, Any]]) -> str: if not isinstance(stats, dict): stats = {} @@ -946,10 +977,13 @@ class SubAgentManager: stats_summary: str, summary: str, deliverables_dir: Optional[str] = None, + duration_seconds: Optional[int] = None, ) -> str: parts = [prefix] if stats_summary: parts.append(stats_summary) + if duration_seconds is not None: + parts.append(f"运行了{duration_seconds}秒") if summary: parts.append(summary) if deliverables_dir: diff --git a/server/chat_flow_task_main.py b/server/chat_flow_task_main.py index 0fad80b..45878f2 100644 --- a/server/chat_flow_task_main.py +++ b/server/chat_flow_task_main.py @@ -197,11 +197,18 @@ async def poll_sub_agent_completion(*, web_terminal, workspace, conversation_id, # 构建 user 消息(后台完成时才发送) prefix = "这是一句系统自动发送的user消息,用于通知你子智能体已经运行完成" + runtime_line = "" + elapsed_seconds = update.get("runtime_seconds") + if elapsed_seconds is None: + elapsed_seconds = update.get("elapsed_seconds") + if status == "completed" and isinstance(elapsed_seconds, (int, float)): + runtime_line = f"\n\n运行了{int(round(elapsed_seconds))}秒" user_message = f"""{prefix} 子智能体{agent_id} ({summary}) 已完成任务。 {result_summary} +{runtime_line} 交付目录:{deliverables_dir}""" diff --git a/server/chat_flow_task_support.py b/server/chat_flow_task_support.py index 45eec2d..fab8b79 100644 --- a/server/chat_flow_task_support.py +++ b/server/chat_flow_task_support.py @@ -56,6 +56,9 @@ async def process_sub_agent_updates(*, messages: List[Dict], inline: bool = Fals updates = synthesized debug_log(f"[SubAgent] synthesized updates count={len(updates)}") + if inline and not hasattr(web_terminal, "_inline_sub_agent_notified"): + web_terminal._inline_sub_agent_notified = set() + for update in updates: task_id = update.get("task_id") task_info = manager.tasks.get(task_id) if task_id else None @@ -78,6 +81,12 @@ async def process_sub_agent_updates(*, messages: List[Dict], inline: bool = Fals debug_log(f"[SubAgent] update missing system_message: task={task_id} keys={list(update.keys())}") continue + if inline: + inline_key = ("task", task_id) if task_id else ("msg", message) + if inline_key in web_terminal._inline_sub_agent_notified: + debug_log(f"[SubAgent] inline 通知已发送,跳过: key={inline_key}") + continue + debug_log(f"[SubAgent] update task={task_id} inline={inline} msg={message}") # 记录到对话历史(用于后续 build_messages 转换为 user 消息) @@ -118,6 +127,8 @@ async def process_sub_agent_updates(*, messages: List[Dict], inline: bool = Fals "content": message, "metadata": {"sub_agent_notice": True, "inline": inline, "task_id": task_id} }) + if inline: + web_terminal._inline_sub_agent_notified.add(inline_key) debug_log(f"[SubAgent] 插入子智能体通知位置: {insert_index} role={insert_role} after_tool_call_id={after_tool_call_id}") sender('system_message', { 'content': message, diff --git a/static/src/components/overlay/SubAgentActivityDialog.vue b/static/src/components/overlay/SubAgentActivityDialog.vue index ec86180..9a5d844 100644 --- a/static/src/components/overlay/SubAgentActivityDialog.vue +++ b/static/src/components/overlay/SubAgentActivityDialog.vue @@ -56,38 +56,38 @@ const normalizeStatus = (status?: string) => { return status || 'running'; }; -const buildText = (entry: ActivityEntry, stateLabel: string) => { +const buildText = (entry: ActivityEntry) => { const tool = entry.tool || ''; const args = entry.args || {}; if (tool === 'read_file') { const path = args.path || args.file_path || ''; - return `阅读 ${path} ${stateLabel}`; + return `阅读 ${path}`; } if (tool === 'search_workspace') { const query = args.query || args.keyword || ''; - return `在工作区搜索 ${query} ${stateLabel}`; + return `在工作区搜索 ${query}`; } if (tool === 'web_search') { const query = args.query || args.q || ''; - return `在互联网中搜索 ${query} ${stateLabel}`; + return `在互联网中搜索 ${query}`; } if (tool === 'extract_webpage') { const url = args.url || ''; - return `在互联网中提取 ${url} ${stateLabel}`; + return `在互联网中提取 ${url}`; } if (tool === 'run_command') { const command = args.command || ''; - return `运行命令 ${command} ${stateLabel}`; + return `运行命令 ${command}`; } if (tool === 'edit_file') { const path = args.path || args.file_path || ''; - return `编辑 ${path} ${stateLabel}`; + return `编辑 ${path}`; } if (tool === 'read_mediafile') { const path = args.path || args.file_path || ''; - return `读取媒体文件 ${path} ${stateLabel}`; + return `读取媒体文件 ${path}`; } - return `${tool || '工具'} ${stateLabel}`; + return `${tool || '工具'}`; }; const displayItems = computed(() => { @@ -112,7 +112,7 @@ const displayItems = computed(() => { key, state, stateLabel, - text: buildText(item, stateLabel) + text: buildText(item) }; }); }); diff --git a/utils/tool_result_formatter.py b/utils/tool_result_formatter.py index fed4dff..a231d85 100644 --- a/utils/tool_result_formatter.py +++ b/utils/tool_result_formatter.py @@ -529,9 +529,14 @@ def _format_create_sub_agent(result_data: Dict[str, Any]) -> str: result_data.get("stats") or (result_data.get("final_result") or {}).get("stats") ) summary = result_data.get("message") or result_data.get("summary") + elapsed_seconds = result_data.get("runtime_seconds") + if elapsed_seconds is None: + elapsed_seconds = result_data.get("elapsed_seconds") lines = [header] if stats_text: lines.append(stats_text) + if status == "completed" and isinstance(elapsed_seconds, (int, float)): + lines.append(f"运行了{int(round(elapsed_seconds))}秒") if summary and status in {"completed", "failed", "timeout", "terminated"}: lines.append(str(summary)) return "\n".join(lines) @@ -541,7 +546,13 @@ def _format_wait_sub_agent(result_data: Dict[str, Any]) -> str: task_id = result_data.get("task_id") agent_id = result_data.get("agent_id") status = result_data.get("status") - stats_text = _format_sub_agent_stats(result_data.get("stats")) + stats_value = result_data.get("stats") + if not isinstance(stats_value, dict) and status == "timeout": + stats_value = {} + stats_text = _format_sub_agent_stats(stats_value) + elapsed_seconds = result_data.get("runtime_seconds") + if elapsed_seconds is None: + elapsed_seconds = result_data.get("elapsed_seconds") if result_data.get("success"): copied_path = result_data.get("copied_path") or result_data.get("deliverables_path") message = result_data.get("message") or "子智能体任务已完成。" @@ -549,6 +560,8 @@ def _format_wait_sub_agent(result_data: Dict[str, Any]) -> str: lines = [f"子智能体 #{agent_id}/{task_id} 完成"] if stats_text: lines.append(stats_text) + if isinstance(elapsed_seconds, (int, float)): + lines.append(f"运行了{int(round(elapsed_seconds))}秒") lines.append(message) lines.append(deliver_note) return "\n".join(lines) @@ -575,14 +588,20 @@ def _format_get_sub_agent_status(result_data: Dict[str, Any]) -> str: status = item.get("status") summary = None final_result = item.get("final_result") or {} + elapsed_seconds = None if isinstance(final_result, dict): summary = final_result.get("message") or final_result.get("summary") + elapsed_seconds = final_result.get("runtime_seconds") + if elapsed_seconds is None: + elapsed_seconds = final_result.get("elapsed_seconds") if not summary: summary = item.get("summary") or "" stats_text = _format_sub_agent_stats(item.get("stats")) lines = [f"子智能体 #{agent_id} 状态: {status}"] if stats_text: lines.append(stats_text) + if status == "completed" and isinstance(elapsed_seconds, (int, float)): + lines.append(f"运行了{int(round(elapsed_seconds))}秒") if summary: lines.append(str(summary)) blocks.append("\n".join(lines))