feat(multi-agent): 扩展 sleep 工具模式并修复 /new 模式切换状态继承
- sleep 工具支持 wait_sub_agent_output / wait_sub_agent_ids / wait_runcommand_id 模式 - 多智能体工具移除 ask_sub_agent,新增 stop_sub_agent - 前后端补齐对应 formatter、渲染与工具执行分支 - 修复从多智能体模式切回 /new 后,新对话继承 terminal.multi_agent_mode 导致实际运行在多智能体模式的问题
This commit is contained in:
parent
d4c3e73134
commit
1d0a7e95fa
@ -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":
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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 模式;指定其他工作区时由前端切换后自动同步。
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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 = '<div class="tool-result-meta">';
|
||||
html += `<div><strong>等待时间:</strong>${seconds}秒</div>`;
|
||||
html += `<div><strong>状态:</strong>${status}</div>`;
|
||||
html += `<div><strong>模式:</strong>${escapeHtml(mode)}</div>`;
|
||||
|
||||
if (mode === 'seconds' || mode === '' || mode === null) {
|
||||
const seconds = args.seconds || args.duration || 0;
|
||||
html += `<div><strong>等待时间:</strong>${seconds}秒</div>`;
|
||||
if (args.reason) {
|
||||
html += `<div><strong>原因:</strong>${escapeHtml(String(args.reason))}</div>`;
|
||||
}
|
||||
} else if (mode === 'wait_sub_agent_output') {
|
||||
const agentId = result?.agent_id ?? args?.wait_sub_agent_output_ids ?? '';
|
||||
if (agentId !== '') {
|
||||
html += `<div><strong>等待子智能体:</strong>#${escapeHtml(String(agentId))}</div>`;
|
||||
}
|
||||
} else if (mode === 'wait_sub_agent_ids') {
|
||||
const agentIds = Array.isArray(result?.agent_ids) ? result.agent_ids : [];
|
||||
if (agentIds.length > 0) {
|
||||
html += `<div><strong>等待子智能体:</strong>${agentIds.map(String).join(', ')}</div>`;
|
||||
}
|
||||
const taskIds = Array.isArray(result?.waited_task_ids) ? result.waited_task_ids : [];
|
||||
if (taskIds.length > 0) {
|
||||
html += `<div><strong>任务 ID:</strong>${taskIds.map(String).join(', ')}</div>`;
|
||||
}
|
||||
} else if (mode === 'wait_runcommand_id') {
|
||||
const commandId = result?.command_id ?? args?.wait_runcommand_id ?? '';
|
||||
if (commandId !== '') {
|
||||
html += `<div><strong>后台命令 ID:</strong>${escapeHtml(String(commandId))}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result?.success && result?.error) {
|
||||
html += `<div><strong>错误:</strong>${escapeHtml(String(result.error))}</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
// content
|
||||
if (mode === 'seconds' || mode === '' || mode === null) {
|
||||
const message = result?.message || '';
|
||||
if (message) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += `<div class="content-label">结果:</div>`;
|
||||
html += `<pre>${escapeHtml(String(message))}</pre>`;
|
||||
html += '</div>';
|
||||
}
|
||||
} else if (mode === 'wait_sub_agent_output') {
|
||||
const message = result?.message || '';
|
||||
if (message) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += '<div class="content-label">子智能体输出:</div>';
|
||||
html += `<pre>${escapeHtml(String(message))}</pre>`;
|
||||
html += '</div>';
|
||||
}
|
||||
} else if (mode === 'wait_sub_agent_ids') {
|
||||
const results = Array.isArray(result?.results) ? result.results : [];
|
||||
if (results.length > 0) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += `<div class="content-label">已等待 ${results.length} 个子智能体:</div>`;
|
||||
results.forEach((item: any) => {
|
||||
if (!item || typeof item !== 'object') return;
|
||||
const agentId = item.agent_id ?? '?';
|
||||
const taskId = item.task_id ?? '?';
|
||||
const itemStatus = item.status ?? 'unknown';
|
||||
const success = item.success === true;
|
||||
html += '<div class="sub-agent-result-item">';
|
||||
html += `<div><strong>子智能体 #${escapeHtml(String(agentId))}</strong>(任务 ${escapeHtml(String(taskId))})· ${escapeHtml(String(itemStatus))} · ${success ? '✓ 成功' : '✗ 失败'}</div>`;
|
||||
const itemMessage = item.message || item.error || '';
|
||||
if (itemMessage) {
|
||||
const runtime = item.runtime_seconds ?? item.elapsed_seconds ?? null;
|
||||
const runtimeNote = runtime !== null ? `运行 ${Math.round(Number(runtime))} 秒` : '';
|
||||
const stats = item.stats;
|
||||
if (runtimeNote || stats) {
|
||||
html += '<div class="sub-agent-meta">';
|
||||
if (runtimeNote) {
|
||||
html += `<span>${escapeHtml(runtimeNote)}</span>`;
|
||||
}
|
||||
if (stats && typeof stats === 'object') {
|
||||
const statParts = [];
|
||||
if (stats.api_calls != null) statParts.push(`调用 ${stats.api_calls} 次`);
|
||||
if (stats.files_read != null) statParts.push(`阅读 ${stats.files_read} 次`);
|
||||
if (stats.edit_files != null) statParts.push(`编辑 ${stats.edit_files} 次`);
|
||||
if (stats.searches != null) statParts.push(`搜索 ${stats.searches} 次`);
|
||||
if (stats.web_pages != null) statParts.push(`网页 ${stats.web_pages} 个`);
|
||||
if (stats.commands != null) statParts.push(`命令 ${stats.commands} 个`);
|
||||
if (statParts.length > 0) {
|
||||
html += `<span>${escapeHtml(statParts.join(' | '))}</span>`;
|
||||
}
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
html += `<pre>${escapeHtml(String(itemMessage))}</pre>`;
|
||||
}
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
}
|
||||
} else if (mode === 'wait_runcommand_id') {
|
||||
const nested = result?.result;
|
||||
if (nested && typeof nested === 'object') {
|
||||
const output = nested.output || nested.stdout || '';
|
||||
const exitCode = nested.exit_code !== undefined ? nested.exit_code : nested.returncode;
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += '<div class="content-label">后台命令输出:</div>';
|
||||
if (exitCode !== undefined) {
|
||||
html += `<div><strong>退出码:</strong>${escapeHtml(String(exitCode))}</div>`;
|
||||
}
|
||||
if (output) {
|
||||
html += `<pre>${escapeHtml(String(output))}</pre>`;
|
||||
} else {
|
||||
html += '<div class="tool-result-empty">[无输出]</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
@ -1378,42 +1485,6 @@ function renderSendMessageToSubAgent(result: any, args: any): string {
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderAskSubAgent(result: any, args: any): string {
|
||||
const status = formatToolStatusLabel(result, '✓ 已回答', '✗ 获取失败');
|
||||
const agentId = args.agent_id ?? '';
|
||||
const question = args.question ?? '';
|
||||
const answer =
|
||||
result?.answer ?? result?.content ?? result?.message ?? result?.output ?? result?.result ?? '';
|
||||
|
||||
let html = '<div class="tool-result-meta">';
|
||||
html += `<div><strong>状态:</strong>${status}</div>`;
|
||||
if (agentId !== '') {
|
||||
html += `<div><strong>子智能体 ID:</strong>${escapeHtml(String(agentId))}</div>`;
|
||||
}
|
||||
if (question) {
|
||||
const preview = String(question).slice(0, 200);
|
||||
const suffix = String(question).length > 200 ? '…' : '';
|
||||
html += `<div><strong>问题:</strong>${escapeHtml(preview + suffix)}</div>`;
|
||||
}
|
||||
if (!result?.success && result?.error) {
|
||||
html += `<div><strong>错误:</strong>${escapeHtml(String(result.error))}</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
if (answer && typeof answer === 'string') {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += '<div class="content-label">回答</div>';
|
||||
html += `<pre>${escapeHtml(answer)}</pre>`;
|
||||
html += '</div>';
|
||||
} else if (result?.success) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += '<div class="tool-result-empty">子智能体未返回答复内容。</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderAnswerSubAgentQuestion(result: any, args: any): string {
|
||||
const status = formatToolStatusLabel(result, '✓ 已回复', '✗ 回复失败');
|
||||
const questionId = args.question_id ?? result.question_id ?? '';
|
||||
|
||||
@ -159,8 +159,6 @@ export function renderEnhancedToolResult(
|
||||
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') {
|
||||
@ -804,14 +802,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 = '<div class="tool-result-meta">';
|
||||
html += `<div><strong>等待时间:</strong>${seconds}秒</div>`;
|
||||
html += `<div><strong>状态:</strong>${status}</div>`;
|
||||
html += `<div><strong>模式:</strong>${escapeHtml(mode)}</div>`;
|
||||
|
||||
if (mode === 'seconds' || mode === '' || mode === null) {
|
||||
const seconds = args.seconds || args.duration || 0;
|
||||
html += `<div><strong>等待时间:</strong>${seconds}秒</div>`;
|
||||
if (args.reason) {
|
||||
html += `<div><strong>原因:</strong>${escapeHtml(String(args.reason))}</div>`;
|
||||
}
|
||||
} else if (mode === 'wait_sub_agent_output') {
|
||||
const agentId = result?.agent_id ?? args?.wait_sub_agent_output_ids ?? '';
|
||||
if (agentId !== '') {
|
||||
html += `<div><strong>等待子智能体:</strong>#${escapeHtml(String(agentId))}</div>`;
|
||||
}
|
||||
} else if (mode === 'wait_sub_agent_ids') {
|
||||
const agentIds = Array.isArray(result?.agent_ids) ? result.agent_ids : [];
|
||||
if (agentIds.length > 0) {
|
||||
html += `<div><strong>等待子智能体:</strong>${agentIds.map(String).join(', ')}</div>`;
|
||||
}
|
||||
const taskIds = Array.isArray(result?.waited_task_ids) ? result.waited_task_ids : [];
|
||||
if (taskIds.length > 0) {
|
||||
html += `<div><strong>任务 ID:</strong>${taskIds.map(String).join(', ')}</div>`;
|
||||
}
|
||||
} else if (mode === 'wait_runcommand_id') {
|
||||
const commandId = result?.command_id ?? args?.wait_runcommand_id ?? '';
|
||||
if (commandId !== '') {
|
||||
html += `<div><strong>后台命令 ID:</strong>${escapeHtml(String(commandId))}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result?.success && result?.error) {
|
||||
html += `<div><strong>错误:</strong>${escapeHtml(String(result.error))}</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
// content
|
||||
if (mode === 'seconds' || mode === '' || mode === null) {
|
||||
const message = result?.message || '';
|
||||
if (message) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += `<div class="content-label">结果:</div>`;
|
||||
html += `<pre>${escapeHtml(String(message))}</pre>`;
|
||||
html += '</div>';
|
||||
}
|
||||
} else if (mode === 'wait_sub_agent_output') {
|
||||
const message = result?.message || '';
|
||||
if (message) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += '<div class="content-label">子智能体输出:</div>';
|
||||
html += `<pre>${escapeHtml(String(message))}</pre>`;
|
||||
html += '</div>';
|
||||
}
|
||||
} else if (mode === 'wait_sub_agent_ids') {
|
||||
const results = Array.isArray(result?.results) ? result.results : [];
|
||||
if (results.length > 0) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += `<div class="content-label">已等待 ${results.length} 个子智能体:</div>`;
|
||||
results.forEach((item: any) => {
|
||||
if (!item || typeof item !== 'object') return;
|
||||
const agentId = item.agent_id ?? '?';
|
||||
const taskId = item.task_id ?? '?';
|
||||
const itemStatus = item.status ?? 'unknown';
|
||||
const success = item.success === true;
|
||||
html += '<div class="sub-agent-result-item">';
|
||||
html += `<div><strong>子智能体 #${escapeHtml(String(agentId))}</strong>(任务 ${escapeHtml(String(taskId))})· ${escapeHtml(String(itemStatus))} · ${success ? '✓ 成功' : '✗ 失败'}</div>`;
|
||||
const itemMessage = item.message || item.error || '';
|
||||
if (itemMessage) {
|
||||
const runtime = item.runtime_seconds ?? item.elapsed_seconds ?? null;
|
||||
const runtimeNote = runtime !== null ? `运行 ${Math.round(Number(runtime))} 秒` : '';
|
||||
const stats = item.stats;
|
||||
if (runtimeNote || stats) {
|
||||
html += '<div class="sub-agent-meta">';
|
||||
if (runtimeNote) {
|
||||
html += `<span>${escapeHtml(runtimeNote)}</span>`;
|
||||
}
|
||||
if (stats && typeof stats === 'object') {
|
||||
const statParts = [];
|
||||
if (stats.api_calls != null) statParts.push(`调用 ${stats.api_calls} 次`);
|
||||
if (stats.files_read != null) statParts.push(`阅读 ${stats.files_read} 次`);
|
||||
if (stats.edit_files != null) statParts.push(`编辑 ${stats.edit_files} 次`);
|
||||
if (stats.searches != null) statParts.push(`搜索 ${stats.searches} 次`);
|
||||
if (stats.web_pages != null) statParts.push(`网页 ${stats.web_pages} 个`);
|
||||
if (stats.commands != null) statParts.push(`命令 ${stats.commands} 个`);
|
||||
if (statParts.length > 0) {
|
||||
html += `<span>${escapeHtml(statParts.join(' | '))}</span>`;
|
||||
}
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
html += `<pre>${escapeHtml(String(itemMessage))}</pre>`;
|
||||
}
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
}
|
||||
} else if (mode === 'wait_runcommand_id') {
|
||||
const nested = result?.result;
|
||||
if (nested && typeof nested === 'object') {
|
||||
const output = nested.output || nested.stdout || '';
|
||||
const exitCode = nested.exit_code !== undefined ? nested.exit_code : nested.returncode;
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += '<div class="content-label">后台命令输出:</div>';
|
||||
if (exitCode !== undefined) {
|
||||
html += `<div><strong>退出码:</strong>${escapeHtml(String(exitCode))}</div>`;
|
||||
}
|
||||
if (output) {
|
||||
html += `<pre>${escapeHtml(String(output))}</pre>`;
|
||||
} else {
|
||||
html += '<div class="tool-result-empty">[无输出]</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
@ -1371,42 +1478,6 @@ function renderSendMessageToSubAgent(result: any, args: any): string {
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderAskSubAgent(result: any, args: any): string {
|
||||
const status = formatToolStatusLabel(result, '✓ 已回答', '✗ 获取失败');
|
||||
const agentId = args.agent_id ?? '';
|
||||
const question = args.question ?? '';
|
||||
const answer =
|
||||
result?.answer ?? result?.content ?? result?.message ?? result?.output ?? result?.result ?? '';
|
||||
|
||||
let html = '<div class="tool-result-meta">';
|
||||
html += `<div><strong>状态:</strong>${status}</div>`;
|
||||
if (agentId !== '') {
|
||||
html += `<div><strong>子智能体 ID:</strong>${escapeHtml(String(agentId))}</div>`;
|
||||
}
|
||||
if (question) {
|
||||
const preview = String(question).slice(0, 200);
|
||||
const suffix = String(question).length > 200 ? '…' : '';
|
||||
html += `<div><strong>问题:</strong>${escapeHtml(preview + suffix)}</div>`;
|
||||
}
|
||||
if (!result?.success && result?.error) {
|
||||
html += `<div><strong>错误:</strong>${escapeHtml(String(result.error))}</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
if (answer && typeof answer === 'string') {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += '<div class="content-label">回答</div>';
|
||||
html += `<pre>${escapeHtml(answer)}</pre>`;
|
||||
html += '</div>';
|
||||
} else if (result?.success) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += '<div class="tool-result-empty">子智能体未返回答复内容。</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderAnswerSubAgentQuestion(result: any, args: any): string {
|
||||
const status = formatToolStatusLabel(result, '✓ 已回复', '✗ 回复失败');
|
||||
const questionId = args.question_id ?? result.question_id ?? '';
|
||||
|
||||
@ -267,13 +267,14 @@ def _format_send_message_to_sub_agent(result_data: Dict[str, Any]) -> str:
|
||||
return "消息已发送。"
|
||||
|
||||
|
||||
def _format_ask_sub_agent(result_data: Dict[str, Any]) -> str:
|
||||
def _format_stop_sub_agent(result_data: Dict[str, Any]) -> str:
|
||||
if not result_data.get("success"):
|
||||
return _format_failure("ask_sub_agent", result_data)
|
||||
answer = result_data.get("answer")
|
||||
if answer is None:
|
||||
return "子智能体未返回答复。"
|
||||
return f"子智能体的回答:\n{answer}"
|
||||
return _format_failure("stop_sub_agent", result_data)
|
||||
agent_id = result_data.get("agent_id")
|
||||
message = result_data.get("message") or "子智能体已暂停。"
|
||||
if agent_id is not None:
|
||||
return f"已暂停子智能体 #{agent_id}。{message}"
|
||||
return message
|
||||
|
||||
|
||||
def _format_answer_sub_agent_question(result_data: Dict[str, Any]) -> str:
|
||||
|
||||
@ -38,7 +38,7 @@ from utils.tool_result_formatter.agent_context import (
|
||||
_format_get_sub_agent_status,
|
||||
_format_terminate_sub_agent,
|
||||
_format_send_message_to_sub_agent,
|
||||
_format_ask_sub_agent,
|
||||
_format_stop_sub_agent,
|
||||
_format_answer_sub_agent_question,
|
||||
_format_create_custom_agent,
|
||||
_format_list_agents,
|
||||
@ -131,7 +131,7 @@ TOOL_FORMATTERS = {
|
||||
"close_sub_agent": _format_close_sub_agent,
|
||||
"terminate_sub_agent": _format_terminate_sub_agent,
|
||||
"send_message_to_sub_agent": _format_send_message_to_sub_agent,
|
||||
"ask_sub_agent": _format_ask_sub_agent,
|
||||
"stop_sub_agent": _format_stop_sub_agent,
|
||||
"answer_sub_agent_question": _format_answer_sub_agent_question,
|
||||
"create_custom_agent": _format_create_custom_agent,
|
||||
"list_agents": _format_list_agents,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user