fix: improve write_file_diff diagnostics
This commit is contained in:
parent
97da631e01
commit
51bb0f1033
@ -75,17 +75,23 @@ class MainTerminal:
|
|||||||
self,
|
self,
|
||||||
project_path: str,
|
project_path: str,
|
||||||
thinking_mode: bool = False,
|
thinking_mode: bool = False,
|
||||||
|
run_mode: Optional[str] = None,
|
||||||
data_dir: Optional[str] = None,
|
data_dir: Optional[str] = None,
|
||||||
container_session: Optional["ContainerHandle"] = None,
|
container_session: Optional["ContainerHandle"] = None,
|
||||||
usage_tracker: Optional[object] = None,
|
usage_tracker: Optional[object] = None,
|
||||||
):
|
):
|
||||||
self.project_path = project_path
|
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.data_dir = Path(data_dir).expanduser().resolve() if data_dir else Path(DATA_DIR).resolve()
|
||||||
self.usage_tracker = usage_tracker
|
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 = ContextManager(project_path, data_dir=str(self.data_dir))
|
||||||
self.context_manager.main_terminal = self
|
self.context_manager.main_terminal = self
|
||||||
self.container_mount_path = TERMINAL_SANDBOX_MOUNT_PATH or "/workspace"
|
self.container_mount_path = TERMINAL_SANDBOX_MOUNT_PATH or "/workspace"
|
||||||
@ -232,7 +238,8 @@ class MainTerminal:
|
|||||||
|
|
||||||
conversation_id = self.context_manager.start_new_conversation(
|
conversation_id = self.context_manager.start_new_conversation(
|
||||||
project_path=self.project_path,
|
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}")
|
print(f"{OUTPUT_FORMATS['info']} 新建对话: {conversation_id}")
|
||||||
|
|
||||||
@ -618,7 +625,7 @@ class MainTerminal:
|
|||||||
# 如果是思考模式,每个新任务重置状态
|
# 如果是思考模式,每个新任务重置状态
|
||||||
# 注意:这里重置的是当前任务的第一次调用标志,确保新用户请求重新思考
|
# 注意:这里重置的是当前任务的第一次调用标志,确保新用户请求重新思考
|
||||||
if self.thinking_mode:
|
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
|
self.current_session_id += 1
|
||||||
@ -837,7 +844,7 @@ class MainTerminal:
|
|||||||
|
|
||||||
# 如果是思考模式,重置状态(下次任务会重新思考)
|
# 如果是思考模式,重置状态(下次任务会重新思考)
|
||||||
if self.thinking_mode:
|
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
|
self.current_session_id += 1
|
||||||
|
|
||||||
@ -859,7 +866,7 @@ class MainTerminal:
|
|||||||
|
|
||||||
# 重置相关状态
|
# 重置相关状态
|
||||||
if self.thinking_mode:
|
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
|
self.current_session_id += 1
|
||||||
|
|
||||||
@ -913,7 +920,7 @@ class MainTerminal:
|
|||||||
terminal_status = self.terminal_manager.list_terminals()
|
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:
|
if self.thinking_mode:
|
||||||
thinking_status += f" ({'等待新任务' if self.api_client.current_task_first_call else '任务进行中'})"
|
thinking_status += f" ({'等待新任务' if self.api_client.current_task_first_call else '任务进行中'})"
|
||||||
|
|
||||||
@ -2319,7 +2326,11 @@ class MainTerminal:
|
|||||||
if sub_agent_prompt:
|
if sub_agent_prompt:
|
||||||
messages.append({"role": "system", "content": 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()
|
thinking_prompt = self.load_prompt("thinking_mode_guidelines").strip()
|
||||||
if thinking_prompt:
|
if thinking_prompt:
|
||||||
messages.append({"role": "system", "content": thinking_prompt})
|
messages.append({"role": "system", "content": thinking_prompt})
|
||||||
@ -2346,6 +2357,9 @@ class MainTerminal:
|
|||||||
"role": conv["role"],
|
"role": conv["role"],
|
||||||
"content": conv["content"]
|
"content": conv["content"]
|
||||||
}
|
}
|
||||||
|
reasoning = conv.get("reasoning_content")
|
||||||
|
if reasoning:
|
||||||
|
message["reasoning_content"] = reasoning
|
||||||
# 如果有工具调用信息,添加到消息中
|
# 如果有工具调用信息,添加到消息中
|
||||||
tool_calls = conv.get("tool_calls") or []
|
tool_calls = conv.get("tool_calls") or []
|
||||||
if tool_calls and self._tool_calls_followed_by_tools(conversation, idx, tool_calls):
|
if tool_calls and self._tool_calls_followed_by_tools(conversation, idx, tool_calls):
|
||||||
@ -2535,16 +2549,54 @@ class MainTerminal:
|
|||||||
print(self.context_manager._build_file_tree(structure))
|
print(self.context_manager._build_file_tree(structure))
|
||||||
print(f"\n总计: {structure['total_files']} 个文件, {structure['total_size'] / 1024 / 1024:.2f} MB")
|
print(f"\n总计: {structure['total_files']} 个文件, {structure['total_size'] / 1024 / 1024:.2f} MB")
|
||||||
|
|
||||||
async def toggle_mode(self, args: str = ""):
|
def set_run_mode(self, mode: str) -> str:
|
||||||
"""切换运行模式(简化版)"""
|
"""统一设置运行模式"""
|
||||||
if self.thinking_mode:
|
allowed = ["fast", "thinking", "deep"]
|
||||||
# 当前是思考模式,切换到快速模式
|
normalized = mode.lower()
|
||||||
self.thinking_mode = False
|
if normalized not in allowed:
|
||||||
self.api_client.thinking_mode = False
|
raise ValueError(f"不支持的模式: {mode}")
|
||||||
print(f"{OUTPUT_FORMATS['info']} 已切换到: 快速模式(不思考)")
|
previous_mode = getattr(self, "run_mode", "fast")
|
||||||
else:
|
self.run_mode = normalized
|
||||||
# 当前是快速模式,切换到思考模式
|
self.thinking_mode = normalized != "fast"
|
||||||
self.thinking_mode = True
|
self.deep_thinking_mode = normalized == "deep"
|
||||||
self.api_client.thinking_mode = True
|
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()
|
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}")
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,8 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
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
|
from datetime import datetime
|
||||||
try:
|
try:
|
||||||
from config import (
|
from config import (
|
||||||
@ -674,6 +675,316 @@ class FileManager:
|
|||||||
"""追加内容到文件"""
|
"""追加内容到文件"""
|
||||||
return self.write_file(path, content, mode="a")
|
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:
|
def apply_modify_blocks(self, path: str, blocks: List[Dict]) -> Dict:
|
||||||
"""
|
"""
|
||||||
应用批量替换块
|
应用批量替换块
|
||||||
@ -815,7 +1126,7 @@ class FileManager:
|
|||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": "要替换的文本过长,可能导致性能问题",
|
"error": "要替换的文本过长,可能导致性能问题",
|
||||||
"suggestion": "请拆分内容或使用 modify_file 提交结构化补丁"
|
"suggestion": "请拆分内容或使用 write_file_diff 提交结构化补丁"
|
||||||
}
|
}
|
||||||
|
|
||||||
if new_text and len(new_text) > 9999999999:
|
if new_text and len(new_text) > 9999999999:
|
||||||
|
|||||||
@ -487,10 +487,10 @@ class ContextManager:
|
|||||||
|
|
||||||
if ("<<<APPEND" in content) or metadata.get("append_payload"):
|
if ("<<<APPEND" in content) or metadata.get("append_payload"):
|
||||||
new_msg["content"] = append_placeholder
|
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"):
|
elif ("<<<MODIFY" in content) or metadata.get("modify_payload"):
|
||||||
new_msg["content"] = append_placeholder
|
new_msg["content"] = append_placeholder
|
||||||
compressed_types.add("modify_file")
|
compressed_types.add("write_file_diff")
|
||||||
|
|
||||||
elif role == "tool":
|
elif role == "tool":
|
||||||
tool_name = new_msg.get("name")
|
tool_name = new_msg.get("name")
|
||||||
@ -498,6 +498,9 @@ class ContextManager:
|
|||||||
if tool_name == "read_file":
|
if tool_name == "read_file":
|
||||||
new_msg["content"] = append_placeholder
|
new_msg["content"] = append_placeholder
|
||||||
compressed_types.add("read_file")
|
compressed_types.add("read_file")
|
||||||
|
elif tool_name == "write_file_diff":
|
||||||
|
new_msg["content"] = append_placeholder
|
||||||
|
compressed_types.add("write_file_diff")
|
||||||
else:
|
else:
|
||||||
payload = None
|
payload = None
|
||||||
|
|
||||||
@ -532,13 +535,12 @@ class ContextManager:
|
|||||||
}
|
}
|
||||||
|
|
||||||
type_labels = {
|
type_labels = {
|
||||||
"append_to_file": "文件追加输出",
|
"write_file_diff": "文件补丁输出",
|
||||||
"modify_file": "文件修改输出",
|
|
||||||
"extract_webpage": "网页提取内容",
|
"extract_webpage": "网页提取内容",
|
||||||
"read_file": "文件读取内容"
|
"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) + "。其他内容不受影响,如需查看原文,请查看对应文件或重新执行相关工具。"
|
summary_text = "系统提示:对话已压缩,之前的对话记录中的以下内容被替换为占位文本:" + "、".join(ordered_types) + "。其他内容不受影响,如需查看原文,请查看对应文件或重新执行相关工具。"
|
||||||
|
|
||||||
system_message = {
|
system_message = {
|
||||||
@ -952,7 +954,15 @@ class ContextManager:
|
|||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
project_name = Path(structure['path']).name
|
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 = ""):
|
def build_tree_recursive(tree_dict: Dict, prefix: str = ""):
|
||||||
"""递归构建树形结构"""
|
"""递归构建树形结构"""
|
||||||
@ -1138,6 +1148,9 @@ class ContextManager:
|
|||||||
"role": conv["role"],
|
"role": conv["role"],
|
||||||
"content": conv["content"]
|
"content": conv["content"]
|
||||||
}
|
}
|
||||||
|
reasoning = conv.get("reasoning_content")
|
||||||
|
if reasoning:
|
||||||
|
message["reasoning_content"] = reasoning
|
||||||
if "tool_calls" in conv and conv["tool_calls"]:
|
if "tool_calls" in conv and conv["tool_calls"]:
|
||||||
message["tool_calls"] = conv["tool_calls"]
|
message["tool_calls"] = conv["tool_calls"]
|
||||||
messages.append(message)
|
messages.append(message)
|
||||||
|
|||||||
@ -179,6 +179,72 @@ def format_tool_result_notice(tool_name: str, tool_call_id: Optional[str], conte
|
|||||||
body = "(无附加输出)"
|
body = "(无附加输出)"
|
||||||
return f"{header}\n{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:
|
def _format_relative_path(path: Optional[str], workspace: Optional[str]) -> str:
|
||||||
"""将绝对路径转换为相对 workspace 的表示,默认返回原始路径。"""
|
"""将绝对路径转换为相对 workspace 的表示,默认返回原始路径。"""
|
||||||
@ -2508,8 +2574,8 @@ def detect_malformed_tool_call(text):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
# 检测特定的工具名称后跟JSON
|
# 检测特定的工具名称后跟JSON
|
||||||
tool_names = ['create_file', 'read_file', 'modify_file', 'delete_file',
|
tool_names = ['create_file', 'read_file', 'write_file_diff', 'delete_file',
|
||||||
'append_to_file', 'terminal_session', 'terminal_input', 'web_search',
|
'terminal_session', 'terminal_input', 'web_search',
|
||||||
'extract_webpage', 'save_webpage',
|
'extract_webpage', 'save_webpage',
|
||||||
'run_python', 'run_command', 'focus_file', 'unfocus_file', 'sleep']
|
'run_python', 'run_command', 'focus_file', 'unfocus_file', 'sleep']
|
||||||
for tool in tool_names:
|
for tool in tool_names:
|
||||||
@ -3317,7 +3383,7 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
|
|||||||
tool_patterns = [
|
tool_patterns = [
|
||||||
(r'(创建|新建|生成).*(文件|file)', 'create_file'),
|
(r'(创建|新建|生成).*(文件|file)', 'create_file'),
|
||||||
(r'(读取|查看|打开).*(文件|file)', 'read_file'),
|
(r'(读取|查看|打开).*(文件|file)', 'read_file'),
|
||||||
(r'(修改|编辑|更新).*(文件|file)', 'modify_file'),
|
(r'(修改|编辑|更新).*(文件|file)', 'write_file_diff'),
|
||||||
(r'(删除|移除).*(文件|file)', 'delete_file'),
|
(r'(删除|移除).*(文件|file)', 'delete_file'),
|
||||||
(r'(搜索|查找|search)', 'web_search'),
|
(r'(搜索|查找|search)', 'web_search'),
|
||||||
(r'(执行|运行).*(Python|python|代码)', 'run_python'),
|
(r'(执行|运行).*(Python|python|代码)', 'run_python'),
|
||||||
@ -3815,6 +3881,8 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
|
|||||||
"content": assistant_content,
|
"content": assistant_content,
|
||||||
"tool_calls": tool_calls
|
"tool_calls": tool_calls
|
||||||
}
|
}
|
||||||
|
if current_thinking:
|
||||||
|
assistant_message["reasoning_content"] = current_thinking
|
||||||
|
|
||||||
messages.append(assistant_message)
|
messages.append(assistant_message)
|
||||||
|
|
||||||
@ -4003,57 +4071,12 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
|
|||||||
action_message = None
|
action_message = None
|
||||||
awaiting_flag = False
|
awaiting_flag = False
|
||||||
|
|
||||||
if function_name == "append_to_file":
|
if function_name == "write_file_diff":
|
||||||
if result_data.get("success") and result_data.get("awaiting_content"):
|
diff_path = result_data.get("path") or arguments.get("path")
|
||||||
append_path = result_data.get("path") or arguments.get("path")
|
summary = result_data.get("summary") or result_data.get("message")
|
||||||
pending_append = {
|
if summary:
|
||||||
"path": append_path,
|
action_message = summary
|
||||||
"tool_call_id": tool_call_id,
|
debug_log(f"write_file_diff 执行完成: {summary or '无摘要'}")
|
||||||
"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 == "wait_sub_agent":
|
if function_name == "wait_sub_agent":
|
||||||
system_msg = result_data.get("system_message")
|
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)
|
sender('update_action', update_payload)
|
||||||
|
|
||||||
# 更新UI状态
|
# 更新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())
|
sender('focused_files_update', web_terminal.get_focused_files_info())
|
||||||
|
|
||||||
if function_name in ['create_file', 'delete_file', 'rename_file', 'create_folder']:
|
if function_name in ['create_file', 'delete_file', 'rename_file', 'create_folder']:
|
||||||
@ -4089,21 +4112,20 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
|
|||||||
sender('file_tree_update', structure)
|
sender('file_tree_update', structure)
|
||||||
|
|
||||||
# ===== 增量保存:立即保存工具结果 =====
|
# ===== 增量保存:立即保存工具结果 =====
|
||||||
try:
|
metadata_payload = None
|
||||||
result_data = json.loads(tool_result)
|
if isinstance(result_data, dict):
|
||||||
if function_name == "read_file":
|
tool_result_content = format_tool_result_for_context(function_name, result_data, tool_result)
|
||||||
tool_result_content = format_read_file_result(result_data)
|
metadata_payload = {"tool_payload": result_data}
|
||||||
else:
|
else:
|
||||||
tool_result_content = tool_result
|
tool_result_content = tool_result
|
||||||
except:
|
|
||||||
tool_result_content = tool_result
|
|
||||||
|
|
||||||
# 立即保存工具结果
|
# 立即保存工具结果
|
||||||
web_terminal.context_manager.add_conversation(
|
web_terminal.context_manager.add_conversation(
|
||||||
"tool",
|
"tool",
|
||||||
tool_result_content,
|
tool_result_content,
|
||||||
tool_call_id=tool_call_id,
|
tool_call_id=tool_call_id,
|
||||||
name=function_name
|
name=function_name,
|
||||||
|
metadata=metadata_payload
|
||||||
)
|
)
|
||||||
debug_log(f"💾 增量保存:工具结果 {function_name}")
|
debug_log(f"💾 增量保存:工具结果 {function_name}")
|
||||||
system_message = result_data.get("system_message") if isinstance(result_data, dict) else None
|
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
|
"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 process_sub_agent_updates(messages, inline=True, after_tool_call_id=tool_call_id)
|
||||||
|
|
||||||
await asyncio.sleep(0.2)
|
await asyncio.sleep(0.2)
|
||||||
|
|||||||
@ -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(
|
conversation_id = self.conversation_manager.create_conversation(
|
||||||
project_path=project_path,
|
project_path=project_path,
|
||||||
thinking_mode=thinking_mode,
|
thinking_mode=thinking_mode,
|
||||||
|
run_mode=run_mode or ("thinking" if thinking_mode else "fast"),
|
||||||
initial_messages=[]
|
initial_messages=[]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -312,6 +313,18 @@ class ContextManager:
|
|||||||
|
|
||||||
self.project_path = resolved_project_path
|
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"📖 加载对话: {conversation_id} - {conversation_data.get('title', '未知标题')}")
|
||||||
print(f"📊 包含 {len(self.conversation_history)} 条消息")
|
print(f"📊 包含 {len(self.conversation_history)} 条消息")
|
||||||
|
|
||||||
@ -332,12 +345,14 @@ class ContextManager:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
run_mode = getattr(self.main_terminal, "run_mode", None) if hasattr(self, "main_terminal") else None
|
||||||
success = self.conversation_manager.save_conversation(
|
success = self.conversation_manager.save_conversation(
|
||||||
conversation_id=self.current_conversation_id,
|
conversation_id=self.current_conversation_id,
|
||||||
messages=self.conversation_history,
|
messages=self.conversation_history,
|
||||||
project_path=str(self.project_path),
|
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
|
thinking_mode=getattr(self.main_terminal, "thinking_mode", None) if hasattr(self, "main_terminal") else None,
|
||||||
|
run_mode=run_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
@ -357,11 +372,14 @@ class ContextManager:
|
|||||||
if not force and not self.conversation_history:
|
if not force and not self.conversation_history:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
run_mode = getattr(self.main_terminal, "run_mode", None) if hasattr(self, "main_terminal") else None
|
||||||
self.conversation_manager.save_conversation(
|
self.conversation_manager.save_conversation(
|
||||||
conversation_id=self.current_conversation_id,
|
conversation_id=self.current_conversation_id,
|
||||||
messages=self.conversation_history,
|
messages=self.conversation_history,
|
||||||
project_path=str(self.project_path),
|
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:
|
except Exception as e:
|
||||||
@ -434,10 +452,10 @@ class ContextManager:
|
|||||||
|
|
||||||
if ("<<<APPEND" in content) or metadata.get("append_payload"):
|
if ("<<<APPEND" in content) or metadata.get("append_payload"):
|
||||||
new_msg["content"] = append_placeholder
|
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"):
|
elif ("<<<MODIFY" in content) or metadata.get("modify_payload"):
|
||||||
new_msg["content"] = append_placeholder
|
new_msg["content"] = append_placeholder
|
||||||
compressed_types.add("modify_file")
|
compressed_types.add("write_file_diff")
|
||||||
|
|
||||||
elif role == "tool":
|
elif role == "tool":
|
||||||
tool_name = new_msg.get("name")
|
tool_name = new_msg.get("name")
|
||||||
@ -445,6 +463,9 @@ class ContextManager:
|
|||||||
if tool_name == "read_file":
|
if tool_name == "read_file":
|
||||||
new_msg["content"] = append_placeholder
|
new_msg["content"] = append_placeholder
|
||||||
compressed_types.add("read_file")
|
compressed_types.add("read_file")
|
||||||
|
elif tool_name == "write_file_diff":
|
||||||
|
new_msg["content"] = append_placeholder
|
||||||
|
compressed_types.add("write_file_diff")
|
||||||
else:
|
else:
|
||||||
payload = None
|
payload = None
|
||||||
|
|
||||||
@ -479,13 +500,12 @@ class ContextManager:
|
|||||||
}
|
}
|
||||||
|
|
||||||
type_labels = {
|
type_labels = {
|
||||||
"append_to_file": "文件追加输出",
|
"write_file_diff": "文件补丁输出",
|
||||||
"modify_file": "文件修改输出",
|
|
||||||
"extract_webpage": "网页提取内容",
|
"extract_webpage": "网页提取内容",
|
||||||
"read_file": "文件读取内容"
|
"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) + "。其他内容不受影响,如需查看原文,请查看对应文件或重新执行相关工具。"
|
summary_text = "系统提示:对话已压缩,之前的对话记录中的以下内容被替换为占位文本:" + "、".join(ordered_types) + "。其他内容不受影响,如需查看原文,请查看对应文件或重新执行相关工具。"
|
||||||
|
|
||||||
system_message = {
|
system_message = {
|
||||||
@ -505,10 +525,12 @@ class ContextManager:
|
|||||||
resolved_project_path = self._resolve_project_path_from_metadata(metadata)
|
resolved_project_path = self._resolve_project_path_from_metadata(metadata)
|
||||||
project_path = str(resolved_project_path)
|
project_path = str(resolved_project_path)
|
||||||
thinking_mode = metadata.get("thinking_mode", False)
|
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(
|
compressed_conversation_id = self.conversation_manager.create_conversation(
|
||||||
project_path=project_path,
|
project_path=project_path,
|
||||||
thinking_mode=thinking_mode,
|
thinking_mode=thinking_mode,
|
||||||
|
run_mode=run_mode,
|
||||||
initial_messages=compressed_messages
|
initial_messages=compressed_messages
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -534,10 +556,12 @@ class ContextManager:
|
|||||||
resolved_project_path = self._resolve_project_path_from_metadata(metadata)
|
resolved_project_path = self._resolve_project_path_from_metadata(metadata)
|
||||||
project_path = str(resolved_project_path)
|
project_path = str(resolved_project_path)
|
||||||
thinking_mode = metadata.get("thinking_mode", False)
|
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(
|
duplicate_conversation_id = self.conversation_manager.create_conversation(
|
||||||
project_path=project_path,
|
project_path=project_path,
|
||||||
thinking_mode=thinking_mode,
|
thinking_mode=thinking_mode,
|
||||||
|
run_mode=run_mode,
|
||||||
initial_messages=original_messages
|
initial_messages=original_messages
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -620,18 +644,24 @@ class ContextManager:
|
|||||||
reasoning_content: Optional[str] = None
|
reasoning_content: Optional[str] = None
|
||||||
):
|
):
|
||||||
"""添加对话记录(改进版:集成自动保存 + 智能token统计)"""
|
"""添加对话记录(改进版:集成自动保存 + 智能token统计)"""
|
||||||
|
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 = {
|
message = {
|
||||||
"role": role,
|
"role": role,
|
||||||
"content": content,
|
"content": content,
|
||||||
"timestamp": datetime.now().isoformat()
|
"timestamp": timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
if metadata:
|
if metadata:
|
||||||
message["metadata"] = metadata
|
message["metadata"] = metadata
|
||||||
|
|
||||||
if reasoning_content:
|
|
||||||
message["reasoning_content"] = reasoning_content
|
|
||||||
|
|
||||||
# 如果是assistant消息且有工具调用,保存完整格式
|
# 如果是assistant消息且有工具调用,保存完整格式
|
||||||
if role == "assistant" and tool_calls:
|
if role == "assistant" and tool_calls:
|
||||||
# 确保工具调用格式完整
|
# 确保工具调用格式完整
|
||||||
@ -880,7 +910,15 @@ class ContextManager:
|
|||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
project_name = Path(structure['path']).name
|
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 = ""):
|
def build_tree_recursive(tree_dict: Dict, prefix: str = ""):
|
||||||
"""递归构建树形结构"""
|
"""递归构建树形结构"""
|
||||||
@ -1066,6 +1104,9 @@ class ContextManager:
|
|||||||
"role": conv["role"],
|
"role": conv["role"],
|
||||||
"content": conv["content"]
|
"content": conv["content"]
|
||||||
}
|
}
|
||||||
|
reasoning = conv.get("reasoning_content")
|
||||||
|
if reasoning:
|
||||||
|
message["reasoning_content"] = reasoning
|
||||||
if "tool_calls" in conv and conv["tool_calls"]:
|
if "tool_calls" in conv and conv["tool_calls"]:
|
||||||
message["tool_calls"] = conv["tool_calls"]
|
message["tool_calls"] = conv["tool_calls"]
|
||||||
messages.append(message)
|
messages.append(message)
|
||||||
|
|||||||
238
web_server.py
238
web_server.py
@ -358,6 +358,73 @@ def format_tool_result_notice(tool_name: str, tool_call_id: Optional[str], conte
|
|||||||
body = "(无附加输出)"
|
body = "(无附加输出)"
|
||||||
return f"{header}\n{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"
|
DEBUG_LOG_FILE = Path(LOGS_DIR).expanduser().resolve() / "debug_stream.log"
|
||||||
CHUNK_BACKEND_LOG_FILE = Path(LOGS_DIR).expanduser().resolve() / "chunk_backend.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)
|
usage_tracker = get_or_create_usage_tracker(username, workspace)
|
||||||
terminal = user_terminals.get(username)
|
terminal = user_terminals.get(username)
|
||||||
if not terminal:
|
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(
|
terminal = WebTerminal(
|
||||||
project_path=str(workspace.project_path),
|
project_path=str(workspace.project_path),
|
||||||
thinking_mode=thinking_mode,
|
thinking_mode=thinking_mode,
|
||||||
|
run_mode=run_mode,
|
||||||
message_callback=make_terminal_callback(username),
|
message_callback=make_terminal_callback(username),
|
||||||
data_dir=str(workspace.data_dir),
|
data_dir=str(workspace.data_dir),
|
||||||
container_session=container_handle,
|
container_session=container_handle,
|
||||||
@ -525,14 +597,16 @@ def build_upload_error_response(exc: UploadSecurityError):
|
|||||||
}), status
|
}), 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
|
created_new = False
|
||||||
if not conversation_id:
|
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"):
|
if not result.get("success"):
|
||||||
raise RuntimeError(result.get("message", "创建对话失败"))
|
raise RuntimeError(result.get("message", "创建对话失败"))
|
||||||
conversation_id = result["conversation_id"]
|
conversation_id = result["conversation_id"]
|
||||||
|
session['run_mode'] = terminal.run_mode
|
||||||
|
session['thinking_mode'] = terminal.thinking_mode
|
||||||
created_new = True
|
created_new = True
|
||||||
else:
|
else:
|
||||||
conversation_id = conversation_id if conversation_id.startswith('conv_') else f"conv_{conversation_id}"
|
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)
|
load_result = terminal.load_conversation(conversation_id)
|
||||||
if not load_result.get("success"):
|
if not load_result.get("success"):
|
||||||
raise RuntimeError(load_result.get("message", "对话加载失败"))
|
raise RuntimeError(load_result.get("message", "对话加载失败"))
|
||||||
# 切换到对话记录的思考模式
|
# 切换到对话记录的运行模式
|
||||||
try:
|
try:
|
||||||
conv_data = terminal.context_manager.conversation_manager.load_conversation(conversation_id) or {}
|
conv_data = terminal.context_manager.conversation_manager.load_conversation(conversation_id) or {}
|
||||||
meta = conv_data.get("metadata", {}) or {}
|
meta = conv_data.get("metadata", {}) or {}
|
||||||
mode = bool(meta.get("thinking_mode", terminal.thinking_mode))
|
run_mode_meta = meta.get("run_mode")
|
||||||
terminal.thinking_mode = mode
|
if run_mode_meta:
|
||||||
terminal.api_client.thinking_mode = mode
|
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()
|
terminal.api_client.start_new_task()
|
||||||
|
session['run_mode'] = terminal.run_mode
|
||||||
|
session['thinking_mode'] = terminal.thinking_mode
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return conversation_id, created_new
|
return conversation_id, created_new
|
||||||
@ -562,7 +645,7 @@ def reset_system_state(terminal: Optional[WebTerminal]):
|
|||||||
# 1. 重置API客户端状态
|
# 1. 重置API客户端状态
|
||||||
if hasattr(terminal, 'api_client') and terminal.api_client:
|
if hasattr(terminal, 'api_client') and terminal.api_client:
|
||||||
debug_log("重置API客户端状态")
|
debug_log("重置API客户端状态")
|
||||||
terminal.api_client.start_new_task() # 重置思考模式状态
|
terminal.api_client.start_new_task(force_deep=getattr(terminal, "deep_thinking_mode", False))
|
||||||
|
|
||||||
# 2. 重置主终端会话状态
|
# 2. 重置主终端会话状态
|
||||||
if hasattr(terminal, 'current_session_id'):
|
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 = ""):
|
def mark_force_thinking(terminal: WebTerminal, reason: str = ""):
|
||||||
"""标记下一次API调用必须使用思考模型。"""
|
"""标记下一次API调用必须使用思考模型。"""
|
||||||
|
if getattr(terminal, "deep_thinking_mode", False):
|
||||||
|
return
|
||||||
if not getattr(terminal, "thinking_mode", False):
|
if not getattr(terminal, "thinking_mode", False):
|
||||||
return
|
return
|
||||||
state = get_thinking_state(terminal)
|
state = get_thinking_state(terminal)
|
||||||
@ -647,6 +732,8 @@ def mark_force_thinking(terminal: WebTerminal, reason: str = ""):
|
|||||||
|
|
||||||
def mark_suppress_thinking(terminal: WebTerminal):
|
def mark_suppress_thinking(terminal: WebTerminal):
|
||||||
"""标记下一次API调用必须跳过思考模型(例如写入窗口)。"""
|
"""标记下一次API调用必须跳过思考模型(例如写入窗口)。"""
|
||||||
|
if getattr(terminal, "deep_thinking_mode", False):
|
||||||
|
return
|
||||||
if not getattr(terminal, "thinking_mode", False):
|
if not getattr(terminal, "thinking_mode", False):
|
||||||
return
|
return
|
||||||
state = get_thinking_state(terminal)
|
state = get_thinking_state(terminal)
|
||||||
@ -656,6 +743,10 @@ def mark_suppress_thinking(terminal: WebTerminal):
|
|||||||
def apply_thinking_schedule(terminal: WebTerminal):
|
def apply_thinking_schedule(terminal: WebTerminal):
|
||||||
"""根据当前状态配置API客户端的思考/快速模式。"""
|
"""根据当前状态配置API客户端的思考/快速模式。"""
|
||||||
client = terminal.api_client
|
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):
|
if not getattr(terminal, "thinking_mode", False):
|
||||||
client.force_thinking_next_call = False
|
client.force_thinking_next_call = False
|
||||||
client.skip_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):
|
def update_thinking_after_call(terminal: WebTerminal):
|
||||||
"""一次API调用完成后更新快速计数。"""
|
"""一次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):
|
if not getattr(terminal, "thinking_mode", False):
|
||||||
return
|
return
|
||||||
state = get_thinking_state(terminal)
|
state = get_thinking_state(terminal)
|
||||||
@ -831,7 +926,9 @@ def login():
|
|||||||
|
|
||||||
session['logged_in'] = True
|
session['logged_in'] = True
|
||||||
session['username'] = record.username
|
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
|
session.permanent = True
|
||||||
clear_failures("login", identifier=client_ip)
|
clear_failures("login", identifier=client_ip)
|
||||||
workspace = user_manager.ensure_user_workspace(record.username)
|
workspace = user_manager.ensure_user_workspace(record.username)
|
||||||
@ -1041,11 +1138,20 @@ def update_thinking_mode(terminal: WebTerminal, workspace: UserWorkspace, userna
|
|||||||
"""切换思考模式"""
|
"""切换思考模式"""
|
||||||
try:
|
try:
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
desired_mode = bool(data.get('thinking_mode'))
|
requested_mode = data.get('mode')
|
||||||
terminal.thinking_mode = desired_mode
|
if requested_mode in {"fast", "thinking", "deep"}:
|
||||||
terminal.api_client.thinking_mode = desired_mode
|
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()
|
terminal.api_client.start_new_task()
|
||||||
session['thinking_mode'] = desired_mode
|
session['thinking_mode'] = terminal.thinking_mode
|
||||||
|
session['run_mode'] = terminal.run_mode
|
||||||
# 更新当前对话的元数据
|
# 更新当前对话的元数据
|
||||||
ctx = terminal.context_manager
|
ctx = terminal.context_manager
|
||||||
if ctx.current_conversation_id:
|
if ctx.current_conversation_id:
|
||||||
@ -1055,7 +1161,8 @@ def update_thinking_mode(terminal: WebTerminal, workspace: UserWorkspace, userna
|
|||||||
messages=ctx.conversation_history,
|
messages=ctx.conversation_history,
|
||||||
project_path=str(ctx.project_path),
|
project_path=str(ctx.project_path),
|
||||||
todo_list=ctx.todo_list,
|
todo_list=ctx.todo_list,
|
||||||
thinking_mode=desired_mode
|
thinking_mode=terminal.thinking_mode,
|
||||||
|
run_mode=terminal.run_mode
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f"[API] 保存思考模式到对话失败: {exc}")
|
print(f"[API] 保存思考模式到对话失败: {exc}")
|
||||||
@ -1065,7 +1172,10 @@ def update_thinking_mode(terminal: WebTerminal, workspace: UserWorkspace, userna
|
|||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
"data": status.get("thinking_mode")
|
"data": {
|
||||||
|
"thinking_mode": terminal.thinking_mode,
|
||||||
|
"mode": terminal.run_mode
|
||||||
|
}
|
||||||
})
|
})
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f"[API] 切换思考模式失败: {exc}")
|
print(f"[API] 切换思考模式失败: {exc}")
|
||||||
@ -1909,7 +2019,7 @@ def handle_message(data):
|
|||||||
|
|
||||||
requested_conversation_id = data.get('conversation_id')
|
requested_conversation_id = data.get('conversation_id')
|
||||||
try:
|
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:
|
except RuntimeError as exc:
|
||||||
emit('error', {'message': str(exc)})
|
emit('error', {'message': str(exc)})
|
||||||
return
|
return
|
||||||
@ -2016,10 +2126,13 @@ def create_conversation(terminal: WebTerminal, workspace: UserWorkspace, usernam
|
|||||||
try:
|
try:
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
thinking_mode = data.get('thinking_mode', terminal.thinking_mode)
|
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"]:
|
if result["success"]:
|
||||||
|
session['run_mode'] = terminal.run_mode
|
||||||
|
session['thinking_mode'] = terminal.thinking_mode
|
||||||
# 广播对话列表更新事件
|
# 广播对话列表更新事件
|
||||||
socketio.emit('conversation_list_update', {
|
socketio.emit('conversation_list_update', {
|
||||||
'action': 'created',
|
'action': 'created',
|
||||||
@ -2493,8 +2606,8 @@ def detect_malformed_tool_call(text):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
# 检测特定的工具名称后跟JSON
|
# 检测特定的工具名称后跟JSON
|
||||||
tool_names = ['create_file', 'read_file', 'modify_file', 'delete_file',
|
tool_names = ['create_file', 'read_file', 'write_file_diff', 'delete_file',
|
||||||
'append_to_file', 'terminal_session', 'terminal_input', 'web_search',
|
'terminal_session', 'terminal_input', 'web_search',
|
||||||
'extract_webpage', 'save_webpage',
|
'extract_webpage', 'save_webpage',
|
||||||
'run_python', 'run_command', 'focus_file', 'unfocus_file', 'sleep']
|
'run_python', 'run_command', 'focus_file', 'unfocus_file', 'sleep']
|
||||||
for tool in tool_names:
|
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:
|
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 = get_thinking_state(web_terminal)
|
||||||
state["fast_streak"] = 0
|
state["fast_streak"] = 0
|
||||||
state["force_next"] = False
|
state["force_next"] = False
|
||||||
@ -3273,7 +3386,7 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
|
|||||||
tool_patterns = [
|
tool_patterns = [
|
||||||
(r'(创建|新建|生成).*(文件|file)', 'create_file'),
|
(r'(创建|新建|生成).*(文件|file)', 'create_file'),
|
||||||
(r'(读取|查看|打开).*(文件|file)', 'read_file'),
|
(r'(读取|查看|打开).*(文件|file)', 'read_file'),
|
||||||
(r'(修改|编辑|更新).*(文件|file)', 'modify_file'),
|
(r'(修改|编辑|更新).*(文件|file)', 'write_file_diff'),
|
||||||
(r'(删除|移除).*(文件|file)', 'delete_file'),
|
(r'(删除|移除).*(文件|file)', 'delete_file'),
|
||||||
(r'(搜索|查找|search)', 'web_search'),
|
(r'(搜索|查找|search)', 'web_search'),
|
||||||
(r'(执行|运行).*(Python|python|代码)', 'run_python'),
|
(r'(执行|运行).*(Python|python|代码)', 'run_python'),
|
||||||
@ -3791,6 +3904,8 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
|
|||||||
"content": assistant_content,
|
"content": assistant_content,
|
||||||
"tool_calls": tool_calls
|
"tool_calls": tool_calls
|
||||||
}
|
}
|
||||||
|
if current_thinking:
|
||||||
|
assistant_message["reasoning_content"] = current_thinking
|
||||||
|
|
||||||
messages.append(assistant_message)
|
messages.append(assistant_message)
|
||||||
if assistant_content or current_thinking or tool_calls:
|
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
|
action_message = None
|
||||||
awaiting_flag = False
|
awaiting_flag = False
|
||||||
|
|
||||||
if function_name == "append_to_file":
|
if function_name == "write_file_diff":
|
||||||
if result_data.get("success") and result_data.get("awaiting_content"):
|
diff_path = result_data.get("path") or arguments.get("path")
|
||||||
append_path = result_data.get("path") or arguments.get("path")
|
summary = result_data.get("summary") or result_data.get("message")
|
||||||
pending_append = {
|
if summary:
|
||||||
"path": append_path,
|
action_message = summary
|
||||||
"tool_call_id": tool_call_id,
|
debug_log(f"write_file_diff 执行完成: {summary or '无摘要'}")
|
||||||
"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 == "wait_sub_agent":
|
if function_name == "wait_sub_agent":
|
||||||
system_msg = result_data.get("system_message")
|
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)
|
sender('update_action', update_payload)
|
||||||
|
|
||||||
# 更新UI状态
|
# 更新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())
|
sender('focused_files_update', web_terminal.get_focused_files_info())
|
||||||
|
|
||||||
if function_name in ['create_file', 'delete_file', 'rename_file', 'create_folder']:
|
if function_name in ['create_file', 'delete_file', 'rename_file', 'create_folder']:
|
||||||
@ -4126,21 +4192,20 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
|
|||||||
sender('file_tree_update', structure)
|
sender('file_tree_update', structure)
|
||||||
|
|
||||||
# ===== 增量保存:立即保存工具结果 =====
|
# ===== 增量保存:立即保存工具结果 =====
|
||||||
try:
|
metadata_payload = None
|
||||||
result_data = json.loads(tool_result)
|
if isinstance(result_data, dict):
|
||||||
if function_name == "read_file":
|
tool_result_content = format_tool_result_for_context(function_name, result_data, tool_result)
|
||||||
tool_result_content = format_read_file_result(result_data)
|
metadata_payload = {"tool_payload": result_data}
|
||||||
else:
|
else:
|
||||||
tool_result_content = tool_result
|
tool_result_content = tool_result
|
||||||
except:
|
|
||||||
tool_result_content = tool_result
|
|
||||||
|
|
||||||
# 立即保存工具结果
|
# 立即保存工具结果
|
||||||
web_terminal.context_manager.add_conversation(
|
web_terminal.context_manager.add_conversation(
|
||||||
"tool",
|
"tool",
|
||||||
tool_result_content,
|
tool_result_content,
|
||||||
tool_call_id=tool_call_id,
|
tool_call_id=tool_call_id,
|
||||||
name=function_name
|
name=function_name,
|
||||||
|
metadata=metadata_payload
|
||||||
)
|
)
|
||||||
debug_log(f"💾 增量保存:工具结果 {function_name}")
|
debug_log(f"💾 增量保存:工具结果 {function_name}")
|
||||||
system_message = result_data.get("system_message") if isinstance(result_data, dict) else None
|
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
|
"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 process_sub_agent_updates(messages, inline=True, after_tool_call_id=tool_call_id)
|
||||||
|
|
||||||
await asyncio.sleep(0.2)
|
await asyncio.sleep(0.2)
|
||||||
@ -4206,7 +4271,7 @@ def handle_command(data):
|
|||||||
if cmd == "clear":
|
if cmd == "clear":
|
||||||
terminal.context_manager.conversation_history.clear()
|
terminal.context_manager.conversation_history.clear()
|
||||||
if terminal.thinking_mode:
|
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', {
|
emit('command_result', {
|
||||||
'command': cmd,
|
'command': cmd,
|
||||||
'success': True,
|
'success': True,
|
||||||
@ -4313,6 +4378,7 @@ def initialize_system(path: str, thinking_mode: bool = False):
|
|||||||
print(f"[Init] 调试日志: {DEBUG_LOG_FILE}")
|
print(f"[Init] 调试日志: {DEBUG_LOG_FILE}")
|
||||||
|
|
||||||
app.config['DEFAULT_THINKING_MODE'] = thinking_mode
|
app.config['DEFAULT_THINKING_MODE'] = thinking_mode
|
||||||
|
app.config['DEFAULT_RUN_MODE'] = "thinking" if thinking_mode else "fast"
|
||||||
print(f"{OUTPUT_FORMATS['success']} Web系统初始化完成(多用户模式)")
|
print(f"{OUTPUT_FORMATS['success']} Web系统初始化完成(多用户模式)")
|
||||||
|
|
||||||
def run_server(path: str, thinking_mode: bool = False, port: int = DEFAULT_PORT, debug: bool = False):
|
def run_server(path: str, thinking_mode: bool = False, port: int = DEFAULT_PORT, debug: bool = False):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user