放弃完全隔离策略,改为在现有 MainTerminal/SubAgentManager/SubAgentTask 主链路 按对话级开关 metadata.multi_agent_mode=true 增加多智能体分支。 新增模块: - modules/multi_agent/__init__.py: 模块入口 - modules/multi_agent/role_store.py: 角色 Markdown Frontmatter 解析与归档 - modules/multi_agent/state.py: 多智能体会话状态机与消息格式化 - modules/multi_agent/prompts.py: 主智能体(Team Leader) + 子智能体提示词 - modules/multi_agent/tools.py: 9 个主智能体工具 + 4 个子智能体工具定义 - server/multi_agent.py: /multiagent/new 页面 + /api/multiagent/* 蓝图 现有代码改动: - modules/sub_agent/task.py: 扩展 multi_agent_mode/multi_agent_state/display_name 字段, 增加 ask_master/ask_other_agent/answer_other_agent/list_active_sub_agents 工具处理逻辑, 子智能体自然结束 assistant 输出即本轮结束(不调用 finish_task),上下文保留。 - modules/sub_agent/manager.py: create_sub_agent 增加 multi_agent_mode/role_id/display_name 参数, 增加 get_or_create_multi_agent_state/get_multi_agent_state/inject_message_to_sub_agent/_on_multi_agent_task_done 方法。 - core/main_terminal_parts/tools_definition/agent_tools.py: 多智能体模式下用 modules.multi_agent.tools 替换旧版工具集。 - core/main_terminal_parts/context/messages.py: 多智能体模式下追加 Team Leader 系统提示词。 - core/main_terminal_parts/tools_execution.py: create_sub_agent handler 增加多智能体分支,新增 send_message_to_sub_agent/ask_sub_agent/answer_sub_agent_question/create_custom_agent/list_agents/list_active_sub_agents handler。 - core/web_terminal.py: load_conversation 时检测 metadata.multi_agent_mode 设置 self.multi_agent_mode。 - server/app_legacy.py: 注册 multi_agent_bp 蓝图。 前端改动: - static/src/auth/LoginApp.vue: 登录页增加'多智能体模式(beta)'按钮 - static/src/app/methods/ui/route.ts: 识别 /multiagent/new 和 /multiagent/conv_xxx 路径,进入多智能体模式并创建带 metadata.multi_agent_mode=true 的对话 - static/src/app/state.ts: 增加 multiAgentMode 状态字段 数据: - ~/.astrion/astrion/host/mutiagents/agents/: 4 个预置角色 ui-operator / full-stack-engineer / code-reviewer / researcher - ~/.astrion/astrion/host/mutiagents/conversations/: 会话数据 验证:所有 Python 文件语法检查通过;冒烟测试 test.test_server_refactor_smoke 6 项全通过;前端构建通过(6.04s);模块导入与功能断言测试全部通过。
199 lines
7.9 KiB
Python
199 lines
7.9 KiB
Python
"""多智能体模式 server 路由。
|
||
|
||
- `/multiagent/new` 返回主 SPA 入口(与 `/new` 一样返回 static/index.html)
|
||
前端通过路径识别多智能体模式后,在创建对话时写入 metadata.multi_agent_mode=true
|
||
- `/api/multiagent/conversations` POST 创建多智能体对话(写入 metadata)
|
||
- `/api/multiagent/roles` GET 列出可用角色
|
||
- `/api/multiagent/roles` POST 创建自定义角色
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
from typing import Any, Dict, List
|
||
|
||
from flask import Blueprint, current_app, jsonify, request, session
|
||
|
||
from server.auth_helpers import api_login_required, get_current_username
|
||
from server.context import get_user_resources
|
||
|
||
multi_agent_bp = Blueprint("multi_agent", __name__)
|
||
|
||
|
||
@multi_agent_bp.route("/multiagent/new")
|
||
@api_login_required
|
||
def multi_agent_new_page():
|
||
"""多智能体模式入口,返回与 /new 相同的 SPA index.html。"""
|
||
return current_app.send_static_file("index.html")
|
||
|
||
|
||
@multi_agent_bp.route("/api/multiagent/roles", methods=["GET"])
|
||
@api_login_required
|
||
def list_roles_api():
|
||
"""列出全部可用角色(预置+自定义)。"""
|
||
try:
|
||
from modules.multi_agent.role_store import list_roles
|
||
roles = list_roles()
|
||
return jsonify({
|
||
"success": True,
|
||
"roles": [r.to_dict() for r in roles],
|
||
})
|
||
except Exception as exc:
|
||
return jsonify({"success": False, "error": str(exc)}), 500
|
||
|
||
|
||
@multi_agent_bp.route("/api/multiagent/roles", methods=["POST"])
|
||
@api_login_required
|
||
def create_role_api():
|
||
"""创建自定义角色。body: { role_id, name, description?, body_prompt, thinking_mode? }"""
|
||
try:
|
||
from modules.multi_agent.role_store import RoleConfig, save_custom_role, list_roles
|
||
data = request.get_json() or {}
|
||
role_id = str(data.get("role_id") or "").strip()
|
||
name = str(data.get("name") or "").strip()
|
||
body_prompt = str(data.get("body_prompt") or "").strip()
|
||
description = str(data.get("description") or "").strip()
|
||
thinking_mode = str(data.get("thinking_mode") or "fast").strip()
|
||
if not role_id or not name or not body_prompt:
|
||
return jsonify({"success": False, "error": "role_id/name/body_prompt 必填"}), 400
|
||
if thinking_mode not in {"fast", "thinking"}:
|
||
thinking_mode = "fast"
|
||
# 不允许覆盖已存在的同名角色
|
||
existing = {r.role_id for r in list_roles()}
|
||
if role_id in existing:
|
||
return jsonify({"success": False, "error": f"角色 {role_id} 已存在"}), 409
|
||
role = RoleConfig(
|
||
role_id=role_id,
|
||
name=name,
|
||
description=description,
|
||
body_prompt=body_prompt,
|
||
thinking_mode=thinking_mode,
|
||
)
|
||
saved = save_custom_role(role)
|
||
return jsonify({"success": True, "role_id": role_id, "file": str(saved)})
|
||
except Exception as exc:
|
||
return jsonify({"success": False, "error": str(exc)}), 500
|
||
|
||
|
||
@multi_agent_bp.route("/api/multiagent/conversations", methods=["POST"])
|
||
@api_login_required
|
||
def create_multi_agent_conversation():
|
||
"""创建多智能体模式对话。在 metadata 中写入 multi_agent_mode=true。
|
||
|
||
body: { workspace_id?, thinking_mode?, run_mode?, preserve_mode? }
|
||
"""
|
||
import time as _time
|
||
from server.conversation import _get_active_workspace_task, _resolve_target_terminal_for_workspace
|
||
from modules.personalization_manager import load_personalization_config
|
||
try:
|
||
from server.user_workspace import UserWorkspace # noqa
|
||
except Exception:
|
||
UserWorkspace = None # type: ignore
|
||
|
||
username = get_current_username()
|
||
if not username:
|
||
return jsonify({"success": False, "error": "未登录"}), 401
|
||
|
||
data = request.get_json() or {}
|
||
target_workspace_id = (data.get("workspace_id") or "").strip()
|
||
|
||
terminal, workspace = get_user_resources(username)
|
||
if not terminal or not workspace:
|
||
return jsonify({"success": False, "error": "工作区未就绪"}), 503
|
||
|
||
if target_workspace_id:
|
||
try:
|
||
terminal, workspace = _resolve_target_terminal_for_workspace(
|
||
username, target_workspace_id, terminal, workspace
|
||
)
|
||
except ValueError as exc:
|
||
return jsonify({"success": False, "error": str(exc)}), 404
|
||
except RuntimeError as exc:
|
||
return jsonify({"success": False, "error": str(exc)}), 503
|
||
|
||
preserve_mode = bool(data.get("preserve_mode"))
|
||
thinking_mode = data.get("thinking_mode") if preserve_mode and "thinking_mode" in data else None
|
||
run_mode = data.get("mode") if preserve_mode and "mode" in data else None
|
||
|
||
effective_workspace_id = target_workspace_id or session.get("workspace_id") or "default"
|
||
active_task = _get_active_workspace_task(username=username, workspace_id=effective_workspace_id)
|
||
|
||
try:
|
||
prefs = load_personalization_config(workspace.data_dir)
|
||
except Exception:
|
||
prefs = {}
|
||
|
||
cm = getattr(getattr(terminal, "context_manager", None), "conversation_manager", None)
|
||
if not cm:
|
||
return jsonify({"success": False, "error": "对话管理器未初始化"}), 500
|
||
|
||
safe_run_mode = run_mode
|
||
if safe_run_mode not in {"fast", "thinking", "deep"}:
|
||
candidate = (prefs or {}).get("default_run_mode")
|
||
safe_run_mode = candidate if candidate in {"fast", "thinking", "deep"} else "fast"
|
||
safe_thinking = bool(thinking_mode) if thinking_mode is not None else safe_run_mode != "fast"
|
||
default_permission_mode = (prefs or {}).get("default_permission_mode")
|
||
if default_permission_mode not in ("readonly", "approval", "auto_approval", "unrestricted"):
|
||
default_permission_mode = None
|
||
|
||
previous_cm_current = getattr(cm, "current_conversation_id", None)
|
||
|
||
conversation_id = cm.create_conversation(
|
||
project_path=str(workspace.project_path),
|
||
thinking_mode=safe_thinking,
|
||
run_mode=safe_run_mode,
|
||
initial_messages=[],
|
||
model_key=(prefs or {}).get("default_model") or getattr(terminal, "model_key", None),
|
||
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": True,
|
||
},
|
||
)
|
||
try:
|
||
cm.current_conversation_id = previous_cm_current
|
||
except Exception:
|
||
pass
|
||
|
||
# 触发对话列表更新事件
|
||
try:
|
||
from server.app_legacy import socketio
|
||
socketio.emit('conversation_list_update', {
|
||
'action': 'created',
|
||
'conversation_id': conversation_id,
|
||
}, room=f"user_{username}")
|
||
except Exception:
|
||
pass
|
||
|
||
return jsonify({
|
||
"success": True,
|
||
"conversation_id": conversation_id,
|
||
"multi_agent_mode": True,
|
||
}), 201
|
||
|
||
|
||
@multi_agent_bp.route("/api/multiagent/active_sub_agents", methods=["GET"])
|
||
@api_login_required
|
||
def list_active_sub_agents_api():
|
||
"""查询当前会话所有子智能体实例(多智能体模式专用)。"""
|
||
username = get_current_username()
|
||
if not username:
|
||
return jsonify({"success": False, "error": "未登录"}), 401
|
||
conversation_id = (request.args.get("conversation_id") or "").strip()
|
||
if not conversation_id:
|
||
return jsonify({"success": False, "error": "缺少 conversation_id 参数"}), 400
|
||
terminal, _ = get_user_resources(username)
|
||
if not terminal:
|
||
return jsonify({"success": False, "error": "工作区未就绪"}), 503
|
||
sub_agent_manager = getattr(terminal, "sub_agent_manager", None)
|
||
if not sub_agent_manager:
|
||
return jsonify({"success": False, "error": "子智能体管理器未就绪"}), 503
|
||
state = sub_agent_manager.get_multi_agent_state(conversation_id)
|
||
if not state:
|
||
return jsonify({"success": True, "agents": []})
|
||
return jsonify({
|
||
"success": True,
|
||
"agents": [a.to_dict() for a in state.list_all()],
|
||
})
|
||
|
||
|
||
__all__ = ["multi_agent_bp"] |