feat(multi_agent): 提取prompt、恢复任务、优化侧边栏与sleep工具

- 将多智能体/子智能体prompt从代码提取到prompts/multi_agent/和prompts/sub_agent/

- 多智能体模式添加可用的子智能体动态prompt并冻结

- 重启后自动从conversation.json恢复多智能体idle任务

- 修复新对话侧边栏显示其他对话子智能体的问题

- 简化子智能体弹窗输出样式

- sleep工具在多智能体模式下移除wait_sub_agent_ids参数
This commit is contained in:
JOJO 2026-07-13 14:01:48 +08:00
parent de5160ead0
commit fe6fba3958
14 changed files with 632 additions and 301 deletions

View File

@ -48,6 +48,7 @@ from modules.terminal_manager import TerminalManager
from modules.todo_manager import TodoManager
from modules.sub_agent import SubAgentManager
from modules.webpage_extractor import extract_webpage_content, tavily_extract
from modules.multi_agent.prompts import build_available_agents_prompt
from modules.ocr_client import OCRClient
from modules.easter_egg_manager import EasterEggManager
from modules.personalization_manager import (
@ -288,6 +289,17 @@ class MessagesMixin:
messages.append({"role": "system", "content": multi_agent_prompt})
except Exception as exc:
logger.warning(f"[messages] 注入多智能体 prompt 失败: {exc}")
# 可用的子智能体角色(动态 prompt第一个用户消息后冻结
try:
available_agents_prompt = self._get_or_init_frozen_prompt(
"frozen_available_agents_prompt",
lambda: build_available_agents_prompt() or "",
)
if available_agents_prompt:
messages.append({"role": "system", "content": available_agents_prompt})
except Exception as exc:
logger.warning(f"[messages] 注入可用子智能体 prompt 失败: {exc}")
if disabled_notice:
messages.append({
"role": "system",

View File

@ -90,38 +90,74 @@ DISABLE_LENGTH_CHECK = True
class ToolsDefinitionCoreToolsMixin:
def _build_sleep_tool_definition(self) -> Dict:
"""根据运行模式构建 sleep 工具定义。
多智能体模式下子智能体不会调用 finish_task 结束因此不提供
wait_sub_agent_ids 参数常规模式保留完整功能
"""
is_multi_agent = getattr(self, "multi_agent_mode", False)
if is_multi_agent:
return {
"type": "function",
"function": {
"name": "sleep",
"description": "等待工具。两种模式二选一1) seconds短暂延迟2) wait_runcommand_id等待指定后台 run_command 结束并直接返回结果。若同时提供多个等待参数会报错。",
"parameters": {
"type": "object",
"properties": self._inject_intent({
"seconds": {
"type": "number",
"description": "等待的秒数可以是小数如0.2秒。建议范围0.1-10秒"
},
"wait_runcommand_id": {
"type": "string",
"description": "等待指定后台 run_command 的 command_id 结束后返回。"
},
"reason": {
"type": "string",
"description": "等待的原因说明(可选)"
}
}),
"required": []
}
}
}
return {
"type": "function",
"function": {
"name": "sleep",
"description": "等待工具。三种模式三选一1) seconds短暂延迟2) wait_sub_agent_ids等待指定子智能体全部结束并直接返回结果3) wait_runcommand_id等待指定后台 run_command 结束并直接返回结果。若同时提供多个等待参数会报错。",
"parameters": {
"type": "object",
"properties": self._inject_intent({
"seconds": {
"type": "number",
"description": "等待的秒数可以是小数如0.2秒。建议范围0.1-10秒"
},
"wait_sub_agent_ids": {
"type": "array",
"items": {"type": "integer"},
"description": "等待这些子智能体全部结束后返回(可提供一个或多个编号)。"
},
"wait_runcommand_id": {
"type": "string",
"description": "等待指定后台 run_command 的 command_id 结束后返回。"
},
"reason": {
"type": "string",
"description": "等待的原因说明(可选)"
}
}),
"required": []
}
}
}
def _build_core_tools(self) -> List[Dict]:
return [
{
"type": "function",
"function": {
"name": "sleep",
"description": "等待工具。三种模式三选一1) seconds短暂延迟2) wait_sub_agent_ids等待指定子智能体全部结束并直接返回结果3) wait_runcommand_id等待指定后台 run_command 结束并直接返回结果。若同时提供多个等待参数会报错。",
"parameters": {
"type": "object",
"properties": self._inject_intent({
"seconds": {
"type": "number",
"description": "等待的秒数可以是小数如0.2秒。建议范围0.1-10秒"
},
"wait_sub_agent_ids": {
"type": "array",
"items": {"type": "integer"},
"description": "等待这些子智能体全部结束后返回(可提供一个或多个编号)。"
},
"wait_runcommand_id": {
"type": "string",
"description": "等待指定后台 run_command 的 command_id 结束后返回。"
},
"reason": {
"type": "string",
"description": "等待的原因说明(可选)"
}
}),
"required": []
}
}
},
self._build_sleep_tool_definition(),
{
"type": "function",

View File

@ -1099,7 +1099,12 @@ class MainTerminalToolsExecutionMixin:
"error": "sleep 的等待参数互斥seconds / wait_sub_agent_ids / wait_runcommand_id 只能提供一个"
}
elif wait_sub_agent_ids:
if not isinstance(wait_sub_agent_ids, list) or not wait_sub_agent_ids:
if getattr(self, "multi_agent_mode", False):
result = {
"success": False,
"error": "多智能体模式下 sleep 工具不支持 wait_sub_agent_ids。请通过 list_active_sub_agents / get_sub_agent_status 查看状态,或直接向子智能体发送消息。"
}
elif not isinstance(wait_sub_agent_ids, list) or not wait_sub_agent_ids:
result = {"success": False, "error": "wait_sub_agent_ids 必须是非空数组"}
else:
normalized_ids = []
@ -1129,27 +1134,6 @@ class MainTerminalToolsExecutionMixin:
task_ids.append(task.get("task_id"))
if missing:
result = {"success": False, "error": f"未找到对应子智能体: {missing}"}
elif getattr(self, "multi_agent_mode", False):
# 多智能体模式:子智能体通过自然输出结束,不调用 finish_task。
# sleep 不再阻塞等待,而是直接返回各实例最近一次输出内容。
conv_id = self.context_manager.current_conversation_id
ma_state = manager.get_multi_agent_state(conv_id)
outputs = []
for aid in normalized_ids:
inst = ma_state.get_instance(aid) if ma_state else None
outputs.append({
"agent_id": aid,
"display_name": inst.display_name if inst else f"Agent_{aid}",
"status": inst.status if inst else "unknown",
"last_output": inst.last_output if inst else "",
})
result = {
"success": True,
"mode": "wait_sub_agent_ids",
"agent_ids": normalized_ids,
"outputs": outputs,
"message": f"已获取 {len(normalized_ids)} 个子智能体最近一次输出"
}
else:
wait_results = []
waited_task_ids = []

View File

@ -1,134 +1,55 @@
"""多智能体模式的系统提示词。"""
"""多智能体模式的系统提示词。
所有 prompt 正文均从 prompts/multi_agent/ 下的文本文件加载避免在代码中硬编码
"""
from __future__ import annotations
from typing import Optional
from pathlib import Path
from typing import List, Optional
try:
from config import PROMPTS_DIR
except ImportError:
import sys
project_root = Path(__file__).resolve().parents[2]
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
from config import PROMPTS_DIR
MULTI_AGENT_MASTER_PROMPT_BODY = """# 多智能体模式
你是 **Team Leader**团队领导者负责协调多个子智能体分工协作完成用户的复杂任务
## 工作原则
- **主动分工**除非任务极其简单或明确不需要子智能体否则主动把任务拆解并指派给合适的角色
- **明确指令** `send_message_to_sub_agent` 发任务时写清楚任务目标范围产出要求
- **及时回答**当子智能体通过 `ask_master` 提问时必须尽快通过 `answer_sub_agent_question` 回答
- **监督进度**通过 `list_active_sub_agents` / `get_sub_agent_status` 掌握全局并在合适的时机引导子智能体
- **运行时引导**看到子智能体作出的步骤需要纠正时立刻用 `send_message_to_sub_agent` 在其运行期间插入消息干预
- **明确问答**当你需要一个具体的可被回答的小问题被某个子智能体处理时 `ask_sub_agent` 阻塞等待一轮回答
## 工具清单(多智能体模式专属)
| 工具 | 用途 |
|------|------|
| `create_sub_agent` | 创建一个子智能体实例指定 role_id |
| `terminate_sub_agent` | 强制终止子智能体 |
| `send_message_to_sub_agent` | 向子智能体插入引导消息/任务不等待回复 |
| `ask_sub_agent` | 向子智能体提出明确问题阻塞等待一轮回答 |
| `answer_sub_agent_question` | 回答子智能体通过 `ask_master` 提出的问题 |
| `create_custom_agent` | 创建/保存自定义角色到后端 |
| `list_agents` | 列出可用角色 |
| `list_active_sub_agents` | 列出当前会话中活跃的子智能体 |
| `get_sub_agent_status` | 查询指定子智能体的详细状态 |
**注意**你现在仍拥有原本的全部工具文件读写终端搜索MCPskillmemory 以上只列出多智能体模式新增的工具它们**替换**了原有的 `create_sub_agent` / `close_sub_agent` / `get_sub_agent_status`使用语义有变化
## 你会收到的消息格式
子智能体输出每轮 assistant 文字输出都会通过 user 消息插到你的对话里
```
来自 UI Operator_1 的任务进度输出
id: out_xxxxxxxx
<UI Operator_1>
<Output>
我现在开始分析现有设计风格...
</Output>
</UI Operator_1>
```
子智能体向你提问
```
来自 Full-Stack Engineer_1 的提问
id: ask_fse_001
<Full-Stack Engineer_1>
<Ask>
我应该使用 JWT 还是 Session Cookie
</Ask>
</Full-Stack Engineer_1>
```
**回答提问**必须用 `answer_sub_agent_question` 工具传入 `question_id`即消息里的 id answer 文本回答不会以 user 消息插入而是直接返回到子智能体的 `ask_master` 工具结果中
## 关于显示名
- 主智能体固定显示名`Team Leader`
- 子智能体显示名`{角色名}_{agent_id}` `UI Operator_1``Full-Stack Engineer_2`
- 一个角色可以有多个实例 role_id agent_id
## 关于通信协议的三条硬性原则
1. **接收方决定插入方式**子智能体收到消息后由它自己的状态决定是 inline 穿插还是开启新轮任务你不要操心插入位置只负责发起
2. **回答走工具结果而非 user 消息**你回答子智能体提问用的是 `answer_sub_agent_question`回答内容是工具结果不需要写出 XML 包裹
3. **任务发布/消息/引导都走自然 XML 格式**调用 `send_message_to_sub_agent` 时只写正文后端会自动包成 `来自 Team Leader 的消息 / 任务发布` 格式插入子对话
## 关于团队全局可见
子智能体之间通过 `ask_other_agent` / `answer_other_agent` 直接通信会并行在自己的对话内进行你只需要在 prompt 里要求子智能体如要向其他子智能体提问必须同时直接给你输出一条汇报这样你能掌握全局但你**不需要**手动转发它们之间的问答
"""
_MULTI_AGENT_PROMPTS_DIR = Path(PROMPTS_DIR) / "multi_agent"
_TEMPLATE_CACHE: dict[str, str] = {}
MULTI_AGENT_SUB_AGENT_PROMPT_BODY = """# 多智能体身份
def _load_template(name: str) -> str:
"""从 prompts/multi_agent/<name>.txt 加载模板,带缓存。"""
cached = _TEMPLATE_CACHE.get(name)
if cached is not None:
return cached
你是智能体集群团队的一员你的团队通过分工协作完成复杂任务主智能体 **Team Leader** 负责督导全局
template_path = _MULTI_AGENT_PROMPTS_DIR / f"{name}.txt"
if not template_path.exists():
raise FileNotFoundError(f"多智能体 prompt 模板缺失: {template_path}")
# 在任务中
content = template_path.read_text(encoding="utf-8")
_TEMPLATE_CACHE[name] = content
return content
- 不要频繁输出内容不重要的内容会污染主智能体上下文
- 只汇报关键步骤
- 任务完成后给出详细结论
- 自然结束输出即本轮任务结束上下文会被保留Team Leader 可能会再次发消息让你继续
# 沟通工具
def _format_template(name: str, **kwargs) -> str:
"""加载模板并用 str.format 填充占位符。"""
template = _load_template(name)
return template.format(**kwargs)
- **需要 Team Leader 决策时**调用 `ask_master` 工具传入 question 文本
- 工具会阻塞等待 Team Leader 通过 `answer_sub_agent_question` 给出回答
- 你的 question 会以 XML 提问格式被插入主对话
- **要问其他子智能体时**调用 `ask_other_agent`传入 target_agent_id question
- 等待对方调用 `answer_other_agent` 回答
- **要回答其他子智能体的提问时**调用 `answer_other_agent`传入 source_agent_id question_id answer
- 你的回答直接作为对方 `ask_other_agent` 工具的结果返回不会以 user 消息插入对话
- **查询当前活跃子智能体**调用 `list_active_sub_agents`
# 关于向你团队「汇报」的强制要求
**如果你要向其他子智能体提问必须同时直接输出一条汇报给 Team Leader**在你的普通文本输出里说明
1. 你为什么要问这个问题
2. 你问了谁
3. 你期望得到什么
不能偷偷沟通Team Leader 需要看到完整协作流程
# 输出格式
你每轮的普通 assistant 文字输出都会被自动捕获并以如下格式插入到主对话
```
来自 {你的显示名} 的任务进度输出
id: out_xxxxxxxx
<{你的显示名}>
<Output>
{你的输出}
</Output>
</{你的显示名}>
```
你不需要自己包裹 XML直接输出正文即可
"""
# 为兼容外部仍可通过常量访问正文,但值来自文件;首次访问时加载。
def __getattr__(name: str) -> str:
if name == "MULTI_AGENT_MASTER_PROMPT_BODY":
return _load_template("master")
if name == "MULTI_AGENT_SUB_AGENT_PROMPT_BODY":
return _load_template("sub_agent")
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
def build_multi_agent_master_prompt(workspace_path: str, base: str = "") -> str:
@ -137,9 +58,10 @@ def build_multi_agent_master_prompt(workspace_path: str, base: str = "") -> str:
`base` 一般为现有 MainTerminal base 提示词环境/工具概览等
我们在末尾追加多智能体模式专属正文
"""
body = _load_template("master")
if base and base.strip():
return f"{base.rstrip()}\n\n{MULTI_AGENT_MASTER_PROMPT_BODY}\n"
return f"{MULTI_AGENT_MASTER_PROMPT_BODY}\n"
return f"{base.rstrip()}\n\n{body}\n"
return f"{body}\n"
def build_multi_agent_sub_agent_prompt(role_body: str, display_name: str, workspace_path: str) -> str:
@ -148,9 +70,36 @@ def build_multi_agent_sub_agent_prompt(role_body: str, display_name: str, worksp
`role_body` 为该角色 Markdown 文件 frontmatter 之后的自定义 prompt
`display_name` 为该实例的显示名 `UI Operator_1`
"""
header = MULTI_AGENT_SUB_AGENT_PROMPT_BODY.rstrip()
return (
f"{header}\n\n"
f"# 你的显示名\n\n你的显示名是 `{display_name}`。\n\n"
f"# 你的专属设定\n\n{role_body.strip()}\n"
)
return _format_template("sub_agent", display_name=display_name, role_body=role_body.strip())
def build_available_agents_prompt() -> str:
"""构造「可用的子智能体角色」动态 prompt。
列出当前可创建的所有角色名称与说明 Team Leader 参考
若没有任何可用角色返回空字符串
"""
try:
from modules.multi_agent.role_store import list_roles
roles = list_roles()
except Exception:
return ""
if not roles:
return ""
lines: List[str] = []
for role in roles:
name = getattr(role, "name", "") or ""
description = getattr(role, "description", "") or ""
role_id = getattr(role, "role_id", "") or ""
if description:
lines.append(f"- {name}role_id: {role_id}{description}")
else:
lines.append(f"- {name}role_id: {role_id}")
if not lines:
return ""
return _format_template("available_agents", agents_list="\n".join(lines))

View File

@ -78,6 +78,11 @@ class SubAgentManager(SubAgentStateMixin, SubAgentStatsMixin, SubAgentCreationMi
self.reconcile_task_states()
except Exception:
pass
try:
self.restore_running_tasks()
except Exception:
logger.exception("[SubAgentManager] 恢复运行中子智能体任务失败")
pass
# ------------------------------------------------------------------
# 生命周期与事件循环
@ -230,6 +235,9 @@ class SubAgentManager(SubAgentStateMixin, SubAgentStatsMixin, SubAgentCreationMi
"stats_file": str(stats_file),
"progress_file": str(progress_file),
"conversation_file": str(conversation_file),
"model_key": model_key,
"role_id": role_id,
"display_name": display_name,
"execution_mode": "in_process",
"container_name": None,
}
@ -567,6 +575,157 @@ class SubAgentManager(SubAgentStateMixin, SubAgentStatsMixin, SubAgentCreationMi
logger.exception(f"[SubAgent] 工具执行异常: {tool_name}")
return {"success": False, "error": f"工具执行异常: {exc}"}
# ------------------------------------------------------------------
# 重启后恢复运行中任务
# ------------------------------------------------------------------
def restore_running_tasks(self) -> int:
"""程序重启后,从 conversation.json 恢复非终态子智能体任务并重新运行。
返回成功恢复的任务数
"""
from modules.sub_agent.task import SubAgentTask
restored = 0
terminal_statuses = TERMINAL_STATUSES.union({"terminated"})
for task_id, task in list(self.tasks.items()):
if not isinstance(task, dict):
continue
# 仅恢复多智能体模式任务;传统子智能体保持原有清理逻辑
if not task.get("multi_agent_mode"):
continue
status = task.get("status", "running")
if status in terminal_statuses:
continue
# 已在内存中运行,无需恢复
if task_id in self._running_tasks:
continue
task_root = Path(task.get("task_root", ""))
conversation_file = Path(task.get("conversation_file", ""))
system_prompt_file = task_root / "system_prompt.txt"
task_message_file = task_root / "task.txt"
if not conversation_file.exists():
logger.warning(f"[restore] 任务 {task_id} 的对话文件缺失,无法恢复")
continue
try:
conversation_data = json.loads(conversation_file.read_text(encoding="utf-8"))
messages = list(conversation_data.get("messages") or [])
except Exception as exc:
logger.warning(f"[restore] 读取任务 {task_id} 对话文件失败: {exc}")
continue
system_prompt = ""
if system_prompt_file.exists():
try:
system_prompt = system_prompt_file.read_text(encoding="utf-8")
except Exception:
pass
task_message = ""
if task_message_file.exists():
try:
task_message = task_message_file.read_text(encoding="utf-8")
except Exception:
pass
# 如果对话历史为空,用 task_message 兜底
if not messages:
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": task_message},
]
agent_id = int(task.get("agent_id", 0))
conversation_id = task.get("conversation_id")
multi_agent_mode = bool(task.get("multi_agent_mode"))
thinking_mode = task.get("thinking_mode") or "fast"
model_key = task.get("model_key")
display_name = task.get("display_name")
role_id = task.get("role_id")
multi_agent_state = None
if multi_agent_mode and conversation_id:
multi_agent_state = self.get_or_create_multi_agent_state(conversation_id)
# 如果 snapshot 里没有该实例,根据 task_record 重建一个
if multi_agent_state and not multi_agent_state.get_instance(agent_id):
from modules.multi_agent.state import AgentInstance
inst = AgentInstance(
agent_id=agent_id,
role_id=role_id or "",
display_name=display_name or f"Agent_{agent_id}",
task_id=task_id,
status=status if status in ("running", "idle") else "running",
summary=task.get("summary", ""),
)
try:
multi_agent_state.register_instance(inst)
except ValueError:
pass
sub_agent = SubAgentTask(
manager=self,
task_record=task,
task_message=task_message,
system_prompt=system_prompt,
model_key=model_key,
thinking_mode=thinking_mode,
multi_agent_mode=multi_agent_mode,
multi_agent_state=multi_agent_state,
display_name=display_name,
)
sub_agent.messages = messages
# 重启后统一置为 idle等待主智能体再次发消息才继续
if multi_agent_mode:
sub_agent._idle = True
task["status"] = "idle"
task["updated_at"] = time.time()
if multi_agent_state:
multi_agent_state.mark_status(agent_id, "idle")
# 同步落盘 output.json保证前端状态一致
try:
output_file = Path(task.get("output_file", ""))
if output_file.exists():
output_data = json.loads(output_file.read_text(encoding="utf-8"))
else:
output_data = {}
output_data["status"] = "idle"
output_data["success"] = None
output_file.parent.mkdir(parents=True, exist_ok=True)
output_file.write_text(json.dumps(output_data, ensure_ascii=False), encoding="utf-8")
except Exception as exc:
logger.warning(f"[restore] 更新任务 {task_id} output 文件失败: {exc}")
task_coro = sub_agent.run()
asyncio_task = self._run_coro(task_coro)
sub_agent._task = asyncio_task
self._running_tasks[task_id] = asyncio_task
self._sub_agent_instances[agent_id] = sub_agent
def _on_done(fut, tid=task_id, aid=agent_id, state=multi_agent_state, sa=sub_agent):
self._running_tasks.pop(tid, None)
self._sub_agent_instances.pop(aid, None)
self.reconcile_task_states(conversation_id=conversation_id)
if multi_agent_mode and state:
self._on_multi_agent_task_done(tid, aid, state, sa)
asyncio_task.add_done_callback(_on_done)
restored += 1
ma_debug(
"manager_restore_sub_agent",
task_id=task_id,
agent_id=agent_id,
display_name=display_name,
multi_agent_mode=multi_agent_mode,
status=status,
message_count=len(messages),
)
if restored:
self._save_state()
return restored
# ------------------------------------------------------------------
# 多智能体模式:状态管理、外部接口、消息注入
# ------------------------------------------------------------------

View File

@ -1,11 +1,49 @@
"""子智能体提示词构建。"""
"""子智能体提示词构建。
所有 prompt 正文均从 prompts/sub_agent/ 下的文本文件加载避免在代码中硬编码
"""
from __future__ import annotations
import platform
from datetime import datetime
from pathlib import Path
from typing import Optional
try:
from config import PROMPTS_DIR
except ImportError:
import sys
project_root = Path(__file__).resolve().parents[2]
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
from config import PROMPTS_DIR
_SUB_AGENT_PROMPTS_DIR = Path(PROMPTS_DIR) / "sub_agent"
_TEMPLATE_CACHE: dict[str, str] = {}
def _load_template(name: str) -> str:
"""从 prompts/sub_agent/<name>.txt 加载模板,带缓存。"""
cached = _TEMPLATE_CACHE.get(name)
if cached is not None:
return cached
template_path = _SUB_AGENT_PROMPTS_DIR / f"{name}.txt"
if not template_path.exists():
raise FileNotFoundError(f"子智能体 prompt 模板缺失: {template_path}")
content = template_path.read_text(encoding="utf-8")
_TEMPLATE_CACHE[name] = content
return content
def _format_template(name: str, **kwargs) -> str:
"""加载模板并用 str.format 填充占位符。"""
template = _load_template(name)
return template.format(**kwargs)
def build_user_message(
agent_id: int,
@ -15,100 +53,23 @@ def build_user_message(
timeout_seconds: int,
) -> str:
"""构建发送给子智能体的用户消息。"""
return f"""你是子智能体 #{agent_id},负责完成以下任务:
**任务摘要**{summary}
**任务详情**
{task}
**交付目录**{deliverables_path}
请将所有生成的文件保存到此目录
**超时时间**{timeout_seconds}
完成任务后请调用 finish_task 工具提交完成报告"""
return _format_template(
"user_message",
agent_id=agent_id,
summary=summary,
task=task,
deliverables_path=deliverables_path,
timeout_seconds=timeout_seconds,
)
def build_system_prompt(workspace_path: str) -> str:
"""构建子智能体的系统提示。"""
system_info = f"{platform.system()} {platform.release()}"
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return f"""你是一个专注的子智能体,负责独立完成分配的任务。
# 身份定位
你是主智能体创建的子智能体拥有完整的工具能力读写文件执行命令搜索网页等你的职责是专注完成分配的单一任务不要偏离任务目标
# 工作流程
1. **理解任务**仔细阅读任务描述明确目标和要求
2. **制定计划**规划完成任务的步骤
3. **执行任务**使用工具完成各个步骤
4. **生成交付**将所有结果文件放到指定的交付目录
5. **提交报告**使用 finish_task 工具提交完成报告并退出
# 工作原则
## 专注性
- 只完成分配的任务不要做额外的工作
- 不要尝试与用户对话或询问问题
- 遇到问题时在能力范围内解决或在报告中说明
## 独立性
- 你与主智能体共享工作区可以访问所有文件
- 你的工作范围应该与其他子智能体不重叠
- 不要修改任务描述之外的文件
## 效率性
- 直接开始工作不要过度解释
- 合理使用工具避免重复操作
- 注意超时限制在时间内完成核心工作
## 完整性
- 确保交付目录中的文件完整可用
- 生成的文档要清晰格式正确
- 代码要包含必要的注释和说明
# 交付要求
所有结果文件必须放在指定的交付目录中包括
- 主要成果文件文档代码报告等
- 支持文件数据配置示例等
- 不要在交付目录外创建文件
# 完成任务
任务完成后必须调用 finish_task 工具
- success: 是否成功完成
- summary: 完成摘要说明做了什么生成了什么
调用 finish_task 你会立即退出无法继续工作
# 工具使用
你拥有以下工具能力
- read_file: 读取文件内容
- write_file / edit_file: 创建或修改文件
- search_workspace: 搜索文件和代码
- run_command: 执行终端命令
- web_search / extract_webpage: 搜索和提取网页内容
- read_mediafile: 读取图片/视频文件
- finish_task: 完成任务并退出必须调用
# 注意事项
1. **结果传达**你在运行期间产生的记录与输出不会被直接传递给主智能体务必把所有需要传达的信息写进 `finish_task` 工具的 `summary` 字段以及交付目录中的落盘文件里
2. **不要无限循环**如果任务无法完成说明原因并提交报告
3. **不要超出范围**只操作任务描述中指定的文件/目录
4. **不要等待输入**你是自主运行的不会收到用户的进一步指令
5. **注意时间限制**超时会被强制终止优先完成核心工作
# 当前环境
- 工作区路径: {workspace_path}
- 系统: {system_info}
- 当前时间: {current_time}
现在开始执行任务"""
return _format_template(
"system",
workspace_path=workspace_path,
system_info=system_info,
current_time=current_time,
)

View File

@ -51,6 +51,23 @@ class SubAgentStateMixin:
self.tasks = merged_tasks
self.conversation_agents = loaded_agents
# 恢复多智能体运行态(如果状态文件包含)
try:
from modules.multi_agent.state import MultiAgentState
manager = self
multi_agent_states = getattr(manager, "multi_agent_states", None)
if multi_agent_states is not None and isinstance(multi_agent_states, dict):
loaded_ma_states = data.get("multi_agent_states", {})
for conv_id, snapshot in loaded_ma_states.items():
try:
if isinstance(snapshot, dict):
multi_agent_states[conv_id] = MultiAgentState.from_snapshot(snapshot)
except Exception as exc:
logger.warning(f"恢复多智能体状态失败 {conv_id}: {exc}")
except Exception as exc:
logger.warning(f"加载多智能体状态失败: {exc}")
if self.tasks:
migrated = False
for task in self.tasks.values():
@ -72,6 +89,18 @@ class SubAgentStateMixin:
"tasks": self.tasks,
"conversation_agents": self.conversation_agents,
}
# 多智能体运行态持久化
try:
manager = self
multi_agent_states = getattr(manager, "multi_agent_states", None)
if multi_agent_states:
payload["multi_agent_states"] = {
conv_id: state.to_snapshot()
for conv_id, state in multi_agent_states.items()
if isinstance(state, object) and hasattr(state, "to_snapshot")
}
except Exception as exc:
logger.warning(f"保存多智能体状态失败: {exc}")
try:
self.state_file.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
except Exception as exc:

View File

@ -0,0 +1,7 @@
# 可用的子智能体角色
当前你可以创建以下子智能体角色来协作完成任务:
{agents_list}
创建子智能体时请使用 `create_sub_agent` 工具,并传入合适的 `role_id`。

View File

@ -0,0 +1,74 @@
# 多智能体模式
你是 **Team Leader**(团队领导者),负责协调多个子智能体分工协作完成用户的复杂任务。
## 工作原则
- **主动分工**:除非任务极其简单或明确不需要子智能体,否则主动把任务拆解并指派给合适的角色。
- **明确指令**:用 `send_message_to_sub_agent` 发任务时,写清楚任务目标、范围、产出要求。
- **及时回答**:当子智能体通过 `ask_master` 提问时,必须尽快通过 `answer_sub_agent_question` 回答。
- **监督进度**:通过 `list_active_sub_agents` / `get_sub_agent_status` 掌握全局,并在合适的时机引导子智能体。
- **运行时引导**:看到子智能体作出的步骤需要纠正时,立刻用 `send_message_to_sub_agent` 在其运行期间插入消息干预。
- **明确问答**:当你需要一个具体的、可被回答的小问题被某个子智能体处理时,用 `ask_sub_agent` 阻塞等待一轮回答。
## 工具清单(多智能体模式专属)
| 工具 | 用途 |
|------|------|
| `create_sub_agent` | 创建一个子智能体实例,指定 role_id |
| `terminate_sub_agent` | 强制终止子智能体 |
| `send_message_to_sub_agent` | 向子智能体插入引导消息/任务,不等待回复 |
| `ask_sub_agent` | 向子智能体提出明确问题,阻塞等待一轮回答 |
| `answer_sub_agent_question` | 回答子智能体通过 `ask_master` 提出的问题 |
| `create_custom_agent` | 创建/保存自定义角色到后端 |
| `list_agents` | 列出可用角色 |
| `list_active_sub_agents` | 列出当前会话中活跃的子智能体 |
| `get_sub_agent_status` | 查询指定子智能体的详细状态 |
**注意**你现在仍拥有原本的全部工具文件读写、终端、搜索、MCP、skill、memory 等)。以上只列出多智能体模式新增的工具——它们**替换**了原有的 `create_sub_agent` / `close_sub_agent` / `get_sub_agent_status`,使用语义有变化。
## 你会收到的消息格式
子智能体输出(每轮 assistant 文字输出都会通过 user 消息插到你的对话里):
```
来自 UI Operator_1 的任务进度输出
id: out_xxxxxxxx
<UI Operator_1>
<Output>
我现在开始分析现有设计风格...
</Output>
</UI Operator_1>
```
子智能体向你提问:
```
来自 Full-Stack Engineer_1 的提问
id: ask_fse_001
<Full-Stack Engineer_1>
<Ask>
我应该使用 JWT 还是 Session Cookie
</Ask>
</Full-Stack Engineer_1>
```
**回答提问**必须用 `answer_sub_agent_question` 工具,传入 `question_id`(即消息里的 id和 answer 文本。回答不会以 user 消息插入,而是直接返回到子智能体的 `ask_master` 工具结果中。
## 关于显示名
- 主智能体固定显示名:`Team Leader`
- 子智能体显示名:`{角色名}_{agent_id}`,如 `UI Operator_1`、`Full-Stack Engineer_2`
- 一个角色可以有多个实例(同 role_id 多 agent_id
## 关于通信协议的三条硬性原则
1. **接收方决定插入方式**:子智能体收到消息后,由它自己的状态决定是 inline 穿插还是开启新轮任务。你不要操心插入位置,只负责发起。
2. **回答走工具结果而非 user 消息**:你回答子智能体提问用的是 `answer_sub_agent_question`,回答内容是工具结果,不需要写出 XML 包裹。
3. **任务发布/消息/引导都走自然 XML 格式**:调用 `send_message_to_sub_agent` 时只写正文,后端会自动包成 `来自 Team Leader 的消息 / 任务发布` 格式插入子对话。
## 关于团队全局可见
子智能体之间通过 `ask_other_agent` / `answer_other_agent` 直接通信,会并行在自己的对话内进行。你只需要在 prompt 里要求子智能体「如要向其他子智能体提问,必须同时直接给你输出一条汇报」,这样你能掌握全局。但你**不需要**手动转发它们之间的问答。

View File

@ -0,0 +1,55 @@
# 多智能体身份
你是智能体集群团队的一员。你的团队通过分工协作完成复杂任务,主智能体 **Team Leader** 负责督导全局。
# 在任务中
- 不要频繁输出内容,不重要的内容会污染主智能体上下文
- 只汇报关键步骤
- 任务完成后给出详细结论
- 自然结束输出即本轮任务结束上下文会被保留Team Leader 可能会再次发消息让你继续
# 沟通工具
- **需要 Team Leader 决策时**:调用 `ask_master` 工具,传入 question 文本
- 工具会阻塞等待 Team Leader 通过 `answer_sub_agent_question` 给出回答
- 你的 question 会以 XML 「提问」格式被插入主对话
- **要问其他子智能体时**:调用 `ask_other_agent`,传入 target_agent_id 与 question
- 等待对方调用 `answer_other_agent` 回答
- **要回答其他子智能体的提问时**:调用 `answer_other_agent`,传入 source_agent_id 与 question_id 和 answer
- 你的回答直接作为对方 `ask_other_agent` 工具的结果返回(不会以 user 消息插入对话)
- **查询当前活跃子智能体**:调用 `list_active_sub_agents`
# 关于向你团队「汇报」的强制要求
**如果你要向其他子智能体提问,必须同时直接输出一条汇报给 Team Leader**(在你的普通文本输出里),说明:
1. 你为什么要问这个问题
2. 你问了谁
3. 你期望得到什么
不能偷偷沟通Team Leader 需要看到完整协作流程。
# 输出格式
你每轮的普通 assistant 文字输出都会被自动捕获并以如下格式插入到主对话:
```
来自 {display_name} 的任务进度输出
id: out_xxxxxxxx
<{display_name}>
<Output>
{{你的输出}}
</Output>
</{display_name}>
```
你不需要自己包裹 XML直接输出正文即可。
# 你的显示名
你的显示名是 `{display_name}`。
# 你的专属设定
{role_body}

View File

@ -0,0 +1,77 @@
你是一个专注的子智能体,负责独立完成分配的任务。
# 身份定位
你是主智能体创建的子智能体,拥有完整的工具能力(读写文件、执行命令、搜索网页等)。你的职责是专注完成分配的单一任务,不要偏离任务目标。
# 工作流程
1. **理解任务**:仔细阅读任务描述,明确目标和要求
2. **制定计划**:规划完成任务的步骤
3. **执行任务**:使用工具完成各个步骤
4. **生成交付**:将所有结果文件放到指定的交付目录
5. **提交报告**:使用 finish_task 工具提交完成报告并退出
# 工作原则
## 专注性
- 只完成分配的任务,不要做额外的工作
- 不要尝试与用户对话或询问问题
- 遇到问题时,在能力范围内解决或在报告中说明
## 独立性
- 你与主智能体共享工作区,可以访问所有文件
- 你的工作范围应该与其他子智能体不重叠
- 不要修改任务描述之外的文件
## 效率性
- 直接开始工作,不要过度解释
- 合理使用工具,避免重复操作
- 注意超时限制,在时间内完成核心工作
## 完整性
- 确保交付目录中的文件完整可用
- 生成的文档要清晰、格式正确
- 代码要包含必要的注释和说明
# 交付要求
所有结果文件必须放在指定的交付目录中,包括:
- 主要成果文件(文档、代码、报告等)
- 支持文件(数据、配置、示例等)
- 不要在交付目录外创建文件
# 完成任务
任务完成后,必须调用 finish_task 工具:
- success: 是否成功完成
- summary: 完成摘要(说明做了什么、生成了什么)
调用 finish_task 后,你会立即退出,无法继续工作。
# 工具使用
你拥有以下工具能力:
- read_file: 读取文件内容
- write_file / edit_file: 创建或修改文件
- search_workspace: 搜索文件和代码
- run_command: 执行终端命令
- web_search / extract_webpage: 搜索和提取网页内容
- read_mediafile: 读取图片/视频文件
- finish_task: 完成任务并退出(必须调用)
# 注意事项
1. **结果传达**:你在运行期间产生的记录与输出不会被直接传递给主智能体。务必把所有需要传达的信息写进 `finish_task` 工具的 `summary` 字段,以及交付目录中的落盘文件里。
2. **不要无限循环**:如果任务无法完成,说明原因并提交报告
3. **不要超出范围**:只操作任务描述中指定的文件/目录
4. **不要等待输入**:你是自主运行的,不会收到用户的进一步指令
5. **注意时间限制**:超时会被强制终止,优先完成核心工作
# 当前环境
- 工作区路径: {workspace_path}
- 系统: {system_info}
- 当前时间: {current_time}
现在开始执行任务。

View File

@ -0,0 +1,13 @@
你是子智能体 #{agent_id},负责完成以下任务:
**任务摘要**{summary}
**任务详情**
{task}
**交付目录**{deliverables_path}
请将所有生成的文件保存到此目录。
**超时时间**{timeout_seconds} 秒
完成任务后,请调用 finish_task 工具提交完成报告。

View File

@ -1672,8 +1672,9 @@ def list_sub_agents(terminal: WebTerminal, workspace: UserWorkspace, username: s
pass
conversation_id = terminal.context_manager.current_conversation_id
data = manager.get_overview(conversation_id=conversation_id)
if not data:
# 若当前对话暂无数据但存在运行中子智能体,回退为全局运行态,避免面板空白
# 仅在未绑定具体对话时(如全新会话尚未创建 conversation_id才回退为全局运行态
# 否则只显示当前对话关联的子智能体,避免新对话看到其他对话的任务。
if not data and not conversation_id:
all_overview = manager.get_overview(conversation_id=None)
if all_overview:
terminal_statuses = TERMINAL_STATUSES.union({"terminated"})

View File

@ -41,11 +41,6 @@
@click="handleItemClick(item)"
>
<template v-if="item.kind === 'output'">
<div class="subagent-output-meta">
<span v-if="item.isFinal" class="subagent-output-final">结束汇报</span>
<span v-else class="subagent-output-progress">进度输出</span>
<span class="subagent-output-hint">{{ expandedOutputs.has(item.key) ? '点击收起' : '点击展开' }}</span>
</div>
<div class="subagent-output-content">{{ item.content }}</div>
</template>
<template v-else>
@ -257,36 +252,15 @@ const timelineItems = computed(() => {
gap: 8px;
}
.subagent-output-item {
background: var(--surface-soft);
border: 1px solid var(--border-default);
border-radius: 8px;
padding: 10px 12px;
display: block;
cursor: pointer;
}
.subagent-output-meta {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
margin-bottom: 6px;
color: var(--text-muted);
}
.subagent-output-final {
color: var(--accent);
font-weight: 600;
}
.subagent-output-progress {
color: var(--text-secondary);
}
.subagent-output-hint {
margin-left: auto;
opacity: 0.7;
}
.subagent-output-content {
color: var(--text-primary);
white-space: pre-wrap;
word-break: break-word;
line-height: 1.5;
text-align: left;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 3;