diff --git a/core/main_terminal_parts/tools_execution.py b/core/main_terminal_parts/tools_execution.py index 0b503f5..0c914ed 100644 --- a/core/main_terminal_parts/tools_execution.py +++ b/core/main_terminal_parts/tools_execution.py @@ -1135,9 +1135,16 @@ class MainTerminalToolsExecutionMixin: "message": msg, } except asyncio.TimeoutError: - result = {"success": False, "error": f"等待子智能体 {agent_id} 输出超时(5分钟)"} + result = {"success": False, "error": f"等待子智能体 {agent_id} 输出超时(5分钟)。可用 send_message_to_sub_agent 重新激活。"} + except asyncio.CancelledError: + result = {"success": False, "error": f"等待子智能体 {agent_id} 被取消。该子智能体现在不可用,可用 send_message_to_sub_agent 重新激活。"} + except RuntimeError as exc: + error_msg = str(exc) + if "send_message_to_sub_agent" not in error_msg: + error_msg += " 可用 send_message_to_sub_agent 重新激活。" + result = {"success": False, "error": error_msg} except Exception as exc: - result = {"success": False, "error": f"等待子智能体 {agent_id} 输出失败: {exc}"} + result = {"success": False, "error": f"等待子智能体 {agent_id} 输出失败: {exc}。可用 send_message_to_sub_agent 重新激活。"} elif wait_sub_agent_ids: if getattr(self, "multi_agent_mode", False): result = { @@ -1923,7 +1930,7 @@ class MainTerminalToolsExecutionMixin: agent_ids=arguments.get("agent_ids", []) ) - # 多智能体模式专属工具:send_message_to_sub_agent / ask_sub_agent / answer_sub_agent_question / create_custom_agent / list_agents / list_active_sub_agents + # 多智能体模式专属工具:send_message_to_sub_agent / stop_sub_agent / answer_sub_agent_question / create_custom_agent / list_agents / list_active_sub_agents elif tool_name == "send_message_to_sub_agent": if not getattr(self, "multi_agent_mode", False): result = {"success": False, "error": "该工具仅在多智能体模式下可用"} @@ -1962,51 +1969,15 @@ class MainTerminalToolsExecutionMixin: logger.exception("[multi_agent] send_message_to_sub_agent failed") result = {"success": False, "error": str(exc)} - elif tool_name == "ask_sub_agent": + elif tool_name == "stop_sub_agent": if not getattr(self, "multi_agent_mode", False): result = {"success": False, "error": "该工具仅在多智能体模式下可用"} else: try: - from modules.multi_agent.state import format_multi_agent_message, TYPE_ASK agent_id = int(arguments.get("agent_id", 0)) - question = arguments.get("question", "") - timeout = int(arguments.get("timeout_seconds", 600)) - conv_id = self.context_manager.current_conversation_id - state = self.sub_agent_manager.get_multi_agent_state(conv_id) - if not state: - result = {"success": False, "error": "多智能体状态未就绪"} - else: - question_id = f"ask_sub_agent_{int(time.time() * 1000)}_{uuid.uuid4().hex[:6]}" - # 构造提问并插入子对话(子智能体下一轮 assistant 输出作为回答返回到工具结果) - text = format_multi_agent_message( - display_name="Team Leader", - msg_type=TYPE_ASK, - content=question, - msg_id=question_id, - target=state.get_instance(agent_id).display_name if state.get_instance(agent_id) else f"Agent_{agent_id}", - ) - ma_debug( - "tool_ask_sub_agent", - agent_id=agent_id, - question=str(question)[:500], - question_id=question_id, - wrapped_message_preview=text[:500], - conversation_id=conv_id, - ) - ok = self.sub_agent_manager.inject_message_to_sub_agent(agent_id, text) - if not ok: - result = {"success": False, "error": f"子智能体 {agent_id} 不存在"} - else: - # 阻塞等待子智能体下一轮输出作为回答 - answer = await state.wait_for_answer( - question_id=question_id, - agent_id=agent_id, - timeout=timeout, - ) - result = {"success": True, "answer": answer} - except asyncio.TimeoutError: - result = {"success": False, "error": "等待子智能体回答超时"} + result = self.sub_agent_manager.stop_sub_agent(agent_id=agent_id) except Exception as exc: + logger.exception("[multi_agent] stop_sub_agent failed") result = {"success": False, "error": str(exc)} elif tool_name == "answer_sub_agent_question": diff --git a/modules/multi_agent/state.py b/modules/multi_agent/state.py index ed6932d..72c743c 100644 --- a/modules/multi_agent/state.py +++ b/modules/multi_agent/state.py @@ -316,17 +316,45 @@ class MultiAgentState: return self.agents.get(aid) def list_active(self) -> List[AgentInstance]: - return [a for a in self.agents.values() if a.status == "running" or a.status == "idle"] + # failed 视为可复活状态,仍算活跃 + return [a for a in self.agents.values() if a.status in ("running", "idle", "failed")] def list_all(self) -> List[AgentInstance]: return list(self.agents.values()) def mark_status(self, agent_id: int, status: str, last_output: str = "") -> None: a = self.agents.get(agent_id) - if a: - a.status = status - if last_output: - a.last_output = last_output + if not a: + return + a.status = status + if last_output: + a.last_output = last_output + # 当子智能体进入终态/idle 时,立即取消 sleep(wait_sub_agent_output_ids) 的等待 + if status in ("failed", "terminated", "idle"): + self._cancel_output_wait(agent_id, status) + + def _cancel_output_wait(self, agent_id: int, status: str) -> None: + """取消指定 agent 的 sleep 输出等待,并提示可用 send_message_to_sub_agent 重新激活。""" + fut = self.output_waits.pop(agent_id, None) + if not fut or fut.done(): + return + if status == "terminated": + msg = f"子智能体 {agent_id} 已终止,无法继续等待输出。" + elif status == "failed": + msg = f"子智能体 {agent_id} 已失败,无法继续等待输出。该子智能体可被复活,可用 send_message_to_sub_agent 重新激活。" + else: + msg = f"子智能体 {agent_id} 已进入空闲状态,无法继续等待输出。可用 send_message_to_sub_agent 重新激活。" + try: + loop = fut.get_loop() + if loop and not loop.is_closed(): + loop.call_soon_threadsafe(fut.set_exception, RuntimeError(msg)) + return + except Exception: + pass + try: + fut.set_exception(RuntimeError(msg)) + except Exception: + pass # ----- 主对话注入 ----- def push_master_message(self, message_text: str) -> None: diff --git a/modules/multi_agent/tools.py b/modules/multi_agent/tools.py index 5f5d345..30d938e 100644 --- a/modules/multi_agent/tools.py +++ b/modules/multi_agent/tools.py @@ -65,6 +65,23 @@ def _master_tool_create_sub_agent() -> Dict[str, Any]: } +def _master_tool_stop_sub_agent() -> Dict[str, Any]: + return { + "type": "function", + "function": { + "name": "stop_sub_agent", + "description": "暂停指定子智能体实例,使其进入 idle 状态而不终结。暂停后可用 send_message_to_sub_agent 重新激活继续工作。", + "parameters": { + "type": "object", + "properties": _inject_intent({ + "agent_id": {"type": "integer", "description": "要暂停的子智能体编号。"}, + }), + "required": ["agent_id"], + }, + }, + } + + def _master_tool_terminate_sub_agent() -> Dict[str, Any]: return { "type": "function", @@ -104,29 +121,6 @@ def _master_tool_send_message_to_sub_agent() -> Dict[str, Any]: } -def _master_tool_ask_sub_agent() -> Dict[str, Any]: - return { - "type": "function", - "function": { - "name": "ask_sub_agent", - "description": ( - "向指定子智能体提出一个明确问题并阻塞等待一轮回答(不是发起任务,是问问题)。" - "问题会以 `来自 Team Leader 的提问` 格式插入子对话,子智能体下一轮 assistant 输出" - "作为回答返回到此工具结果中。" - ), - "parameters": { - "type": "object", - "properties": _inject_intent({ - "agent_id": {"type": "integer", "description": "目标子智能体编号。"}, - "question": {"type": "string", "description": "要询问的问题,应简短明确。"}, - "timeout_seconds": {"type": "integer", "description": "等待回答超时秒数,默认 600。"}, - }), - "required": ["agent_id", "question"], - }, - }, - } - - def _master_tool_answer_sub_agent_question() -> Dict[str, Any]: return { "type": "function", @@ -210,9 +204,9 @@ def _master_tool_get_sub_agent_status() -> Dict[str, Any]: MULTI_AGENT_MASTER_TOOLS: List[Dict[str, Any]] = [ _master_tool_create_sub_agent(), + _master_tool_stop_sub_agent(), _master_tool_terminate_sub_agent(), _master_tool_send_message_to_sub_agent(), - _master_tool_ask_sub_agent(), _master_tool_answer_sub_agent_question(), _master_tool_create_custom_agent(), _master_tool_list_agents(), diff --git a/server/conversation.py b/server/conversation.py index 5d678ad..c734aa8 100644 --- a/server/conversation.py +++ b/server/conversation.py @@ -644,13 +644,20 @@ def create_conversation(terminal: WebTerminal, workspace: UserWorkspace, usernam metadata_overrides={ "permission_mode": default_permission_mode or getattr(terminal, "get_permission_mode", lambda: "unrestricted")(), "execution_mode": getattr(terminal, "get_execution_mode", lambda: "sandbox")(), - "multi_agent_mode": multi_agent_mode, + "multi_agent_mode": bool(multi_agent_mode), }, ) try: cm.current_conversation_id = previous_cm_current except Exception: pass + # 同步 terminal 级别的多智能体开关,避免新对话继承旧状态 + terminal.multi_agent_mode = bool(multi_agent_mode) + try: + if hasattr(terminal, "sub_agent_manager"): + terminal.sub_agent_manager.multi_agent_mode = bool(multi_agent_mode) + except Exception: + pass result = { "success": True, "conversation_id": conversation_id, @@ -661,8 +668,15 @@ def create_conversation(terminal: WebTerminal, workspace: UserWorkspace, usernam result = terminal.create_new_conversation( thinking_mode=thinking_mode, run_mode=run_mode, - metadata_overrides={"multi_agent_mode": multi_agent_mode} if multi_agent_mode else None, + metadata_overrides={"multi_agent_mode": bool(multi_agent_mode)}, ) + # 同步 terminal 级别的多智能体开关,避免新对话继承旧状态 + terminal.multi_agent_mode = bool(multi_agent_mode) + try: + if hasattr(terminal, "sub_agent_manager"): + terminal.sub_agent_manager.multi_agent_mode = bool(multi_agent_mode) + except Exception: + pass if result["success"]: # 仅在当前工作区创建时更新 session 模式;指定其他工作区时由前端切换后自动同步。 diff --git a/server/multi_agent.py b/server/multi_agent.py index 1697f49..0d963b1 100644 --- a/server/multi_agent.py +++ b/server/multi_agent.py @@ -329,6 +329,13 @@ def create_multi_agent_conversation(): ctx_manager.current_conversation_id = previous_cm_current except Exception: pass + # 同步 terminal 级别的多智能体开关 + terminal.multi_agent_mode = True + try: + if hasattr(terminal, "sub_agent_manager"): + terminal.sub_agent_manager.multi_agent_mode = True + except Exception: + pass # 触发对话列表更新事件 try: diff --git a/static/src/components/chat/actions/ToolAction.vue b/static/src/components/chat/actions/ToolAction.vue index 98a0e32..eae9c4f 100644 --- a/static/src/components/chat/actions/ToolAction.vue +++ b/static/src/components/chat/actions/ToolAction.vue @@ -204,8 +204,6 @@ function renderToolResult(): string { return renderTerminateSubAgent(result, args); } else if (name === 'send_message_to_sub_agent') { return renderSendMessageToSubAgent(result, args); - } else if (name === 'ask_sub_agent') { - return renderAskSubAgent(result, args); } else if (name === 'answer_sub_agent_question') { return renderAnswerSubAgentQuestion(result, args); } else if (name === 'create_custom_agent') { @@ -832,14 +830,123 @@ function renderTerminalSnapshot(result: any, args: any): string { } function renderSleep(result: any, args: any): string { - const seconds = args.seconds || args.duration || 0; const status = formatToolStatusLabel(result, '✓ 完成'); - + const mode = result?.mode || 'seconds'; let html = '
'; + // content + if (mode === 'seconds' || mode === '' || mode === null) { + const message = result?.message || ''; + if (message) { + html += '${escapeHtml(String(message))}`;
+ html += '${escapeHtml(String(message))}`;
+ html += '${escapeHtml(String(itemMessage))}`;
+ }
+ html += '${escapeHtml(String(output))}`;
+ } else {
+ html += '${escapeHtml(answer)}`;
- html += '${escapeHtml(String(message))}`;
+ html += '${escapeHtml(String(message))}`;
+ html += '${escapeHtml(String(itemMessage))}`;
+ }
+ html += '${escapeHtml(String(output))}`;
+ } else {
+ html += '${escapeHtml(answer)}`;
- html += '