From 351cd311c2f8c820ac6c7bd5a10509e4c9bbb9e3 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Tue, 14 Jul 2026 23:22:06 +0800 Subject: [PATCH] =?UTF-8?q?fix(multi-agent):=20=E4=BF=AE=E5=A4=8D=E5=8E=8B?= =?UTF-8?q?=E7=BC=A9404=E3=80=81prompt=E5=86=BB=E7=BB=93=E4=B8=B2=E6=89=B0?= =?UTF-8?q?=E3=80=81=E5=AD=90=E6=99=BA=E8=83=BD=E4=BD=93=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=B8=B2=E6=89=B0=E5=8F=8A/multiagent/new=E8=B7=B3=E8=BD=AC?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 19 +++++++++ core/main_terminal_parts/context/messages.py | 44 +++++++++++++------- core/main_terminal_parts/context/mode.py | 7 +++- server/conversation.py | 15 ++++++- server/deep_compression.py | 11 +++-- static/src/app/methods/taskPolling/sync.ts | 6 ++- static/src/app/methods/ui/route.ts | 36 ++-------------- static/src/app/methods/ui/socket.ts | 3 ++ static/src/composables/useLegacySocket.ts | 7 ++-- utils/context_manager/compression_mixin.py | 7 +++- 10 files changed, 95 insertions(+), 60 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 61fa4e0..d8af071 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -201,6 +201,25 @@ 1. **meta 部分只放参数**:`tool-result-meta` 仅用于展示工具调用的参数、配置、状态标识等元信息。例如:状态、ID、路径、任务描述、超时时间等。禁止在 meta 区域放置执行结果、统计摘要、输出内容等。 2. **content 部分只放结果**:`tool-result-content` 仅用于展示工具执行后的结果、输出、统计、总结等。例如:文件内容、命令输出、搜索命中、执行统计(工作时间 / 调用次数 / 工具次数)、最终回复摘要等。禁止在 content 区域重复展示工具参数。 +## 5.7) 调试输出规范(强制) + +> 约束级别:需要临时加调试日志排查问题时**必须遵守**。 + +### 前端调试日志 + +- **统一筛选词**:所有临时 `console.log` 必须使用**同一个**筛选前缀(例如 `[route-debug]`),方便用户在浏览器控制台用该前缀一次性筛选。 +- **控制数量与时机**: + - 禁止一次性输出上百上千条日志刷屏。 + - 只在关键路径(入口、分支判断、状态变化、导航动作)打印,避免在循环、高频事件、每帧渲染中输出。 + - 需要大量结构化数据时,优先用 `console.group` / `console.table` 或对象快照,而不是逐条打印。 +- **用完即清**:问题定位后应及时删除或注释掉临时调试日志,不要长期留在代码里。 + +### 后端调试日志 + +- **必须写入文件**:后端调试信息禁止直接 `print` 到终端刷屏,必须写入日志文件。 +- **复用现有 logger**:优先复用项目已有 logger(如 `utils/logger.py`、`modules/multi_agent/debug_logger.py` 等),按模块落到 `~/.astrion/astrion//logs/` 下。 +- **关键状态转换必打**:多智能体、子智能体、任务轮询等复杂链路应在关键状态转换点写结构化日志,便于复现问题后按时间线追溯。 + ## 6) Git 工作流(开发 + Review) ### 6.1 核心原则 diff --git a/core/main_terminal_parts/context/messages.py b/core/main_terminal_parts/context/messages.py index cb06a7a..1c6a4ba 100644 --- a/core/main_terminal_parts/context/messages.py +++ b/core/main_terminal_parts/context/messages.py @@ -117,15 +117,29 @@ class MessagesMixin: model_key = getattr(self, "model_key", None) prompt_replacements = get_model_prompt_replacements(model_key) + is_multi_agent_mode = bool(getattr(self, "multi_agent_mode", False)) + def _build_main_system_prompt() -> str: + if is_multi_agent_mode: + # 多智能体模式下主 prompt 使用 Team Leader 专属模板 + try: + from modules.multi_agent.prompts import _load_template + return _load_template("master") + except Exception as exc: + logger.warning(f"[messages] 加载多智能体主 prompt 失败: {exc}") system_prompt_template = self.load_prompt(prompt_name) # main_system.txt / main_system_vl.txt 仅使用 {model_description} return system_prompt_template.format( model_description=prompt_replacements.get("model_description", "") ) + main_system_frozen_key = ( + "frozen_main_system_prompt_multi_agent" + if is_multi_agent_mode + else "frozen_main_system_prompt" + ) system_prompt = self._get_or_init_frozen_prompt( - "frozen_main_system_prompt", + main_system_frozen_key, _build_main_system_prompt, ) @@ -225,12 +239,19 @@ class MessagesMixin: return agents_md_template.replace("{{AGENTS_MD_CONTENT}}", agents_md_content) return f"【AGENTS.md 项目规范】\n\n{agents_md_content}\n\n---\n请注意:以上规范来自工作区根目录的 AGENTS.md 文件,若有冲突请以 AGENTS.md 为准。" - agents_md_text = self._get_or_init_frozen_prompt( - "frozen_agents_md_prompt", - _build_agents_md_prompt, + # AGENTS.md 注入开关可能变化,只在开启时生成并缓存,避免关闭时把空字符串冻住 + agents_md_inject_enabled = ( + bool(personalization_config.get("agents_md_auto_inject", False)) + if isinstance(personalization_config, dict) + else False ) - if agents_md_text: - messages.append({"role": "system", "content": agents_md_text}) + if agents_md_inject_enabled: + agents_md_text = self._get_or_init_frozen_prompt( + "frozen_agents_md_prompt", + _build_agents_md_prompt, + ) + if agents_md_text: + messages.append({"role": "system", "content": agents_md_text}) # skills 列表 skills_catalog = get_skills_catalog(private_dir=infer_private_skills_dir(self.data_dir)) @@ -280,17 +301,8 @@ class MessagesMixin: lambda: self._format_disabled_tool_notice() or "", ) - # 多智能体模式提示词:当会话 metadata.multi_agent_mode 为真时追加 + # 多智能体模式额外提示词:可用的子智能体角色(动态 prompt,第一个用户消息后冻结) if getattr(self, "multi_agent_mode", False): - try: - from modules.multi_agent.prompts import build_multi_agent_master_prompt - multi_agent_prompt = build_multi_agent_master_prompt(self.project_path, base="") - if multi_agent_prompt: - 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", diff --git a/core/main_terminal_parts/context/mode.py b/core/main_terminal_parts/context/mode.py index d72ff47..eee359d 100644 --- a/core/main_terminal_parts/context/mode.py +++ b/core/main_terminal_parts/context/mode.py @@ -221,7 +221,12 @@ class ModeMixin: conv_id = getattr(cm, "current_conversation_id", None) if cm else None if cm and conv_id: try: - cm.conversation_manager.update_conversation_metadata(conv_id, {key: built}) + target_manager = ( + cm._get_conversation_manager_for_id(conv_id) + if hasattr(cm, "_get_conversation_manager_for_id") + else cm.conversation_manager + ) + target_manager.update_conversation_metadata(conv_id, {key: built}) if isinstance(cm.conversation_metadata, dict): cm.conversation_metadata[key] = built except Exception: diff --git a/server/conversation.py b/server/conversation.py index 0b3e7c1..5d678ad 100644 --- a/server/conversation.py +++ b/server/conversation.py @@ -1675,13 +1675,26 @@ 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) + + # 传统模式子智能体列表必须排除多智能体任务,避免 /new 等无当前对话场景 + # 把最近一次多智能体模式的 running/idle 子智能体串过来。 + def _is_multi_agent_task(item: dict) -> bool: + task_id = item.get("task_id") + raw_task = manager.tasks.get(task_id) if task_id else None + return bool(raw_task.get("multi_agent_mode")) if isinstance(raw_task, dict) else False + + data = [item for item in data if not _is_multi_agent_task(item)] + # 仅在未绑定具体对话时(如全新会话尚未创建 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"}) - running_only = [item for item in all_overview if item.get("status") not in terminal_statuses] + running_only = [ + item for item in all_overview + if item.get("status") not in terminal_statuses and not _is_multi_agent_task(item) + ] if running_only: data = running_only debug_log( diff --git a/server/deep_compression.py b/server/deep_compression.py index 677b14e..0d63c90 100644 --- a/server/deep_compression.py +++ b/server/deep_compression.py @@ -312,7 +312,12 @@ async def run_deep_compression( sender=None, ) -> Dict[str, Any]: cm = web_terminal.context_manager - conv_data = cm.conversation_manager.load_conversation(conversation_id) + target_manager = ( + cm._get_conversation_manager_for_id(conversation_id) + if hasattr(cm, "_get_conversation_manager_for_id") + else cm.conversation_manager + ) + conv_data = target_manager.load_conversation(conversation_id) if not conv_data: return {"success": False, "error": f"对话不存在: {conversation_id}"} @@ -419,7 +424,7 @@ async def run_deep_compression( # 关键:重置 current_context_tokens,避免自动压缩续接后阈值判断仍读到压缩前的大值而陷入死循环。 # 真实上下文长度会在下一次 API 响应后被重新写入。 try: - cm.conversation_manager.update_token_statistics( + target_manager.update_token_statistics( conversation_id, input_tokens=0, output_tokens=0, @@ -484,7 +489,7 @@ async def run_deep_compression( } for frozen_key in REBUILD_FROZEN_KEYS: meta_updates[frozen_key] = None - cm.conversation_manager.update_conversation_metadata(conversation_id, meta_updates) + target_manager.update_conversation_metadata(conversation_id, meta_updates) # 同步内存中的 metadata,清除 frozen 缓存 try: if getattr(cm, "current_conversation_id", None) == conversation_id and isinstance(cm.conversation_metadata, dict): diff --git a/static/src/app/methods/taskPolling/sync.ts b/static/src/app/methods/taskPolling/sync.ts index c0e9557..e028a79 100644 --- a/static/src/app/methods/taskPolling/sync.ts +++ b/static/src/app/methods/taskPolling/sync.ts @@ -134,10 +134,12 @@ export const syncMethods = { const pathFragment = this.stripConversationPrefix(data.conversation_id); const currentPath = window.location.pathname.replace(/^\/+/, ''); + const urlPrefix = this.multiAgentMode ? '/multiagent/' : '/'; + const targetPath = `${urlPrefix}${pathFragment}`; if (data.created) { - history.pushState({ conversationId: data.conversation_id }, '', `/${pathFragment}`); + history.pushState({ conversationId: data.conversation_id }, '', targetPath); } else if (currentPath !== pathFragment) { - history.replaceState({ conversationId: data.conversation_id }, '', `/${pathFragment}`); + history.replaceState({ conversationId: data.conversation_id }, '', targetPath); } } } diff --git a/static/src/app/methods/ui/route.ts b/static/src/app/methods/ui/route.ts index ebfbb6f..1fbfd23 100644 --- a/static/src/app/methods/ui/route.ts +++ b/static/src/app/methods/ui/route.ts @@ -36,6 +36,8 @@ export const routeMethods = { this.multiAgentMode = true; this.currentConversationId = null; this.currentConversationTitle = '多智能体模式'; + this.logMessageState('bootstrapRoute:clear-messages-for-multiagent-new'); + this.messages = []; this.titleReady = true; this.suppressTitleTyping = false; this.startTitleTyping('多智能体模式', { animate: false }); @@ -47,38 +49,6 @@ export const routeMethods = { } catch (_e) { // 重建失败不阻断主流程 } - // 多智能体模式下自动创建一个带 metadata.multi_agent_mode=true 的新对话 - try { - const resp = await fetch('/api/multiagent/conversations', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({}) - }); - const result = await resp.json(); - if (result && result.success && result.conversation_id) { - this.currentConversationId = result.conversation_id; - // 拉取完整会话信息 - try { - const loadResp = await fetch(`/api/conversations/${result.conversation_id}/load`, { method: 'PUT' }); - const loadResult = await loadResp.json(); - if (loadResult.success) { - if (typeof loadResult.run_mode === 'string') { - this.runMode = loadResult.run_mode; - this.thinkingMode = typeof loadResult.thinking_mode === 'boolean' ? loadResult.thinking_mode : loadResult.run_mode !== 'fast'; - } - if (typeof loadResult.model_key === 'string' && loadResult.model_key) this.modelSet(loadResult.model_key); - this.currentConversationTitle = loadResult.title || '多智能体模式'; - this.startTitleTyping(this.currentConversationTitle, { animate: false }); - history.replaceState({ conversationId: result.conversation_id }, '', `/multiagent/${result.conversation_id.replace(/^conv_/, '')}`); - this.logMessageState('bootstrapRoute:multi-agent-loaded'); - } - } catch (_e) { - // 加载失败也继续,主对话 已创建 - } - } - } catch (error) { - console.warn('[multiagent] 初始化多智能体对话失败:', error); - } await this.restoreComposerDraftState('bootstrap-route:multiagent'); return; } @@ -214,7 +184,7 @@ export const routeMethods = { }, isExplicitNewConversationRoute() { const normalizedPath = window.location.pathname.replace(/^\/+|\/+$/g, ''); - return normalizedPath === 'new'; + return normalizedPath === 'new' || normalizedPath === 'multiagent/new' || normalizedPath === 'multiagent'; }, stripConversationPrefix(conversationId) { if (!conversationId) return ''; diff --git a/static/src/app/methods/ui/socket.ts b/static/src/app/methods/ui/socket.ts index 9932412..dbc56d0 100644 --- a/static/src/app/methods/ui/socket.ts +++ b/static/src/app/methods/ui/socket.ts @@ -321,10 +321,13 @@ export const socketMethods = { const statusConversationId = statusData.conversation && statusData.conversation.current_id; const resumeConversationId = statusConversationId || runningConversationId; const shouldResumeOnNewRoute = isExplicitNewRoute && !!runningConversationId; + const currentPath = window.location.pathname.replace(/^\/+/, ''); + const isMultiAgentNewRoute = currentPath === 'multiagent/new' || currentPath === 'multiagent'; if ( resumeConversationId && !this.currentConversationId && + !isMultiAgentNewRoute && (!isExplicitNewRoute || shouldResumeOnNewRoute) ) { this.skipConversationHistoryReload = true; diff --git a/static/src/composables/useLegacySocket.ts b/static/src/composables/useLegacySocket.ts index a28f125..0e1951b 100644 --- a/static/src/composables/useLegacySocket.ts +++ b/static/src/composables/useLegacySocket.ts @@ -804,7 +804,6 @@ export async function initializeLegacySocket(ctx: any) { return; } const convId = data.conversation_id; - console.log('[conv-trace] socket:conversation_resolved', data); // 初始化期间不修改 currentConversationId,避免与 bootstrapRoute 冲突 if (ctx.initialRouteResolved) { @@ -815,10 +814,12 @@ export async function initializeLegacySocket(ctx: any) { ctx.promoteConversationToTop(convId); const pathFragment = ctx.stripConversationPrefix(convId); const currentPath = window.location.pathname.replace(/^\/+/, ''); + const urlPrefix = ctx.multiAgentMode ? '/multiagent/' : '/'; + const targetPath = `${urlPrefix}${pathFragment}`; if (data.created) { - history.pushState({ conversationId: convId }, '', `/${pathFragment}`); + history.pushState({ conversationId: convId }, '', targetPath); } else if (currentPath !== pathFragment) { - history.replaceState({ conversationId: convId }, '', `/${pathFragment}`); + history.replaceState({ conversationId: convId }, '', targetPath); } } }); diff --git a/utils/context_manager/compression_mixin.py b/utils/context_manager/compression_mixin.py index 10865db..0665ba7 100644 --- a/utils/context_manager/compression_mixin.py +++ b/utils/context_manager/compression_mixin.py @@ -102,7 +102,12 @@ class CompressionMixin: self.conversation_metadata[k] = v if self.current_conversation_id: try: - self.conversation_manager.update_conversation_metadata(self.current_conversation_id, updates) + target_manager = ( + self._get_conversation_manager_for_id(self.current_conversation_id) + if hasattr(self, "_get_conversation_manager_for_id") + else self.conversation_manager + ) + target_manager.update_conversation_metadata(self.current_conversation_id, updates) except Exception as exc: print(f"[ContextCompression] 更新压缩状态失败: {exc}")