From 12c7a4bdd9f151c948587d085752597232897382 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Tue, 17 Mar 2026 22:43:51 +0800 Subject: [PATCH] fix: remove legacy file edit tags --- CLAUDE.md | 19 +- README.md | 2 +- core/main_terminal_parts/context.py | 3 + core/tool_config.py | 2 - core/web_terminal.py | 7 - modules/file_manager.py | 2 +- modules/personalization_manager.py | 7 + scripts/api_tool_role_experiment.py | 25 +- server/app_legacy.py | 8 +- server/chat_flow_helpers.py | 8 +- server/chat_flow_pending_writes.py | 546 ------------------ server/chat_flow_stream_loop.py | 248 +------- server/chat_flow_task_main.py | 242 +------- server/state.py | 2 +- static/src/app.ts | 2 - static/src/app/methods/history.ts | 46 +- .../components/chat/actions/ToolAction.vue | 65 --- .../components/chat/actions/toolRenderers.ts | 77 --- .../personalization/PersonalizationDrawer.vue | 25 +- static/src/stores/chat.ts | 20 - static/src/stores/monitor.ts | 2 - static/src/stores/personalization.ts | 3 + static/src/utils/chatDisplay.ts | 6 - static/src/utils/icons.ts | 2 - utils/api_client.py | 32 - 25 files changed, 107 insertions(+), 1294 deletions(-) delete mode 100644 server/chat_flow_pending_writes.py diff --git a/CLAUDE.md b/CLAUDE.md index 1675512..7aa28c6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -96,24 +96,9 @@ tail -f logs/container_stats.log ## Important Implementation Details -### File Operations with Streaming +### File Operations -**append_to_file** 和 **modify_file** 使用特殊的流式输出格式: - -``` -<<>> -文件内容... -<<>> - -<<>> -[replace:1] -<>原文内容<> -<>新内容<> -[/replace] -<<>> -``` - -处理逻辑在 `web_server.py` 的 `handle_task_with_sender` 中,通过检测标记并在流式输出中即时执行。 +文件写入统一使用 `write_file`/`edit_file` 工具完成:`write_file` 负责覆盖或追加内容,`edit_file` 负责精确字符串替换,不再依赖历史版本的流式标签标记。 ### Container Architecture diff --git a/README.md b/README.md index 1c0bbdb..d9e2421 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ npm run dev | --- | --- | | `/help` | CLI 指令列表 | | `/status` | 查看系统/容器资源状态 | -| `read_file` / `modify_file` | 读写项目文件(自动通过容器代理) | +| `read_file` / `write_file` / `edit_file` | 读写项目文件(自动通过容器代理) | | `terminal_session` / `terminal_input` | 管理多终端会话 | | `run_command` / `run_python` | 工具容器快速执行命令/Python 代码 | | `todo_*`, `update_memory` | 维护待办与长期记忆 | diff --git a/core/main_terminal_parts/context.py b/core/main_terminal_parts/context.py index fa3e8fa..d4ed3dc 100644 --- a/core/main_terminal_parts/context.py +++ b/core/main_terminal_parts/context.py @@ -258,6 +258,9 @@ class MainTerminalContextMixin: self.context_manager._build_content_with_images(conv["content"], images, videos) if (images or videos) else conv["content"] ) + # 调试:记录所有 system 消息 + if conv["role"] == "system": + logger.info(f"[DEBUG build_messages] 添加 system 消息: content前50字={conv['content'][:50]}") messages.append({ "role": conv["role"], "content": content_payload diff --git a/core/tool_config.py b/core/tool_config.py index e507d62..a8f2a46 100644 --- a/core/tool_config.py +++ b/core/tool_config.py @@ -33,8 +33,6 @@ TOOL_CATEGORIES: Dict[str, ToolCategory] = { "create_file", "write_file", "edit_file", - "append_to_file", - "modify_file", "delete_file", "rename_file", "create_folder", diff --git a/core/web_terminal.py b/core/web_terminal.py index 76daf3a..af5d22f 100644 --- a/core/web_terminal.py +++ b/core/web_terminal.py @@ -395,13 +395,6 @@ class WebTerminal(MainTerminal): 'status': 'reading', 'detail': f'读取文件({read_type}): {arguments.get("path", "未知路径")}' }) - elif tool_name == "modify_file": - path = arguments.get("path", "未知路径") - self.broadcast('tool_status', { - 'tool': tool_name, - 'status': 'modifying', - 'detail': f'准备修改文件: {path}' - }) elif tool_name == "delete_file": self.broadcast('tool_status', { 'tool': tool_name, diff --git a/modules/file_manager.py b/modules/file_manager.py index d0bab80..c7c8a72 100644 --- a/modules/file_manager.py +++ b/modules/file_manager.py @@ -1076,7 +1076,7 @@ class FileManager: if old_text is None or new_text is None: block_result["status"] = "error" block_result["reason"] = "缺少 OLD 或 NEW 内容" - block_result["hint"] = "请确保粘贴的补丁包含成对的 <<>> / <<>> 标记。" + block_result["hint"] = "请确保补丁包含成对的 OLD/NEW 段落。" failed_details.append({"index": index, "reason": "缺少 OLD/NEW 标记"}) results.append(block_result) continue diff --git a/modules/personalization_manager.py b/modules/personalization_manager.py index 25bd697..14a3340 100644 --- a/modules/personalization_manager.py +++ b/modules/personalization_manager.py @@ -39,6 +39,7 @@ DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = { "default_run_mode": None, "auto_generate_title": True, "tool_intent_enabled": True, + "skill_hints_enabled": False, # Skill 提示系统开关(默认关闭) "default_model": "kimi-k2.5", "image_compression": "original", # original / 1080p / 720p / 540p "silent_tool_disable": False, # 禁用工具时不向模型插入提示 @@ -145,6 +146,12 @@ def sanitize_personalization_payload( else: base["tool_intent_enabled"] = bool(base.get("tool_intent_enabled")) + # Skill 提示系统开关 + if "skill_hints_enabled" in data: + base["skill_hints_enabled"] = bool(data.get("skill_hints_enabled")) + else: + base["skill_hints_enabled"] = bool(base.get("skill_hints_enabled")) + if "disabled_tool_categories" in data: base["disabled_tool_categories"] = _sanitize_tool_categories(data.get("disabled_tool_categories"), allowed_tool_categories) else: diff --git a/scripts/api_tool_role_experiment.py b/scripts/api_tool_role_experiment.py index 87a1cc8..ca018fc 100644 --- a/scripts/api_tool_role_experiment.py +++ b/scripts/api_tool_role_experiment.py @@ -48,40 +48,41 @@ def load_conversation_messages(path: Path) -> List[Dict[str, Any]]: def minimal_tool_definitions() -> List[Dict[str, Any]]: - """返回涵盖 append/modify 的最小工具定义集合。""" + """返回涵盖 write/edit 的最小工具定义集合。""" return [ { "type": "function", "function": { - "name": "append_to_file", + "name": "write_file", "description": ( - "准备向文件追加大段内容。调用后系统会发放 <<>>…<<>> " - "格式的写入窗口,AI 必须在窗口内一次性输出需要追加的全部内容。" + "将内容写入本地文件系统;append 为 True 时追加到末尾,False 时覆盖原文件。" ), "parameters": { "type": "object", "properties": { - "path": {"type": "string", "description": "目标文件的相对路径"}, - "reason": {"type": "string", "description": "为什么需要追加(可选)"} + "file_path": {"type": "string", "description": "目标文件的相对路径"}, + "content": {"type": "string", "description": "要写入的内容"}, + "append": {"type": "boolean", "description": "是否追加到末尾", "default": False} }, - "required": ["path"] + "required": ["file_path", "content"] } } }, { "type": "function", "function": { - "name": "modify_file", + "name": "edit_file", "description": ( - "准备替换文件中的指定内容。模型必须在 <<>>…<<>> " - "结构内输出若干 [replace:n] 补丁块,每块包含 <> 原文 和 <> 新内容。" + "在文件中执行精确字符串替换,要求 old_string 与文件内容精确匹配。" ), "parameters": { "type": "object", "properties": { - "path": {"type": "string", "description": "目标文件的相对路径"} + "file_path": {"type": "string", "description": "目标文件的相对路径"}, + "old_string": {"type": "string", "description": "需要替换的原文"}, + "new_string": {"type": "string", "description": "替换后的新内容"} }, - "required": ["path"] + "required": ["file_path", "old_string", "new_string"] } } } diff --git a/server/app_legacy.py b/server/app_legacy.py index b3fdffa..01c340d 100644 --- a/server/app_legacy.py +++ b/server/app_legacy.py @@ -1173,16 +1173,10 @@ def apply_thinking_schedule(terminal: WebTerminal): client.skip_thinking_next_call = False return state = get_thinking_state(terminal) - awaiting_writes = getattr(terminal, "pending_append_request", None) or getattr(terminal, "pending_modify_request", None) - if awaiting_writes: - client.skip_thinking_next_call = True - state["suppress_next"] = False - debug_log("[Thinking] 检测到写入窗口请求,跳过思考。") - return if state.get("suppress_next"): client.skip_thinking_next_call = True state["suppress_next"] = False - debug_log("[Thinking] 由于写入窗口,下一次跳过思考。") + debug_log("[Thinking] 已标记跳过思考模式。") return if state.get("force_next"): client.force_thinking_next_call = True diff --git a/server/chat_flow_helpers.py b/server/chat_flow_helpers.py index 9f8b621..815c124 100644 --- a/server/chat_flow_helpers.py +++ b/server/chat_flow_helpers.py @@ -137,16 +137,10 @@ def apply_thinking_schedule(terminal: WebTerminal, default_interval: int, debug_ return state = get_thinking_state(terminal) - awaiting_writes = getattr(terminal, "pending_append_request", None) or getattr(terminal, "pending_modify_request", None) - if awaiting_writes: - client.skip_thinking_next_call = True - state["suppress_next"] = False - debug_logger("[Thinking] 检测到写入窗口请求,跳过思考。") - return if state.get("suppress_next"): client.skip_thinking_next_call = True state["suppress_next"] = False - debug_logger("[Thinking] 由于写入窗口,下一次跳过思考。") + debug_logger("[Thinking] 已标记跳过思考模式。") return if state.get("force_next"): client.force_thinking_next_call = True diff --git a/server/chat_flow_pending_writes.py b/server/chat_flow_pending_writes.py deleted file mode 100644 index c131f83..0000000 --- a/server/chat_flow_pending_writes.py +++ /dev/null @@ -1,546 +0,0 @@ -from __future__ import annotations - -import json -import re -from typing import Dict, List, Optional - - -async def finalize_pending_append(*, pending_append, append_probe_buffer: str, response_text: str, stream_completed: bool, finish_reason: str = None, web_terminal, sender, debug_log): - """在流式输出结束后处理追加写入""" - - result = { - "handled": False, - "success": False, - "summary": None, - "summary_message": None, - "tool_content": None, - "tool_call_id": None, - "path": None, - "forced": False, - "error": None, - "assistant_content": response_text, - "lines": 0, - "bytes": 0, - "finish_reason": finish_reason, - "appended_content": "", - "assistant_metadata": None - } - - if not pending_append: - return result, pending_append, append_probe_buffer - - state = pending_append - path = state.get("path") - tool_call_id = state.get("tool_call_id") - buffer = state.get("buffer", "") - start_marker = state.get("start_marker") - end_marker = state.get("end_marker") - start_idx = state.get("content_start") - end_idx = state.get("end_index") - - display_id = state.get("display_id") - - result.update({ - "handled": True, - "path": path, - "tool_call_id": tool_call_id, - "display_id": display_id - }) - - if path is None or tool_call_id is None: - error_msg = "append_to_file 状态不完整,缺少路径或ID。" - debug_log(error_msg) - result["error"] = error_msg - result["summary_message"] = error_msg - result["tool_content"] = json.dumps({ - "success": False, - "error": error_msg - }, ensure_ascii=False) - if display_id: - sender('update_action', { - 'id': display_id, - 'status': 'failed', - 'preparing_id': tool_call_id, - 'message': error_msg - }) - pending_append = None - return result, pending_append, append_probe_buffer - - if start_idx is None: - error_msg = f"未检测到格式正确的开始标识 {start_marker}。" - debug_log(error_msg) - result["error"] = error_msg - result["summary_message"] = error_msg - result["tool_content"] = json.dumps({ - "success": False, - "path": path, - "error": error_msg - }, ensure_ascii=False) - if display_id: - sender('update_action', { - 'id': display_id, - 'status': 'failed', - 'preparing_id': tool_call_id, - 'message': error_msg - }) - pending_append = None - return result, pending_append, append_probe_buffer - - forced = False - if end_idx is None: - forced = True - # 查找下一个<<<,否则使用整个缓冲结尾 - remaining = buffer[start_idx:] - next_marker = remaining.find("<<<", len(end_marker)) - if next_marker != -1: - end_idx = start_idx + next_marker - else: - end_idx = len(buffer) - - content = buffer[start_idx:end_idx] - if content.startswith('\n'): - content = content[1:] - - if not content: - error_msg = "未检测到需要追加的内容,请严格按照<<>>...<<>>格式输出。" - debug_log(error_msg) - result["error"] = error_msg - result["forced"] = forced - result["tool_content"] = json.dumps({ - "success": False, - "path": path, - "error": error_msg - }, ensure_ascii=False) - if display_id: - sender('update_action', { - 'id': display_id, - 'status': 'failed', - 'preparing_id': tool_call_id, - 'message': error_msg - }) - pending_append = None - return result, pending_append, append_probe_buffer - - assistant_message_lines = [] - if start_marker: - assistant_message_lines.append(start_marker) - assistant_message_lines.append(content) - if not forced and end_marker: - assistant_message_lines.append(end_marker) - assistant_message_text = "\n".join(assistant_message_lines) - result["assistant_content"] = assistant_message_text - assistant_metadata = { - "append_payload": { - "path": path, - "tool_call_id": tool_call_id, - "forced": forced, - "has_end_marker": not forced - } - } - result["assistant_metadata"] = assistant_metadata - - write_result = web_terminal.file_manager.append_file(path, content) - if write_result.get("success"): - bytes_written = len(content.encode('utf-8')) - line_count = content.count('\n') - if content and not content.endswith('\n'): - line_count += 1 - - summary = f"已向 {path} 追加 {line_count} 行({bytes_written} 字节)" - if forced: - summary += "。未检测到 <<>> 标记,系统已在流结束处完成写入。如内容未完成,请重新调用 append_to_file 并按标准格式补充;如已完成,可继续后续步骤。" - - result.update({ - "success": True, - "summary": summary, - "summary_message": summary, - "forced": forced, - "lines": line_count, - "bytes": bytes_written, - "appended_content": content, - "tool_content": json.dumps({ - "success": True, - "path": path, - "lines": line_count, - "bytes": bytes_written, - "forced": forced, - "message": summary, - "finish_reason": finish_reason - }, ensure_ascii=False) - }) - - assistant_meta_payload = result["assistant_metadata"]["append_payload"] - assistant_meta_payload["lines"] = line_count - assistant_meta_payload["bytes"] = bytes_written - assistant_meta_payload["success"] = True - - summary_payload = { - "success": True, - "path": path, - "lines": line_count, - "bytes": bytes_written, - "forced": forced, - "message": summary - } - - if display_id: - sender('update_action', { - 'id': display_id, - 'status': 'completed', - 'result': summary_payload, - 'preparing_id': tool_call_id, - 'message': summary - }) - - debug_log(f"追加写入完成: {summary}") - else: - error_msg = write_result.get("error", "追加写入失败") - result.update({ - "error": error_msg, - "summary_message": error_msg, - "forced": forced, - "appended_content": content, - "tool_content": json.dumps({ - "success": False, - "path": path, - "error": error_msg, - "finish_reason": finish_reason - }, ensure_ascii=False) - }) - debug_log(f"追加写入失败: {error_msg}") - - if result["assistant_metadata"]: - assistant_meta_payload = result["assistant_metadata"]["append_payload"] - assistant_meta_payload["lines"] = content.count('\n') + (0 if content.endswith('\n') or not content else 1) - assistant_meta_payload["bytes"] = len(content.encode('utf-8')) - assistant_meta_payload["success"] = False - - failure_payload = { - "success": False, - "path": path, - "error": error_msg, - "forced": forced - } - - if display_id: - sender('update_action', { - 'id': display_id, - 'status': 'completed', - 'result': failure_payload, - 'preparing_id': tool_call_id, - 'message': error_msg - }) - - pending_append = None - append_probe_buffer = "" - if hasattr(web_terminal, "pending_append_request"): - web_terminal.pending_append_request = None - return result, pending_append, append_probe_buffer - - - -async def finalize_pending_modify(*, pending_modify, modify_probe_buffer: str, response_text: str, stream_completed: bool, finish_reason: str = None, web_terminal, sender, debug_log): - """在流式输出结束后处理修改写入""" - - result = { - "handled": False, - "success": False, - "path": None, - "tool_call_id": None, - "display_id": None, - "total_blocks": 0, - "completed_blocks": [], - "failed_blocks": [], - "forced": False, - "details": [], - "error": None, - "assistant_content": response_text, - "assistant_metadata": None, - "tool_content": None, - "summary_message": None, - "finish_reason": finish_reason - } - - if not pending_modify: - return result, pending_modify, modify_probe_buffer - - state = pending_modify - path = state.get("path") - tool_call_id = state.get("tool_call_id") - display_id = state.get("display_id") - start_marker = state.get("start_marker") - end_marker = state.get("end_marker") - buffer = state.get("buffer", "") - raw_buffer = state.get("raw_buffer", "") - end_index = state.get("end_index") - - result.update({ - "handled": True, - "path": path, - "tool_call_id": tool_call_id, - "display_id": display_id - }) - - if not state.get("start_seen"): - error_msg = "未检测到格式正确的 <<>> 标记。" - debug_log(error_msg) - result["error"] = error_msg - result["summary_message"] = error_msg - result["tool_content"] = json.dumps({ - "success": False, - "path": path, - "error": error_msg, - "finish_reason": finish_reason - }, ensure_ascii=False) - if display_id: - sender('update_action', { - 'id': display_id, - 'status': 'failed', - 'preparing_id': tool_call_id, - 'message': error_msg - }) - if hasattr(web_terminal, "pending_modify_request"): - web_terminal.pending_modify_request = None - pending_modify = None - modify_probe_buffer = "" - return result, pending_modify, modify_probe_buffer - - forced = end_index is None - apply_text = buffer if forced else buffer[:end_index] - raw_content = raw_buffer if forced else raw_buffer[:len(start_marker) + end_index + len(end_marker)] - if raw_content: - result["assistant_content"] = raw_content - - blocks_info = [] - block_reports = {} - detected_indices = set() - block_pattern = re.compile(r"\[replace:(\d+)\](.*?)\[/replace\]", re.DOTALL) - structure_warnings: List[str] = [] - structure_detail_entries: List[Dict] = [] - - def record_structure_warning(message: str, hint: Optional[str] = None): - """记录结构性缺陷,便于给出更具体的反馈。""" - if message in structure_warnings: - return - structure_warnings.append(message) - structure_detail_entries.append({ - "index": 0, - "status": "failed", - "reason": message, - "removed_lines": 0, - "added_lines": 0, - "hint": hint or "请严格按照模板输出:[replace:n] + <>/<> + [/replace],并使用 <<>> 收尾。" - }) - - def extract_segment(body: str, tag: str): - marker = f"<<{tag}>>" - end_tag = "<>" - start_pos = body.find(marker) - if start_pos == -1: - return None, f"缺少 {marker}" - start_pos += len(marker) - if body[start_pos:start_pos+2] == "\r\n": - start_pos += 2 - elif body[start_pos:start_pos+1] == "\n": - start_pos += 1 - end_pos = body.find(end_tag, start_pos) - if end_pos == -1: - return None, f"缺少 {end_tag}" - segment = body[start_pos:end_pos] - return segment, None - - for match in block_pattern.finditer(apply_text): - try: - index = int(match.group(1)) - except ValueError: - continue - body = match.group(2) - if index in detected_indices: - continue - detected_indices.add(index) - block_reports[index] = { - "index": index, - "status": "pending", - "reason": None, - "removed_lines": 0, - "added_lines": 0, - "hint": None - } - old_content, old_error = extract_segment(body, "OLD") - new_content, new_error = extract_segment(body, "NEW") - if old_error or new_error: - reason = old_error or new_error - block_reports[index]["status"] = "failed" - block_reports[index]["reason"] = reason - blocks_info.append({ - "index": index, - "old": old_content, - "new": new_content, - "error": old_error or new_error - }) - - if not blocks_info: - has_replace_start = bool(re.search(r"\[replace:\s*\d+\]", apply_text)) - has_replace_end = "[/replace]" in apply_text - has_old_tag = "<>" in apply_text - has_new_tag = "<>" in apply_text - - if has_replace_start and not has_replace_end: - record_structure_warning("检测到 [replace:n] 标记但缺少对应的 [/replace] 结束标记。") - if has_replace_end and not has_replace_start: - record_structure_warning("检测到 [/replace] 结束标记但缺少对应的 [replace:n] 起始标记。") - - old_tags = len(re.findall(r"<>", apply_text)) - completed_old_tags = len(re.findall(r"<>[\s\S]*?<>", apply_text)) - if old_tags and completed_old_tags < old_tags: - record_structure_warning("检测到 <> 段落但未看到对应的 <> 结束标记。") - - new_tags = len(re.findall(r"<>", apply_text)) - completed_new_tags = len(re.findall(r"<>[\s\S]*?<>", apply_text)) - if new_tags and completed_new_tags < new_tags: - record_structure_warning("检测到 <> 段落但未看到对应的 <> 结束标记。") - - if (has_replace_start or has_replace_end or has_old_tag or has_new_tag) and not structure_warnings: - record_structure_warning("检测到部分补丁标记,但整体结构不完整,请严格按照模板填写所有标记。") - - total_blocks = len(blocks_info) - result["total_blocks"] = total_blocks - if forced: - debug_log("未检测到 <<>>,将在流结束处执行已识别的修改块。") - result["forced"] = True - - blocks_to_apply = [ - {"index": block["index"], "old": block["old"], "new": block["new"]} - for block in blocks_info - if block["error"] is None and block["old"] is not None and block["new"] is not None - ] - - # 记录格式残缺的块 - for block in blocks_info: - if block["error"]: - idx = block["index"] - block_reports[idx]["status"] = "failed" - block_reports[idx]["reason"] = block["error"] - block_reports[idx]["hint"] = "请检查补丁块的 OLD/NEW 标记是否完整,必要时复用 terminal_snapshot 或终端命令重新调整。" - - apply_result = {} - if blocks_to_apply: - apply_result = web_terminal.file_manager.apply_modify_blocks(path, blocks_to_apply) - else: - apply_result = {"success": False, "completed": [], "failed": [], "results": [], "write_performed": False, "error": None} - - block_result_map = {item["index"]: item for item in apply_result.get("results", [])} - - for block in blocks_info: - idx = block["index"] - report = block_reports.get(idx) - if report is None: - continue - if report["status"] == "failed": - continue - block_apply = block_result_map.get(idx) - if not block_apply: - report["status"] = "failed" - report["reason"] = "未执行,可能未找到匹配原文" - report["hint"] = report.get("hint") or "请确认 OLD 文本与文件内容完全一致;若多次失败,可改用终端命令/Python 进行精准替换。" - continue - status = block_apply.get("status") - report["removed_lines"] = block_apply.get("removed_lines", 0) - report["added_lines"] = block_apply.get("added_lines", 0) - if block_apply.get("hint"): - report["hint"] = block_apply.get("hint") - if status == "success": - report["status"] = "completed" - elif status == "not_found": - report["status"] = "failed" - report["reason"] = block_apply.get("reason") or "未找到匹配的原文" - if not report.get("hint"): - report["hint"] = "请使用 terminal_snapshot/grep -n 校验原文,或在说明后改用 run_command/python 精确替换。" - else: - report["status"] = "failed" - report["reason"] = block_apply.get("reason") or "替换失败" - if not report.get("hint"): - report["hint"] = block_apply.get("hint") or "若多次尝试仍失败,可考虑利用终端命令或 Python 小脚本完成此次修改。" - - completed_blocks = sorted([idx for idx, rep in block_reports.items() if rep["status"] == "completed"]) - failed_blocks = sorted([idx for idx, rep in block_reports.items() if rep["status"] != "completed"]) - - result["completed_blocks"] = completed_blocks - result["failed_blocks"] = failed_blocks - details = sorted(block_reports.values(), key=lambda x: x["index"]) - if structure_detail_entries: - details = structure_detail_entries + details - result["details"] = details - - summary_parts = [] - if total_blocks == 0: - summary_parts.append("未检测到有效的修改块,未执行任何修改。") - summary_parts.extend(structure_warnings) - else: - if not completed_blocks and failed_blocks: - summary_parts.append(f"共检测到 {total_blocks} 个修改块,全部未执行。") - elif completed_blocks and not failed_blocks: - summary_parts.append(f"共 {total_blocks} 个修改块全部完成。") - else: - summary_parts.append( - f"共检测到 {total_blocks} 个修改块,其中成功 {len(completed_blocks)} 个,失败 {len(failed_blocks)} 个。" - ) - if forced: - summary_parts.append("未检测到 <<>> 标记,系统已在流结束处执行补丁。") - if apply_result.get("error"): - summary_parts.append(apply_result["error"]) - - matching_note = "提示:补丁匹配基于完整文本,包含注释和空白符,请确保 <<>> 段落与文件内容逐字一致。如果修改成功,请忽略,如果失败,请明确原文后再次尝试。" - summary_parts.append(matching_note) - summary_message = " ".join(summary_parts).strip() - result["summary_message"] = summary_message - result["success"] = bool(completed_blocks) and not failed_blocks and apply_result.get("error") is None - - tool_payload = { - "success": result["success"], - "path": path, - "total_blocks": total_blocks, - "completed": completed_blocks, - "failed": [ - { - "index": rep["index"], - "reason": rep.get("reason"), - "hint": rep.get("hint") - } - for rep in result["details"] if rep["status"] != "completed" - ], - "forced": forced, - "message": summary_message, - "finish_reason": finish_reason, - "details": result["details"] - } - if apply_result.get("error"): - tool_payload["error"] = apply_result["error"] - - result["tool_content"] = json.dumps(tool_payload, ensure_ascii=False) - result["assistant_metadata"] = { - "modify_payload": { - "path": path, - "total_blocks": total_blocks, - "completed": completed_blocks, - "failed": failed_blocks, - "forced": forced, - "details": result["details"] - } - } - - if display_id: - sender('update_action', { - 'id': display_id, - 'status': 'completed' if result["success"] else 'failed', - 'result': tool_payload, - 'preparing_id': tool_call_id, - 'message': summary_message - }) - - pending_modify = None - modify_probe_buffer = "" - if hasattr(web_terminal, "pending_modify_request"): - web_terminal.pending_modify_request = None - return result, pending_modify, modify_probe_buffer - diff --git a/server/chat_flow_stream_loop.py b/server/chat_flow_stream_loop.py index c6b1af4..c3ee766 100644 --- a/server/chat_flow_stream_loop.py +++ b/server/chat_flow_stream_loop.py @@ -3,19 +3,17 @@ from __future__ import annotations import asyncio import json import time -import re from typing import Any, Dict, Optional from config.model_profiles import get_model_profile from .utils_common import debug_log, brief_log, log_backend_chunk from .chat_flow_runner_helpers import extract_intent_from_partial -from .chat_flow_pending_writes import finalize_pending_append, finalize_pending_modify from .chat_flow_task_support import wait_retry_delay, cancel_pending_tools from .state import get_stop_flag, clear_stop_flag -async def run_streaming_attempts(*, web_terminal, messages, tools, sender, client_sid: str, username: str, conversation_id: Optional[str], current_iteration: int, max_api_retries: int, retry_delay_seconds: int, pending_append, append_probe_buffer: str, pending_modify, modify_probe_buffer: str, detected_tool_intent: Dict[str, str], full_response: str, tool_calls: list, current_thinking: str, detected_tools: Dict[str, str], last_usage_payload, in_thinking: bool, thinking_started: bool, thinking_ended: bool, text_started: bool, text_has_content: bool, text_streaming: bool, text_chunk_index: int, last_text_chunk_time, chunk_count: int, reasoning_chunks: int, content_chunks: int, tool_chunks: int, append_result: Dict[str, Any], modify_result: Dict[str, Any], last_finish_reason: Optional[str], accumulated_response: str) -> Dict[str, Any]: +async def run_streaming_attempts(*, web_terminal, messages, tools, sender, client_sid: str, username: str, conversation_id: Optional[str], current_iteration: int, max_api_retries: int, retry_delay_seconds: int, detected_tool_intent: Dict[str, str], full_response: str, tool_calls: list, current_thinking: str, detected_tools: Dict[str, str], last_usage_payload, in_thinking: bool, thinking_started: bool, thinking_ended: bool, text_started: bool, text_has_content: bool, text_streaming: bool, text_chunk_index: int, last_text_chunk_time, chunk_count: int, reasoning_chunks: int, content_chunks: int, tool_chunks: int, last_finish_reason: Optional[str], accumulated_response: str) -> Dict[str, Any]: api_error = None for api_attempt in range(max_api_retries + 1): api_error = None @@ -37,13 +35,8 @@ async def run_streaming_attempts(*, web_terminal, messages, tools, sender, clien reasoning_chunks = 0 content_chunks = 0 tool_chunks = 0 - append_result = {"handled": False} - modify_result = {"handled": False} last_finish_reason = None - append_break_triggered = False - modify_break_triggered = False - # 收集流式响应 async for chunk in web_terminal.api_client.chat(messages, tools, stream=True): chunk_count += 1 @@ -54,10 +47,6 @@ async def run_streaming_attempts(*, web_terminal, messages, tools, sender, clien stop_requested = client_stop_info.get('stop', False) if isinstance(client_stop_info, dict) else client_stop_info if stop_requested: debug_log(f"检测到停止请求,中断流处理") - if pending_append: - append_result, pending_append, append_probe_buffer = await finalize_pending_append(pending_append=pending_append, append_probe_buffer=append_probe_buffer, response_text=full_response, stream_completed=False, finish_reason="user_stop", web_terminal=web_terminal, sender=sender, debug_log=debug_log) - if pending_modify: - modify_result, pending_modify, modify_probe_buffer = await finalize_pending_modify(pending_modify=pending_modify, modify_probe_buffer=modify_probe_buffer, response_text=full_response, stream_completed=False, finish_reason="user_stop", web_terminal=web_terminal, sender=sender, debug_log=debug_log) cancel_pending_tools(tool_calls_list=tool_calls, sender=sender, messages=messages) sender('task_stopped', { 'message': '命令执行被用户取消', @@ -83,13 +72,7 @@ async def run_streaming_attempts(*, web_terminal, messages, tools, sender, clien "reasoning_chunks": reasoning_chunks, "content_chunks": content_chunks, "tool_chunks": tool_chunks, - "append_result": append_result, - "modify_result": modify_result, "last_finish_reason": last_finish_reason, - "pending_append": pending_append, - "append_probe_buffer": append_probe_buffer, - "pending_modify": pending_modify, - "modify_probe_buffer": modify_probe_buffer, "accumulated_response": accumulated_response, } @@ -158,168 +141,6 @@ async def run_streaming_attempts(*, web_terminal, messages, tools, sender, clien sender('thinking_end', {'full_content': current_thinking}) await asyncio.sleep(0.1) - - expecting_modify = bool(pending_modify) or bool(getattr(web_terminal, "pending_modify_request", None)) - expecting_append = bool(pending_append) or bool(getattr(web_terminal, "pending_append_request", None)) - - if pending_modify: - if not pending_modify.get("start_seen"): - probe_buffer = pending_modify.get("probe_buffer", "") + content - if len(probe_buffer) > 10000: - probe_buffer = probe_buffer[-10000:] - marker = pending_modify.get("start_marker") - marker_index = probe_buffer.find(marker) - if marker_index == -1: - pending_modify["probe_buffer"] = probe_buffer - continue - after_marker = marker_index + len(marker) - remainder = probe_buffer[after_marker:] - pending_modify["buffer"] = remainder - pending_modify["raw_buffer"] = marker + remainder - pending_modify["start_seen"] = True - pending_modify["detected_blocks"] = set() - pending_modify["probe_buffer"] = "" - if pending_modify.get("display_id"): - sender('update_action', { - 'id': pending_modify["display_id"], - 'status': 'running', - 'preparing_id': pending_modify.get("tool_call_id"), - 'message': f"正在修改 {pending_modify['path']}..." - }) - else: - pending_modify["buffer"] += content - pending_modify["raw_buffer"] += content - - if pending_modify.get("start_seen"): - block_text = pending_modify["buffer"] - for match in re.finditer(r"\[replace:(\d+)\]", block_text): - try: - block_index = int(match.group(1)) - except ValueError: - continue - detected_blocks = pending_modify.setdefault("detected_blocks", set()) - if block_index not in detected_blocks: - detected_blocks.add(block_index) - if pending_modify.get("display_id"): - sender('update_action', { - 'id': pending_modify["display_id"], - 'status': 'running', - 'preparing_id': pending_modify.get("tool_call_id"), - 'message': f"正在对 {pending_modify['path']} 进行第 {block_index} 处修改..." - }) - - if pending_modify.get("start_seen"): - end_pos = pending_modify["buffer"].find(pending_modify["end_marker"]) - if end_pos != -1: - pending_modify["end_index"] = end_pos - modify_break_triggered = True - debug_log("检测到<<>>,即将终止流式输出并应用修改") - break - continue - elif expecting_modify: - modify_probe_buffer += content - if len(modify_probe_buffer) > 10000: - modify_probe_buffer = modify_probe_buffer[-10000:] - - marker_match = re.search(r"<<>>", modify_probe_buffer) - if marker_match: - detected_raw_path = marker_match.group(1) - detected_path = detected_raw_path.strip() - marker_full = marker_match.group(0) - after_marker_index = modify_probe_buffer.find(marker_full) + len(marker_full) - remainder = modify_probe_buffer[after_marker_index:] - modify_probe_buffer = "" - - if not detected_path: - debug_log("检测到 MODIFY 起始标记但路径为空,忽略。") - continue - - pending_modify = { - "path": detected_path, - "tool_call_id": None, - "buffer": remainder, - "raw_buffer": marker_full + remainder, - "start_marker": marker_full, - "end_marker": "<<>>", - "start_seen": True, - "end_index": None, - "display_id": None, - "detected_blocks": set() - } - if hasattr(web_terminal, "pending_modify_request"): - web_terminal.pending_modify_request = {"path": detected_path} - debug_log(f"直接检测到modify起始标记,构建修改缓冲: {detected_path}") - - end_pos = pending_modify["buffer"].find(pending_modify["end_marker"]) - if end_pos != -1: - pending_modify["end_index"] = end_pos - modify_break_triggered = True - debug_log("检测到<<>>,即将终止流式输出并应用修改") - break - continue - - if pending_append: - pending_append["buffer"] += content - - if pending_append.get("content_start") is None: - marker_index = pending_append["buffer"].find(pending_append["start_marker"]) - if marker_index != -1: - pending_append["content_start"] = marker_index + len(pending_append["start_marker"]) - debug_log(f"检测到追加起始标识: {pending_append['start_marker']}") - - if pending_append.get("content_start") is not None: - end_index = pending_append["buffer"].find( - pending_append["end_marker"], - pending_append["content_start"] - ) - if end_index != -1: - pending_append["end_index"] = end_index - append_break_triggered = True - debug_log("检测到<<>>,即将终止流式输出并写入文件") - break - - # 继续累积追加内容 - continue - elif expecting_append: - append_probe_buffer += content - # 限制缓冲区大小防止过长 - if len(append_probe_buffer) > 10000: - append_probe_buffer = append_probe_buffer[-10000:] - - marker_match = re.search(r"<<>>", append_probe_buffer) - if marker_match: - detected_raw_path = marker_match.group(1) - detected_path = detected_raw_path.strip() - if not detected_path: - append_probe_buffer = append_probe_buffer[marker_match.end():] - continue - marker_full = marker_match.group(0) - after_marker_index = append_probe_buffer.find(marker_full) + len(marker_full) - remainder = append_probe_buffer[after_marker_index:] - append_probe_buffer = "" - pending_append = { - "path": detected_path, - "tool_call_id": None, - "buffer": remainder, - "start_marker": marker_full, - "end_marker": "<<>>", - "content_start": 0, - "end_index": None, - "display_id": None - } - if hasattr(web_terminal, "pending_append_request"): - web_terminal.pending_append_request = {"path": detected_path} - debug_log(f"直接检测到append起始标记,构建追加缓冲: {detected_path}") - # 检查是否立即包含结束标记 - if pending_append["buffer"]: - end_index = pending_append["buffer"].find(pending_append["end_marker"], pending_append["content_start"]) - if end_index != -1: - pending_append["end_index"] = end_index - append_break_triggered = True - debug_log("检测到<<>>,即将终止流式输出并写入文件") - break - continue - if not text_started: text_started = True text_streaming = True @@ -327,27 +148,26 @@ async def run_streaming_attempts(*, web_terminal, messages, tools, sender, clien brief_log("模型输出了内容") await asyncio.sleep(0.05) - if not pending_append: - full_response += content - accumulated_response += content - text_has_content = True - emit_time = time.time() - elapsed = 0.0 if last_text_chunk_time is None else emit_time - last_text_chunk_time - last_text_chunk_time = emit_time - text_chunk_index += 1 - log_backend_chunk( - conversation_id, - current_iteration, - text_chunk_index, - elapsed, - len(content), - content[:32] - ) - sender('text_chunk', { - 'content': content, - 'index': text_chunk_index, - 'elapsed': elapsed - }) + full_response += content + accumulated_response += content + text_has_content = True + emit_time = time.time() + elapsed = 0.0 if last_text_chunk_time is None else emit_time - last_text_chunk_time + last_text_chunk_time = emit_time + text_chunk_index += 1 + log_backend_chunk( + conversation_id, + current_iteration, + text_chunk_index, + elapsed, + len(content), + content[:32] + ) + sender('text_chunk', { + 'content': content, + 'index': text_chunk_index, + 'elapsed': elapsed + }) # 收集工具调用 - 实时发送准备状态 if "tool_calls" in delta: @@ -472,13 +292,7 @@ async def run_streaming_attempts(*, web_terminal, messages, tools, sender, clien "reasoning_chunks": reasoning_chunks, "content_chunks": content_chunks, "tool_chunks": tool_chunks, - "append_result": append_result, - "modify_result": modify_result, "last_finish_reason": last_finish_reason, - "pending_append": pending_append, - "append_probe_buffer": append_probe_buffer, - "pending_modify": pending_modify, - "modify_probe_buffer": modify_probe_buffer, "accumulated_response": accumulated_response, } @@ -546,8 +360,6 @@ async def run_streaming_attempts(*, web_terminal, messages, tools, sender, clien and not full_response and not tool_calls and not current_thinking - and not pending_append - and not pending_modify ) sender('error', { 'message': error_message, @@ -590,13 +402,7 @@ async def run_streaming_attempts(*, web_terminal, messages, tools, sender, clien "reasoning_chunks": reasoning_chunks, "content_chunks": content_chunks, "tool_chunks": tool_chunks, - "append_result": append_result, - "modify_result": modify_result, "last_finish_reason": last_finish_reason, - "pending_append": pending_append, - "append_probe_buffer": append_probe_buffer, - "pending_modify": pending_modify, - "modify_probe_buffer": modify_probe_buffer, "accumulated_response": accumulated_response, } continue @@ -620,13 +426,7 @@ async def run_streaming_attempts(*, web_terminal, messages, tools, sender, clien "reasoning_chunks": reasoning_chunks, "content_chunks": content_chunks, "tool_chunks": tool_chunks, - "append_result": append_result, - "modify_result": modify_result, "last_finish_reason": last_finish_reason, - "pending_append": pending_append, - "append_probe_buffer": append_probe_buffer, - "pending_modify": pending_modify, - "modify_probe_buffer": modify_probe_buffer, "accumulated_response": accumulated_response, } break @@ -650,12 +450,6 @@ async def run_streaming_attempts(*, web_terminal, messages, tools, sender, clien "reasoning_chunks": reasoning_chunks, "content_chunks": content_chunks, "tool_chunks": tool_chunks, - "append_result": append_result, - "modify_result": modify_result, "last_finish_reason": last_finish_reason, - "pending_append": pending_append, - "append_probe_buffer": append_probe_buffer, - "pending_modify": pending_modify, - "modify_probe_buffer": modify_probe_buffer, "accumulated_response": accumulated_response, } diff --git a/server/chat_flow_task_main.py b/server/chat_flow_task_main.py index 45878f2..95550db 100644 --- a/server/chat_flow_task_main.py +++ b/server/chat_flow_task_main.py @@ -39,6 +39,7 @@ from modules.personalization_manager import ( THINKING_INTERVAL_MIN, THINKING_INTERVAL_MAX, ) +from modules.skill_hint_manager import SkillHintManager from modules.upload_security import UploadSecurityError from modules.user_manager import UserWorkspace from modules.usage_tracker import QUOTA_DEFAULTS @@ -62,7 +63,7 @@ from .utils_common import ( CHUNK_FRONTEND_LOG_FILE, STREAMING_DEBUG_LOG_FILE, ) -from .security import rate_limited, format_tool_result_notice, compact_web_search_result, consume_socket_token, prune_socket_tokens, validate_csrf_request, requires_csrf_protection, get_csrf_token +from .security import rate_limited, compact_web_search_result, consume_socket_token, prune_socket_tokens, validate_csrf_request, requires_csrf_protection, get_csrf_token from .monitor import cache_monitor_snapshot, get_cached_monitor_snapshot from .extensions import socketio from .state import ( @@ -125,7 +126,6 @@ from .chat_flow_runtime import ( detect_malformed_tool_call, ) -from .chat_flow_pending_writes import finalize_pending_append, finalize_pending_modify from .chat_flow_task_support import process_sub_agent_updates from .chat_flow_tool_loop import execute_tool_calls from .chat_flow_stream_loop import run_streaming_attempts @@ -465,7 +465,30 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac history_len_before = len(getattr(web_terminal.context_manager, "conversation_history", []) or []) is_first_user_message = history_len_before == 0 web_terminal.context_manager.add_conversation("user", message, images=images, videos=videos) - + + # Skill 提示系统:检测关键词并在用户消息之后插入 system 消息 + try: + personal_config = load_personalization_config(workspace.data_dir) + skill_hints_enabled = personal_config.get("skill_hints_enabled", False) + + if skill_hints_enabled and message: + hint_manager = SkillHintManager() + hint_manager.set_enabled(True) + hint_messages = hint_manager.build_hint_messages(message) + + # 将提示消息插入到对话历史中(在用户消息之后) + for hint_msg in hint_messages: + debug_log(f"[Skill Hints] 插入提示消息: {hint_msg['content'][:100]}") + web_terminal.context_manager.add_conversation( + "system", + hint_msg["content"] + ) + # 验证插入后的消息 + last_msg = web_terminal.context_manager.conversation_history[-1] + debug_log(f"[Skill Hints] 插入后验证 - role: {last_msg.get('role')}, content: {last_msg.get('content')[:100]}") + except Exception as exc: + debug_log(f"Skill hints 处理失败: {exc}") + if is_first_user_message and getattr(web_terminal, "context_manager", None): try: personal_config = load_personalization_config(workspace.data_dir) @@ -551,10 +574,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac max_api_retries = 4 retry_delay_seconds = 10 - pending_append = None # {"path": str, "tool_call_id": str, "buffer": str, ...} - append_probe_buffer = "" - pending_modify = None # {"path": str, "tool_call_id": str, "buffer": str, ...} - modify_probe_buffer = "" iteration = 0 while max_iterations is None or iteration < max_iterations: @@ -596,8 +615,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac reasoning_chunks = 0 content_chunks = 0 tool_chunks = 0 - append_result = {"handled": False} - modify_result = {"handled": False} last_finish_reason = None thinking_expected = web_terminal.api_client.get_current_thinking_mode() @@ -638,10 +655,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac current_iteration=current_iteration, max_api_retries=max_api_retries, retry_delay_seconds=retry_delay_seconds, - pending_append=pending_append, - append_probe_buffer=append_probe_buffer, - pending_modify=pending_modify, - modify_probe_buffer=modify_probe_buffer, detected_tool_intent=detected_tool_intent, full_response=full_response, tool_calls=tool_calls, @@ -660,8 +673,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac reasoning_chunks=reasoning_chunks, content_chunks=content_chunks, tool_chunks=tool_chunks, - append_result=append_result, - modify_result=modify_result, last_finish_reason=last_finish_reason, accumulated_response=accumulated_response, ) @@ -685,13 +696,7 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac reasoning_chunks = stream_result["reasoning_chunks"] content_chunks = stream_result["content_chunks"] tool_chunks = stream_result["tool_chunks"] - append_result = stream_result["append_result"] - modify_result = stream_result["modify_result"] last_finish_reason = stream_result["last_finish_reason"] - pending_append = stream_result["pending_append"] - append_probe_buffer = stream_result["append_probe_buffer"] - pending_modify = stream_result["pending_modify"] - modify_probe_buffer = stream_result["modify_probe_buffer"] accumulated_response = stream_result["accumulated_response"] # 流结束后的处理 @@ -704,11 +709,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac debug_log(f" 收集到的正文: {len(full_response)} 字符") debug_log(f" 收集到的工具: {len(tool_calls)} 个") - if not append_result["handled"] and pending_append: - append_result, pending_append, append_probe_buffer = await finalize_pending_append(pending_append=pending_append, append_probe_buffer=append_probe_buffer, response_text=full_response, stream_completed=True, finish_reason=last_finish_reason, web_terminal=web_terminal, sender=sender, debug_log=debug_log) - if not modify_result["handled"] and pending_modify: - modify_result, pending_modify, modify_probe_buffer = await finalize_pending_modify(pending_modify=pending_modify, modify_probe_buffer=modify_probe_buffer, response_text=full_response, stream_completed=True, finish_reason=last_finish_reason, web_terminal=web_terminal, sender=sender, debug_log=debug_log) - # 结束未完成的流 if in_thinking and not thinking_ended: sender('thinking_end', {'full_content': current_thinking}) @@ -716,7 +716,7 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac # 确保text_end事件被发送 - if text_started and text_has_content and not append_result["handled"] and not modify_result["handled"]: + if text_started and text_has_content: debug_log(f"发送text_end事件,完整内容长度: {len(full_response)}") sender('text_end', {'full_content': full_response}) await asyncio.sleep(0.1) @@ -725,159 +725,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac if full_response.strip(): debug_log(f"流式文本内容长度: {len(full_response)} 字符") - if append_result["handled"]: - append_metadata = append_result.get("assistant_metadata") - append_content_text = append_result.get("assistant_content") - if append_content_text: - web_terminal.context_manager.add_conversation( - "assistant", - append_content_text, - metadata=append_metadata - ) - debug_log("💾 增量保存:追加正文快照") - - payload_info = append_metadata.get("append_payload") if append_metadata else {} - sender('append_payload', { - 'path': payload_info.get("path") or append_result.get("path"), - 'forced': payload_info.get("forced", False), - 'lines': payload_info.get("lines"), - 'bytes': payload_info.get("bytes"), - 'tool_call_id': payload_info.get("tool_call_id") or append_result.get("tool_call_id"), - 'success': payload_info.get("success", append_result.get("success", False)), - 'conversation_id': conversation_id - }) - - if append_result["tool_content"]: - tool_call_id = append_result.get("tool_call_id") or f"append_{int(time.time() * 1000)}" - system_notice = format_tool_result_notice("append_to_file", tool_call_id, append_result["tool_content"]) - web_terminal.context_manager.add_conversation("system", system_notice) - append_result["tool_call_id"] = tool_call_id - debug_log("💾 增量保存:append_to_file 工具结果(system 通知)") - - finish_reason = append_result.get("finish_reason") - path_for_prompt = append_result.get("path") - need_follow_prompt = ( - finish_reason == "length" or - append_result.get("forced") or - not append_result.get("success") - ) - - if need_follow_prompt and path_for_prompt: - prompt_lines = [ - f"append_to_file 在处理 {path_for_prompt} 时未完成,需要重新发起写入。" - ] - if finish_reason == "length": - prompt_lines.append( - "上一次输出达到系统单次输出上限,已写入的内容已保存。" - ) - if append_result.get("forced"): - prompt_lines.append( - "收到的内容缺少 <<>> 标记,系统依据流式结束位置落盘。" - ) - if not append_result.get("success"): - prompt_lines.append("系统未能识别有效的追加标记。") - prompt_lines.append( - "请再次调用 append_to_file 工具获取新的写入窗口,并在工具调用的输出中遵循以下格式:" - ) - prompt_lines.append(f"<<>>") - prompt_lines.append("...填写剩余正文,如内容已完成可留空...") - prompt_lines.append("<<>>") - prompt_lines.append("不要在普通回复中粘贴上述标记,必须通过 append_to_file 工具发送。") - follow_prompt = "\n".join(prompt_lines) - messages.append({ - "role": "system", - "content": follow_prompt - }) - web_terminal.context_manager.add_conversation("system", follow_prompt) - debug_log("已注入追加任务提示") - - if append_result["handled"] and append_result.get("forced") and append_result.get("success"): - mark_force_thinking(web_terminal, reason="append_forced_finish") - if append_result["handled"] and not append_result.get("success"): - sender('system_message', { - 'content': f'⚠️ 追加写入失败:{append_result.get("error")}' - }) - maybe_mark_failure_from_message(web_terminal, f'⚠️ 追加写入失败:{append_result.get("error")}') - mark_force_thinking(web_terminal, reason="append_failed") - - if modify_result["handled"]: - modify_metadata = modify_result.get("assistant_metadata") - modify_content_text = modify_result.get("assistant_content") - if modify_content_text: - web_terminal.context_manager.add_conversation( - "assistant", - modify_content_text, - metadata=modify_metadata - ) - debug_log("💾 增量保存:修改正文快照") - - payload_info = modify_metadata.get("modify_payload") if modify_metadata else {} - sender('modify_payload', { - 'path': payload_info.get("path") or modify_result.get("path"), - 'total': payload_info.get("total_blocks") or modify_result.get("total_blocks"), - 'completed': payload_info.get("completed") or modify_result.get("completed_blocks"), - 'failed': payload_info.get("failed") or modify_result.get("failed_blocks"), - 'forced': payload_info.get("forced", modify_result.get("forced", False)), - 'success': modify_result.get("success", False), - 'conversation_id': conversation_id - }) - - if modify_result["tool_content"]: - tool_call_id = modify_result.get("tool_call_id") or f"modify_{int(time.time() * 1000)}" - system_notice = format_tool_result_notice("modify_file", tool_call_id, modify_result["tool_content"]) - web_terminal.context_manager.add_conversation("system", system_notice) - modify_result["tool_call_id"] = tool_call_id - debug_log("💾 增量保存:modify_file 工具结果(system 通知)") - - path_for_prompt = modify_result.get("path") - failed_blocks = modify_result.get("failed_blocks") or [] - need_follow_prompt = modify_result.get("forced") or bool(failed_blocks) - - if need_follow_prompt and path_for_prompt: - prompt_lines = [ - f"modify_file 在处理 {path_for_prompt} 时未完成,需要重新发起补丁。" - ] - if modify_result.get("forced"): - prompt_lines.append( - "刚才的内容缺少 <<>> 标记,系统仅应用了已识别的部分。" - ) - if failed_blocks: - failed_text = "、".join(str(idx) for idx in failed_blocks) - prompt_lines.append(f"以下补丁未成功:第 {failed_text} 处。") - prompt_lines.append( - "请再次调用 modify_file 工具,并在新的工具调用中按以下模板提供完整补丁:" - ) - prompt_lines.append(f"<<>>") - prompt_lines.append("[replace:序号]") - prompt_lines.append("<>") - prompt_lines.append("...原文(必须逐字匹配,包含全部缩进、空格和换行)...") - prompt_lines.append("<>") - prompt_lines.append("<>") - prompt_lines.append("...新内容,可留空表示清空,注意保持结构完整...") - prompt_lines.append("<>") - prompt_lines.append("[/replace]") - prompt_lines.append("<<>>") - prompt_lines.append("请勿在普通回复中直接粘贴补丁,必须通过 modify_file 工具发送。") - follow_prompt = "\n".join(prompt_lines) - messages.append({ - "role": "system", - "content": follow_prompt - }) - web_terminal.context_manager.add_conversation("system", follow_prompt) - debug_log("已注入修改任务提示") - - if modify_result["handled"] and modify_result.get("failed_blocks"): - mark_force_thinking(web_terminal, reason="modify_partial_failure") - if modify_result["handled"] and modify_result.get("forced") and modify_result.get("success"): - mark_force_thinking(web_terminal, reason="modify_forced_finish") - if modify_result["handled"] and not modify_result.get("success"): - error_message = modify_result.get("summary_message") or modify_result.get("error") or "修改操作未成功,请根据提示重新执行。" - sender('system_message', { - 'content': f'⚠️ 修改操作存在未完成的内容:{error_message}' - }) - maybe_mark_failure_from_message(web_terminal, f'⚠️ 修改操作存在未完成的内容:{error_message}') - mark_force_thinking(web_terminal, reason="modify_failed") - if web_terminal.api_client.last_call_used_thinking and current_thinking: web_terminal.api_client.current_task_thinking = current_thinking or "" if web_terminal.api_client.current_task_first_call: @@ -885,7 +732,7 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac update_thinking_after_call(web_terminal) # 检测是否有格式错误的工具调用 - if not tool_calls and full_response and AUTO_FIX_TOOL_CALL and not append_result["handled"] and not modify_result["handled"]: + if not tool_calls and full_response and AUTO_FIX_TOOL_CALL: if detect_malformed_tool_call(full_response): auto_fix_attempts += 1 @@ -919,10 +766,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac if full_response: assistant_content_parts.append(full_response) - elif append_result["handled"] and append_result["assistant_content"]: - assistant_content_parts.append(append_result["assistant_content"]) - elif modify_result["handled"] and modify_result.get("assistant_content"): - assistant_content_parts.append(modify_result["assistant_content"]) assistant_content = "\n".join(assistant_content_parts) if assistant_content_parts else "" @@ -949,35 +792,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac text_started = False text_has_content = False full_response = "" - - if append_result["handled"] and append_result.get("tool_content"): - tool_call_id = append_result.get("tool_call_id") or f"append_{int(time.time() * 1000)}" - system_notice = format_tool_result_notice("append_to_file", tool_call_id, append_result["tool_content"]) - messages.append({ - "role": "system", - "content": system_notice - }) - append_result["tool_call_id"] = tool_call_id - debug_log("已将 append_to_file 工具结果以 system 形式追加到对话上下文") - if modify_result["handled"] and modify_result.get("tool_content"): - tool_call_id = modify_result.get("tool_call_id") or f"modify_{int(time.time() * 1000)}" - system_notice = format_tool_result_notice("modify_file", tool_call_id, modify_result["tool_content"]) - messages.append({ - "role": "system", - "content": system_notice - }) - modify_result["tool_call_id"] = tool_call_id - debug_log("已将 modify_file 工具结果以 system 形式追加到对话上下文") - - force_continue = append_result["handled"] or modify_result["handled"] - if force_continue: - if append_result["handled"]: - debug_log("append_to_file 已处理,继续下一轮以让模型返回确认回复") - elif modify_result["handled"]: - debug_log("modify_file 已处理,继续下一轮以让模型返回确认回复") - else: - debug_log("补丁处理完成,继续下一轮以获取模型回复") - continue if not tool_calls: debug_log("没有工具调用,结束迭代") diff --git a/server/state.py b/server/state.py index aae1e39..0ac7877 100644 --- a/server/state.py +++ b/server/state.py @@ -28,7 +28,7 @@ stop_flags: Dict[str, Dict[str, Any]] = {} active_polling_tasks: Dict[str, bool] = {} # conversation_id -> is_polling # 监控/限流/用量 -MONITOR_FILE_TOOLS = {'append_to_file', 'modify_file', 'write_file', 'edit_file'} +MONITOR_FILE_TOOLS = {'write_file', 'edit_file'} MONITOR_MEMORY_TOOLS = {'update_memory'} MONITOR_SNAPSHOT_CHAR_LIMIT = 60000 MONITOR_MEMORY_ENTRY_LIMIT = 256 diff --git a/static/src/app.ts b/static/src/app.ts index 9f8120b..0cfb11c 100644 --- a/static/src/app.ts +++ b/static/src/app.ts @@ -94,8 +94,6 @@ const appOptions = { chatAppendTextChunk: 'appendTextChunk', chatCompleteTextAction: 'completeText', chatAddSystemMessage: 'addSystemMessage', - chatAddAppendPayloadAction: 'addAppendPayloadAction', - chatAddModifyPayloadAction: 'addModifyPayloadAction', chatEnsureAssistantMessage: 'ensureAssistantMessage' }), ...mapActions(useInputStore, { diff --git a/static/src/app/methods/history.ts b/static/src/app/methods/history.ts index 54eccdf..9558d8c 100644 --- a/static/src/app/methods/history.ts +++ b/static/src/app/methods/history.ts @@ -202,47 +202,8 @@ export const historyMethods = { } // 处理普通文本内容(移除思考标签后的内容) - const metadata = message.metadata || {}; - const appendPayloadMeta = metadata.append_payload; - const modifyPayloadMeta = metadata.modify_payload; - const isAppendMessage = message.name === 'append_to_file'; - const isModifyMessage = message.name === 'modify_file'; - const containsAppendMarkers = /<<<\s*(APPEND|MODIFY)/i.test(content || '') || /<<>>/i.test(content || ''); - const textContent = content.trim(); - - if (appendPayloadMeta) { - currentAssistantMessage.actions.push({ - id: `history-append-payload-${Date.now()}-${Math.random()}`, - type: 'append_payload', - append: { - path: appendPayloadMeta.path || '未知文件', - forced: !!appendPayloadMeta.forced, - success: appendPayloadMeta.success === undefined ? true : !!appendPayloadMeta.success, - lines: appendPayloadMeta.lines ?? null, - bytes: appendPayloadMeta.bytes ?? null - }, - timestamp: Date.now() - }); - debugLog('添加append占位信息:', appendPayloadMeta.path); - } else if (modifyPayloadMeta) { - currentAssistantMessage.actions.push({ - id: `history-modify-payload-${Date.now()}-${Math.random()}`, - type: 'modify_payload', - modify: { - path: modifyPayloadMeta.path || '未知文件', - total: modifyPayloadMeta.total_blocks ?? null, - completed: modifyPayloadMeta.completed || [], - failed: modifyPayloadMeta.failed || [], - forced: !!modifyPayloadMeta.forced, - details: modifyPayloadMeta.details || [] - }, - timestamp: Date.now() - }); - debugLog('添加modify占位信息:', modifyPayloadMeta.path); - } - - if (textContent && !appendPayloadMeta && !modifyPayloadMeta && !isAppendMessage && !isModifyMessage && !containsAppendMarkers) { + if (textContent) { currentAssistantMessage.actions.push({ id: `history-text-${Date.now()}-${Math.random()}`, type: 'text', @@ -341,12 +302,7 @@ export const historyMethods = { toolAction.tool.message = result.message; } } - if (message.name === 'append_to_file' && result && result.message) { - toolAction.tool.message = result.message; - } debugLog(`更新工具结果: ${message.name} -> ${message.content?.substring(0, 50)}...`); - - // append_to_file 的摘要在 append_payload 占位中呈现,此处无需重复 } else { console.warn('找不到对应的工具调用:', message.name, message.tool_call_id); } diff --git a/static/src/components/chat/actions/ToolAction.vue b/static/src/components/chat/actions/ToolAction.vue index 76227dd..c123aeb 100644 --- a/static/src/components/chat/actions/ToolAction.vue +++ b/static/src/components/chat/actions/ToolAction.vue @@ -105,10 +105,6 @@ function renderEnhancedToolResult(): string { return renderWriteFile(result, args); } else if (name === 'edit_file') { return renderEditFile(result, args); - } else if (name === 'append_to_file') { - return renderAppendToFile(result, args); - } else if (name === 'modify_file') { - return renderModifyFile(result, args); } else if (name === 'delete_file') { return renderDeleteFile(result, args); } else if (name === 'rename_file') { @@ -297,67 +293,6 @@ function renderEditFile(result: any, args: any): string { return html; } -function renderAppendToFile(result: any, args: any): string { - const path = args.path || result.path || ''; - const status = result.success ? '✓ 已追加' : '✗ 失败'; - const content = args.content || ''; - - let html = '
'; - html += `
路径:${escapeHtml(path)}
`; - html += `
状态:${status}
`; - html += '
'; - - if (content) { - html += '
'; - html += ''; - html += `
${escapeHtml(content)}
`; - html += '
'; - } - - return html; -} - -function renderModifyFile(result: any, args: any): string { - const path = args.path || result.path || ''; - const status = result.success ? '✓ 已修改' : '✗ 失败'; - const operations = args.operations || []; - - let html = '
'; - html += `
路径:${escapeHtml(path)}
`; - html += `
状态:${status}
`; - html += '
'; - - if (operations.length > 0) { - html += '
'; - operations.forEach((op: any, idx: number) => { - if (idx > 0) html += '
'; - - const type = op.type || op.operation; - const oldText = op.old_text || op.old || ''; - const newText = op.new_text || op.new || ''; - - html += `
[${idx + 1}] ${escapeHtml(type)}
`; - - if (oldText) { - const oldLines = oldText.split('\n'); - oldLines.forEach((line: string) => { - html += `
- ${escapeHtml(line)}
`; - }); - } - - if (newText) { - const newLines = newText.split('\n'); - newLines.forEach((line: string) => { - html += `
+ ${escapeHtml(line)}
`; - }); - } - }); - html += '
'; - } - - return html; -} - function renderDeleteFile(result: any, args: any): string { const path = args.path || result.path || ''; const status = result.success ? '✓ 已删除' : '✗ 失败'; diff --git a/static/src/components/chat/actions/toolRenderers.ts b/static/src/components/chat/actions/toolRenderers.ts index 7e9646a..9edaa18 100644 --- a/static/src/components/chat/actions/toolRenderers.ts +++ b/static/src/components/chat/actions/toolRenderers.ts @@ -45,10 +45,6 @@ export function renderEnhancedToolResult( return renderWriteFile(result, args); } else if (name === 'edit_file') { return renderEditFile(result, args); - } else if (name === 'append_to_file') { - return renderAppendToFile(result, args); - } else if (name === 'modify_file') { - return renderModifyFile(result, args); } else if (name === 'delete_file') { return renderDeleteFile(result, args); } else if (name === 'rename_file') { @@ -242,79 +238,6 @@ function renderEditFile(result: any, args: any): string { return html; } -function renderAppendToFile(result: any, args: any): string { - const path = args.file_path || args.path || result.path || ''; - const status = result.success ? '✓ 已追加' : '✗ 失败'; - let content = args.content || ''; - - // 处理转义的 \n - content = content.replace(/\\n/g, '\n'); - - let html = '
'; - html += `
路径:${escapeHtml(path)}
`; - html += `
状态:${status}
`; - html += '
'; - - if (content) { - html += '
'; - html += ''; - const lines = content.split('\n'); - html += '
'; - lines.forEach((line: string) => { - html += `
+ ${escapeHtml(line)}
`; - }); - html += '
'; - html += '
'; - } - - return html; -} - -function renderModifyFile(result: any, args: any): string { - const path = args.file_path || args.path || result.path || ''; - const status = result.success ? '✓ 已修改' : '✗ 失败'; - const operations = args.operations || []; - - let html = '
'; - html += `
路径:${escapeHtml(path)}
`; - html += `
状态:${status}
`; - html += '
'; - - if (operations.length > 0) { - html += '
'; - operations.forEach((op: any, idx: number) => { - if (idx > 0) html += '
'; - - const type = op.type || op.operation; - let oldText = op.old_text || op.old || ''; - let newText = op.new_text || op.new || ''; - - // 处理转义的 \n - oldText = oldText.replace(/\\n/g, '\n'); - newText = newText.replace(/\\n/g, '\n'); - - html += `
[${idx + 1}] ${escapeHtml(type)}
`; - - if (oldText) { - const oldLines = oldText.split('\n'); - oldLines.forEach((line: string) => { - html += `
- ${escapeHtml(line)}
`; - }); - } - - if (newText) { - const newLines = newText.split('\n'); - newLines.forEach((line: string) => { - html += `
+ ${escapeHtml(line)}
`; - }); - } - }); - html += '
'; - } - - return html; -} - function renderDeleteFile(result: any, args: any): string { const path = args.path || result.path || ''; const status = result.success ? '✓ 已删除' : '✗ 失败'; diff --git a/static/src/components/personalization/PersonalizationDrawer.vue b/static/src/components/personalization/PersonalizationDrawer.vue index ef57583..d426859 100644 --- a/static/src/components/personalization/PersonalizationDrawer.vue +++ b/static/src/components/personalization/PersonalizationDrawer.vue @@ -517,7 +517,30 @@ > - 在工具块显示“我要做什么”的简短提示 + 在工具块显示"我要做什么"的简短提示 + + +
+
+ Skill 提示系统 +

开启后,检测到特定关键词时会自动提示模型阅读相关 skill(如 terminal-guide、sub-agent-guide 等)。

+
+
diff --git a/static/src/stores/chat.ts b/static/src/stores/chat.ts index 37e9f95..21adc6a 100644 --- a/static/src/stores/chat.ts +++ b/static/src/stores/chat.ts @@ -286,26 +286,6 @@ export const useChatStore = defineStore('chat', { timestamp: Date.now() }); }, - addAppendPayloadAction(data: any) { - const msg = this.ensureAssistantMessage(); - clearAwaitingFirstContent(msg); - msg.actions.push({ - id: `append-payload-${Date.now()}-${Math.random()}`, - type: 'append_payload', - append: data, - timestamp: Date.now() - }); - }, - addModifyPayloadAction(data: any) { - const msg = this.ensureAssistantMessage(); - clearAwaitingFirstContent(msg); - msg.actions.push({ - id: `modify-payload-${Date.now()}-${Math.random()}`, - type: 'modify_payload', - modify: data, - timestamp: Date.now() - }); - }, getActiveThinkingAction(msg: any) { if (!msg || !Array.isArray(msg.actions)) { return null; diff --git a/static/src/stores/monitor.ts b/static/src/stores/monitor.ts index 47c6363..1dd863b 100644 --- a/static/src/stores/monitor.ts +++ b/static/src/stores/monitor.ts @@ -101,8 +101,6 @@ const TOOL_SCENE_MAP: Record = { create_file: 'createFile', rename_file: 'renameFile', delete_file: 'deleteFile', - append_to_file: 'appendFile', - modify_file: 'modifyFile', write_file: 'modifyFile', edit_file: 'modifyFile', run_command: 'runCommand', diff --git a/static/src/stores/personalization.ts b/static/src/stores/personalization.ts index b1ed19b..59c87b3 100644 --- a/static/src/stores/personalization.ts +++ b/static/src/stores/personalization.ts @@ -6,6 +6,7 @@ interface PersonalForm { enabled: boolean; auto_generate_title: boolean; tool_intent_enabled: boolean; + skill_hints_enabled: boolean; silent_tool_disable: boolean; enhanced_tool_display: boolean; enabled_skills: string[]; @@ -63,6 +64,7 @@ const defaultForm = (): PersonalForm => ({ enabled: false, auto_generate_title: true, tool_intent_enabled: true, + skill_hints_enabled: false, silent_tool_disable: false, enhanced_tool_display: true, enabled_skills: [], @@ -196,6 +198,7 @@ export const usePersonalizationStore = defineStore('personalization', { enabled: !!data.enabled, auto_generate_title: data.auto_generate_title !== false, tool_intent_enabled: !!data.tool_intent_enabled, + skill_hints_enabled: !!data.skill_hints_enabled, silent_tool_disable: !!data.silent_tool_disable, enhanced_tool_display: data.enhanced_tool_display !== false, enabled_skills: Array.isArray(data.enabled_skills) diff --git a/static/src/utils/chatDisplay.ts b/static/src/utils/chatDisplay.ts index 0073142..7ee6ab0 100644 --- a/static/src/utils/chatDisplay.ts +++ b/static/src/utils/chatDisplay.ts @@ -9,8 +9,6 @@ const RUNNING_ANIMATIONS: Record = { rename_file: 'file-animation', write_file: 'file-animation', edit_file: 'file-animation', - modify_file: 'file-animation', - append_to_file: 'file-animation', create_folder: 'file-animation', web_search: 'search-animation', extract_webpage: 'search-animation', @@ -36,8 +34,6 @@ const RUNNING_STATUS_TEXTS: Record = { rename_file: '正在重命名文件...', write_file: '正在写入文件...', edit_file: '正在编辑文件...', - modify_file: '正在修改文件...', - append_to_file: '正在追加文件...', create_folder: '正在创建文件夹...', web_search: '正在搜索网络...', extract_webpage: '正在提取网页...', @@ -57,8 +53,6 @@ const COMPLETED_STATUS_TEXTS: Record = { rename_file: '文件重命名成功', write_file: '文件写入完成', edit_file: '文件编辑完成', - modify_file: '文件修改成功', - append_to_file: '文件追加完成', create_folder: '文件夹创建成功', web_search: '搜索完成', extract_webpage: '网页提取完成', diff --git a/static/src/utils/icons.ts b/static/src/utils/icons.ts index 21dfcfb..c877de9 100644 --- a/static/src/utils/icons.ts +++ b/static/src/utils/icons.ts @@ -41,14 +41,12 @@ export const ICONS = Object.freeze({ }); export const TOOL_ICON_MAP = Object.freeze({ - append_to_file: 'pencil', close_sub_agent: 'bot', create_file: 'file', create_folder: 'folder', create_sub_agent: 'bot', delete_file: 'trash', extract_webpage: 'globe', - modify_file: 'pencil', write_file: 'pencil', edit_file: 'pencil', vlm_analyze: 'camera', diff --git a/utils/api_client.py b/utils/api_client.py index 3700e41..a38c936 100644 --- a/utils/api_client.py +++ b/utils/api_client.py @@ -831,9 +831,6 @@ class DeepSeekClient: full_response = "" tool_calls = [] current_thinking = "" - # 针对 append_to_file / modify_file 的占位结构,防止未定义变量导致异常 - append_result = {"handled": False} - modify_result = {"handled": False} # 状态标志 in_thinking = False @@ -935,10 +932,6 @@ class DeepSeekClient: # 添加正式回复内容(如果有) if full_response: assistant_content_parts.append(full_response) - elif append_result["handled"] and append_result["assistant_content"]: - assistant_content_parts.append(append_result["assistant_content"]) - elif modify_result["handled"] and modify_result.get("assistant_content"): - assistant_content_parts.append(modify_result["assistant_content"]) # 添加工具调用说明 if tool_calls: @@ -1000,31 +993,6 @@ class DeepSeekClient: self._print(f"\n{OUTPUT_FORMATS['action']} 调用工具: {function_name}") - # 额外的参数长度检查(针对特定工具) - if function_name == "modify_file" and "content" in arguments: - content_length = len(arguments.get("content", "")) - if content_length > 9999999999: # 降低到50KB限制 - error_msg = f"内容过长({content_length}字符),超过50KB限制" - self._print(f"{OUTPUT_FORMATS['warning']} {error_msg}") - - messages.append({ - "role": "tool", - "tool_call_id": tool_call["id"], - "name": function_name, - "content": json.dumps({ - "success": False, - "error": error_msg, - "suggestion": "请将内容分成多个小块分别修改,或使用replace操作只修改必要部分" - }, ensure_ascii=False) - }) - - all_tool_results.append({ - "tool": function_name, - "args": arguments, - "result": error_msg - }) - continue - tool_result = await tool_handler(function_name, arguments) # 解析工具结果,提取关键信息