From d337140577acfd1c2f123a50182be19ef0af968e Mon Sep 17 00:00:00 2001 From: zhayujie Date: Sun, 1 Feb 2026 17:46:43 +0800 Subject: [PATCH] feat: optimize editing tools --- agent/prompt/builder.py | 10 +++--- agent/tools/edit/edit.py | 67 +++++++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/agent/prompt/builder.py b/agent/prompt/builder.py index 44a4eee..d80af06 100644 --- a/agent/prompt/builder.py +++ b/agent/prompt/builder.py @@ -305,8 +305,8 @@ def _build_memory_section(memory_manager: Any, tools: Optional[List[Any]], langu "", "在回答关于以前的工作、决定、日期、人物、偏好或待办事项的任何问题之前:", "", - "1. 不确定信息位置 → 先用 `memory_search` 通过关键词和语义检索相关内容", - "2. 已知文件和大致位置 → 直接用 `memory_get` 读取相应的行", + "1. 不确定记忆文件位置 → 先用 `memory_search` 通过关键词和语义检索相关内容", + "2. 已知文件位置 → 直接用 `memory_get` 读取相应的行", "3. search 无结果 → 尝试用 `memory_get` 读取最近两天的记忆文件", "", "**记忆文件结构**:", @@ -317,10 +317,10 @@ def _build_memory_section(memory_manager: Any, tools: Optional[List[Any]], langu "- 自然使用记忆,就像你本来就知道; 不用刻意提起或列举记忆,除非用户提起相关内容", "", "**写入记忆的正确方式**:", - "- 追加到现有文件末尾 → 用 `read` 读取文件最后几行(offset=-10),然后用 `edit` 追加", - " 例: read(path=memory/2026-02-01.md, offset=-10) → 看到最后内容 → edit(oldText=最后几行完整文本, newText=最后几行+新内容)", + "- 追加到现有文件末尾 → 用 `edit` 工具,oldText 留空", + " 例: edit(path=memory/2026-02-01.md, oldText=\"\", newText=\"\\n## 新内容\\n...\")", + "- 修改文件中的某段文字 → 用 `edit` 工具,oldText 填写要替换的文本", "- 创建新文件 → 用 `write`", - "- ⚠️ 不要用 `memory_get` 读取后再 `edit`,因为会截断长文本", "", ] diff --git a/agent/tools/edit/edit.py b/agent/tools/edit/edit.py index b07c8f1..e17e624 100644 --- a/agent/tools/edit/edit.py +++ b/agent/tools/edit/edit.py @@ -22,7 +22,7 @@ class Edit(BaseTool): """Tool for precise file editing""" name: str = "edit" - description: str = "Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits." + description: str = "Edit a file by replacing exact text, or append to end if oldText is empty. For append: use empty oldText. For replace: oldText must match exactly (including whitespace)." params: dict = { "type": "object", @@ -33,7 +33,7 @@ class Edit(BaseTool): }, "oldText": { "type": "string", - "description": "Exact text to find and replace (must match exactly, cannot be empty). To append to end of file, include the last few lines as oldText." + "description": "Text to find and replace. Use empty string to append to end of file. For replacement: must match exactly including whitespace." }, "newText": { "type": "string", @@ -89,34 +89,45 @@ class Edit(BaseTool): normalized_old_text = normalize_to_lf(old_text) normalized_new_text = normalize_to_lf(new_text) - # Use fuzzy matching to find old text (try exact match first, then fuzzy match) - match_result = fuzzy_find_text(normalized_content, normalized_old_text) - - if not match_result.found: - return ToolResult.fail( - f"Error: Could not find the exact text in {path}. " - "The old text must match exactly including all whitespace and newlines." + # Special case: empty oldText means append to end of file + if not old_text or not old_text.strip(): + # Append mode: add newText to the end + # Add newline before newText if file doesn't end with one + if normalized_content and not normalized_content.endswith('\n'): + new_content = normalized_content + '\n' + normalized_new_text + else: + new_content = normalized_content + normalized_new_text + base_content = normalized_content # For verification + else: + # Normal edit mode: find and replace + # Use fuzzy matching to find old text (try exact match first, then fuzzy match) + match_result = fuzzy_find_text(normalized_content, normalized_old_text) + + if not match_result.found: + return ToolResult.fail( + f"Error: Could not find the exact text in {path}. " + "The old text must match exactly including all whitespace and newlines." + ) + + # Calculate occurrence count (use fuzzy normalized content for consistency) + fuzzy_content = normalize_for_fuzzy_match(normalized_content) + fuzzy_old_text = normalize_for_fuzzy_match(normalized_old_text) + occurrences = fuzzy_content.count(fuzzy_old_text) + + if occurrences > 1: + return ToolResult.fail( + f"Error: Found {occurrences} occurrences of the text in {path}. " + "The text must be unique. Please provide more context to make it unique." + ) + + # Execute replacement (use matched text position) + base_content = match_result.content_for_replacement + new_content = ( + base_content[:match_result.index] + + normalized_new_text + + base_content[match_result.index + match_result.match_length:] ) - # Calculate occurrence count (use fuzzy normalized content for consistency) - fuzzy_content = normalize_for_fuzzy_match(normalized_content) - fuzzy_old_text = normalize_for_fuzzy_match(normalized_old_text) - occurrences = fuzzy_content.count(fuzzy_old_text) - - if occurrences > 1: - return ToolResult.fail( - f"Error: Found {occurrences} occurrences of the text in {path}. " - "The text must be unique. Please provide more context to make it unique." - ) - - # Execute replacement (use matched text position) - base_content = match_result.content_for_replacement - new_content = ( - base_content[:match_result.index] + - normalized_new_text + - base_content[match_result.index + match_result.match_length:] - ) - # Verify replacement actually changed content if base_content == new_content: return ToolResult.fail(