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:
JOJO 2026-07-17 22:51:04 +08:00
parent d4c3e73134
commit 1d0a7e95fa
9 changed files with 320 additions and 163 deletions

View File

@ -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":

View File

@ -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:

View File

@ -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(),

View File

@ -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 模式;指定其他工作区时由前端切换后自动同步。

View File

@ -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:

View File

@ -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 ?? '';

View File

@ -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 ?? '';

View File

@ -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:

View File

@ -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,