feat(memory): 新增项目记忆系统,整合总体+项目记忆注入
- 新建 .agents/memory/ 项目记忆目录,支持 YAML frontmatter + Markdown - 新增 update_project_memory / recall_project_memory 工具(复用 write_file/read_file) - 新建 memory_system.txt prompt,总体长期记忆 + 项目记忆统一注入 - 记忆从 workspace_system.txt 中移出,独立为 memory_prompt 注入 - 重排 system prompt 注入顺序:主→权限→环境→对话→个性化→工作区→AGENTS.md→skills→记忆→禁用 - 压缩后选择性 invalidate 4 个 frozen keys(skills/workspace/personalization/memory) - 前端:notebook/notebook-pen 图标、动画、状态文案、展开内容渲染 - 优化三个记忆工具的 description 和 memory_system prompt,引导模型主动调用
This commit is contained in:
parent
5d2da692b0
commit
820ec69354
@ -225,6 +225,79 @@ class MainTerminalContextMixin:
|
||||
# 构建上下文
|
||||
return self.context_manager.build_main_context(memory)
|
||||
|
||||
def _scan_project_memories(self):
|
||||
"""扫描 .agents/memory/*.md 并解析 frontmatter 中的 name 和 description"""
|
||||
try:
|
||||
memory_dir = Path(self.project_path) / ".agents" / "memory"
|
||||
except Exception:
|
||||
return []
|
||||
if not memory_dir.exists() or not memory_dir.is_dir():
|
||||
return []
|
||||
results = []
|
||||
for md_file in sorted(memory_dir.glob("*.md")):
|
||||
try:
|
||||
text = md_file.read_text(encoding="utf-8")
|
||||
name = None
|
||||
description = None
|
||||
if text.startswith("---"):
|
||||
end = text.find("---", 3)
|
||||
if end > 0:
|
||||
frontmatter = text[3:end]
|
||||
for line in frontmatter.strip().split("\n"):
|
||||
line_stripped = line.strip()
|
||||
if line_stripped.startswith("name:"):
|
||||
name = line_stripped.split(":", 1)[1].strip()
|
||||
elif line_stripped.startswith("description:"):
|
||||
description = line_stripped.split(":", 1)[1].strip()
|
||||
results.append({
|
||||
"file": md_file.name,
|
||||
"name": name or md_file.stem,
|
||||
"description": description or "",
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
return results
|
||||
|
||||
def _build_memory_system_content(self) -> str:
|
||||
"""构建记忆系统的 prompt 内容"""
|
||||
template = self.load_prompt("memory_system").strip()
|
||||
if not template:
|
||||
return ""
|
||||
|
||||
try:
|
||||
global_memory = self.memory_manager.read_main_memory()
|
||||
global_memory_text = global_memory.strip() if global_memory else ""
|
||||
except Exception:
|
||||
global_memory_text = ""
|
||||
|
||||
project_memories = self._scan_project_memories()
|
||||
if project_memories:
|
||||
lines = []
|
||||
for m in project_memories:
|
||||
desc = m.get("description", "")
|
||||
if desc:
|
||||
lines.append(f".agents/memory/{m['file']}:{desc}")
|
||||
else:
|
||||
lines.append(f".agents/memory/{m['file']}")
|
||||
project_memory_list = "\n".join(lines)
|
||||
else:
|
||||
project_memory_list = ""
|
||||
|
||||
result = template
|
||||
if global_memory_text:
|
||||
result = result.replace("{global_memory}", global_memory_text)
|
||||
result = result.replace("[global_memory_empty]", "").replace("[/global_memory_empty]", "")
|
||||
else:
|
||||
result = result.replace("{global_memory}", "")
|
||||
|
||||
if project_memory_list:
|
||||
result = result.replace("{project_memory_list}", project_memory_list)
|
||||
result = result.replace("[project_memory_empty]", "").replace("[/project_memory_empty]", "")
|
||||
else:
|
||||
result = result.replace("{project_memory_list}", "")
|
||||
|
||||
return result.strip()
|
||||
|
||||
def _build_recent_conversations_message(self, limit: int = 10) -> Optional[str]:
|
||||
"""构建最近对话提示(仅当前工作区)。"""
|
||||
try:
|
||||
@ -318,7 +391,6 @@ class MainTerminalContextMixin:
|
||||
container_memory=container_memory,
|
||||
project_storage=project_storage,
|
||||
file_tree=context["project_info"]["file_tree"],
|
||||
memory=context["memory"],
|
||||
current_time=datetime.now().strftime("%Y-%m-%d %H"),
|
||||
model_description=prompt_replacements.get("model_description", "")
|
||||
)
|
||||
@ -334,31 +406,26 @@ class MainTerminalContextMixin:
|
||||
|
||||
personalization_config = getattr(self.context_manager, "custom_personalization_config", None) or load_personalization_config(self.data_dir)
|
||||
shallow_replace_enabled = bool(personalization_config.get("auto_shallow_compress_enabled", False)) if isinstance(personalization_config, dict) else False
|
||||
skills_catalog = get_skills_catalog(private_dir=infer_private_skills_dir(self.data_dir))
|
||||
enabled_skills = merge_enabled_skills(
|
||||
personalization_config.get("enabled_skills") if isinstance(personalization_config, dict) else None,
|
||||
skills_catalog,
|
||||
personalization_config.get("skills_catalog_snapshot") if isinstance(personalization_config, dict) else None,
|
||||
)
|
||||
def _build_skills_system_prompt() -> str:
|
||||
skills_template = self.load_prompt("skills_system").strip()
|
||||
skills_list = build_skills_list(skills_catalog, enabled_skills)
|
||||
return build_skills_prompt(skills_template, skills_list) or ""
|
||||
|
||||
skills_prompt = self._get_or_init_frozen_prompt(
|
||||
"frozen_skills_prompt",
|
||||
_build_skills_system_prompt,
|
||||
)
|
||||
if skills_prompt:
|
||||
messages.append({"role": "system", "content": skills_prompt})
|
||||
# 顺序:主prompt → 权限模式 → 执行环境 → 最近对话 → 个性化配置 → 工作区信息 → AGENTS.md → skills → 记忆 → 自定义 → 禁用提示
|
||||
|
||||
workspace_system = self._get_or_init_frozen_prompt(
|
||||
"frozen_workspace_prompt",
|
||||
lambda: self.context_manager._build_workspace_system_message(context) or "",
|
||||
# 权限模式
|
||||
permission_mode_message = self._get_or_init_frozen_mode_prompt(
|
||||
"frozen_permission_prompt",
|
||||
self._build_permission_mode_message,
|
||||
)
|
||||
if workspace_system:
|
||||
messages.append({"role": "system", "content": workspace_system})
|
||||
if permission_mode_message:
|
||||
messages.append({"role": "system", "content": permission_mode_message})
|
||||
|
||||
# 执行环境
|
||||
execution_mode_message = self._get_or_init_frozen_mode_prompt(
|
||||
"frozen_execution_prompt",
|
||||
self._build_execution_mode_message,
|
||||
)
|
||||
if execution_mode_message:
|
||||
messages.append({"role": "system", "content": execution_mode_message})
|
||||
|
||||
# 最近对话
|
||||
def _build_recent_conversations_prompt() -> str:
|
||||
recent_conversations_enabled = (
|
||||
bool(personalization_config.get("recent_conversations_prompt_enabled", False))
|
||||
@ -389,21 +456,7 @@ class MainTerminalContextMixin:
|
||||
if recent_conversations_prompt:
|
||||
messages.append({"role": "system", "content": recent_conversations_prompt})
|
||||
|
||||
# 注入权限模式说明(根据当前模式动态生成)
|
||||
permission_mode_message = self._get_or_init_frozen_mode_prompt(
|
||||
"frozen_permission_prompt",
|
||||
self._build_permission_mode_message,
|
||||
)
|
||||
if permission_mode_message:
|
||||
messages.append({"role": "system", "content": permission_mode_message})
|
||||
execution_mode_message = self._get_or_init_frozen_mode_prompt(
|
||||
"frozen_execution_prompt",
|
||||
self._build_execution_mode_message,
|
||||
)
|
||||
if execution_mode_message:
|
||||
messages.append({"role": "system", "content": execution_mode_message})
|
||||
|
||||
# 支持按对话覆盖的个性化配置
|
||||
# 个性化配置
|
||||
def _build_personalization_system_prompt() -> str:
|
||||
personalization_block = build_personalization_prompt(personalization_config, include_header=False)
|
||||
if not personalization_block:
|
||||
@ -422,7 +475,15 @@ class MainTerminalContextMixin:
|
||||
if personalization_text:
|
||||
messages.append({"role": "system", "content": personalization_text})
|
||||
|
||||
# AGENTS.md 自动注入
|
||||
# 工作区信息
|
||||
workspace_system = self._get_or_init_frozen_prompt(
|
||||
"frozen_workspace_prompt",
|
||||
lambda: self.context_manager._build_workspace_system_message(context) or "",
|
||||
)
|
||||
if workspace_system:
|
||||
messages.append({"role": "system", "content": workspace_system})
|
||||
|
||||
# AGENTS.md
|
||||
def _build_agents_md_prompt() -> str:
|
||||
agents_md_inject_enabled = bool(personalization_config.get("agents_md_auto_inject", False)) if isinstance(personalization_config, dict) else False
|
||||
if not agents_md_inject_enabled:
|
||||
@ -442,8 +503,37 @@ class MainTerminalContextMixin:
|
||||
if agents_md_text:
|
||||
messages.append({"role": "system", "content": agents_md_text})
|
||||
|
||||
# 支持按对话覆盖的自定义 system prompt(API 用途)。
|
||||
# 放在最后一个 system 消息位置,确保优先级最高,便于业务场景强约束。
|
||||
# skills 列表
|
||||
skills_catalog = get_skills_catalog(private_dir=infer_private_skills_dir(self.data_dir))
|
||||
enabled_skills = merge_enabled_skills(
|
||||
personalization_config.get("enabled_skills") if isinstance(personalization_config, dict) else None,
|
||||
skills_catalog,
|
||||
personalization_config.get("skills_catalog_snapshot") if isinstance(personalization_config, dict) else None,
|
||||
)
|
||||
def _build_skills_system_prompt() -> str:
|
||||
skills_template = self.load_prompt("skills_system").strip()
|
||||
skills_list = build_skills_list(skills_catalog, enabled_skills)
|
||||
return build_skills_prompt(skills_template, skills_list) or ""
|
||||
|
||||
skills_prompt = self._get_or_init_frozen_prompt(
|
||||
"frozen_skills_prompt",
|
||||
_build_skills_system_prompt,
|
||||
)
|
||||
if skills_prompt:
|
||||
messages.append({"role": "system", "content": skills_prompt})
|
||||
|
||||
# 记忆系统(总体长期记忆 + 项目记忆)
|
||||
def _build_memory_system_prompt() -> str:
|
||||
return self._build_memory_system_content()
|
||||
|
||||
memory_prompt = self._get_or_init_frozen_prompt(
|
||||
"frozen_memory_prompt",
|
||||
_build_memory_system_prompt,
|
||||
)
|
||||
if memory_prompt:
|
||||
messages.append({"role": "system", "content": memory_prompt})
|
||||
|
||||
# API 自定义 system prompt
|
||||
custom_system_prompt = self._get_or_init_frozen_prompt(
|
||||
"frozen_custom_system_prompt",
|
||||
lambda: (
|
||||
@ -455,7 +545,7 @@ class MainTerminalContextMixin:
|
||||
if custom_system_prompt:
|
||||
messages.append({"role": "system", "content": custom_system_prompt})
|
||||
|
||||
# 禁用工具提示也作为“开头 system prompt”注入,便于后续统一合并。
|
||||
# 禁用工具提示
|
||||
disabled_notice = self._get_or_init_frozen_prompt(
|
||||
"frozen_disabled_tools_prompt",
|
||||
lambda: self._format_disabled_tool_notice() or "",
|
||||
|
||||
@ -835,7 +835,7 @@ class MainTerminalToolsDefinitionMixin:
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "update_memory",
|
||||
"description": "按条目更新记忆列表(自动编号)。append 追加新条目;replace 用序号替换;delete 用序号删除。应积极记录长期偏好、项目事实、已验证经验、用户明确要求记住的内容;不要记录密钥、隐私或未确认猜测。",
|
||||
"description": "按条目管理总体长期记忆(自动编号,跨项目通用)。append/replace/delete。当用户提到个人信息、偏好、跨项目习惯,或用户主动要求「记住xxx」时,应积极主动地调用本工具。不记录密钥、隐私、未确认猜测。与特定项目绑定的技术约定用 update_project_memory。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": self._inject_intent({
|
||||
@ -847,6 +847,48 @@ class MainTerminalToolsDefinitionMixin:
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "recall_project_memory",
|
||||
"description": "读取指定的项目记忆文件,返回完整内容(含 frontmatter)。项目记忆存储在 .agents/memory/ 目录下。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": self._inject_intent({
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "记忆名称,对应 .agents/memory/{name}.md 文件名"
|
||||
}
|
||||
}),
|
||||
"required": ["name"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "update_project_memory",
|
||||
"description": "创建或覆盖项目记忆文件。当你发现有关当前项目的重要约定/决策/坑、用户表现出对项目的偏好,或用户主动要求「在当前项目里,下次要xxx/记住xxx」时,应积极主动地调用本工具。记忆名称用英文下划线,描述格式为'当xxxx时,应该索引本记忆'。不记录可从代码直接推断的或一次性信息。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": self._inject_intent({
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "记忆名称,同时作为文件名 .agents/memory/{name}.md。建议英文+下划线,如 docker_compose"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "记忆简述,格式为'当xxxx时,应该索引本记忆',用于在 prompt 中作为索引展示(≤100字)"
|
||||
},
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "记忆正文(Markdown),记录具体内容"
|
||||
}
|
||||
}),
|
||||
"required": ["name", "description", "content"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
|
||||
@ -185,6 +185,8 @@ class MainTerminalToolsExecutionMixin:
|
||||
"vlm_analyze",
|
||||
"ocr_image",
|
||||
"update_memory",
|
||||
"recall_project_memory",
|
||||
"update_project_memory",
|
||||
"conversation_search",
|
||||
"conversation_review",
|
||||
"todo_create",
|
||||
@ -535,6 +537,35 @@ class MainTerminalToolsExecutionMixin:
|
||||
result.pop("target_dir", None)
|
||||
return result
|
||||
|
||||
def _handle_update_project_memory(self, name: str, description: str, content: str) -> Dict[str, Any]:
|
||||
"""处理 update_project_memory:写入 .agents/memory/{name}.md"""
|
||||
safe_name = str(name).strip()
|
||||
if not safe_name or "/" in safe_name or "\\" in safe_name:
|
||||
return {"success": False, "error": f"记忆名称不合法: {name}"}
|
||||
|
||||
# 确保 .agents/memory/ 目录存在
|
||||
memory_dir = Path(self.project_path) / ".agents" / "memory"
|
||||
try:
|
||||
memory_dir.mkdir(parents=True, exist_ok=True)
|
||||
except Exception as exc:
|
||||
return {"success": False, "error": f"创建记忆目录失败: {exc}"}
|
||||
|
||||
# 拼接完整文件内容(YAML frontmatter + markdown)
|
||||
full_content = f"---\nname: {safe_name}\ndescription: {description}\n---\n\n{content}\n"
|
||||
file_path = memory_dir / f"{safe_name}.md"
|
||||
|
||||
try:
|
||||
file_path.write_text(full_content, encoding="utf-8")
|
||||
except Exception as exc:
|
||||
return {"success": False, "error": f"写入记忆文件失败: {exc}"}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"memory_name": safe_name,
|
||||
"path": str(file_path),
|
||||
"summary": f"已{'更新' if file_path.exists() else '创建'}项目记忆: {safe_name}",
|
||||
}
|
||||
|
||||
def _mark_skill_read_from_result(self, tool_name: str, arguments: Dict[str, Any], result: Dict[str, Any]) -> None:
|
||||
if tool_name not in {"read_file", "read_skill"}:
|
||||
return
|
||||
@ -1504,6 +1535,26 @@ class MainTerminalToolsExecutionMixin:
|
||||
index=index
|
||||
)
|
||||
|
||||
elif tool_name == "recall_project_memory":
|
||||
name = str(arguments.get("name", "")).strip()
|
||||
if not name:
|
||||
result = {"success": False, "error": "recall_project_memory 需要 name 参数"}
|
||||
else:
|
||||
result = self._handle_recall_project_memory(name)
|
||||
|
||||
elif tool_name == "update_project_memory":
|
||||
name = str(arguments.get("name", "")).strip()
|
||||
description = str(arguments.get("description", "")).strip()
|
||||
content_text = str(arguments.get("content", "")).strip()
|
||||
if not name:
|
||||
result = {"success": False, "error": "update_project_memory 需要 name 参数"}
|
||||
elif not description:
|
||||
result = {"success": False, "error": "update_project_memory 需要 description 参数"}
|
||||
elif not content_text:
|
||||
result = {"success": False, "error": "update_project_memory 需要 content 参数"}
|
||||
else:
|
||||
result = self._handle_update_project_memory(name, description, content_text)
|
||||
|
||||
elif tool_name == "conversation_search":
|
||||
query = str(arguments.get("query") or "").strip()
|
||||
raw_keywords = arguments.get("keywords")
|
||||
|
||||
@ -158,6 +158,21 @@ class MainTerminalToolsReadMixin:
|
||||
result["skill_name"] = skill_name
|
||||
return result
|
||||
|
||||
def _handle_recall_project_memory(self, name: str) -> Dict:
|
||||
"""处理 recall_project_memory:读取 .agents/memory/{name}.md"""
|
||||
safe_name = str(name).strip()
|
||||
if not safe_name or "/" in safe_name or "\\" in safe_name:
|
||||
return {"success": False, "error": f"记忆名称不合法: {name}"}
|
||||
file_path = f".agents/memory/{safe_name}.md"
|
||||
read_args = {
|
||||
"path": file_path,
|
||||
"type": "read",
|
||||
}
|
||||
result = self._handle_read_tool(read_args)
|
||||
if result.get("success"):
|
||||
result["memory_name"] = safe_name
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _clamp_int(value, default, min_value=None, max_value=None):
|
||||
"""将输入转换为整数并限制范围。"""
|
||||
|
||||
14
prompts/memory_system.txt
Normal file
14
prompts/memory_system.txt
Normal file
@ -0,0 +1,14 @@
|
||||
项目记忆系统已启用。总体长期记忆来自用户的全局记录,项目记忆以文件形式存放在 .agents/memory/ 下。
|
||||
|
||||
- 开始涉及项目约定、历史决策或已知问题的任务前,先检查项目记忆索引,如有相关记忆则用 recall_project_memory 读取
|
||||
- 当你发现有关当前项目的重要约定/决策/调试坑、用户表现出对项目的偏好,或用户说「在这个项目里下次/记住」时,用 update_project_memory 主动记录
|
||||
- 当用户提到个人信息、偏好,或说「记住xxx」时,用 update_memory 记录为总体长期记忆
|
||||
- 记忆内容为提示而非事实,执行前应与实际代码验证
|
||||
|
||||
### 总体长期记忆
|
||||
{global_memory}
|
||||
[global_memory_empty]暂无总体长期记忆[/global_memory_empty]
|
||||
|
||||
### 项目记忆(.agents/memory/)
|
||||
{project_memory_list}
|
||||
[project_memory_empty]暂无项目记忆[/project_memory_empty]
|
||||
@ -9,5 +9,3 @@
|
||||
```
|
||||
{file_tree}
|
||||
```
|
||||
|
||||
- **长期记忆**:{memory}
|
||||
|
||||
@ -457,6 +457,13 @@ async def run_deep_compression(
|
||||
)
|
||||
|
||||
# 更新对话 metadata:压缩记录 + 清理压缩状态标记(同一对话,无切换)。
|
||||
# 同时清除需要重建的 frozen prompt 缓存,使压缩后下一次请求自动重新加载动态内容。
|
||||
REBUILD_FROZEN_KEYS = (
|
||||
"frozen_skills_prompt",
|
||||
"frozen_workspace_prompt",
|
||||
"frozen_personalization_prompt",
|
||||
"frozen_memory_prompt",
|
||||
)
|
||||
meta_updates = {
|
||||
"compression_count": target_count,
|
||||
"deep_compression_records": all_records,
|
||||
@ -469,10 +476,14 @@ async def run_deep_compression(
|
||||
"compression_resume_payload": None,
|
||||
"is_ultra_long_conversation": False,
|
||||
}
|
||||
for frozen_key in REBUILD_FROZEN_KEYS:
|
||||
meta_updates[frozen_key] = None
|
||||
cm.conversation_manager.update_conversation_metadata(conversation_id, meta_updates)
|
||||
# 同步内存中的 metadata,保证后续在同一对话内的判断读到最新值。
|
||||
# 同步内存中的 metadata,清除 frozen 缓存
|
||||
try:
|
||||
if getattr(cm, "current_conversation_id", None) == conversation_id and isinstance(cm.conversation_metadata, dict):
|
||||
for frozen_key in REBUILD_FROZEN_KEYS:
|
||||
cm.conversation_metadata.pop(frozen_key, None)
|
||||
cm.conversation_metadata.update(meta_updates)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
1
static/icons/notebook-pen.svg
Normal file
1
static/icons/notebook-pen.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity:1;"><path d="M13.4 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.4M2 6h4m-4 4h4m-4 4h4m-4 4h4"/><path d="M21.378 5.626a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"/></svg>
|
||||
|
After Width: | Height: | Size: 440 B |
1
static/icons/notebook.svg
Normal file
1
static/icons/notebook.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity:1;"><path d="M2 6h4m-4 4h4m-4 4h4m-4 4h4"/><rect width="16" height="20" x="4" y="2" rx="2"/><path d="M16 2v20"/></svg>
|
||||
|
After Width: | Height: | Size: 309 B |
@ -100,6 +100,10 @@ export function renderEnhancedToolResult(
|
||||
// 记忆类
|
||||
else if (name === 'update_memory') {
|
||||
return renderUpdateMemory(result, args);
|
||||
} else if (name === 'recall_project_memory') {
|
||||
return renderRecallProjectMemory(result, args);
|
||||
} else if (name === 'update_project_memory') {
|
||||
return renderUpdateProjectMemory(result, args);
|
||||
} else if (name === 'conversation_search') {
|
||||
return renderConversationSearch(result, args);
|
||||
} else if (name === 'conversation_review') {
|
||||
@ -704,6 +708,54 @@ function renderUpdateMemory(result: any, args: any): string {
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderRecallProjectMemory(result: any, args: any): string {
|
||||
const name = args.name || result.memory_name || '';
|
||||
const status = formatToolStatusLabel(result, '✓ 已读取');
|
||||
const content = result.content || result.text || '';
|
||||
|
||||
let html = '<div class="tool-result-meta">';
|
||||
html += `<div><strong>状态:</strong>${status}</div>`;
|
||||
html += `<div><strong>记忆:</strong>${escapeHtml(name)}</div>`;
|
||||
html += '</div>';
|
||||
|
||||
if (result.success && content) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += `<pre>${escapeHtml(content)}</pre>`;
|
||||
html += '</div>';
|
||||
} else if (!result.success && result.error) {
|
||||
html += `<div class="tool-result-error">${escapeHtml(String(result.error))}</div>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderUpdateProjectMemory(result: any, args: any): string {
|
||||
const name = args.name || result.memory_name || '';
|
||||
const description = args.description || '';
|
||||
const content = args.content || '';
|
||||
const status = formatToolStatusLabel(result, '✓ 已更新');
|
||||
|
||||
let html = '<div class="tool-result-meta">';
|
||||
html += `<div><strong>状态:</strong>${status}</div>`;
|
||||
html += `<div><strong>记忆:</strong>${escapeHtml(name)}</div>`;
|
||||
if (description) {
|
||||
html += `<div><strong>描述:</strong>${escapeHtml(description)}</div>`;
|
||||
}
|
||||
if (!result.success && result.error) {
|
||||
html += `<div><strong>错误:</strong>${escapeHtml(String(result.error))}</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
if (result.success && content) {
|
||||
html += '<div class="tool-result-content scrollable">';
|
||||
html += '<div class="content-label">记忆内容:</div>';
|
||||
html += `<pre>${escapeHtml(content)}</pre>`;
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderConversationSearch(result: any, args: any): string {
|
||||
const results = Array.isArray(result?.results) ? result.results : [];
|
||||
const keywords = Array.isArray(result?.keywords)
|
||||
|
||||
@ -113,6 +113,8 @@ const TOOL_SCENE_MAP: Record<string, string> = {
|
||||
terminal_snapshot: 'terminalSnapshot',
|
||||
sleep: 'terminalSleep',
|
||||
update_memory: 'memoryUpdate',
|
||||
recall_project_memory: 'reader',
|
||||
update_project_memory: 'memoryUpdate',
|
||||
todo_create: 'todoCreate',
|
||||
todo_update_task: 'todoUpdate',
|
||||
todo_delete_task: 'todoDelete',
|
||||
|
||||
@ -18,6 +18,8 @@ const RUNNING_ANIMATIONS: Record<string, string> = {
|
||||
vlm_analyze: 'file-animation',
|
||||
run_command: 'terminal-animation',
|
||||
update_memory: 'memory-animation',
|
||||
recall_project_memory: 'read-animation',
|
||||
update_project_memory: 'memory-animation',
|
||||
sleep: 'wait-animation',
|
||||
terminal_session: 'terminal-animation',
|
||||
terminal_input: 'terminal-animation',
|
||||
@ -41,6 +43,8 @@ const RUNNING_STATUS_TEXTS: Record<string, string> = {
|
||||
save_webpage: '正在保存网页...',
|
||||
run_command: '调用 run_command',
|
||||
update_memory: '正在更新记忆...',
|
||||
recall_project_memory: '正在回顾项目记忆...',
|
||||
update_project_memory: '正在更新项目记忆...',
|
||||
terminal_session: '正在管理终端会话...',
|
||||
terminal_input: '调用 terminal_input',
|
||||
terminal_snapshot: '正在获取终端快照...',
|
||||
@ -63,6 +67,8 @@ const COMPLETED_STATUS_TEXTS: Record<string, string> = {
|
||||
vlm_analyze: '图片解析完成',
|
||||
run_command: '命令执行完成',
|
||||
update_memory: '记忆更新成功',
|
||||
recall_project_memory: '项目记忆已读取',
|
||||
update_project_memory: '项目记忆已更新',
|
||||
terminal_session: '终端操作完成',
|
||||
terminal_input: '终端输入完成',
|
||||
terminal_snapshot: '终端快照已返回',
|
||||
|
||||
@ -29,6 +29,8 @@ export const ICONS = Object.freeze({
|
||||
mcpLogo: '/static/icons/mcp-logo.svg',
|
||||
monitor: '/static/icons/monitor.svg',
|
||||
navigation: '/static/icons/navigation.svg',
|
||||
notebook: '/static/icons/notebook.svg',
|
||||
notebookPen: '/static/icons/notebook-pen.svg',
|
||||
octagon: '/static/icons/octagon.svg',
|
||||
pencil: '/static/icons/pencil.svg',
|
||||
python: '/static/icons/python.svg',
|
||||
@ -77,6 +79,8 @@ export const TOOL_ICON_MAP = Object.freeze({
|
||||
terminal_snapshot: 'clipboard',
|
||||
unfocus_file: 'eye',
|
||||
update_memory: 'brain',
|
||||
recall_project_memory: 'notebook',
|
||||
update_project_memory: 'notebookPen',
|
||||
conversation_search: 'search',
|
||||
conversation_review: 'book',
|
||||
get_sub_agent_status: 'bot',
|
||||
|
||||
@ -2331,7 +2331,6 @@ class ContextManager:
|
||||
if context["project_info"].get("file_tree")
|
||||
else ""
|
||||
),
|
||||
memory=context["memory"],
|
||||
)
|
||||
|
||||
return content
|
||||
|
||||
Loading…
Reference in New Issue
Block a user