From ba4714742561af16b26ccadb77acec1427690b1e Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Sat, 15 Nov 2025 11:21:31 +0800 Subject: [PATCH] chore: snapshot sub agent baseline --- core/main_terminal.py | 3 +- modules/sub_agent_manager.py | 82 +- static/app.js | 55 +- static/index.html | 40 +- static/style.css | 61 + sub_agent/config/__init__.py | 31 + sub_agent/config/api.py | 25 + sub_agent/config/auth.py | 9 + sub_agent/config/conversation.py | 52 + sub_agent/config/limits.py | 64 + sub_agent/config/memory.py | 11 + sub_agent/config/paths.py | 21 + sub_agent/config/security.py | 48 + sub_agent/config/service.py | 30 + sub_agent/config/sub_agent.py | 24 + sub_agent/config/terminal.py | 23 + sub_agent/config/todo.py | 11 + sub_agent/config/ui.py | 29 + sub_agent/core/main_terminal.py | 2242 +++++++++ sub_agent/core/sub_agent_terminal.py | 129 + sub_agent/core/tool_config.py | 63 + sub_agent/core/web_terminal.py | 607 +++ sub_agent/main.py | 274 ++ sub_agent/modules/file_manager.py | 865 ++++ sub_agent/modules/gui_file_manager.py | 335 ++ sub_agent/modules/memory_manager.py | 307 ++ sub_agent/modules/persistent_terminal.py | 678 +++ sub_agent/modules/search_engine.py | 492 ++ sub_agent/modules/sub_agent_manager.py | 443 ++ sub_agent/modules/terminal_manager.py | 504 ++ sub_agent/modules/terminal_ops.py | 388 ++ sub_agent/modules/todo_manager.py | 219 + sub_agent/modules/user_manager.py | 271 ++ sub_agent/modules/webpage_extractor.py | 125 + sub_agent/prompts/main_system.txt | 259 ++ sub_agent/prompts/main_system_prev.txt | 382 ++ sub_agent/prompts/sub_agent_system.txt | 13 + sub_agent/prompts/todo_guidelines.txt | 168 + sub_agent/prompts/todo_guidelines_prev.txt | 13 + sub_agent/prompts/tool_prompts.txt | 35 + sub_agent/server.py | 135 +- sub_agent/static/app.js | 3369 ++++++++++++++ .../static/backup_20251026_183122/app.js | 2801 ++++++++++++ .../claude-colors-simple.html | 194 + .../static/backup_20251026_183122/debug.html | 399 ++ .../static/backup_20251026_183122/index.html | 591 +++ .../static/backup_20251026_183122/login.html | 136 + .../backup_20251026_183122/register.html | 149 + .../static/backup_20251026_183122/style.css | 2021 ++++++++ .../backup_20251026_183122/terminal.html | 803 ++++ .../static/backup_20251026_184346/app.js | 2854 ++++++++++++ .../claude-colors-simple.html | 194 + .../static/backup_20251026_184346/debug.html | 399 ++ .../static/backup_20251026_184346/index.html | 591 +++ .../static/backup_20251026_184346/login.html | 136 + .../backup_20251026_184346/register.html | 149 + .../static/backup_20251026_184346/style.css | 2049 +++++++++ .../backup_20251026_184346/terminal.html | 803 ++++ sub_agent/static/claude-colors-simple.html | 194 + sub_agent/static/debug.html | 399 ++ sub_agent/static/file_manager/app.js | 899 ++++ sub_agent/static/file_manager/editor.css | 60 + sub_agent/static/file_manager/editor.html | 30 + sub_agent/static/file_manager/editor.js | 135 + sub_agent/static/file_manager/index.html | 60 + sub_agent/static/file_manager/style.css | 357 ++ sub_agent/static/index.html | 645 +++ sub_agent/static/login.html | 147 + sub_agent/static/register.html | 149 + sub_agent/static/style.css | 2158 +++++++++ sub_agent/static/terminal.html | 803 ++++ sub_agent/static/vendor/socket.io.min.js | 7 + sub_agent/utils/api_client.py | 614 +++ sub_agent/utils/context_manager.py | 1149 +++++ sub_agent/utils/conversation_manager.py | 854 ++++ sub_agent/utils/logger.py | 131 + sub_agent/utils/terminal_factory.py | 319 ++ sub_agent/web_server.py | 4047 +++++++++++++++++ web_server.py | 16 + 79 files changed, 40239 insertions(+), 138 deletions(-) create mode 100644 sub_agent/config/__init__.py create mode 100644 sub_agent/config/api.py create mode 100644 sub_agent/config/auth.py create mode 100644 sub_agent/config/conversation.py create mode 100644 sub_agent/config/limits.py create mode 100644 sub_agent/config/memory.py create mode 100644 sub_agent/config/paths.py create mode 100644 sub_agent/config/security.py create mode 100644 sub_agent/config/service.py create mode 100644 sub_agent/config/sub_agent.py create mode 100644 sub_agent/config/terminal.py create mode 100644 sub_agent/config/todo.py create mode 100644 sub_agent/config/ui.py create mode 100644 sub_agent/core/main_terminal.py create mode 100644 sub_agent/core/sub_agent_terminal.py create mode 100644 sub_agent/core/tool_config.py create mode 100644 sub_agent/core/web_terminal.py create mode 100644 sub_agent/main.py create mode 100644 sub_agent/modules/file_manager.py create mode 100644 sub_agent/modules/gui_file_manager.py create mode 100644 sub_agent/modules/memory_manager.py create mode 100644 sub_agent/modules/persistent_terminal.py create mode 100644 sub_agent/modules/search_engine.py create mode 100644 sub_agent/modules/sub_agent_manager.py create mode 100644 sub_agent/modules/terminal_manager.py create mode 100644 sub_agent/modules/terminal_ops.py create mode 100644 sub_agent/modules/todo_manager.py create mode 100644 sub_agent/modules/user_manager.py create mode 100644 sub_agent/modules/webpage_extractor.py create mode 100644 sub_agent/prompts/main_system.txt create mode 100644 sub_agent/prompts/main_system_prev.txt create mode 100644 sub_agent/prompts/sub_agent_system.txt create mode 100644 sub_agent/prompts/todo_guidelines.txt create mode 100644 sub_agent/prompts/todo_guidelines_prev.txt create mode 100644 sub_agent/prompts/tool_prompts.txt create mode 100644 sub_agent/static/app.js create mode 100644 sub_agent/static/backup_20251026_183122/app.js create mode 100644 sub_agent/static/backup_20251026_183122/claude-colors-simple.html create mode 100644 sub_agent/static/backup_20251026_183122/debug.html create mode 100644 sub_agent/static/backup_20251026_183122/index.html create mode 100644 sub_agent/static/backup_20251026_183122/login.html create mode 100644 sub_agent/static/backup_20251026_183122/register.html create mode 100644 sub_agent/static/backup_20251026_183122/style.css create mode 100644 sub_agent/static/backup_20251026_183122/terminal.html create mode 100644 sub_agent/static/backup_20251026_184346/app.js create mode 100644 sub_agent/static/backup_20251026_184346/claude-colors-simple.html create mode 100644 sub_agent/static/backup_20251026_184346/debug.html create mode 100644 sub_agent/static/backup_20251026_184346/index.html create mode 100644 sub_agent/static/backup_20251026_184346/login.html create mode 100644 sub_agent/static/backup_20251026_184346/register.html create mode 100644 sub_agent/static/backup_20251026_184346/style.css create mode 100644 sub_agent/static/backup_20251026_184346/terminal.html create mode 100644 sub_agent/static/claude-colors-simple.html create mode 100644 sub_agent/static/debug.html create mode 100644 sub_agent/static/file_manager/app.js create mode 100644 sub_agent/static/file_manager/editor.css create mode 100644 sub_agent/static/file_manager/editor.html create mode 100644 sub_agent/static/file_manager/editor.js create mode 100644 sub_agent/static/file_manager/index.html create mode 100644 sub_agent/static/file_manager/style.css create mode 100644 sub_agent/static/index.html create mode 100644 sub_agent/static/login.html create mode 100644 sub_agent/static/register.html create mode 100644 sub_agent/static/style.css create mode 100644 sub_agent/static/terminal.html create mode 100644 sub_agent/static/vendor/socket.io.min.js create mode 100644 sub_agent/utils/api_client.py create mode 100644 sub_agent/utils/context_manager.py create mode 100644 sub_agent/utils/conversation_manager.py create mode 100644 sub_agent/utils/logger.py create mode 100644 sub_agent/utils/terminal_factory.py create mode 100644 sub_agent/web_server.py diff --git a/core/main_terminal.py b/core/main_terminal.py index 6c9f7a2..e605a81 100644 --- a/core/main_terminal.py +++ b/core/main_terminal.py @@ -1971,7 +1971,8 @@ class MainTerminal: task=arguments.get("task", ""), target_dir=arguments.get("target_dir", ""), reference_files=arguments.get("reference_files", []), - timeout_seconds=arguments.get("timeout_seconds") + timeout_seconds=arguments.get("timeout_seconds"), + conversation_id=self.context_manager.current_conversation_id ) elif tool_name == "wait_sub_agent": diff --git a/modules/sub_agent_manager.py b/modules/sub_agent_manager.py index 4edefad..7228b0a 100644 --- a/modules/sub_agent_manager.py +++ b/modules/sub_agent_manager.py @@ -5,7 +5,7 @@ import shutil import time import uuid from pathlib import Path -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Optional, Tuple, Any import httpx @@ -34,12 +34,15 @@ class SubAgentManager: self.base_dir = Path(SUB_AGENT_TASKS_BASE_DIR).resolve() self.results_dir = Path(SUB_AGENT_PROJECT_RESULTS_DIR).resolve() self.state_file = Path(SUB_AGENT_STATE_FILE).resolve() + self.sub_agent_conversations_dir = (self.data_dir / "conversations" / "sub_agent").resolve() self.base_dir.mkdir(parents=True, exist_ok=True) self.results_dir.mkdir(parents=True, exist_ok=True) self.state_file.parent.mkdir(parents=True, exist_ok=True) + self.sub_agent_conversations_dir.mkdir(parents=True, exist_ok=True) self.tasks: Dict[str, Dict] = {} + self.conversation_agents: Dict[str, List[int]] = {} self._load_state() # ------------------------------------------------------------------ @@ -54,6 +57,7 @@ class SubAgentManager: target_dir: str, reference_files: Optional[List[str]] = None, timeout_seconds: Optional[int] = None, + conversation_id: Optional[str] = None, ) -> Dict: """创建子智能体任务并启动远端服务。""" reference_files = reference_files or [] @@ -61,6 +65,15 @@ class SubAgentManager: if validation_error: return {"success": False, "error": validation_error} + if not conversation_id: + return {"success": False, "error": "缺少对话ID,无法创建子智能体"} + + if not self._ensure_agent_slot_available(conversation_id, agent_id): + return { + "success": False, + "error": f"该对话已使用过编号 {agent_id},请更换新的子智能体代号。" + } + if self._active_task_count() >= SUB_AGENT_MAX_ACTIVE: return { "success": False, @@ -69,9 +82,9 @@ class SubAgentManager: task_id = self._generate_task_id(agent_id) task_root = self.base_dir / task_id - references_dir = task_root / "references" - deliverables_dir = task_root / "deliverables" workspace_dir = task_root / "workspace" + references_dir = workspace_dir / "references" + deliverables_dir = workspace_dir / "deliverables" for path in (task_root, references_dir, deliverables_dir, workspace_dir): path.mkdir(parents=True, exist_ok=True) @@ -96,6 +109,10 @@ class SubAgentManager: "references_dir": str(references_dir), "deliverables_dir": str(deliverables_dir), "timeout_seconds": timeout_seconds, + "parent_conversation_id": conversation_id, + "data_dir": str(self.data_dir), + "reference_manifest": copied_refs, + "conversation_storage_dir": str(self.sub_agent_conversations_dir), } service_response = self._call_service("POST", "/tasks", payload, timeout_seconds + 5) @@ -108,6 +125,7 @@ class SubAgentManager: } status = service_response.get("status", "pending") + sub_conversation_id = service_response.get("sub_conversation_id") task_record = { "task_id": task_id, "agent_id": agent_id, @@ -122,8 +140,11 @@ class SubAgentManager: "timeout_seconds": timeout_seconds, "service_payload": payload, "created_at": time.time(), + "conversation_id": conversation_id, + "sub_conversation_id": sub_conversation_id, } self.tasks[task_id] = task_record + self._mark_agent_id_used(conversation_id, agent_id) self._save_state() message = f"子智能体{agent_id} 已创建,任务ID: {task_id},当前状态:{status}" @@ -137,6 +158,7 @@ class SubAgentManager: "message": message, "deliverables_dir": str(deliverables_dir), "copied_references": copied_refs, + "sub_conversation_id": sub_conversation_id, } def wait_for_completion( @@ -191,14 +213,20 @@ class SubAgentManager: try: data = json.loads(self.state_file.read_text(encoding="utf-8")) self.tasks = data.get("tasks", {}) + self.conversation_agents = data.get("conversation_agents", {}) except json.JSONDecodeError: logger.warning("子智能体状态文件损坏,已忽略。") self.tasks = {} + self.conversation_agents = {} else: self.tasks = {} + self.conversation_agents = {} def _save_state(self): - payload = {"tasks": self.tasks} + payload = { + "tasks": self.tasks, + "conversation_agents": self.conversation_agents + } self.state_file.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") def _generate_task_id(self, agent_id: int) -> str: @@ -335,6 +363,7 @@ class SubAgentManager: "status": status, "message": message or f"子智能体状态:{status}", "details": service_payload, + "sub_conversation_id": task.get("sub_conversation_id"), "system_message": self._build_system_message(task, status, None, message), } task["final_result"] = result @@ -379,6 +408,7 @@ class SubAgentManager: "message": message or "子智能体已完成任务。", "deliverables_path": str(deliverables_dir), "copied_path": str(copied_path), + "sub_conversation_id": task.get("sub_conversation_id"), "system_message": system_message, "details": service_payload, } @@ -401,6 +431,15 @@ class SubAgentManager: if task_root.exists(): shutil.rmtree(task_root, ignore_errors=True) + def _ensure_agent_slot_available(self, conversation_id: str, agent_id: int) -> bool: + used = self.conversation_agents.setdefault(conversation_id, []) + return agent_id not in used + + def _mark_agent_id_used(self, conversation_id: str, agent_id: int): + used = self.conversation_agents.setdefault(conversation_id, []) + if agent_id not in used: + used.append(agent_id) + def _validate_create_params(self, agent_id: Optional[int], summary: str, task: str, target_dir: str) -> Optional[str]: if agent_id is None: return "子智能体代号不能为空" @@ -408,8 +447,8 @@ class SubAgentManager: agent_id = int(agent_id) except ValueError: return "子智能体代号必须是整数" - if not (1 <= agent_id <= SUB_AGENT_MAX_ACTIVE): - return f"子智能体代号必须在 1~{SUB_AGENT_MAX_ACTIVE} 范围内" + if agent_id <= 0: + return "子智能体代号必须为正整数" if not summary or not summary.strip(): return "任务摘要不能为空" if not task or not task.strip(): @@ -441,3 +480,34 @@ class SubAgentManager: return f"{prefix} 执行失败:" + (extra if extra else "请检查交付目录或任务状态。") return f"{prefix} 状态:{status}。" + (extra if extra else "") + + def get_overview(self, conversation_id: Optional[str] = None) -> List[Dict[str, Any]]: + """返回子智能体任务概览,用于前端展示。""" + overview: List[Dict[str, Any]] = [] + for task_id, task in self.tasks.items(): + if conversation_id and task.get("conversation_id") != conversation_id: + continue + snapshot = { + "task_id": task_id, + "agent_id": task.get("agent_id"), + "summary": task.get("summary"), + "status": task.get("status"), + "created_at": task.get("created_at"), + "updated_at": task.get("updated_at"), + "target_dir": task.get("target_project_dir"), + "last_tool": task.get("last_tool"), + "deliverables_dir": task.get("deliverables_dir"), + "copied_path": task.get("copied_path"), + "conversation_id": task.get("conversation_id"), + "sub_conversation_id": task.get("sub_conversation_id"), + } + # 对于运行中的任务,尝试获取最新状态 + if snapshot["status"] not in TERMINAL_STATUSES: + remote = self._call_service("GET", f"/tasks/{task_id}", timeout=5) + if remote.get("success"): + snapshot["status"] = remote.get("status", snapshot["status"]) + snapshot["remote_message"] = remote.get("message") + snapshot["last_tool"] = remote.get("last_tool") + task["last_tool"] = snapshot["last_tool"] + overview.append(snapshot) + return overview diff --git a/static/app.js b/static/app.js index 7989074..d3b2751 100644 --- a/static/app.js +++ b/static/app.js @@ -160,7 +160,9 @@ async function bootstrapApp() { // 对话历史侧边栏 sidebarCollapsed: true, // 默认收起对话侧边栏 - showTodoList: false, + panelMode: 'files', // files | todo | subAgents + subAgents: [], + subAgentPollTimer: null, conversations: [], conversationsLoading: false, hasMoreConversations: false, @@ -277,6 +279,13 @@ async function bootstrapApp() { document.addEventListener('click', this.onDocumentClick); window.addEventListener('scroll', this.onWindowScroll, true); document.addEventListener('keydown', this.onKeydownListener); + + this.fetchSubAgents(); + this.subAgentPollTimer = setInterval(() => { + if (this.panelMode === 'subAgents') { + this.fetchSubAgents(); + } + }, 5000); }, beforeUnmount() { @@ -295,6 +304,10 @@ async function bootstrapApp() { document.removeEventListener('keydown', this.onKeydownListener); this.onKeydownListener = null; } + if (this.subAgentPollTimer) { + clearInterval(this.subAgentPollTimer); + this.subAgentPollTimer = null; + } }, methods: { @@ -2056,8 +2069,13 @@ async function bootstrapApp() { }; }, - toggleTodoPanel() { - this.showTodoList = !this.showTodoList; + cycleSidebarPanel() { + const order = ['files', 'todo', 'subAgents']; + const nextIndex = (order.indexOf(this.panelMode) + 1) % order.length; + this.panelMode = order[nextIndex]; + if (this.panelMode === 'subAgents') { + this.fetchSubAgents(); + } }, formatTaskStatus(task) { @@ -2073,6 +2091,37 @@ async function bootstrapApp() { return this.toolCategoryEmojis[categoryId] || '⚙️'; }, + async fetchSubAgents() { + try { + const resp = await fetch('/api/sub_agents'); + if (!resp.ok) { + throw new Error(await resp.text()); + } + const data = await resp.json(); + if (data.success) { + this.subAgents = Array.isArray(data.data) ? data.data : []; + } + } catch (error) { + console.error('获取子智能体列表失败:', error); + } + }, + + openSubAgent(agent) { + if (!agent || !agent.task_id) { + return; + } + const { protocol, hostname } = window.location; + const parentConv = agent.conversation_id || this.currentConversationId || ''; + const convSegment = this.stripConversationPrefix(parentConv); + const agentLabel = agent.agent_id ? `sub_agent${agent.agent_id}` : agent.task_id; + const base = `${protocol}//${hostname}:8092`; + const pathSuffix = convSegment + ? `/${convSegment}+${agentLabel}` + : `/sub_agent/${agent.task_id}`; + const url = `${base}${pathSuffix}`; + window.open(url, '_blank'); + }, + async fetchTodoList() { try { const response = await fetch('/api/todo-list'); diff --git a/static/index.html b/static/index.html index 39a8e0a..c644a0a 100644 --- a/static/index.html +++ b/static/index.html @@ -129,19 +129,24 @@