refactor(messages): 统一运行期消息插入为 [系统通知|source] 前缀的 user 消息

- 新增 server/chat_flow_task_support.inject_runtime_user_message 作为唯一入口,
  统一处理 add_conversation + messages.insert + sender(user_message) 三件事
- 子智能体 / 后台 run_command / runtime_guidance / mode notice 全部走该入口,
  role 一律 user,inline 仅作 metadata 标记,不再复用 system role 表达语义
- inline poll 移出 tool 执行循环,改为整轮 tool 完成后批量注入,
  避免在 assistant.tool_calls 与 tool 序列之间插入 user 导致 API 报错
- 删除 _insert_completion_notice_message / _record_sub_agent_message
  以及 build_messages 中 system→user 的回转分支
- 移除已废弃的 wait_sub_agent 工具残留(后端注册、前端图标/动画、
  prompts、SKILL 文档;保留 sleep 的 wait_sub_agent_ids 参数)
- 前端取消对 is_auto_generated/auto_message_type 的过滤,
  让运行期注入的 user 消息在历史和实时推送中正常渲染

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
JOJO 2026-05-23 01:58:23 +08:00
parent 30e7709df1
commit 67fdb715f5
19 changed files with 224 additions and 257 deletions

View File

@ -95,7 +95,6 @@ python3 -m server.app --path <当前目录> --port 8091 --thinking-mode
工具接口:
- `create_sub_agent`: 创建并启动子智能体
- `wait_sub_agent`: 等待子智能体完成并获取结果
- `close_sub_agent`: 终止运行中的子智能体
### 🛠️ 丰富的工具集

View File

@ -38,7 +38,7 @@ description: run_command 工具使用指南。介绍前台与后台模式、后
- timeout 最长 3600 秒。命令在后台继续执行,工具会等约 5 秒收集当前输出并返回,然后 **立即释放控制权**,让你继续处理其他任务。
- 返回值里会包含 `command_id``status` 可能是 `running_background`。你会拿到已经产生的输出片段,但最终结果要靠系统通知或手动等待。
- 成功或失败完成后,系统会自动发送一条 `system` 消息(内容类似 `后台命令已完成completed`,并附带 `command_id`、`command`、`output` 摘要)通知你。不要再依赖 `wait_sub_agent`/手动轮询。
- 成功或失败完成后,系统会自动插入一条 user 消息(前缀 `[系统通知|background_command]`,附带 `command_id` 与输出摘要)通知你。不要依赖任何手动轮询。
- 适合长时间任务(构建/安装/批处理)或你想“先发起再继续别的工作”。
- 返回的 `command_id` 需要保留,用于后续通过 `sleep(wait_runcommand_id=...)` 等待结果或根据通知定位输出。

View File

@ -122,7 +122,7 @@ create_sub_agent(
- 你会在对话中看到类似:"这是一句系统自动发送的user消息用于通知你子智能体已经运行完成..."
**完成通知机制说明:**
- 后台子智能体完成的通知是系统自动插入的 user/system 消息,不依赖手动调用 `wait_sub_agent` 或其它轮询接口。
- 后台子智能体完成的通知是系统自动插入的 user 消息(前缀 `[系统通知|sub_agent]`),不依赖手动调用任何轮询接口。
- 你只需在收到通知后直接查看交付目录即可,系统会把所有必需信息一并带上。
**何时使用:**
@ -163,8 +163,8 @@ create_sub_agent(agent_id=1, task="大规模日志分析", run_in_background=tru
**重要提示:**
- 系统会自动处理通知,你只需要在收到通知消息后处理结果即可
- 如果你正在执行工具调用,系统会延迟通知,避免打断你的工作流
- 通知类型取决于你是否继续工作:继续工作时收到 system 消息,停止输出时收到 user 消息
- 后台完成由上述系统通知负责,不要再依赖 `wait_sub_agent` 或类似的手动等待流,系统会在能插入消息时自动通知你。
- 不论运行中还是空闲,通知一律是带 `[系统通知|sub_agent]` 前缀的 user 消息;运行中只是通知不触发新一轮回复,空闲时等同于一条新输入会触发回复
- 后台完成由上述系统通知负责,不要再依赖任何手动等待流,系统会在能插入消息时自动通知你。
### 模式选择决策树

View File

@ -332,7 +332,17 @@ class MainTerminalCommandMixin:
)
system_message = tool_result.get("system_message")
if system_message:
self._record_sub_agent_message(system_message, tool_result.get("task_id"), inline=False)
from server.chat_flow_task_support import inject_runtime_user_message
inject_runtime_user_message(
web_terminal=self,
messages=None,
text=system_message,
source="sub_agent",
sender=None,
conversation_id=getattr(self.context_manager, "current_conversation_id", None),
inline=False,
extra_metadata={"task_id": tool_result.get("task_id")},
)
# 4. 在终端显示执行信息(不保存到历史)
if collected_tool_calls:

View File

@ -431,12 +431,6 @@ class MainTerminalContextMixin:
}
messages.append(message)
elif conv["role"] == "system" and metadata.get("sub_agent_notice"):
# 转换为用户消息,让模型能及时响应
messages.append({
"role": "user",
"content": conv["content"]
})
else:
# User 或普通 System 消息
images = conv.get("images") or metadata.get("images") or []

View File

@ -164,7 +164,6 @@ class MainTerminalToolsExecutionMixin:
}
_SUB_AGENT_SERIES_TOOLS = {
"create_sub_agent",
"wait_sub_agent",
"close_sub_agent",
"terminate_sub_agent",
"get_sub_agent_status",
@ -198,26 +197,6 @@ class MainTerminalToolsExecutionMixin:
"terminal_session",
}
def _record_sub_agent_message(self, message: Optional[str], task_id: Optional[str] = None, inline: bool = False):
"""以 system 消息记录子智能体状态。"""
if not message:
return
if task_id and task_id in self._announced_sub_agent_tasks:
return
if task_id:
self._announced_sub_agent_tasks.add(task_id)
logger.info(
"[SubAgent] record message | task=%s | inline=%s | content=%s",
task_id,
inline,
message.replace("\n", "\\n")[:200],
)
metadata = {"sub_agent_notice": True, "inline": inline, "is_auto_generated": True, "auto_message_type": "sub_agent_notice"}
if task_id:
metadata["task_id"] = task_id
self.context_manager.add_conversation("system", message, metadata=metadata)
print(f"{OUTPUT_FORMATS['info']} {message}")
# 扩展的只读命令白名单(包含常用管道命令)
_READONLY_ALLOWED_EXECUTABLES = {
"grep", "find", "ls", "pwd", "tree", "cat", "head", "tail",

View File

@ -75,7 +75,7 @@ TOOL_CATEGORIES: Dict[str, ToolCategory] = {
),
"sub_agent": ToolCategory(
label="子智能体",
tools=["create_sub_agent", "wait_sub_agent", "close_sub_agent"],
tools=["create_sub_agent", "close_sub_agent"],
),
"easter_egg": ToolCategory(
label="彩蛋实验",

View File

@ -958,7 +958,7 @@ class SubAgentManager:
return None
def lookup_task(self, *, task_id: Optional[str] = None, agent_id: Optional[int] = None) -> Optional[Dict]:
"""只读查询任务信息,供 wait_sub_agent 自动调整超时时间。"""
"""只读查询任务信息,供 sleep(wait_sub_agent_ids=...) 自动调整超时时间。"""
task = self._select_task(task_id, agent_id)
if not task:
return None

View File

@ -138,7 +138,7 @@
## 5. 操作技巧
- **待办事项**任务≥2步时使用 `todo_create`当用户提出稍微复杂的要求、预计超过3个步骤时应积极创建待办事项概述≤50字任务2-6条完成立即更新
- **子智能体**独立子任务并行处理最多5个同时运行`wait_sub_agent` 超时需≥创建时设置;刚开始修改一个项目、需要先找到某个功能模块的修改位置时,优先考虑创建子智能体快速了解
- **子智能体**独立子任务并行处理最多5个同时运行刚开始修改一个项目、需要先找到某个功能模块的修改位置时优先考虑创建子智能体快速了解
- **记忆管理**`update_memory` 支持追加/替换/删除main 为长期记忆task 为当前对话记忆
---

View File

@ -155,7 +155,7 @@
## 5. 操作技巧
- **待办事项**任务≥2步时使用 `todo_create`当用户提出稍微复杂的要求、预计超过3个步骤时应积极创建待办事项概述≤50字任务2-6条完成立即更新
- **子智能体**独立子任务并行处理最多5个同时运行`wait_sub_agent` 超时需≥创建时设置;刚开始修改一个项目、需要先找到某个功能模块的修改位置时,优先考虑创建子智能体快速了解
- **子智能体**独立子任务并行处理最多5个同时运行刚开始修改一个项目、需要先找到某个功能模块的修改位置时优先考虑创建子智能体快速了解
- **记忆管理**`update_memory` 支持追加/替换/删除main 为长期记忆task 为当前对话记忆
---

View File

@ -53,12 +53,6 @@ def _dispatch_runtime_mode_notice(terminal: WebTerminal, username: str, text: st
notice = str(text or "").strip()
if not notice:
return
metadata = {
"runtime_guidance": True,
"runtime_guidance_original": notice,
"runtime_mode_notice": True,
"message_source": "notify",
}
# 若有运行任务,优先入队到该任务(下一轮工具后注入),确保会进入后续 API 请求上下文。
try:
@ -84,26 +78,25 @@ def _dispatch_runtime_mode_notice(terminal: WebTerminal, username: str, text: st
pass
# 无运行任务时,直接写入当前会话并广播前端显示(来源固定为 notify
try:
if getattr(terminal, "context_manager", None):
terminal.context_manager.add_conversation("user", notice, metadata=metadata)
except Exception:
pass
try:
socketio.emit(
"user_message",
{
"message": notice,
"runtime_guidance": True,
"runtime_guidance_original": notice,
"runtime_mode_notice": True,
"message_source": "notify",
"conversation_id": getattr(getattr(terminal, "context_manager", None), "current_conversation_id", None),
},
room=f"user_{username}",
)
except Exception:
pass
from .chat_flow_task_support import inject_runtime_user_message
conversation_id = getattr(getattr(terminal, "context_manager", None), "current_conversation_id", None)
def _emit(event: str, payload: Dict[str, Any]) -> None:
try:
socketio.emit(event, payload, room=f"user_{username}")
except Exception:
pass
inject_runtime_user_message(
web_terminal=terminal,
messages=None,
text=notice,
source="notify",
sender=_emit,
conversation_id=conversation_id,
inline=True,
)
@chat_bp.route('/api/thinking-mode', methods=['POST'])
@api_login_required

View File

@ -2,43 +2,110 @@ from __future__ import annotations
import asyncio
import time
from typing import Dict, List, Optional
from typing import Any, Callable, Dict, List, Optional
from modules.sub_agent_manager import TERMINAL_STATUSES
def _insert_completion_notice_message(
_VALID_SOURCES = {
"guidance",
"notify",
"user",
"presend",
"sub_agent",
"background_command",
}
def inject_runtime_user_message(
*,
messages: List[Dict],
after_tool_call_id: Optional[str],
message: str,
web_terminal,
messages: Optional[List[Dict]],
text: str,
source: str,
sender: Optional[Callable[[str, Dict[str, Any]], None]] = None,
conversation_id: Optional[str] = None,
after_tool_call_id: Optional[str] = None,
inline: bool,
sender,
metadata: Optional[Dict] = None,
sub_agent_notice: bool = False,
):
"""统一插入完成通知system/user并发送事件。"""
insert_index = len(messages)
if after_tool_call_id:
for idx, msg in enumerate(messages):
if msg.get("role") == "tool" and msg.get("tool_call_id") == after_tool_call_id:
insert_index = idx + 1
break
extra_metadata: Optional[Dict[str, Any]] = None,
persist: bool = True,
) -> Optional[str]:
"""运行期/空闲期统一向对话插入一条 user 消息(带 [系统通知|{source}] 前缀)。
insert_role = "system" if inline else "user"
meta = dict(metadata or {})
meta["inline"] = inline
messages.insert(insert_index, {
"role": insert_role,
"content": message,
"metadata": meta
})
- inline=True 表示运行期插入仅作为通知不主动触发新一轮回复
- inline=False 表示空闲期插入等价于一条用户消息由调用方决定是否启动新一轮回复
- messages=None 时仅持久化到对话历史并广播前端无运行中的 messages 列表可写
"""
raw = "" if text is None else str(text).strip()
if not raw:
return None
payload = {'content': message, 'inline': inline}
if sub_agent_notice:
payload['sub_agent_notice'] = True
payload.update({k: v for k, v in meta.items() if k in {"command_id", "task_id", "background_command_notice"}})
sender('system_message', payload)
src = str(source or "").strip().lower()
if src not in _VALID_SOURCES:
src = "guidance"
formatted = f"[系统通知|{src}]\n{raw}"
metadata: Dict[str, Any] = {
"runtime_injected": True,
"source": src,
"inline": inline,
"is_auto_generated": True,
"auto_message_type": f"runtime_{src}",
"message_source": src,
"runtime_guidance": True,
"runtime_guidance_original": raw,
}
if extra_metadata:
metadata.update({k: v for k, v in extra_metadata.items() if v is not None})
if persist:
try:
ctx_manager = getattr(web_terminal, "context_manager", None)
if ctx_manager is not None:
ctx_manager.add_conversation("user", formatted, metadata=metadata)
except Exception:
pass
if messages is not None:
insert_index = len(messages)
if after_tool_call_id:
for idx, msg in enumerate(messages):
if msg.get("role") == "tool" and msg.get("tool_call_id") == after_tool_call_id:
# 跳过紧随其后的所有 tool 消息,避免把 user 插进同一 assistant.tool_calls 的 tool 序列中间,
# 否则会触发 "insufficient tool messages following tool_calls" 错误。
end = idx + 1
while end < len(messages) and messages[end].get("role") == "tool":
end += 1
insert_index = end
break
messages.insert(insert_index, {
"role": "user",
"content": formatted,
"metadata": dict(metadata),
})
if callable(sender):
payload: Dict[str, Any] = {
"message": formatted,
"content": formatted,
"conversation_id": conversation_id,
"inline": inline,
"source": src,
"message_source": src,
"runtime_guidance": True,
"runtime_guidance_original": raw,
}
for key in ("task_id", "command_id"):
value = (extra_metadata or {}).get(key)
if value is not None:
payload[key] = value
try:
sender("user_message", payload)
except Exception:
pass
return formatted
async def process_sub_agent_updates(*, messages: List[Dict], inline: bool = False, after_tool_call_id: Optional[str] = None, web_terminal, sender, debug_log, maybe_mark_failure_from_message):
@ -126,13 +193,6 @@ async def process_sub_agent_updates(*, messages: List[Dict], inline: bool = Fals
debug_log(f"[SubAgent] update task={task_id} inline={inline} msg={message}")
# 记录到对话历史(用于后续 build_messages 转换为 user 消息)
if hasattr(web_terminal, "_record_sub_agent_message"):
try:
web_terminal._record_sub_agent_message(message, task_id, inline=inline)
except Exception as exc:
debug_log(f"[SubAgent] 记录子智能体消息失败: {exc}")
# 标记任务已通知
if task_id:
web_terminal._announced_sub_agent_tasks.add(task_id)
@ -144,19 +204,17 @@ async def process_sub_agent_updates(*, messages: List[Dict], inline: bool = Fals
except Exception as exc:
debug_log(f"[SubAgent] 保存通知状态失败: {exc}")
# 运行中插入 system 消息,避免触发新的 user 轮次;非运行中保持 user 通知
if not inline:
prefix = "这是一句系统自动发送的user消息用于通知你子智能体已经运行完成"
if not message.startswith(prefix):
message = f"{prefix}\n\n{message}"
_insert_completion_notice_message(
conversation_id = getattr(getattr(web_terminal, "context_manager", None), "current_conversation_id", None)
inject_runtime_user_message(
web_terminal=web_terminal,
messages=messages,
after_tool_call_id=after_tool_call_id,
message=message,
inline=inline,
text=message,
source="sub_agent",
sender=sender,
metadata={"sub_agent_notice": True, "task_id": task_id},
sub_agent_notice=True,
conversation_id=conversation_id,
after_tool_call_id=after_tool_call_id,
inline=inline,
extra_metadata={"task_id": task_id},
)
if inline:
web_terminal._inline_sub_agent_notified.add(inline_key)
@ -194,34 +252,16 @@ async def process_background_command_updates(*, messages: List[Dict], inline: bo
f"output_len={len(output)} inline={inline}"
)
# 与子智能体 inline 通知一致:写入对话历史,确保前端/历史都可见
try:
web_terminal.context_manager.add_conversation(
"system",
message,
metadata={
"background_command_notice": True,
"inline": inline,
"command_id": command_id,
"is_auto_generated": True,
"auto_message_type": "background_command_notice",
},
)
debug_log(f"[BgCmdDebug] persisted system notice command_id={command_id}")
except Exception as exc:
debug_log(f"[BgCmdDebug] persist system notice failed command_id={command_id}: {exc}")
_insert_completion_notice_message(
inject_runtime_user_message(
web_terminal=web_terminal,
messages=messages,
after_tool_call_id=after_tool_call_id,
message=message,
inline=inline,
text=message,
source="background_command",
sender=sender,
metadata={
"background_command_notice": True,
"command_id": command_id,
},
sub_agent_notice=False,
conversation_id=conversation_id,
after_tool_call_id=after_tool_call_id,
inline=inline,
extra_metadata={"command_id": command_id},
)
try:
manager.mark_notified(str(command_id))

View File

@ -22,6 +22,7 @@ from config import TOOL_CALL_COOLDOWN
from modules.personalization_manager import load_personalization_config, resolve_context_compression_settings
from modules.auto_approval_service import run_auto_approval
from .deep_compression import run_deep_compression
from .chat_flow_task_support import inject_runtime_user_message
def _format_numbered_lines(lines: List[str], start_line_no: int) -> List[Dict[str, Any]]:
@ -186,44 +187,15 @@ def _inject_runtime_mode_notice(
sender=None,
conversation_id: Optional[str] = None,
) -> None:
if not isinstance(content, str) or not content.strip():
return
text = content.strip()
metadata = {
"runtime_guidance": True,
"runtime_guidance_original": text,
"runtime_mode_notice": True,
"message_source": "notify",
}
try:
web_terminal.context_manager.add_conversation(
"user",
text,
metadata=metadata,
)
except Exception:
pass
messages.append(
{
"role": "user",
"content": text,
"metadata": metadata,
}
inject_runtime_user_message(
web_terminal=web_terminal,
messages=messages,
text=content,
source="notify",
sender=sender,
conversation_id=conversation_id,
inline=True,
)
if callable(sender):
try:
sender(
"user_message",
{
"message": text,
"conversation_id": conversation_id,
"runtime_guidance": True,
"runtime_guidance_original": text,
"message_source": "notify",
},
)
except Exception:
pass
def _format_rejected_tool_text(reason: str) -> str:
@ -863,18 +835,6 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie
action_message = summary
debug_log(f"{function_name} 执行完成: {summary or '无摘要'}")
if function_name == "wait_sub_agent":
system_msg = result_data.get("system_message")
if system_msg:
messages.append({
"role": "system",
"content": system_msg
})
sender('system_message', {
'content': system_msg,
'inline': False
})
maybe_mark_failure_from_message(web_terminal, system_msg)
if function_name == "sleep":
try:
if isinstance(result_data, dict) and result_data.get("mode") == "wait_sub_agent_ids":
@ -1121,11 +1081,20 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie
except Exception as exc:
debug_log(f"[ContextCompression] 同步替换本轮 messages 失败: {exc}")
debug_log(f"💾 增量保存:工具结果 {function_name}")
if function_name != "wait_sub_agent":
system_message = result_data.get("system_message") if isinstance(result_data, dict) else None
if system_message:
web_terminal._record_sub_agent_message(system_message, result_data.get("task_id"), inline=False)
maybe_mark_failure_from_message(web_terminal, system_message)
system_message = result_data.get("system_message") if isinstance(result_data, dict) else None
if system_message:
inject_runtime_user_message(
web_terminal=web_terminal,
messages=messages,
text=system_message,
source="sub_agent",
sender=sender,
conversation_id=conversation_id,
after_tool_call_id=tool_call_id,
inline=False,
extra_metadata={"task_id": result_data.get("task_id")},
)
maybe_mark_failure_from_message(web_terminal, system_message)
# 自动深层压缩(工具调用后触发)
current_context_tokens = web_terminal.context_manager.get_current_context_tokens(conversation_id)
@ -1176,18 +1145,36 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie
except Exception as exc:
debug_log(f"[RuntimeMode] 应用挂起模式变更失败: {exc}")
debug_log(f"[SubAgent] after tool={function_name} call_id={tool_call_id} -> poll updates")
await process_sub_agent_updates(messages=messages, inline=True, after_tool_call_id=tool_call_id, web_terminal=web_terminal, sender=sender, debug_log=debug_log, maybe_mark_failure_from_message=maybe_mark_failure_from_message)
debug_log(f"[BgCmdDebug] after tool={function_name} call_id={tool_call_id} -> poll background command updates (inline)")
await process_background_command_updates(messages=messages, inline=True, after_tool_call_id=tool_call_id, web_terminal=web_terminal, sender=sender, debug_log=debug_log, maybe_mark_failure_from_message=maybe_mark_failure_from_message)
await asyncio.sleep(0.2)
if tool_failed:
mark_force_thinking(web_terminal, reason=f"{function_name}_failed")
# 同一轮全部 tool_call 完成后,再统一 poll 子智能体/后台命令并注入通知,
# 避免在 assistant.tool_calls 与对应 tool 消息序列之间插入 user 消息导致 API 报错。
debug_log("[SubAgent] poll updates after all tool_calls finished")
await process_sub_agent_updates(
messages=messages,
inline=True,
after_tool_call_id=None,
web_terminal=web_terminal,
sender=sender,
debug_log=debug_log,
maybe_mark_failure_from_message=maybe_mark_failure_from_message,
)
debug_log("[BgCmdDebug] poll background command updates after all tool_calls finished")
await process_background_command_updates(
messages=messages,
inline=True,
after_tool_call_id=None,
web_terminal=web_terminal,
sender=sender,
debug_log=debug_log,
maybe_mark_failure_from_message=maybe_mark_failure_from_message,
)
# 运行期模式通知:必须等待同一轮全部 tool_call 都完成后再注入,
# 避免在 assistant.tool_calls 与对应 tool 消息之间插入 system 消息导致 API 报错。
# 避免在 assistant.tool_calls 与对应 tool 消息之间插入 user 消息导致 API 报错。
if pending_runtime_mode_notices:
for notice in pending_runtime_mode_notices:
_inject_runtime_mode_notice(
@ -1222,39 +1209,16 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie
)
else:
runtime_guidance_text = str(raw_item or "").strip()
if runtime_guidance_source not in {"guidance", "notify", "user", "presend", "sub_agent", "background_command"}:
runtime_guidance_source = "guidance"
if not runtime_guidance_text:
continue
runtime_guidance_meta = {
"runtime_guidance": True,
"runtime_guidance_original": runtime_guidance_text,
"message_source": runtime_guidance_source,
}
try:
web_terminal.context_manager.add_conversation(
"user",
runtime_guidance_text,
metadata=runtime_guidance_meta,
)
except Exception as exc:
debug_log(f"[RuntimeGuidance] 持久化引导消息失败: {exc}")
messages.append(
{
"role": "user",
"content": runtime_guidance_text,
"metadata": runtime_guidance_meta,
}
)
sender(
"user_message",
{
"message": runtime_guidance_text,
"conversation_id": conversation_id,
"runtime_guidance": True,
"runtime_guidance_original": runtime_guidance_text,
"message_source": runtime_guidance_source,
},
inject_runtime_user_message(
web_terminal=web_terminal,
messages=messages,
text=runtime_guidance_text,
source=runtime_guidance_source,
sender=sender,
conversation_id=conversation_id,
inline=True,
)
injected_count += 1
if injected_count:

View File

@ -261,14 +261,6 @@ export const historyMethods = {
debugLog('跳过系统代发的图片/视频消息(仅用于模型查看,不在前端展示)');
return;
}
if (message.role === 'user' && isSystemAutoUserMessageMeta(meta)) {
debugLog('跳过系统自动代发的 user 消息(前端不渲染)', {
auto_message_type: meta.auto_message_type || null,
sub_agent_notice: !!meta.sub_agent_notice,
background_command_notice: !!meta.background_command_notice
});
return;
}
if (message.role === 'user') {
// 用户消息 - 先结束之前的assistant消息

View File

@ -1547,15 +1547,14 @@ export const taskPollingMethods = {
background_command_notice: !!data?.background_command_notice
});
const isAutoUserMessage = isSystemAutoUserMessagePayload(data);
const isRuntimeModeNotice = isRuntimeModeNoticePayload(data);
const incomingImages = Array.isArray(data?.images) ? data.images : [];
const incomingVideos = Array.isArray(data?.videos) ? data.videos : [];
const incomingMediaRefs = Array.isArray(data?.media_refs)
? data.media_refs
: Array.isArray(data?.mediaRefs)
? data.mediaRefs
: [];
if (!isAutoUserMessage) {
const incomingImages = Array.isArray(data?.images) ? data.images : [];
const incomingVideos = Array.isArray(data?.videos) ? data.videos : [];
const incomingMediaRefs = Array.isArray(data?.media_refs)
? data.media_refs
: Array.isArray(data?.mediaRefs)
? data.mediaRefs
: [];
const last = getOptimisticUserEchoTarget(this.messages || []);
const isLikelyOptimisticEcho = !!(
last &&
@ -1582,13 +1581,16 @@ export const taskPollingMethods = {
this.taskInProgress = true;
this.streamingMessage = false;
this.stopRequested = false;
} else if (isRuntimeModeNotice) {
this.chatAddUserMessage(message, [], [], [], 'notify');
} else {
debugLog('[TaskPolling] 跳过自动代发 user_message 渲染', {
messagePreview: message.slice(0, 80),
data
});
// 运行期/空闲期由系统注入的 user 消息runtime_guidance / sub_agent / background_command / notify 等)
// 现已统一为前缀 [系统通知|...] 的 user 消息,前端按普通 user 渲染。
this.chatAddUserMessage(
message,
incomingImages,
incomingVideos,
incomingMediaRefs,
resolveUserMessageSource(data)
);
}
if (data?.sub_agent_notice) {
if (typeof data?.has_running_sub_agents === 'boolean') {
@ -1660,7 +1662,7 @@ export const taskPollingMethods = {
this.uiPushToast({
title: '即将重试',
message: `将在 ${retryIn} 秒后重试(第 ${attempt}/${maxAttempts} 次)`,
message: `将在 ${retryIn} 秒后重试(第 ${attempt}/${maxAttempts} 次)\n错误${data?.message || '未知错误'}`,
type: 'info',
duration: Math.max(retryIn, 1) * 1000
});

View File

@ -682,9 +682,6 @@ const filteredMessages = computed(() => {
if (m?.role === 'system') {
return false;
}
if (isSystemAutoUserMessage(m)) {
return false;
}
if (isEmptyAssistantMessage(m)) {
userMDebug('ChatArea.filteredMessages:drop-empty-assistant', {
actions: Array.isArray(m?.actions) ? m.actions.map((a: any) => a?.type) : [],

View File

@ -24,8 +24,7 @@ const RUNNING_ANIMATIONS: Record<string, string> = {
terminal_snapshot: 'terminal-animation',
todo_create: 'file-animation',
todo_update_task: 'file-animation',
create_sub_agent: 'terminal-animation',
wait_sub_agent: 'wait-animation'
create_sub_agent: 'terminal-animation'
};
const RUNNING_STATUS_TEXTS: Record<string, string> = {

View File

@ -70,7 +70,6 @@ export const TOOL_ICON_MAP = Object.freeze({
terminal_snapshot: 'clipboard',
unfocus_file: 'eye',
update_memory: 'brain',
wait_sub_agent: 'bot',
get_sub_agent_status: 'bot',
web_search: 'search',
trigger_easter_egg: 'sparkles',

View File

@ -1062,7 +1062,6 @@ TOOL_FORMATTERS = {
"update_memory": _format_update_memory,
"manage_personalization": _format_manage_personalization,
"create_sub_agent": _format_create_sub_agent,
"wait_sub_agent": _format_wait_sub_agent,
"close_sub_agent": _format_close_sub_agent,
"get_sub_agent_status": _format_get_sub_agent_status,
}