fix: improve write_file_diff diagnostics

This commit is contained in:
JOJO 2025-12-01 23:44:56 +08:00
parent 97da631e01
commit 51bb0f1033
6 changed files with 707 additions and 202 deletions

View File

@ -75,17 +75,23 @@ class MainTerminal:
self,
project_path: str,
thinking_mode: bool = False,
run_mode: Optional[str] = None,
data_dir: Optional[str] = None,
container_session: Optional["ContainerHandle"] = None,
usage_tracker: Optional[object] = None,
):
self.project_path = project_path
self.thinking_mode = thinking_mode # False=快速模式, True=思考模式
allowed_modes = {"fast", "thinking", "deep"}
initial_mode = run_mode if run_mode in allowed_modes else ("thinking" if thinking_mode else "fast")
self.run_mode = initial_mode
self.thinking_mode = initial_mode != "fast" # False=快速模式, True=思考模式
self.deep_thinking_mode = initial_mode == "deep"
self.data_dir = Path(data_dir).expanduser().resolve() if data_dir else Path(DATA_DIR).resolve()
self.usage_tracker = usage_tracker
# 初始化组件
self.api_client = DeepSeekClient(thinking_mode=thinking_mode)
self.api_client = DeepSeekClient(thinking_mode=self.thinking_mode)
self.api_client.set_deep_thinking_mode(self.deep_thinking_mode)
self.context_manager = ContextManager(project_path, data_dir=str(self.data_dir))
self.context_manager.main_terminal = self
self.container_mount_path = TERMINAL_SANDBOX_MOUNT_PATH or "/workspace"
@ -232,7 +238,8 @@ class MainTerminal:
conversation_id = self.context_manager.start_new_conversation(
project_path=self.project_path,
thinking_mode=self.thinking_mode
thinking_mode=self.thinking_mode,
run_mode=self.run_mode
)
print(f"{OUTPUT_FORMATS['info']} 新建对话: {conversation_id}")
@ -618,7 +625,7 @@ class MainTerminal:
# 如果是思考模式,每个新任务重置状态
# 注意:这里重置的是当前任务的第一次调用标志,确保新用户请求重新思考
if self.thinking_mode:
self.api_client.start_new_task()
self.api_client.start_new_task(force_deep=self.deep_thinking_mode)
# 新增:开始新的任务会话
self.current_session_id += 1
@ -837,7 +844,7 @@ class MainTerminal:
# 如果是思考模式,重置状态(下次任务会重新思考)
if self.thinking_mode:
self.api_client.start_new_task()
self.api_client.start_new_task(force_deep=self.deep_thinking_mode)
self.current_session_id += 1
@ -859,7 +866,7 @@ class MainTerminal:
# 重置相关状态
if self.thinking_mode:
self.api_client.start_new_task()
self.api_client.start_new_task(force_deep=self.deep_thinking_mode)
self.current_session_id += 1
@ -913,7 +920,7 @@ class MainTerminal:
terminal_status = self.terminal_manager.list_terminals()
# 思考模式状态
thinking_status = '思考模式' if self.thinking_mode else '快速模式'
thinking_status = self.get_run_mode_label()
if self.thinking_mode:
thinking_status += f" ({'等待新任务' if self.api_client.current_task_first_call else '任务进行中'})"
@ -2319,7 +2326,11 @@ class MainTerminal:
if sub_agent_prompt:
messages.append({"role": "system", "content": sub_agent_prompt})
if self.thinking_mode:
if self.deep_thinking_mode:
deep_prompt = self.load_prompt("deep_thinking_mode_guidelines").strip()
if deep_prompt:
messages.append({"role": "system", "content": deep_prompt})
elif self.thinking_mode:
thinking_prompt = self.load_prompt("thinking_mode_guidelines").strip()
if thinking_prompt:
messages.append({"role": "system", "content": thinking_prompt})
@ -2346,6 +2357,9 @@ class MainTerminal:
"role": conv["role"],
"content": conv["content"]
}
reasoning = conv.get("reasoning_content")
if reasoning:
message["reasoning_content"] = reasoning
# 如果有工具调用信息,添加到消息中
tool_calls = conv.get("tool_calls") or []
if tool_calls and self._tool_calls_followed_by_tools(conversation, idx, tool_calls):
@ -2534,17 +2548,55 @@ class MainTerminal:
print(f"\n📁 项目文件结构:")
print(self.context_manager._build_file_tree(structure))
print(f"\n总计: {structure['total_files']} 个文件, {structure['total_size'] / 1024 / 1024:.2f} MB")
async def toggle_mode(self, args: str = ""):
"""切换运行模式(简化版)"""
if self.thinking_mode:
# 当前是思考模式,切换到快速模式
self.thinking_mode = False
self.api_client.thinking_mode = False
print(f"{OUTPUT_FORMATS['info']} 已切换到: 快速模式(不思考)")
else:
# 当前是快速模式,切换到思考模式
self.thinking_mode = True
self.api_client.thinking_mode = True
def set_run_mode(self, mode: str) -> str:
"""统一设置运行模式"""
allowed = ["fast", "thinking", "deep"]
normalized = mode.lower()
if normalized not in allowed:
raise ValueError(f"不支持的模式: {mode}")
previous_mode = getattr(self, "run_mode", "fast")
self.run_mode = normalized
self.thinking_mode = normalized != "fast"
self.deep_thinking_mode = normalized == "deep"
self.api_client.thinking_mode = self.thinking_mode
self.api_client.set_deep_thinking_mode(self.deep_thinking_mode)
if self.deep_thinking_mode:
self.api_client.force_thinking_next_call = False
self.api_client.skip_thinking_next_call = False
if not self.thinking_mode:
self.api_client.start_new_task()
print(f"{OUTPUT_FORMATS['info']} 已切换到: 思考模式(智能思考)")
elif previous_mode == "deep" and normalized != "deep":
self.api_client.start_new_task()
return self.run_mode
def get_run_mode_label(self) -> str:
labels = {
"fast": "快速模式(无思考)",
"thinking": "思考模式(首次调用使用思考模型)",
"deep": "深度思考模式(整轮使用思考模型)"
}
return labels.get(self.run_mode, "快速模式(无思考)")
async def toggle_mode(self, args: str = ""):
"""切换运行模式"""
modes = ["fast", "thinking", "deep"]
target_mode = ""
if args:
candidate = args.strip().lower()
if candidate not in modes:
print(f"{OUTPUT_FORMATS['error']} 无效模式: {args}。可选: fast / thinking / deep")
return
target_mode = candidate
else:
current_index = modes.index(self.run_mode) if self.run_mode in modes else 0
target_mode = modes[(current_index + 1) % len(modes)]
if target_mode == self.run_mode:
print(f"{OUTPUT_FORMATS['info']} 当前已是 {self.get_run_mode_label()}")
return
try:
self.set_run_mode(target_mode)
print(f"{OUTPUT_FORMATS['info']} 已切换到: {self.get_run_mode_label()}")
except ValueError as exc:
print(f"{OUTPUT_FORMATS['error']} {exc}")

View File

@ -3,7 +3,8 @@
import os
import shutil
from pathlib import Path
from typing import Optional, Dict, List, Tuple, TYPE_CHECKING
import re
from typing import Optional, Dict, List, Set, Tuple, TYPE_CHECKING
from datetime import datetime
try:
from config import (
@ -673,6 +674,316 @@ class FileManager:
def append_file(self, path: str, content: str) -> Dict:
"""追加内容到文件"""
return self.write_file(path, content, mode="a")
def _parse_diff_patch(self, patch_text: str) -> Dict:
"""解析统一diff格式的补丁转换为 apply_modify_blocks 所需的块结构。"""
if not patch_text or "*** Begin Patch" not in patch_text or "*** End Patch" not in patch_text:
return {
"success": False,
"error": "补丁格式错误:缺少 *** Begin Patch / *** End Patch 标记。"
}
start = patch_text.find("*** Begin Patch")
end = patch_text.rfind("*** End Patch")
if end <= start:
return {
"success": False,
"error": "补丁格式错误:结束标记位置异常。"
}
body = patch_text[start + len("*** Begin Patch"):end]
lines = body.splitlines(True) # 保留换行符,便于逐字匹配
blocks: List[Dict] = []
current_block: Optional[Dict] = None
auto_index = 1
id_pattern = re.compile(r"\[id:\s*(\d+)\]", re.IGNORECASE)
for raw_line in lines:
stripped = raw_line.strip()
if not stripped and current_block is None:
continue
if stripped.startswith("@@"):
if current_block:
if not current_block["lines"]:
return {
"success": False,
"error": f"补丁块缺少内容:{current_block.get('header', '').strip()}"
}
blocks.append(current_block)
header = stripped
id_match = id_pattern.search(header)
block_id: Optional[int] = None
if id_match:
try:
block_id = int(id_match.group(1))
except ValueError:
return {
"success": False,
"error": f"补丁块编号必须是整数:{header}"
}
current_block = {"id": block_id, "header": header, "lines": []}
continue
if current_block is None:
if stripped:
return {
"success": False,
"error": "补丁格式错误:在检测到第一个 @@ 块之前出现内容。"
}
continue
if raw_line.startswith("\\ No newline at end of file"):
continue
current_block["lines"].append(raw_line)
if current_block:
if not current_block["lines"]:
return {
"success": False,
"error": f"补丁块缺少内容:{current_block.get('header', '').strip()}"
}
blocks.append(current_block)
if not blocks:
return {
"success": False,
"error": "补丁格式错误:未检测到任何 @@ [id:n] 块。"
}
parsed_blocks: List[Dict] = []
used_indices: Set[int] = set()
for block in blocks:
idx = block["id"]
if idx is None:
while auto_index in used_indices:
auto_index += 1
idx = auto_index
auto_index += 1
elif idx in used_indices:
while idx in used_indices:
idx += 1
auto_index = max(auto_index, idx + 1)
used_indices.add(idx)
old_lines: List[str] = []
new_lines: List[str] = []
has_anchor = False
has_content = False
for line in block["lines"]:
if not line:
continue
prefix = line[0]
if prefix == ' ':
has_anchor = True
old_lines.append(line[1:])
new_lines.append(line[1:])
has_content = True
elif prefix == '-':
has_anchor = True
old_lines.append(line[1:])
has_content = True
elif prefix == '+':
new_lines.append(line[1:])
has_content = True
else:
# 容忍空白符或意外格式,直接作为上下文
old_lines.append(line)
new_lines.append(line)
has_anchor = True
has_content = True
if not has_content:
return {
"success": False,
"error": f"补丁块 {idx} 未包含任何 + / - / 上下文行。"
}
append_only = False
if not has_anchor:
append_only = True
old_text = "".join(old_lines)
new_text = "".join(new_lines)
raw_patch = f"{block['header']}\n{''.join(block['lines'])}"
parsed_blocks.append({
"index": idx,
"old": old_text,
"new": new_text,
"append_only": append_only,
"raw_patch": raw_patch
})
return {
"success": True,
"blocks": parsed_blocks
}
def apply_diff_patch(self, path: str, patch_text: str) -> Dict:
"""解析统一diff并写入文件支持多块依次执行。"""
valid, error, full_path = self._validate_path(path)
if not valid:
return {"success": False, "error": error}
parse_result = self._parse_diff_patch(patch_text)
if not parse_result.get("success"):
return parse_result
blocks = parse_result.get("blocks") or []
if not blocks:
return {
"success": False,
"error": "未检测到有效的补丁块。"
}
relative_path = str(self._relative_path(full_path))
parsed_blocks: List[Dict] = blocks
block_lookup: Dict[int, Dict] = {block["index"]: block for block in parsed_blocks}
def attach_block_context(entries: Optional[List[Dict]]):
if not entries:
return
for entry in entries:
if not isinstance(entry, dict):
continue
idx = entry.get("index")
if idx is None:
continue
block_info = block_lookup.get(idx)
if not block_info:
continue
patch_text = block_info.get("raw_patch")
if patch_text:
entry.setdefault("block_patch", patch_text)
if "old_text" not in entry:
entry["old_text"] = block_info.get("old")
if "new_text" not in entry:
entry["new_text"] = block_info.get("new")
entry.setdefault("append_only", block_info.get("append_only", False))
append_only_blocks = [b for b in parsed_blocks if b.get("append_only")]
modify_blocks = [
{"index": b["index"], "old": b["old"], "new": b["new"]}
for b in parsed_blocks
if not b.get("append_only")
]
apply_result = {"results": []}
completed_indices: List[int] = []
failed_entries: List[Dict] = []
write_error = None
if modify_blocks:
modify_result = self.apply_modify_blocks(path, modify_blocks)
apply_result.update(modify_result)
completed_indices.extend(modify_result.get("completed", []))
failed_entries.extend(modify_result.get("failed", []))
write_error = modify_result.get("error")
else:
apply_result.update({
"success": True,
"completed": [],
"failed": [],
"results": [],
"write_performed": False,
"error": None
})
results_blocks = apply_result.get("results", []).copy()
append_results: List[Dict] = []
append_bytes = 0
append_lines_total = 0
append_success = True
if append_only_blocks:
try:
with open(full_path, 'a', encoding='utf-8') as f:
for block in append_only_blocks:
chunk = block.get("new", "")
if not chunk:
append_results.append({
"index": block["index"],
"status": "failed",
"reason": "追加块为空"
})
failed_entries.append({
"index": block["index"],
"reason": "追加块为空"
})
append_success = False
continue
f.write(chunk)
added_lines = chunk.count('\n')
if chunk and not chunk.endswith('\n'):
added_lines += 1
append_lines_total += added_lines
append_bytes += len(chunk.encode('utf-8'))
append_results.append({
"index": block["index"],
"status": "success",
"removed_lines": 0,
"added_lines": added_lines
})
completed_indices.append(block["index"])
except Exception as e:
append_success = False
write_error = f"追加写入失败: {e}"
append_results.append({
"index": append_only_blocks[-1]["index"],
"status": "failed",
"reason": str(e)
})
failed_entries.append({
"index": append_only_blocks[-1]["index"],
"reason": str(e)
})
attach_block_context(failed_entries)
total_blocks = len(parsed_blocks)
completed_unique = sorted(set(completed_indices))
summary_parts = [
f"{relative_path} 应用 {total_blocks} 个补丁块",
f"成功 {len(completed_unique)}",
f"失败 {len(failed_entries)}"
]
if append_only_blocks:
summary_parts.append(f"追加 {len(append_only_blocks)} 块,写入 {append_lines_total} 行({append_bytes} 字节)")
if write_error:
summary_parts.append(write_error)
summary = "".join(summary_parts)
results_blocks.extend(append_results)
apply_result["results"] = results_blocks
apply_result["blocks"] = results_blocks
apply_result["path"] = relative_path
apply_result["total_blocks"] = total_blocks
apply_result["summary"] = summary
apply_result["message"] = summary
apply_result["completed"] = completed_unique
apply_result["failed"] = failed_entries
apply_result["append_bytes"] = append_bytes
apply_result["append_blocks"] = len(append_only_blocks)
apply_result["success"] = (
append_success
and apply_result.get("success", True)
and not failed_entries
and not write_error
)
apply_result["error"] = write_error
return apply_result
def apply_modify_blocks(self, path: str, blocks: List[Dict]) -> Dict:
"""
@ -815,7 +1126,7 @@ class FileManager:
return {
"success": False,
"error": "要替换的文本过长,可能导致性能问题",
"suggestion": "请拆分内容或使用 modify_file 提交结构化补丁"
"suggestion": "请拆分内容或使用 write_file_diff 提交结构化补丁"
}
if new_text and len(new_text) > 9999999999:

View File

@ -487,10 +487,10 @@ class ContextManager:
if ("<<<APPEND" in content) or metadata.get("append_payload"):
new_msg["content"] = append_placeholder
compressed_types.add("append_to_file")
compressed_types.add("write_file_diff")
elif ("<<<MODIFY" in content) or metadata.get("modify_payload"):
new_msg["content"] = append_placeholder
compressed_types.add("modify_file")
compressed_types.add("write_file_diff")
elif role == "tool":
tool_name = new_msg.get("name")
@ -498,6 +498,9 @@ class ContextManager:
if tool_name == "read_file":
new_msg["content"] = append_placeholder
compressed_types.add("read_file")
elif tool_name == "write_file_diff":
new_msg["content"] = append_placeholder
compressed_types.add("write_file_diff")
else:
payload = None
@ -532,13 +535,12 @@ class ContextManager:
}
type_labels = {
"append_to_file": "文件追加输出",
"modify_file": "文件修改输出",
"write_file_diff": "文件补丁输出",
"extract_webpage": "网页提取内容",
"read_file": "文件读取内容"
}
ordered_types = [type_labels[t] for t in ["append_to_file", "modify_file", "extract_webpage", "read_file"] if t in compressed_types]
ordered_types = [type_labels[t] for t in ["write_file_diff", "extract_webpage", "read_file"] if t in compressed_types]
summary_text = "系统提示:对话已压缩,之前的对话记录中的以下内容被替换为占位文本:" + "".join(ordered_types) + "。其他内容不受影响,如需查看原文,请查看对应文件或重新执行相关工具。"
system_message = {
@ -952,7 +954,15 @@ class ContextManager:
lines = []
project_name = Path(structure['path']).name
lines.append(f"📁 {project_name}/")
container_root = (self.container_mount_path or "").strip() or "/workspace"
container_root = container_root.rstrip("/") or "/"
if container_root == "/":
root_label = f"/ (项目根)"
elif container_root.endswith(project_name):
root_label = f"{container_root} (项目根)"
else:
root_label = f"{container_root} (映射自 {project_name})"
lines.append(f"📁 {root_label}/")
def build_tree_recursive(tree_dict: Dict, prefix: str = ""):
"""递归构建树形结构"""
@ -1138,6 +1148,9 @@ class ContextManager:
"role": conv["role"],
"content": conv["content"]
}
reasoning = conv.get("reasoning_content")
if reasoning:
message["reasoning_content"] = reasoning
if "tool_calls" in conv and conv["tool_calls"]:
message["tool_calls"] = conv["tool_calls"]
messages.append(message)

View File

@ -179,6 +179,72 @@ def format_tool_result_notice(tool_name: str, tool_call_id: Optional[str], conte
body = "(无附加输出)"
return f"{header}\n{body}"
def format_tool_result_for_context(function_name: str, result_data: Any, raw_text: str) -> str:
if function_name == "read_file" and isinstance(result_data, dict):
return format_read_file_result(result_data)
if function_name == "write_file_diff" and isinstance(result_data, dict):
path = result_data.get("path", "目标文件")
summary = result_data.get("summary") or result_data.get("message")
completed = result_data.get("completed") or []
failed_blocks = result_data.get("failed") or []
lines = [f"[文件补丁] {path}"]
if summary:
lines.append(summary)
if completed:
lines.append(f"✅ 成功块: {', '.join(str(i) for i in completed)}")
if failed_blocks:
fail_descriptions = []
for item in failed_blocks[:3]:
idx = item.get("index")
reason = item.get("reason") or item.get("error") or "未说明原因"
fail_descriptions.append(f"#{idx}: {reason}")
lines.append("⚠️ 失败块: " + "".join(fail_descriptions))
if len(failed_blocks) > 3:
lines.append(f"(其余 {len(failed_blocks) - 3} 个失败块略)")
detail_sections: List[str] = []
for item in failed_blocks:
idx = item.get("index")
reason = item.get("reason") or item.get("error") or "未说明原因"
block_patch = item.get("block_patch") or item.get("patch")
if not block_patch:
old_text = item.get("old_text") or ""
new_text = item.get("new_text") or ""
synthetic_lines: List[str] = []
if old_text:
synthetic_lines.extend(f"-{line}" for line in old_text.splitlines())
if new_text:
synthetic_lines.extend(f"+{line}" for line in new_text.splitlines())
if synthetic_lines:
block_patch = "\n".join(synthetic_lines)
detail_sections.append(f"- #{idx}: {reason}")
if block_patch:
detail_sections.append("```diff")
detail_sections.append(block_patch.rstrip("\n"))
detail_sections.append("```")
detail_sections.append("")
if detail_sections and detail_sections[-1] == "":
detail_sections.pop()
if detail_sections:
lines.append("⚠️ 失败块详情:")
lines.extend(detail_sections)
if result_data.get("success") is False and result_data.get("error"):
lines.append(f"⚠️ 错误: {result_data.get('error')}")
formatted = "\n".join(line for line in lines if line)
return formatted or raw_text
if isinstance(result_data, dict):
parts = []
summary = result_data.get("summary") or result_data.get("message")
if summary:
parts.append(str(summary))
error_msg = result_data.get("error")
if error_msg:
parts.append(f"⚠️ 错误: {error_msg}")
if parts:
return "\n".join(parts)
return raw_text
def _format_relative_path(path: Optional[str], workspace: Optional[str]) -> str:
"""将绝对路径转换为相对 workspace 的表示,默认返回原始路径。"""
@ -2508,8 +2574,8 @@ def detect_malformed_tool_call(text):
return True
# 检测特定的工具名称后跟JSON
tool_names = ['create_file', 'read_file', 'modify_file', 'delete_file',
'append_to_file', 'terminal_session', 'terminal_input', 'web_search',
tool_names = ['create_file', 'read_file', 'write_file_diff', 'delete_file',
'terminal_session', 'terminal_input', 'web_search',
'extract_webpage', 'save_webpage',
'run_python', 'run_command', 'focus_file', 'unfocus_file', 'sleep']
for tool in tool_names:
@ -3314,10 +3380,10 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
# 通过文本内容提前检测工具调用意图
if not detected_tools:
# 检测常见的工具调用模式
tool_patterns = [
(r'(创建|新建|生成).*(文件|file)', 'create_file'),
(r'(读取|查看|打开).*(文件|file)', 'read_file'),
(r'(修改|编辑|更新).*(文件|file)', 'modify_file'),
tool_patterns = [
(r'(创建|新建|生成).*(文件|file)', 'create_file'),
(r'(读取|查看|打开).*(文件|file)', 'read_file'),
(r'(修改|编辑|更新).*(文件|file)', 'write_file_diff'),
(r'(删除|移除).*(文件|file)', 'delete_file'),
(r'(搜索|查找|search)', 'web_search'),
(r'(执行|运行).*(Python|python|代码)', 'run_python'),
@ -3815,6 +3881,8 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
"content": assistant_content,
"tool_calls": tool_calls
}
if current_thinking:
assistant_message["reasoning_content"] = current_thinking
messages.append(assistant_message)
@ -4003,57 +4071,12 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
action_message = None
awaiting_flag = False
if function_name == "append_to_file":
if result_data.get("success") and result_data.get("awaiting_content"):
append_path = result_data.get("path") or arguments.get("path")
pending_append = {
"path": append_path,
"tool_call_id": tool_call_id,
"buffer": "",
"start_marker": f"<<<APPEND:{append_path}>>>",
"end_marker": "<<<END_APPEND>>>",
"content_start": None,
"end_index": None,
"display_id": tool_display_id
}
append_probe_buffer = ""
awaiting_flag = True
action_status = 'running'
action_message = f"正在向 {append_path} 追加内容..."
text_started = False
text_streaming = False
text_has_content = False
debug_log(f"append_to_file 等待输出: {append_path}")
else:
debug_log("append_to_file 返回完成状态")
elif function_name == "modify_file":
if result_data.get("success") and result_data.get("awaiting_content"):
modify_path = result_data.get("path") or arguments.get("path")
pending_modify = {
"path": modify_path,
"tool_call_id": tool_call_id,
"buffer": "",
"raw_buffer": "",
"start_marker": f"<<<MODIFY:{modify_path}>>>",
"end_marker": "<<<END_MODIFY>>>",
"start_seen": False,
"end_index": None,
"display_id": tool_display_id,
"detected_blocks": set(),
"probe_buffer": ""
}
modify_probe_buffer = ""
if hasattr(web_terminal, "pending_modify_request"):
web_terminal.pending_modify_request = {"path": modify_path}
awaiting_flag = True
action_status = 'running'
action_message = f"正在修改 {modify_path}..."
text_started = False
text_streaming = False
text_has_content = False
debug_log(f"modify_file 等待输出: {modify_path}")
else:
debug_log("modify_file 返回完成状态")
if function_name == "write_file_diff":
diff_path = result_data.get("path") or arguments.get("path")
summary = result_data.get("summary") or result_data.get("message")
if summary:
action_message = summary
debug_log(f"write_file_diff 执行完成: {summary or '无摘要'}")
if function_name == "wait_sub_agent":
system_msg = result_data.get("system_message")
@ -4081,7 +4104,7 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
sender('update_action', update_payload)
# 更新UI状态
if function_name in ['focus_file', 'unfocus_file', 'modify_file']:
if function_name in ['focus_file', 'unfocus_file', 'write_file_diff']:
sender('focused_files_update', web_terminal.get_focused_files_info())
if function_name in ['create_file', 'delete_file', 'rename_file', 'create_folder']:
@ -4089,13 +4112,11 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
sender('file_tree_update', structure)
# ===== 增量保存:立即保存工具结果 =====
try:
result_data = json.loads(tool_result)
if function_name == "read_file":
tool_result_content = format_read_file_result(result_data)
else:
tool_result_content = tool_result
except:
metadata_payload = None
if isinstance(result_data, dict):
tool_result_content = format_tool_result_for_context(function_name, result_data, tool_result)
metadata_payload = {"tool_payload": result_data}
else:
tool_result_content = tool_result
# 立即保存工具结果
@ -4103,7 +4124,8 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
"tool",
tool_result_content,
tool_call_id=tool_call_id,
name=function_name
name=function_name,
metadata=metadata_payload
)
debug_log(f"💾 增量保存:工具结果 {function_name}")
system_message = result_data.get("system_message") if isinstance(result_data, dict) else None
@ -4121,7 +4143,7 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
"content": tool_result_content
})
if function_name not in ['append_to_file', 'modify_file']:
if function_name != 'write_file_diff':
await process_sub_agent_updates(messages, inline=True, after_tool_call_id=tool_call_id)
await asyncio.sleep(0.2)

View File

@ -236,7 +236,7 @@ class ContextManager:
# 新增:对话持久化相关方法
# ===========================================
def start_new_conversation(self, project_path: str = None, thinking_mode: bool = False) -> str:
def start_new_conversation(self, project_path: str = None, thinking_mode: bool = False, run_mode: Optional[str] = None) -> str:
"""
开始新对话
@ -258,6 +258,7 @@ class ContextManager:
conversation_id = self.conversation_manager.create_conversation(
project_path=project_path,
thinking_mode=thinking_mode,
run_mode=run_mode or ("thinking" if thinking_mode else "fast"),
initial_messages=[]
)
@ -312,6 +313,18 @@ class ContextManager:
self.project_path = resolved_project_path
run_mode = metadata.get("run_mode")
if self.main_terminal:
try:
if run_mode:
self.main_terminal.set_run_mode(run_mode)
elif metadata.get("thinking_mode"):
self.main_terminal.set_run_mode("thinking")
else:
self.main_terminal.set_run_mode("fast")
except Exception:
pass
print(f"📖 加载对话: {conversation_id} - {conversation_data.get('title', '未知标题')}")
print(f"📊 包含 {len(self.conversation_history)} 条消息")
@ -332,12 +345,14 @@ class ContextManager:
return False
try:
run_mode = getattr(self.main_terminal, "run_mode", None) if hasattr(self, "main_terminal") else None
success = self.conversation_manager.save_conversation(
conversation_id=self.current_conversation_id,
messages=self.conversation_history,
project_path=str(self.project_path),
todo_list=self.todo_list,
thinking_mode=getattr(self.main_terminal, "thinking_mode", None) if hasattr(self, "main_terminal") else None
thinking_mode=getattr(self.main_terminal, "thinking_mode", None) if hasattr(self, "main_terminal") else None,
run_mode=run_mode
)
if success:
@ -357,11 +372,14 @@ class ContextManager:
if not force and not self.conversation_history:
return
try:
run_mode = getattr(self.main_terminal, "run_mode", None) if hasattr(self, "main_terminal") else None
self.conversation_manager.save_conversation(
conversation_id=self.current_conversation_id,
messages=self.conversation_history,
project_path=str(self.project_path),
todo_list=self.todo_list
todo_list=self.todo_list,
thinking_mode=getattr(self.main_terminal, "thinking_mode", None) if hasattr(self, "main_terminal") else None,
run_mode=run_mode
)
# 静默保存,不输出日志
except Exception as e:
@ -434,10 +452,10 @@ class ContextManager:
if ("<<<APPEND" in content) or metadata.get("append_payload"):
new_msg["content"] = append_placeholder
compressed_types.add("append_to_file")
compressed_types.add("write_file_diff")
elif ("<<<MODIFY" in content) or metadata.get("modify_payload"):
new_msg["content"] = append_placeholder
compressed_types.add("modify_file")
compressed_types.add("write_file_diff")
elif role == "tool":
tool_name = new_msg.get("name")
@ -445,6 +463,9 @@ class ContextManager:
if tool_name == "read_file":
new_msg["content"] = append_placeholder
compressed_types.add("read_file")
elif tool_name == "write_file_diff":
new_msg["content"] = append_placeholder
compressed_types.add("write_file_diff")
else:
payload = None
@ -479,13 +500,12 @@ class ContextManager:
}
type_labels = {
"append_to_file": "文件追加输出",
"modify_file": "文件修改输出",
"write_file_diff": "文件补丁输出",
"extract_webpage": "网页提取内容",
"read_file": "文件读取内容"
}
ordered_types = [type_labels[t] for t in ["append_to_file", "modify_file", "extract_webpage", "read_file"] if t in compressed_types]
ordered_types = [type_labels[t] for t in ["write_file_diff", "extract_webpage", "read_file"] if t in compressed_types]
summary_text = "系统提示:对话已压缩,之前的对话记录中的以下内容被替换为占位文本:" + "".join(ordered_types) + "。其他内容不受影响,如需查看原文,请查看对应文件或重新执行相关工具。"
system_message = {
@ -505,10 +525,12 @@ class ContextManager:
resolved_project_path = self._resolve_project_path_from_metadata(metadata)
project_path = str(resolved_project_path)
thinking_mode = metadata.get("thinking_mode", False)
run_mode = metadata.get("run_mode") or ("thinking" if thinking_mode else "fast")
compressed_conversation_id = self.conversation_manager.create_conversation(
project_path=project_path,
thinking_mode=thinking_mode,
run_mode=run_mode,
initial_messages=compressed_messages
)
@ -534,10 +556,12 @@ class ContextManager:
resolved_project_path = self._resolve_project_path_from_metadata(metadata)
project_path = str(resolved_project_path)
thinking_mode = metadata.get("thinking_mode", False)
run_mode = metadata.get("run_mode") or ("thinking" if thinking_mode else "fast")
duplicate_conversation_id = self.conversation_manager.create_conversation(
project_path=project_path,
thinking_mode=thinking_mode,
run_mode=run_mode,
initial_messages=original_messages
)
@ -620,18 +644,24 @@ class ContextManager:
reasoning_content: Optional[str] = None
):
"""添加对话记录(改进版:集成自动保存 + 智能token统计"""
message = {
"role": role,
"content": content,
"timestamp": datetime.now().isoformat()
}
timestamp = datetime.now().isoformat()
if role == "assistant":
message = {
"role": role,
"reasoning_content": reasoning_content if reasoning_content is not None else "",
"content": content or "",
"timestamp": timestamp
}
else:
message = {
"role": role,
"content": content,
"timestamp": timestamp
}
if metadata:
message["metadata"] = metadata
if reasoning_content:
message["reasoning_content"] = reasoning_content
# 如果是assistant消息且有工具调用保存完整格式
if role == "assistant" and tool_calls:
# 确保工具调用格式完整
@ -880,7 +910,15 @@ class ContextManager:
lines = []
project_name = Path(structure['path']).name
lines.append(f"📁 {project_name}/")
container_root = (self.container_mount_path or "").strip() or "/workspace"
container_root = container_root.rstrip("/") or "/"
if container_root == "/":
root_label = f"/ (项目根)"
elif container_root.endswith(project_name):
root_label = f"{container_root} (项目根)"
else:
root_label = f"{container_root} (映射自 {project_name})"
lines.append(f"📁 {root_label}/")
def build_tree_recursive(tree_dict: Dict, prefix: str = ""):
"""递归构建树形结构"""
@ -1066,6 +1104,9 @@ class ContextManager:
"role": conv["role"],
"content": conv["content"]
}
reasoning = conv.get("reasoning_content")
if reasoning:
message["reasoning_content"] = reasoning
if "tool_calls" in conv and conv["tool_calls"]:
message["tool_calls"] = conv["tool_calls"]
messages.append(message)

View File

@ -358,6 +358,73 @@ def format_tool_result_notice(tool_name: str, tool_call_id: Optional[str], conte
body = "(无附加输出)"
return f"{header}\n{body}"
def format_tool_result_for_context(function_name: str, result_data: Any, raw_text: str) -> str:
"""将工具结果转成适合写入对话的简洁文本。"""
if function_name == "read_file" and isinstance(result_data, dict):
return format_read_file_result(result_data)
if function_name == "write_file_diff" and isinstance(result_data, dict):
path = result_data.get("path", "目标文件")
summary = result_data.get("summary") or result_data.get("message")
completed = result_data.get("completed") or []
failed_blocks = result_data.get("failed") or []
lines = [f"[文件补丁] {path}"]
if summary:
lines.append(summary)
if completed:
lines.append(f"✅ 成功块: {', '.join(str(i) for i in completed)}")
if failed_blocks:
fail_descriptions = []
for item in failed_blocks[:3]:
idx = item.get("index")
reason = item.get("reason") or item.get("error") or "未说明原因"
fail_descriptions.append(f"#{idx}: {reason}")
lines.append("⚠️ 失败块: " + "".join(fail_descriptions))
if len(failed_blocks) > 3:
lines.append(f"(其余 {len(failed_blocks) - 3} 个失败块略)")
detail_sections: List[str] = []
for item in failed_blocks:
idx = item.get("index")
reason = item.get("reason") or item.get("error") or "未说明原因"
block_patch = item.get("block_patch") or item.get("patch")
if not block_patch:
old_text = item.get("old_text") or ""
new_text = item.get("new_text") or ""
synthetic_lines: List[str] = []
if old_text:
synthetic_lines.extend(f"-{line}" for line in old_text.splitlines())
if new_text:
synthetic_lines.extend(f"+{line}" for line in new_text.splitlines())
if synthetic_lines:
block_patch = "\n".join(synthetic_lines)
detail_sections.append(f"- #{idx}: {reason}")
if block_patch:
detail_sections.append("```diff")
detail_sections.append(block_patch.rstrip("\n"))
detail_sections.append("```")
detail_sections.append("")
if detail_sections and detail_sections[-1] == "":
detail_sections.pop()
if detail_sections:
lines.append("⚠️ 失败块详情:")
lines.extend(detail_sections)
if result_data.get("success") is False and result_data.get("error"):
lines.append(f"⚠️ 错误: {result_data.get('error')}")
formatted = "\n".join(line for line in lines if line)
return formatted or raw_text
if isinstance(result_data, dict):
parts = []
summary = result_data.get("summary") or result_data.get("message")
if summary:
parts.append(str(summary))
error_msg = result_data.get("error")
if error_msg:
parts.append(f"⚠️ 错误: {error_msg}")
if parts:
return "\n".join(parts)
return raw_text
# 创建调试日志文件
DEBUG_LOG_FILE = Path(LOGS_DIR).expanduser().resolve() / "debug_stream.log"
CHUNK_BACKEND_LOG_FILE = Path(LOGS_DIR).expanduser().resolve() / "chunk_backend.log"
@ -421,10 +488,15 @@ def get_user_resources(username: Optional[str] = None) -> Tuple[Optional[WebTerm
usage_tracker = get_or_create_usage_tracker(username, workspace)
terminal = user_terminals.get(username)
if not terminal:
thinking_mode = session.get('thinking_mode', False)
run_mode = session.get('run_mode')
thinking_mode_flag = session.get('thinking_mode')
if run_mode not in {"fast", "thinking", "deep"}:
run_mode = "thinking" if thinking_mode_flag else "fast"
thinking_mode = run_mode != "fast"
terminal = WebTerminal(
project_path=str(workspace.project_path),
thinking_mode=thinking_mode,
run_mode=run_mode,
message_callback=make_terminal_callback(username),
data_dir=str(workspace.data_dir),
container_session=container_handle,
@ -525,14 +597,16 @@ def build_upload_error_response(exc: UploadSecurityError):
}), status
def ensure_conversation_loaded(terminal: WebTerminal, conversation_id: Optional[str], thinking_mode: bool) -> Tuple[str, bool]:
def ensure_conversation_loaded(terminal: WebTerminal, conversation_id: Optional[str], run_mode: Optional[str]) -> Tuple[str, bool]:
"""确保终端加载指定对话,若无则创建新的"""
created_new = False
if not conversation_id:
result = terminal.create_new_conversation(thinking_mode=thinking_mode)
result = terminal.create_new_conversation(run_mode=run_mode)
if not result.get("success"):
raise RuntimeError(result.get("message", "创建对话失败"))
conversation_id = result["conversation_id"]
session['run_mode'] = terminal.run_mode
session['thinking_mode'] = terminal.thinking_mode
created_new = True
else:
conversation_id = conversation_id if conversation_id.startswith('conv_') else f"conv_{conversation_id}"
@ -541,14 +615,23 @@ def ensure_conversation_loaded(terminal: WebTerminal, conversation_id: Optional[
load_result = terminal.load_conversation(conversation_id)
if not load_result.get("success"):
raise RuntimeError(load_result.get("message", "对话加载失败"))
# 切换到对话记录的思考模式
# 切换到对话记录的运行模式
try:
conv_data = terminal.context_manager.conversation_manager.load_conversation(conversation_id) or {}
meta = conv_data.get("metadata", {}) or {}
mode = bool(meta.get("thinking_mode", terminal.thinking_mode))
terminal.thinking_mode = mode
terminal.api_client.thinking_mode = mode
terminal.api_client.start_new_task()
run_mode_meta = meta.get("run_mode")
if run_mode_meta:
terminal.set_run_mode(run_mode_meta)
elif meta.get("thinking_mode"):
terminal.set_run_mode("thinking")
else:
terminal.set_run_mode("fast")
if terminal.thinking_mode:
terminal.api_client.start_new_task(force_deep=terminal.deep_thinking_mode)
else:
terminal.api_client.start_new_task()
session['run_mode'] = terminal.run_mode
session['thinking_mode'] = terminal.thinking_mode
except Exception:
pass
return conversation_id, created_new
@ -562,7 +645,7 @@ def reset_system_state(terminal: Optional[WebTerminal]):
# 1. 重置API客户端状态
if hasattr(terminal, 'api_client') and terminal.api_client:
debug_log("重置API客户端状态")
terminal.api_client.start_new_task() # 重置思考模式状态
terminal.api_client.start_new_task(force_deep=getattr(terminal, "deep_thinking_mode", False))
# 2. 重置主终端会话状态
if hasattr(terminal, 'current_session_id'):
@ -637,6 +720,8 @@ def get_thinking_state(terminal: WebTerminal) -> Dict[str, Any]:
def mark_force_thinking(terminal: WebTerminal, reason: str = ""):
"""标记下一次API调用必须使用思考模型。"""
if getattr(terminal, "deep_thinking_mode", False):
return
if not getattr(terminal, "thinking_mode", False):
return
state = get_thinking_state(terminal)
@ -647,6 +732,8 @@ def mark_force_thinking(terminal: WebTerminal, reason: str = ""):
def mark_suppress_thinking(terminal: WebTerminal):
"""标记下一次API调用必须跳过思考模型例如写入窗口"""
if getattr(terminal, "deep_thinking_mode", False):
return
if not getattr(terminal, "thinking_mode", False):
return
state = get_thinking_state(terminal)
@ -656,6 +743,10 @@ def mark_suppress_thinking(terminal: WebTerminal):
def apply_thinking_schedule(terminal: WebTerminal):
"""根据当前状态配置API客户端的思考/快速模式。"""
client = terminal.api_client
if getattr(terminal, "deep_thinking_mode", False):
client.force_thinking_next_call = False
client.skip_thinking_next_call = False
return
if not getattr(terminal, "thinking_mode", False):
client.force_thinking_next_call = False
client.skip_thinking_next_call = False
@ -696,6 +787,10 @@ def apply_thinking_schedule(terminal: WebTerminal):
def update_thinking_after_call(terminal: WebTerminal):
"""一次API调用完成后更新快速计数。"""
if getattr(terminal, "deep_thinking_mode", False):
state = get_thinking_state(terminal)
state["fast_streak"] = 0
return
if not getattr(terminal, "thinking_mode", False):
return
state = get_thinking_state(terminal)
@ -831,7 +926,9 @@ def login():
session['logged_in'] = True
session['username'] = record.username
session['thinking_mode'] = app.config.get('DEFAULT_THINKING_MODE', False)
default_thinking = app.config.get('DEFAULT_THINKING_MODE', False)
session['thinking_mode'] = default_thinking
session['run_mode'] = app.config.get('DEFAULT_RUN_MODE', "thinking" if default_thinking else "fast")
session.permanent = True
clear_failures("login", identifier=client_ip)
workspace = user_manager.ensure_user_workspace(record.username)
@ -1041,11 +1138,20 @@ def update_thinking_mode(terminal: WebTerminal, workspace: UserWorkspace, userna
"""切换思考模式"""
try:
data = request.get_json() or {}
desired_mode = bool(data.get('thinking_mode'))
terminal.thinking_mode = desired_mode
terminal.api_client.thinking_mode = desired_mode
terminal.api_client.start_new_task()
session['thinking_mode'] = desired_mode
requested_mode = data.get('mode')
if requested_mode in {"fast", "thinking", "deep"}:
target_mode = requested_mode
elif 'thinking_mode' in data:
target_mode = "thinking" if bool(data.get('thinking_mode')) else "fast"
else:
target_mode = terminal.run_mode
terminal.set_run_mode(target_mode)
if terminal.thinking_mode:
terminal.api_client.start_new_task(force_deep=terminal.deep_thinking_mode)
else:
terminal.api_client.start_new_task()
session['thinking_mode'] = terminal.thinking_mode
session['run_mode'] = terminal.run_mode
# 更新当前对话的元数据
ctx = terminal.context_manager
if ctx.current_conversation_id:
@ -1055,7 +1161,8 @@ def update_thinking_mode(terminal: WebTerminal, workspace: UserWorkspace, userna
messages=ctx.conversation_history,
project_path=str(ctx.project_path),
todo_list=ctx.todo_list,
thinking_mode=desired_mode
thinking_mode=terminal.thinking_mode,
run_mode=terminal.run_mode
)
except Exception as exc:
print(f"[API] 保存思考模式到对话失败: {exc}")
@ -1065,7 +1172,10 @@ def update_thinking_mode(terminal: WebTerminal, workspace: UserWorkspace, userna
return jsonify({
"success": True,
"data": status.get("thinking_mode")
"data": {
"thinking_mode": terminal.thinking_mode,
"mode": terminal.run_mode
}
})
except Exception as exc:
print(f"[API] 切换思考模式失败: {exc}")
@ -1909,7 +2019,7 @@ def handle_message(data):
requested_conversation_id = data.get('conversation_id')
try:
conversation_id, created_new = ensure_conversation_loaded(terminal, requested_conversation_id, terminal.thinking_mode)
conversation_id, created_new = ensure_conversation_loaded(terminal, requested_conversation_id, terminal.run_mode)
except RuntimeError as exc:
emit('error', {'message': str(exc)})
return
@ -2016,10 +2126,13 @@ def create_conversation(terminal: WebTerminal, workspace: UserWorkspace, usernam
try:
data = request.get_json() or {}
thinking_mode = data.get('thinking_mode', terminal.thinking_mode)
run_mode = data.get('mode')
result = terminal.create_new_conversation(thinking_mode=thinking_mode)
result = terminal.create_new_conversation(thinking_mode=thinking_mode, run_mode=run_mode)
if result["success"]:
session['run_mode'] = terminal.run_mode
session['thinking_mode'] = terminal.thinking_mode
# 广播对话列表更新事件
socketio.emit('conversation_list_update', {
'action': 'created',
@ -2493,8 +2606,8 @@ def detect_malformed_tool_call(text):
return True
# 检测特定的工具名称后跟JSON
tool_names = ['create_file', 'read_file', 'modify_file', 'delete_file',
'append_to_file', 'terminal_session', 'terminal_input', 'web_search',
tool_names = ['create_file', 'read_file', 'write_file_diff', 'delete_file',
'terminal_session', 'terminal_input', 'web_search',
'extract_webpage', 'save_webpage',
'run_python', 'run_command', 'focus_file', 'unfocus_file', 'sleep']
for tool in tool_names:
@ -2511,7 +2624,7 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
# 如果是思考模式,重置状态
if web_terminal.thinking_mode:
web_terminal.api_client.start_new_task()
web_terminal.api_client.start_new_task(force_deep=web_terminal.deep_thinking_mode)
state = get_thinking_state(web_terminal)
state["fast_streak"] = 0
state["force_next"] = False
@ -3273,7 +3386,7 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
tool_patterns = [
(r'(创建|新建|生成).*(文件|file)', 'create_file'),
(r'(读取|查看|打开).*(文件|file)', 'read_file'),
(r'(修改|编辑|更新).*(文件|file)', 'modify_file'),
(r'(修改|编辑|更新).*(文件|file)', 'write_file_diff'),
(r'(删除|移除).*(文件|file)', 'delete_file'),
(r'(搜索|查找|search)', 'web_search'),
(r'(执行|运行).*(Python|python|代码)', 'run_python'),
@ -3791,6 +3904,8 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
"content": assistant_content,
"tool_calls": tool_calls
}
if current_thinking:
assistant_message["reasoning_content"] = current_thinking
messages.append(assistant_message)
if assistant_content or current_thinking or tool_calls:
@ -4035,61 +4150,12 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
action_message = None
awaiting_flag = False
if function_name == "append_to_file":
if result_data.get("success") and result_data.get("awaiting_content"):
append_path = result_data.get("path") or arguments.get("path")
pending_append = {
"path": append_path,
"tool_call_id": tool_call_id,
"buffer": "",
"start_marker": f"<<<APPEND:{append_path}>>>",
"end_marker": "<<<END_APPEND>>>",
"content_start": None,
"end_index": None,
"display_id": tool_display_id
}
append_probe_buffer = ""
awaiting_flag = True
action_status = 'running'
action_message = f"正在向 {append_path} 追加内容..."
text_started = False
text_streaming = False
text_has_content = False
if hasattr(web_terminal, "pending_append_request"):
web_terminal.pending_append_request = {"path": append_path}
mark_suppress_thinking(web_terminal)
debug_log(f"append_to_file 等待输出: {append_path}")
else:
debug_log("append_to_file 返回完成状态")
elif function_name == "modify_file":
if result_data.get("success") and result_data.get("awaiting_content"):
modify_path = result_data.get("path") or arguments.get("path")
pending_modify = {
"path": modify_path,
"tool_call_id": tool_call_id,
"buffer": "",
"raw_buffer": "",
"start_marker": f"<<<MODIFY:{modify_path}>>>",
"end_marker": "<<<END_MODIFY>>>",
"start_seen": False,
"end_index": None,
"display_id": tool_display_id,
"detected_blocks": set(),
"probe_buffer": ""
}
modify_probe_buffer = ""
if hasattr(web_terminal, "pending_modify_request"):
web_terminal.pending_modify_request = {"path": modify_path}
awaiting_flag = True
action_status = 'running'
action_message = f"正在修改 {modify_path}..."
text_started = False
text_streaming = False
text_has_content = False
mark_suppress_thinking(web_terminal)
debug_log(f"modify_file 等待输出: {modify_path}")
else:
debug_log("modify_file 返回完成状态")
if function_name == "write_file_diff":
diff_path = result_data.get("path") or arguments.get("path")
summary = result_data.get("summary") or result_data.get("message")
if summary:
action_message = summary
debug_log(f"write_file_diff 执行完成: {summary or '无摘要'}")
if function_name == "wait_sub_agent":
system_msg = result_data.get("system_message")
@ -4118,7 +4184,7 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
sender('update_action', update_payload)
# 更新UI状态
if function_name in ['focus_file', 'unfocus_file', 'modify_file']:
if function_name in ['focus_file', 'unfocus_file', 'write_file_diff']:
sender('focused_files_update', web_terminal.get_focused_files_info())
if function_name in ['create_file', 'delete_file', 'rename_file', 'create_folder']:
@ -4126,13 +4192,11 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
sender('file_tree_update', structure)
# ===== 增量保存:立即保存工具结果 =====
try:
result_data = json.loads(tool_result)
if function_name == "read_file":
tool_result_content = format_read_file_result(result_data)
else:
tool_result_content = tool_result
except:
metadata_payload = None
if isinstance(result_data, dict):
tool_result_content = format_tool_result_for_context(function_name, result_data, tool_result)
metadata_payload = {"tool_payload": result_data}
else:
tool_result_content = tool_result
# 立即保存工具结果
@ -4140,7 +4204,8 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
"tool",
tool_result_content,
tool_call_id=tool_call_id,
name=function_name
name=function_name,
metadata=metadata_payload
)
debug_log(f"💾 增量保存:工具结果 {function_name}")
system_message = result_data.get("system_message") if isinstance(result_data, dict) else None
@ -4160,7 +4225,7 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
"content": tool_result_content
})
if function_name not in ['append_to_file', 'modify_file']:
if function_name != 'write_file_diff':
await process_sub_agent_updates(messages, inline=True, after_tool_call_id=tool_call_id)
await asyncio.sleep(0.2)
@ -4206,7 +4271,7 @@ def handle_command(data):
if cmd == "clear":
terminal.context_manager.conversation_history.clear()
if terminal.thinking_mode:
terminal.api_client.start_new_task()
terminal.api_client.start_new_task(force_deep=terminal.deep_thinking_mode)
emit('command_result', {
'command': cmd,
'success': True,
@ -4313,6 +4378,7 @@ def initialize_system(path: str, thinking_mode: bool = False):
print(f"[Init] 调试日志: {DEBUG_LOG_FILE}")
app.config['DEFAULT_THINKING_MODE'] = thinking_mode
app.config['DEFAULT_RUN_MODE'] = "thinking" if thinking_mode else "fast"
print(f"{OUTPUT_FORMATS['success']} Web系统初始化完成多用户模式")
def run_server(path: str, thinking_mode: bool = False, port: int = DEFAULT_PORT, debug: bool = False):