refactor: replace file diff tools, simplify todos, disable typewriter
This commit is contained in:
parent
e5c2943cb2
commit
453df30f45
File diff suppressed because one or more lines are too long
@ -31,6 +31,8 @@ TOOL_CATEGORIES: Dict[str, ToolCategory] = {
|
||||
label="文件编辑",
|
||||
tools=[
|
||||
"create_file",
|
||||
"write_file",
|
||||
"edit_file",
|
||||
"append_to_file",
|
||||
"modify_file",
|
||||
"delete_file",
|
||||
@ -42,8 +44,6 @@ TOOL_CATEGORIES: Dict[str, ToolCategory] = {
|
||||
label="阅读聚焦",
|
||||
tools=[
|
||||
"read_file",
|
||||
"focus_file",
|
||||
"unfocus_file",
|
||||
"vlm_analyze",
|
||||
"ocr_image",
|
||||
"view_image",
|
||||
@ -69,7 +69,7 @@ TOOL_CATEGORIES: Dict[str, ToolCategory] = {
|
||||
),
|
||||
"todo": ToolCategory(
|
||||
label="待办事项",
|
||||
tools=["todo_create", "todo_update_task", "todo_finish", "todo_finish_confirm"],
|
||||
tools=["todo_create", "todo_update_task"],
|
||||
),
|
||||
"sub_agent": ToolCategory(
|
||||
label="子智能体",
|
||||
|
||||
@ -92,7 +92,6 @@ class WebTerminal(MainTerminal):
|
||||
# 设置token更新回调
|
||||
if message_callback is not None:
|
||||
self.context_manager._web_terminal_callback = message_callback
|
||||
self.context_manager._focused_files = self.focused_files
|
||||
print(f"[WebTerminal] 实时token统计已启用")
|
||||
else:
|
||||
print(f"[WebTerminal] 警告:message_callback为None,无法启用实时token统计")
|
||||
@ -302,14 +301,8 @@ class WebTerminal(MainTerminal):
|
||||
memory_stats = self.memory_manager.get_memory_stats()
|
||||
structure = self.context_manager.get_project_structure()
|
||||
|
||||
# 聚焦文件状态 - 使用与 /api/focused 相同的格式(字典格式)
|
||||
# 聚焦功能已废弃
|
||||
focused_files_dict = {}
|
||||
for path, content in self.focused_files.items():
|
||||
focused_files_dict[path] = {
|
||||
"content": content,
|
||||
"size": len(content),
|
||||
"lines": content.count('\n') + 1
|
||||
}
|
||||
|
||||
# 终端状态
|
||||
terminal_status = None
|
||||
@ -333,8 +326,8 @@ class WebTerminal(MainTerminal):
|
||||
"total_size": context_status['sizes']['total'],
|
||||
"conversation_count": len(self.context_manager.conversation_history)
|
||||
},
|
||||
"focused_files": focused_files_dict, # 使用字典格式,与 /api/focused 一致
|
||||
"focused_files_count": len(self.focused_files), # 单独提供计数
|
||||
"focused_files": focused_files_dict,
|
||||
"focused_files_count": 0,
|
||||
"terminals": terminal_status,
|
||||
"project": {
|
||||
"total_files": structure['total_files'],
|
||||
@ -363,18 +356,6 @@ class WebTerminal(MainTerminal):
|
||||
"""获取思考模式状态描述"""
|
||||
return "思考模式" if self.thinking_mode else "快速模式"
|
||||
|
||||
def get_focused_files_info(self) -> Dict:
|
||||
"""获取聚焦文件信息(用于WebSocket更新)- 使用与 /api/focused 一致的格式"""
|
||||
focused_files_dict = {}
|
||||
for path, content in self.focused_files.items():
|
||||
focused_files_dict[path] = {
|
||||
"content": content,
|
||||
"size": len(content),
|
||||
"lines": content.count('\n') + 1
|
||||
}
|
||||
|
||||
return focused_files_dict
|
||||
|
||||
def broadcast(self, event_type: str, data: Dict):
|
||||
"""广播事件到WebSocket"""
|
||||
if self.message_callback:
|
||||
@ -426,18 +407,6 @@ class WebTerminal(MainTerminal):
|
||||
'status': 'deleting',
|
||||
'detail': f'删除文件: {arguments.get("path", "未知路径")}'
|
||||
})
|
||||
elif tool_name == "focus_file":
|
||||
self.broadcast('tool_status', {
|
||||
'tool': tool_name,
|
||||
'status': 'focusing',
|
||||
'detail': f'聚焦文件: {arguments.get("path", "未知路径")}'
|
||||
})
|
||||
elif tool_name == "unfocus_file":
|
||||
self.broadcast('tool_status', {
|
||||
'tool': tool_name,
|
||||
'status': 'unfocusing',
|
||||
'detail': f'取消聚焦: {arguments.get("path", "未知路径")}'
|
||||
})
|
||||
elif tool_name == "web_search":
|
||||
query = arguments.get("query", "")
|
||||
filters = []
|
||||
@ -602,19 +571,6 @@ class WebTerminal(MainTerminal):
|
||||
except Exception as e:
|
||||
logger.error(f"广播文件树更新失败: {e}")
|
||||
|
||||
|
||||
# 如果是聚焦操作,广播聚焦文件更新
|
||||
if tool_name in ['focus_file', 'unfocus_file', 'modify_file']:
|
||||
try:
|
||||
focused_files_dict = self.get_focused_files_info()
|
||||
self.broadcast('focused_files_update', focused_files_dict)
|
||||
|
||||
# 聚焦文件变化后,更新token统计
|
||||
self.context_manager.safe_broadcast_token_update()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"广播聚焦文件更新失败: {e}")
|
||||
|
||||
# 如果是记忆操作,广播记忆状态更新
|
||||
if tool_name == 'update_memory':
|
||||
try:
|
||||
|
||||
@ -1129,7 +1129,7 @@ class FileManager:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "要替换的文本过长,可能导致性能问题",
|
||||
"suggestion": "请拆分内容或使用 write_file_diff 提交结构化补丁"
|
||||
"suggestion": "请拆分内容或使用 edit_file/逐块写入完成修改"
|
||||
}
|
||||
|
||||
if new_text and len(new_text) > 9999999999:
|
||||
|
||||
@ -28,7 +28,7 @@ except ImportError: # pragma: no cover
|
||||
class TodoManager:
|
||||
"""负责创建、更新和结束 TODO 列表"""
|
||||
|
||||
MAX_TASKS = TODO_MAX_TASKS
|
||||
MAX_TASKS = 8 # 固定为8,覆盖配置
|
||||
MAX_OVERVIEW_LENGTH = TODO_MAX_OVERVIEW_LENGTH
|
||||
MAX_TASK_LENGTH = TODO_MAX_TASK_LENGTH
|
||||
|
||||
@ -59,12 +59,8 @@ class TodoManager:
|
||||
return normalized
|
||||
|
||||
def create_todo_list(self, overview: str, tasks: List[Any]) -> Dict[str, Any]:
|
||||
current = self._get_current()
|
||||
if current and current.get("status") == "active":
|
||||
return {
|
||||
"success": False,
|
||||
"error": "已有进行中的 TODO 列表,请先完成或结束后再创建新的列表。"
|
||||
}
|
||||
# 若已有列表,直接覆盖
|
||||
current = None
|
||||
|
||||
overview = (overview or "").strip()
|
||||
if not overview:
|
||||
@ -109,7 +105,7 @@ class TodoManager:
|
||||
self._save(todo)
|
||||
return {
|
||||
"success": True,
|
||||
"message": "待办列表已创建。请先完成某项任务,再调用待办工具将其标记完成。",
|
||||
"message": "待办列表已创建(覆盖之前的列表)。",
|
||||
"todo_list": todo
|
||||
}
|
||||
|
||||
@ -127,91 +123,20 @@ class TodoManager:
|
||||
|
||||
task = todo["tasks"][task_index - 1]
|
||||
new_status = "done" if completed else "pending"
|
||||
if task["status"] == new_status:
|
||||
return {
|
||||
"success": True,
|
||||
"message": "任务状态未发生变化。",
|
||||
"todo_list": todo
|
||||
}
|
||||
|
||||
task["status"] = new_status
|
||||
|
||||
self._save(todo)
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"任务 task{task_index} 已标记为 {'完成' if completed else '未完成'}。",
|
||||
"todo_list": todo
|
||||
}
|
||||
|
||||
def finish_todo(self, reason: Optional[str] = None) -> Dict[str, Any]:
|
||||
todo = self._get_current()
|
||||
if not todo:
|
||||
return {"success": False, "error": "当前没有待办列表。"}
|
||||
|
||||
if todo.get("status") in {"completed", "closed"}:
|
||||
return {
|
||||
"success": True,
|
||||
"message": "待办列表已结束,无需重复操作。",
|
||||
"todo_list": todo
|
||||
}
|
||||
|
||||
all_done = all(task["status"] == "done" for task in todo["tasks"])
|
||||
all_done = all(t["status"] == "done" for t in todo["tasks"])
|
||||
if all_done:
|
||||
todo["status"] = "completed"
|
||||
todo["forced_finish"] = False
|
||||
todo["forced_reason"] = None
|
||||
self._save(todo)
|
||||
system_note = "✅ TODO 列表中的所有任务已完成,可以整理成果并向用户汇报,如果已经进行过汇报,请忽略这条信息。"
|
||||
return {
|
||||
"success": True,
|
||||
"message": "所有任务已完成,待办列表已结束。",
|
||||
"todo_list": todo,
|
||||
"system_note": system_note
|
||||
}
|
||||
|
||||
remaining = [
|
||||
f"task{task['index']}"
|
||||
for task in todo["tasks"]
|
||||
if task["status"] != "done"
|
||||
]
|
||||
return {
|
||||
"success": False,
|
||||
"requires_confirmation": True,
|
||||
"message": "仍有未完成的任务,确认要提前结束吗?",
|
||||
"remaining": remaining,
|
||||
"todo_list": todo
|
||||
}
|
||||
|
||||
def confirm_finish(self, confirm: bool, reason: Optional[str] = None) -> Dict[str, Any]:
|
||||
todo = self._get_current()
|
||||
if not todo:
|
||||
return {"success": False, "error": "当前没有待办列表。"}
|
||||
if todo.get("status") in {"completed", "closed"}:
|
||||
return {
|
||||
"success": True,
|
||||
"message": "待办列表已结束,无需重复操作。",
|
||||
"message": "所有任务已完成。",
|
||||
"todo_list": todo
|
||||
}
|
||||
|
||||
if not confirm:
|
||||
return {
|
||||
"success": True,
|
||||
"message": "已取消结束待办列表,继续执行剩余任务。",
|
||||
"todo_list": todo
|
||||
}
|
||||
|
||||
todo["status"] = "closed"
|
||||
todo["forced_finish"] = True
|
||||
todo["forced_reason"] = (reason or "").strip() or None
|
||||
self._save(todo)
|
||||
|
||||
system_note = "⚠️ TODO 列表在任务未全部完成的情况下被结束,请在总结中说明原因。"
|
||||
self.context_manager.add_conversation("system", system_note)
|
||||
return {
|
||||
"success": True,
|
||||
"message": "待办列表已强制结束。",
|
||||
"todo_list": todo,
|
||||
"system_note": system_note
|
||||
"message": f"任务 {task_index}{'完成' if completed else '取消完成'}。",
|
||||
"todo_list": todo
|
||||
}
|
||||
|
||||
def get_snapshot(self) -> Optional[Dict[str, Any]]:
|
||||
|
||||
@ -194,12 +194,11 @@ tree -L 2
|
||||
1. **什么时候用**:任务需要2步以上、涉及多个文件或工具时
|
||||
2. **清单要求**:
|
||||
- 概述:用一句话说明任务目标(不超过50字)
|
||||
- 任务:最多4条,按执行顺序排列
|
||||
- 任务:最多8条(建议2-6条),按执行顺序排列
|
||||
- 每条任务要说清楚具体做什么,不要用"优化""处理"这种模糊词
|
||||
3. **执行方式**:
|
||||
- 完成一项,勾选一项
|
||||
- 如果计划有变,先告诉用户
|
||||
- 全部完成后,用 todo_finish 结束
|
||||
- 完成/撤销一项,立即用 todo_update_task 勾选/取消
|
||||
- 如果计划有变,先告诉用户,并更新任务后继续勾选
|
||||
|
||||
### 示例:整理文档
|
||||
```
|
||||
|
||||
@ -208,12 +208,11 @@ tree -L 2
|
||||
1. **什么时候用**:任务需要2步以上、涉及多个文件或工具时
|
||||
2. **清单要求**:
|
||||
- 概述:用一句话说明任务目标(不超过50字)
|
||||
- 任务:最多4条,按执行顺序排列
|
||||
- 任务:最多8条(建议2-6条),按执行顺序排列
|
||||
- 每条任务要说清楚具体做什么,不要用"优化""处理"这种模糊词
|
||||
3. **执行方式**:
|
||||
- 完成一项,勾选一项
|
||||
- 如果计划有变,先告诉用户
|
||||
- 全部完成后,用 todo_finish 结束
|
||||
- 完成/撤销一项,立即用 todo_update_task 勾选/取消
|
||||
- 如果计划有变,先告诉用户,并更新任务后继续勾选
|
||||
|
||||
### 示例:整理文档
|
||||
```
|
||||
|
||||
@ -24,8 +24,8 @@
|
||||
- ✅ "整理家庭照片,按年份分类并压缩,不超过2GB"
|
||||
- ❌ "处理照片"(太模糊)
|
||||
|
||||
### 2. 任务列表(最多4条)
|
||||
- **数量**:建议2-4条,最多不超过4条
|
||||
### 2. 任务列表(最多8条)
|
||||
- **数量**:建议2-6条,最多不超过8条
|
||||
- **顺序**:按照实际操作顺序排列
|
||||
- **要求**:每条任务要说清楚具体做什么
|
||||
|
||||
@ -68,9 +68,9 @@
|
||||
- 完成一项任务后,立即调用 `todo_update_task` 勾选
|
||||
- 如果发现计划需要调整,先告诉用户,再修改
|
||||
|
||||
### 第4步:结束清单
|
||||
- 全部完成:直接调用 `todo_finish`
|
||||
- 中途需要停止:说明原因,询问是否结束
|
||||
### 第4步:持续更新
|
||||
- 每完成/撤销一项,调用 `todo_update_task` 勾选/取消勾选
|
||||
- 如需修改任务描述或顺序,先向用户说明后直接更新清单并同步勾选状态
|
||||
|
||||
## 常见场景示例
|
||||
|
||||
@ -124,35 +124,12 @@
|
||||
- 不要跳过步骤(按顺序执行)
|
||||
- 不要忘记勾选已完成的任务
|
||||
|
||||
## 如果任务未完成就要结束
|
||||
|
||||
有时候会遇到:
|
||||
- 缺少必要信息,无法继续
|
||||
- 发现技术限制,做不了
|
||||
- 用户改变想法,不做了
|
||||
|
||||
**正确做法**:
|
||||
1. 调用 `todo_finish` 尝试结束
|
||||
2. 系统会提示有未完成任务
|
||||
3. 调用 `todo_finish_confirm` 并说明原因
|
||||
4. 告诉用户哪些完成了,哪些没做
|
||||
|
||||
**例子**:
|
||||
```
|
||||
"由于xxx文件找不到,任务2无法执行。
|
||||
已完成:任务1(读取文件)
|
||||
未完成:任务2-4
|
||||
是否结束当前清单?"
|
||||
```
|
||||
|
||||
## 快速参考
|
||||
|
||||
| 工具 | 用途 | 什么时候用 |
|
||||
|-----|------|---------|
|
||||
| todo_create | 创建清单 | 开始多步骤任务时 |
|
||||
| todo_update_task | 勾选任务 | 每完成一项任务后 |
|
||||
| todo_finish | 结束清单 | 全部任务完成时 |
|
||||
| todo_finish_confirm | 确认提前结束 | 有未完成任务但需要停止时 |
|
||||
| todo_create | 创建清单(最多8条,覆盖旧清单) | 开始多步骤任务时 |
|
||||
| todo_update_task | 勾选/取消勾选 | 每完成或撤销一项时 |
|
||||
|
||||
## 总结
|
||||
|
||||
@ -165,4 +142,3 @@
|
||||
6. **灵活调整**:发现问题及时沟通
|
||||
|
||||
记住:清单是给你自己看的,要给自己明确可执行的规划,同时要让用户知道你在做什么、完成到哪一步了。在用户明确给出“好的,请开始”的指令时,才能开始创建待办事项哦!
|
||||
|
||||
|
||||
@ -236,15 +236,8 @@ def get_monitor_snapshot_api():
|
||||
@api_login_required
|
||||
@with_terminal
|
||||
def get_focused_files(terminal: WebTerminal, workspace: UserWorkspace, username: str):
|
||||
"""获取聚焦文件"""
|
||||
focused = {}
|
||||
for path, content in terminal.focused_files.items():
|
||||
focused[path] = {
|
||||
"content": content,
|
||||
"size": len(content),
|
||||
"lines": content.count('\n') + 1
|
||||
}
|
||||
return jsonify(focused)
|
||||
"""聚焦功能已废弃,返回空列表保持接口兼容。"""
|
||||
return jsonify({})
|
||||
|
||||
@app.route('/api/todo-list')
|
||||
@api_login_required
|
||||
@ -553,4 +546,3 @@ def issue_socket_token():
|
||||
"expires_in": SOCKET_TOKEN_TTL_SECONDS
|
||||
})
|
||||
|
||||
|
||||
|
||||
@ -249,15 +249,8 @@ def get_monitor_snapshot_api():
|
||||
@api_login_required
|
||||
@with_terminal
|
||||
def get_focused_files(terminal: WebTerminal, workspace: UserWorkspace, username: str):
|
||||
"""获取聚焦文件"""
|
||||
focused = {}
|
||||
for path, content in terminal.focused_files.items():
|
||||
focused[path] = {
|
||||
"content": content,
|
||||
"size": len(content),
|
||||
"lines": content.count('\n') + 1
|
||||
}
|
||||
return jsonify(focused)
|
||||
"""聚焦功能已废弃,返回空列表保持接口兼容。"""
|
||||
return jsonify({})
|
||||
|
||||
@app.route('/api/todo-list')
|
||||
@api_login_required
|
||||
@ -566,4 +559,3 @@ def issue_socket_token():
|
||||
"expires_in": SOCKET_TOKEN_TTL_SECONDS
|
||||
})
|
||||
|
||||
|
||||
|
||||
@ -627,10 +627,10 @@ def detect_malformed_tool_call(text):
|
||||
return True
|
||||
|
||||
# 检测特定的工具名称后跟JSON
|
||||
tool_names = ['create_file', 'read_file', 'write_file_diff', 'delete_file',
|
||||
tool_names = ['create_file', 'read_file', 'write_file', 'edit_file', 'delete_file',
|
||||
'terminal_session', 'terminal_input', 'web_search',
|
||||
'extract_webpage', 'save_webpage',
|
||||
'run_python', 'run_command', 'focus_file', 'unfocus_file', 'sleep']
|
||||
'run_python', 'run_command', 'sleep']
|
||||
for tool in tool_names:
|
||||
if tool in text and '{' in text:
|
||||
# 可能是工具调用但格式错误
|
||||
@ -943,13 +943,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
'message': summary
|
||||
})
|
||||
|
||||
# 更新聚焦文件内容
|
||||
if path in web_terminal.focused_files:
|
||||
refreshed = web_terminal.file_manager.read_file(path)
|
||||
if refreshed.get("success"):
|
||||
web_terminal.focused_files[path] = refreshed["content"]
|
||||
debug_log(f"聚焦文件已刷新: {path}")
|
||||
|
||||
debug_log(f"追加写入完成: {summary}")
|
||||
else:
|
||||
error_msg = write_result.get("error", "追加写入失败")
|
||||
@ -1295,12 +1288,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
'message': summary_message
|
||||
})
|
||||
|
||||
if path in web_terminal.focused_files and tool_payload.get("success"):
|
||||
refreshed = web_terminal.file_manager.read_file(path)
|
||||
if refreshed.get("success"):
|
||||
web_terminal.focused_files[path] = refreshed["content"]
|
||||
debug_log(f"聚焦文件已刷新: {path}")
|
||||
|
||||
pending_modify = None
|
||||
modify_probe_buffer = ""
|
||||
if hasattr(web_terminal, "pending_modify_request"):
|
||||
@ -2297,12 +2284,12 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
action_message = None
|
||||
awaiting_flag = False
|
||||
|
||||
if function_name == "write_file_diff":
|
||||
diff_path = result_data.get("path") or arguments.get("path")
|
||||
if function_name in {"write_file", "edit_file"}:
|
||||
diff_path = result_data.get("path") or arguments.get("file_path")
|
||||
summary = result_data.get("summary") or result_data.get("message")
|
||||
if summary:
|
||||
action_message = summary
|
||||
debug_log(f"write_file_diff 执行完成: {summary or '无摘要'}")
|
||||
debug_log(f"{function_name} 执行完成: {summary or '无摘要'}")
|
||||
|
||||
if function_name == "wait_sub_agent":
|
||||
system_msg = result_data.get("system_message")
|
||||
@ -2363,10 +2350,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
|
||||
sender('update_action', update_payload)
|
||||
|
||||
# 更新UI状态
|
||||
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']:
|
||||
structure = web_terminal.context_manager.get_project_structure()
|
||||
sender('file_tree_update', structure)
|
||||
@ -2399,10 +2382,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
if system_message:
|
||||
web_terminal._record_sub_agent_message(system_message, result_data.get("task_id"), inline=False)
|
||||
maybe_mark_failure_from_message(web_terminal, system_message)
|
||||
todo_note = result_data.get("system_note") if isinstance(result_data, dict) else None
|
||||
if todo_note:
|
||||
web_terminal.context_manager.add_conversation("system", todo_note)
|
||||
maybe_mark_failure_from_message(web_terminal, todo_note)
|
||||
|
||||
# 添加到消息历史(用于API继续对话)
|
||||
messages.append({
|
||||
@ -2412,7 +2391,7 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
"content": tool_result_content
|
||||
})
|
||||
|
||||
# 处理图片注入:必须紧跟在对应的 tool 消息之后,且工具成功时才插入
|
||||
# 收集图片注入请求,延后统一追加
|
||||
if (
|
||||
function_name == "view_image"
|
||||
and getattr(web_terminal, "pending_image_view", None)
|
||||
@ -2421,30 +2400,10 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
):
|
||||
inj = web_terminal.pending_image_view
|
||||
web_terminal.pending_image_view = None
|
||||
injected_text = "这是一条系统控制发送的信息,并非用户主动发送,目的是返回你需要查看的图片。"
|
||||
# 记录到对话历史
|
||||
web_terminal.context_manager.add_conversation(
|
||||
"user",
|
||||
injected_text,
|
||||
images=[inj["path"]],
|
||||
metadata={"system_injected_image": True}
|
||||
)
|
||||
# 同步到当前消息列表(直接带多模态 content),保证顺序为 tool_call -> tool -> (系统代发)user
|
||||
content_payload = web_terminal.context_manager._build_content_with_images(
|
||||
injected_text,
|
||||
[inj["path"]]
|
||||
)
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": content_payload,
|
||||
"metadata": {"system_injected_image": True}
|
||||
})
|
||||
# 提示前端
|
||||
sender('system_message', {
|
||||
'content': f'系统已按模型请求插入图片: {inj.get("path")}'
|
||||
})
|
||||
if inj and inj.get("path"):
|
||||
image_injections.append(inj["path"])
|
||||
|
||||
if function_name != 'write_file_diff':
|
||||
if function_name not in {'write_file', 'edit_file'}:
|
||||
await process_sub_agent_updates(messages, inline=True, after_tool_call_id=tool_call_id)
|
||||
|
||||
await asyncio.sleep(0.2)
|
||||
@ -2455,6 +2414,29 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
# 标记不再是第一次迭代
|
||||
is_first_iteration = False
|
||||
|
||||
# 统一附加图片消息,保证所有 tool 响应先完成
|
||||
if image_injections:
|
||||
for img_path in image_injections:
|
||||
injected_text = "这是一条系统控制发送的信息,并非用户主动发送,目的是返回你需要查看的图片。"
|
||||
web_terminal.context_manager.add_conversation(
|
||||
"user",
|
||||
injected_text,
|
||||
images=[img_path],
|
||||
metadata={"system_injected_image": True}
|
||||
)
|
||||
content_payload = web_terminal.context_manager._build_content_with_images(
|
||||
injected_text,
|
||||
[img_path]
|
||||
)
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": content_payload,
|
||||
"metadata": {"system_injected_image": True}
|
||||
})
|
||||
sender('system_message', {
|
||||
'content': f'系统已按模型请求插入图片: {img_path}'
|
||||
})
|
||||
|
||||
# 最终统计
|
||||
debug_log(f"\n{'='*40}")
|
||||
debug_log(f"任务完成统计:")
|
||||
|
||||
@ -6,7 +6,7 @@ terminal_rooms: Dict[str, set] = {}
|
||||
connection_users: Dict[str, str] = {}
|
||||
stop_flags: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
MONITOR_FILE_TOOLS = {'append_to_file', 'modify_file', 'write_file_diff'}
|
||||
MONITOR_FILE_TOOLS = {'append_to_file', 'modify_file', 'write_file', 'edit_file'}
|
||||
MONITOR_MEMORY_TOOLS = {'update_memory'}
|
||||
MONITOR_SNAPSHOT_CHAR_LIMIT = 60000
|
||||
MONITOR_MEMORY_ENTRY_LIMIT = 256
|
||||
@ -23,4 +23,3 @@ ADMIN_CUSTOM_TOOLS_DIR = (Path(app.static_folder) / 'custom_tools').resolve()
|
||||
ADMIN_CUSTOM_TOOLS_DIR = (Path(app.static_folder) / 'custom_tools').resolve()
|
||||
RECENT_UPLOAD_EVENT_LIMIT = 150
|
||||
RECENT_UPLOAD_FEED_LIMIT = 60
|
||||
|
||||
|
||||
@ -274,15 +274,8 @@ def get_monitor_snapshot_api():
|
||||
@api_login_required
|
||||
@with_terminal
|
||||
def get_focused_files(terminal: WebTerminal, workspace: UserWorkspace, username: str):
|
||||
"""获取聚焦文件"""
|
||||
focused = {}
|
||||
for path, content in terminal.focused_files.items():
|
||||
focused[path] = {
|
||||
"content": content,
|
||||
"size": len(content),
|
||||
"lines": content.count('\n') + 1
|
||||
}
|
||||
return jsonify(focused)
|
||||
"""聚焦功能已废弃,返回空列表保持接口兼容。"""
|
||||
return jsonify({})
|
||||
|
||||
@chat_bp.route('/api/todo-list')
|
||||
@api_login_required
|
||||
|
||||
@ -448,10 +448,10 @@ def detect_malformed_tool_call(text):
|
||||
return True
|
||||
|
||||
# 检测特定的工具名称后跟JSON
|
||||
tool_names = ['create_file', 'read_file', 'write_file_diff', 'delete_file',
|
||||
tool_names = ['create_file', 'read_file', 'write_file', 'edit_file', 'delete_file',
|
||||
'terminal_session', 'terminal_input', 'web_search',
|
||||
'extract_webpage', 'save_webpage',
|
||||
'run_python', 'run_command', 'focus_file', 'unfocus_file', 'sleep']
|
||||
'run_python', 'run_command', 'sleep']
|
||||
for tool in tool_names:
|
||||
if tool in text and '{' in text:
|
||||
# 可能是工具调用但格式错误
|
||||
@ -764,13 +764,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
'message': summary
|
||||
})
|
||||
|
||||
# 更新聚焦文件内容
|
||||
if path in web_terminal.focused_files:
|
||||
refreshed = web_terminal.file_manager.read_file(path)
|
||||
if refreshed.get("success"):
|
||||
web_terminal.focused_files[path] = refreshed["content"]
|
||||
debug_log(f"聚焦文件已刷新: {path}")
|
||||
|
||||
debug_log(f"追加写入完成: {summary}")
|
||||
else:
|
||||
error_msg = write_result.get("error", "追加写入失败")
|
||||
@ -1116,12 +1109,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
'message': summary_message
|
||||
})
|
||||
|
||||
if path in web_terminal.focused_files and tool_payload.get("success"):
|
||||
refreshed = web_terminal.file_manager.read_file(path)
|
||||
if refreshed.get("success"):
|
||||
web_terminal.focused_files[path] = refreshed["content"]
|
||||
debug_log(f"聚焦文件已刷新: {path}")
|
||||
|
||||
pending_modify = None
|
||||
modify_probe_buffer = ""
|
||||
if hasattr(web_terminal, "pending_modify_request"):
|
||||
@ -1928,6 +1915,8 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
# 更新统计
|
||||
total_tool_calls += len(tool_calls)
|
||||
|
||||
image_injections: list[str] = []
|
||||
|
||||
# 执行每个工具
|
||||
for tool_call in tool_calls:
|
||||
# 检查停止标志
|
||||
@ -2118,12 +2107,12 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
action_message = None
|
||||
awaiting_flag = False
|
||||
|
||||
if function_name == "write_file_diff":
|
||||
diff_path = result_data.get("path") or arguments.get("path")
|
||||
if function_name in {"write_file", "edit_file"}:
|
||||
diff_path = result_data.get("path") or arguments.get("file_path")
|
||||
summary = result_data.get("summary") or result_data.get("message")
|
||||
if summary:
|
||||
action_message = summary
|
||||
debug_log(f"write_file_diff 执行完成: {summary or '无摘要'}")
|
||||
debug_log(f"{function_name} 执行完成: {summary or '无摘要'}")
|
||||
|
||||
if function_name == "wait_sub_agent":
|
||||
system_msg = result_data.get("system_message")
|
||||
@ -2184,10 +2173,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
|
||||
sender('update_action', update_payload)
|
||||
|
||||
# 更新UI状态
|
||||
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']:
|
||||
structure = web_terminal.context_manager.get_project_structure()
|
||||
sender('file_tree_update', structure)
|
||||
@ -2220,10 +2205,6 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
if system_message:
|
||||
web_terminal._record_sub_agent_message(system_message, result_data.get("task_id"), inline=False)
|
||||
maybe_mark_failure_from_message(web_terminal, system_message)
|
||||
todo_note = result_data.get("system_note") if isinstance(result_data, dict) else None
|
||||
if todo_note:
|
||||
web_terminal.context_manager.add_conversation("system", todo_note)
|
||||
maybe_mark_failure_from_message(web_terminal, todo_note)
|
||||
|
||||
# 添加到消息历史(用于API继续对话)
|
||||
messages.append({
|
||||
@ -2233,7 +2214,7 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
"content": tool_result_content
|
||||
})
|
||||
|
||||
# 处理图片注入:必须紧跟在对应的 tool 消息之后,且工具成功时才插入
|
||||
# 收集图片注入请求,延后统一追加
|
||||
if (
|
||||
function_name == "view_image"
|
||||
and getattr(web_terminal, "pending_image_view", None)
|
||||
@ -2242,30 +2223,10 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
):
|
||||
inj = web_terminal.pending_image_view
|
||||
web_terminal.pending_image_view = None
|
||||
injected_text = "这是一条系统控制发送的信息,并非用户主动发送,目的是返回你需要查看的图片。"
|
||||
# 记录到对话历史
|
||||
web_terminal.context_manager.add_conversation(
|
||||
"user",
|
||||
injected_text,
|
||||
images=[inj["path"]],
|
||||
metadata={"system_injected_image": True}
|
||||
)
|
||||
# 同步到当前消息列表(直接带多模态 content),保证顺序为 tool_call -> tool -> (系统代发)user
|
||||
content_payload = web_terminal.context_manager._build_content_with_images(
|
||||
injected_text,
|
||||
[inj["path"]]
|
||||
)
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": content_payload,
|
||||
"metadata": {"system_injected_image": True}
|
||||
})
|
||||
# 提示前端
|
||||
sender('system_message', {
|
||||
'content': f'系统已按模型请求插入图片: {inj.get("path")}'
|
||||
})
|
||||
if inj and inj.get("path"):
|
||||
image_injections.append(inj["path"])
|
||||
|
||||
if function_name != 'write_file_diff':
|
||||
if function_name not in {'write_file', 'edit_file'}:
|
||||
await process_sub_agent_updates(messages, inline=True, after_tool_call_id=tool_call_id)
|
||||
|
||||
await asyncio.sleep(0.2)
|
||||
@ -2276,6 +2237,29 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
# 标记不再是第一次迭代
|
||||
is_first_iteration = False
|
||||
|
||||
# 统一附加图片消息,保证所有 tool 响应先完成
|
||||
if image_injections:
|
||||
for img_path in image_injections:
|
||||
injected_text = "这是一条系统控制发送的信息,并非用户主动发送,目的是返回你需要查看的图片。"
|
||||
web_terminal.context_manager.add_conversation(
|
||||
"user",
|
||||
injected_text,
|
||||
images=[img_path],
|
||||
metadata={"system_injected_image": True}
|
||||
)
|
||||
content_payload = web_terminal.context_manager._build_content_with_images(
|
||||
injected_text,
|
||||
[img_path]
|
||||
)
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": content_payload,
|
||||
"metadata": {"system_injected_image": True}
|
||||
})
|
||||
sender('system_message', {
|
||||
'content': f'系统已按模型请求插入图片: {img_path}'
|
||||
})
|
||||
|
||||
# 最终统计
|
||||
debug_log(f"\n{'='*40}")
|
||||
debug_log(f"任务完成统计:")
|
||||
|
||||
@ -27,7 +27,7 @@ RECENT_UPLOAD_FEED_LIMIT = 60
|
||||
stop_flags: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
# 监控/限流/用量
|
||||
MONITOR_FILE_TOOLS = {'append_to_file', 'modify_file', 'write_file_diff'}
|
||||
MONITOR_FILE_TOOLS = {'append_to_file', 'modify_file', 'write_file', 'edit_file'}
|
||||
MONITOR_MEMORY_TOOLS = {'update_memory'}
|
||||
MONITOR_SNAPSHOT_CHAR_LIMIT = 60000
|
||||
MONITOR_MEMORY_ENTRY_LIMIT = 256
|
||||
|
||||
@ -21,6 +21,8 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
|
||||
ctx.socket = createSocketClient('/', socketOptions);
|
||||
|
||||
// 可开关的逐字打印功能,默认关闭
|
||||
const STREAMING_ENABLED = false;
|
||||
const STREAMING_CHAR_DELAY = 22;
|
||||
const STREAMING_FINALIZE_DELAY = 1000;
|
||||
const STREAMING_DEBUG = false;
|
||||
@ -109,10 +111,11 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
};
|
||||
|
||||
const hasPendingStreamingText = () =>
|
||||
!!streamingState.buffer.length ||
|
||||
!!streamingState.pendingCompleteContent ||
|
||||
streamingState.timer !== null ||
|
||||
streamingState.completionTimer !== null;
|
||||
STREAMING_ENABLED &&
|
||||
( !!streamingState.buffer.length ||
|
||||
!!streamingState.pendingCompleteContent ||
|
||||
streamingState.timer !== null ||
|
||||
streamingState.completionTimer !== null);
|
||||
|
||||
const resetPendingToolEvents = () => {
|
||||
pendingToolEvents.length = 0;
|
||||
@ -513,6 +516,14 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
return;
|
||||
}
|
||||
stopCompletionTimer();
|
||||
if (!STREAMING_ENABLED) {
|
||||
// 关闭逐字模式:整块入缓冲并立即刷新,保持与 chunk 顺序一致
|
||||
streamingState.pendingCompleteContent = '';
|
||||
streamingState.buffer.length = 0;
|
||||
streamingState.apiCompleted = false;
|
||||
applyTextChunk(text);
|
||||
return;
|
||||
}
|
||||
logStreamingDebug('enqueueStreamingContent', { incomingLength: text.length });
|
||||
for (const ch of Array.from(text)) {
|
||||
streamingState.buffer.push(ch);
|
||||
@ -931,7 +942,12 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
console.warn('上报chunk日志失败:', error);
|
||||
}
|
||||
if (data && typeof data.content === 'string' && data.content.length) {
|
||||
enqueueStreamingContent(data.content);
|
||||
if (STREAMING_ENABLED) {
|
||||
enqueueStreamingContent(data.content);
|
||||
} else {
|
||||
// 关闭逐字模式时,直接追加当前chunk
|
||||
applyTextChunk(data.content);
|
||||
}
|
||||
const speech = sanitizeBubbleText(data.content);
|
||||
if (speech) {
|
||||
ctx.monitorShowSpeech(speech);
|
||||
@ -946,6 +962,22 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
finalLength: (data?.full_content || '').length,
|
||||
snapshot: snapshotStreamingState()
|
||||
});
|
||||
if (!STREAMING_ENABLED) {
|
||||
// 已逐块追加,这里保证动作收尾
|
||||
const full = data?.full_content || '';
|
||||
if (full) {
|
||||
ctx.chatCompleteTextAction(full);
|
||||
} else {
|
||||
ctx.chatCompleteTextAction('');
|
||||
}
|
||||
streamingState.buffer.length = 0;
|
||||
streamingState.pendingCompleteContent = '';
|
||||
streamingState.renderedText = '';
|
||||
streamingState.apiCompleted = false;
|
||||
ctx.monitorEndModelOutput();
|
||||
flushPendingToolEvents();
|
||||
return;
|
||||
}
|
||||
streamingState.apiCompleted = true;
|
||||
streamingState.pendingCompleteContent = data?.full_content || '';
|
||||
const hidden = typeof document !== 'undefined' && document.hidden === true;
|
||||
|
||||
@ -94,8 +94,6 @@ const TOOL_SCENE_MAP: Record<string, string> = {
|
||||
extract_webpage: 'webExtract',
|
||||
save_webpage: 'webSave',
|
||||
read_file: 'reader',
|
||||
focus_file: 'focus',
|
||||
unfocus_file: 'unfocus',
|
||||
vlm_analyze: 'ocr',
|
||||
ocr_image: 'ocr',
|
||||
create_folder: 'createFolder',
|
||||
@ -104,7 +102,8 @@ const TOOL_SCENE_MAP: Record<string, string> = {
|
||||
delete_file: 'deleteFile',
|
||||
append_to_file: 'appendFile',
|
||||
modify_file: 'modifyFile',
|
||||
write_file_diff: 'modifyFile',
|
||||
write_file: 'modifyFile',
|
||||
edit_file: 'modifyFile',
|
||||
run_command: 'runCommand',
|
||||
run_python: 'runPython',
|
||||
terminal_session: 'terminalSession',
|
||||
@ -115,8 +114,6 @@ const TOOL_SCENE_MAP: Record<string, string> = {
|
||||
update_memory: 'memoryUpdate',
|
||||
todo_create: 'todoCreate',
|
||||
todo_update_task: 'todoUpdate',
|
||||
todo_finish: 'todoFinish',
|
||||
todo_finish_confirm: 'todoFinishConfirm',
|
||||
todo_delete_task: 'todoDelete',
|
||||
terminal_run: 'runCommand'
|
||||
};
|
||||
|
||||
@ -7,11 +7,11 @@ const RUNNING_ANIMATIONS: Record<string, string> = {
|
||||
read_file: 'read-animation',
|
||||
delete_file: 'file-animation',
|
||||
rename_file: 'file-animation',
|
||||
write_file: 'file-animation',
|
||||
edit_file: 'file-animation',
|
||||
modify_file: 'file-animation',
|
||||
append_to_file: 'file-animation',
|
||||
create_folder: 'file-animation',
|
||||
focus_file: 'focus-animation',
|
||||
unfocus_file: 'focus-animation',
|
||||
web_search: 'search-animation',
|
||||
extract_webpage: 'search-animation',
|
||||
save_webpage: 'file-animation',
|
||||
@ -26,8 +26,6 @@ const RUNNING_ANIMATIONS: Record<string, string> = {
|
||||
terminal_reset: 'terminal-animation',
|
||||
todo_create: 'file-animation',
|
||||
todo_update_task: 'file-animation',
|
||||
todo_finish: 'file-animation',
|
||||
todo_finish_confirm: 'file-animation',
|
||||
create_sub_agent: 'terminal-animation',
|
||||
wait_sub_agent: 'wait-animation'
|
||||
};
|
||||
@ -37,11 +35,11 @@ const RUNNING_STATUS_TEXTS: Record<string, string> = {
|
||||
sleep: '正在等待...',
|
||||
delete_file: '正在删除文件...',
|
||||
rename_file: '正在重命名文件...',
|
||||
write_file: '正在写入文件...',
|
||||
edit_file: '正在编辑文件...',
|
||||
modify_file: '正在修改文件...',
|
||||
append_to_file: '正在追加文件...',
|
||||
create_folder: '正在创建文件夹...',
|
||||
focus_file: '正在聚焦文件...',
|
||||
unfocus_file: '正在取消聚焦...',
|
||||
web_search: '正在搜索网络...',
|
||||
extract_webpage: '正在提取网页...',
|
||||
save_webpage: '正在保存网页...',
|
||||
@ -59,11 +57,11 @@ const COMPLETED_STATUS_TEXTS: Record<string, string> = {
|
||||
delete_file: '文件删除成功',
|
||||
sleep: '等待完成',
|
||||
rename_file: '文件重命名成功',
|
||||
write_file: '文件写入完成',
|
||||
edit_file: '文件编辑完成',
|
||||
modify_file: '文件修改成功',
|
||||
append_to_file: '文件追加完成',
|
||||
create_folder: '文件夹创建成功',
|
||||
focus_file: '文件聚焦成功',
|
||||
unfocus_file: '取消聚焦成功',
|
||||
web_search: '搜索完成',
|
||||
extract_webpage: '网页提取完成',
|
||||
save_webpage: '网页保存完成(纯文本)',
|
||||
|
||||
@ -48,9 +48,9 @@ export const TOOL_ICON_MAP = Object.freeze({
|
||||
create_sub_agent: 'bot',
|
||||
delete_file: 'trash',
|
||||
extract_webpage: 'globe',
|
||||
focus_file: 'eye',
|
||||
modify_file: 'pencil',
|
||||
write_file_diff: 'pencil',
|
||||
write_file: 'pencil',
|
||||
edit_file: 'pencil',
|
||||
vlm_analyze: 'camera',
|
||||
ocr_image: 'camera',
|
||||
read_file: 'book',
|
||||
@ -60,8 +60,6 @@ export const TOOL_ICON_MAP = Object.freeze({
|
||||
save_webpage: 'save',
|
||||
sleep: 'clock',
|
||||
todo_create: 'stickyNote',
|
||||
todo_finish: 'flag',
|
||||
todo_finish_confirm: 'circleAlert',
|
||||
todo_update_task: 'check',
|
||||
terminal_input: 'keyboard',
|
||||
terminal_reset: 'recycle',
|
||||
|
||||
@ -63,7 +63,6 @@ class ContextManager:
|
||||
|
||||
# 用于接收Web终端的回调函数
|
||||
self._web_terminal_callback = None
|
||||
self._focused_files = {}
|
||||
|
||||
self.load_annotations()
|
||||
|
||||
@ -121,10 +120,6 @@ class ContextManager:
|
||||
"""设置Web终端回调函数,用于广播事件"""
|
||||
self._web_terminal_callback = callback
|
||||
|
||||
def set_focused_files(self, focused_files: Dict):
|
||||
"""设置聚焦文件信息,用于token计算"""
|
||||
self._focused_files = focused_files
|
||||
|
||||
def load_annotations(self):
|
||||
"""加载文件备注"""
|
||||
annotations_file = self.data_dir / "file_annotations.json"
|
||||
@ -532,10 +527,10 @@ class ContextManager:
|
||||
|
||||
if ("<<<APPEND" in content) or metadata.get("append_payload"):
|
||||
new_msg["content"] = append_placeholder
|
||||
compressed_types.add("write_file_diff")
|
||||
compressed_types.add("write_file")
|
||||
elif ("<<<MODIFY" in content) or metadata.get("modify_payload"):
|
||||
new_msg["content"] = append_placeholder
|
||||
compressed_types.add("write_file_diff")
|
||||
compressed_types.add("edit_file")
|
||||
|
||||
elif role == "tool":
|
||||
tool_name = new_msg.get("name")
|
||||
@ -543,9 +538,9 @@ class ContextManager:
|
||||
if tool_name == "read_file":
|
||||
new_msg["content"] = append_placeholder
|
||||
compressed_types.add("read_file")
|
||||
elif tool_name == "write_file_diff":
|
||||
elif tool_name in {"write_file", "edit_file"}:
|
||||
new_msg["content"] = append_placeholder
|
||||
compressed_types.add("write_file_diff")
|
||||
compressed_types.add(tool_name)
|
||||
else:
|
||||
payload = None
|
||||
|
||||
@ -580,12 +575,13 @@ class ContextManager:
|
||||
}
|
||||
|
||||
type_labels = {
|
||||
"write_file_diff": "文件补丁输出",
|
||||
"write_file": "文件写入内容",
|
||||
"edit_file": "文件精确替换",
|
||||
"extract_webpage": "网页提取内容",
|
||||
"read_file": "文件读取内容"
|
||||
}
|
||||
|
||||
ordered_types = [type_labels[t] for t in ["write_file_diff", "extract_webpage", "read_file"] if t in compressed_types]
|
||||
ordered_types = [type_labels[t] for t in ["write_file", "edit_file", "extract_webpage", "read_file"] if t in compressed_types]
|
||||
summary_text = "系统提示:对话已压缩,之前的对话记录中的以下内容被替换为占位文本:" + "、".join(ordered_types) + "。其他内容不受影响,如需查看原文,请查看对应文件或重新执行相关工具。"
|
||||
|
||||
system_message = {
|
||||
@ -1318,22 +1314,6 @@ class ContextManager:
|
||||
"content": content_payload
|
||||
})
|
||||
|
||||
# 添加聚焦文件内容
|
||||
if self._focused_files:
|
||||
focused_content = "\n\n=== 🔍 正在聚焦的文件 ===\n"
|
||||
focused_content += f"(共 {len(self._focused_files)} 个文件处于聚焦状态)\n"
|
||||
|
||||
for path, content in self._focused_files.items():
|
||||
size_kb = len(content) / 1024
|
||||
focused_content += f"\n--- 文件: {path} ({size_kb:.1f}KB) ---\n"
|
||||
focused_content += f"```\n{content}\n```\n"
|
||||
|
||||
focused_content += "\n=== 聚焦文件结束 ===\n"
|
||||
messages.append({
|
||||
"role": "system",
|
||||
"content": focused_content
|
||||
})
|
||||
|
||||
# 添加终端内容(如果有的话)
|
||||
# 这里需要从参数传入或获取
|
||||
|
||||
|
||||
@ -196,6 +196,33 @@ def _format_create_file(result_data: Dict[str, Any]) -> str:
|
||||
return _format_failure("create_file", result_data)
|
||||
return result_data.get("message") or f"已创建空文件: {result_data.get('path', '未知路径')}"
|
||||
|
||||
def _format_write_file(result_data: Dict[str, Any]) -> str:
|
||||
if not result_data.get("success"):
|
||||
return _format_failure("write_file", result_data)
|
||||
path = result_data.get("path") or "未知路径"
|
||||
mode = str(result_data.get("mode") or "w")
|
||||
action = "追加" if mode == "a" else "覆盖"
|
||||
size = result_data.get("size")
|
||||
message = result_data.get("message")
|
||||
parts = [f"{action}写入: {path}"]
|
||||
if size is not None:
|
||||
parts.append(f"{size} 字节")
|
||||
if message:
|
||||
parts.append(str(message))
|
||||
return ",".join(parts)
|
||||
|
||||
def _format_edit_file(result_data: Dict[str, Any]) -> str:
|
||||
if not result_data.get("success"):
|
||||
return _format_failure("edit_file", result_data)
|
||||
path = result_data.get("path") or "目标文件"
|
||||
count = result_data.get("replacements")
|
||||
msg = result_data.get("message")
|
||||
replaced_note = f"替换 {count} 处" if isinstance(count, int) else "已完成替换"
|
||||
parts = [f"{replaced_note}: {path}"]
|
||||
if msg:
|
||||
parts.append(str(msg))
|
||||
return ",".join(parts)
|
||||
|
||||
|
||||
def _classify_diff_block_issue(block: Dict[str, Any], result_data: Dict[str, Any]) -> str:
|
||||
"""
|
||||
@ -257,25 +284,6 @@ def _format_create_folder(result_data: Dict[str, Any]) -> str:
|
||||
return f"已创建文件夹: {result_data.get('path', '未知路径')}"
|
||||
|
||||
|
||||
def _format_focus_file(result_data: Dict[str, Any]) -> str:
|
||||
if not result_data.get("success"):
|
||||
return _format_failure("focus_file", result_data)
|
||||
message = result_data.get("message") or "文件已聚焦"
|
||||
size = result_data.get("file_size")
|
||||
size_note = f"({size} 字符)" if isinstance(size, int) else ""
|
||||
focused = result_data.get("focused_files") or []
|
||||
focused_note = f"当前聚焦: {', '.join(focused)}" if focused else "当前没有其他聚焦文件"
|
||||
return f"{message}{size_note}\n{focused_note}"
|
||||
|
||||
|
||||
def _format_unfocus_file(result_data: Dict[str, Any]) -> str:
|
||||
if not result_data.get("success"):
|
||||
return _format_failure("unfocus_file", result_data)
|
||||
message = result_data.get("message") or "已取消聚焦"
|
||||
remaining = result_data.get("remaining_focused") or []
|
||||
remain_note = f"剩余聚焦: {', '.join(remaining)}" if remaining else "当前没有聚焦文件"
|
||||
return f"{message}\n{remain_note}"
|
||||
|
||||
|
||||
def _format_terminal_session(result_data: Dict[str, Any]) -> str:
|
||||
action = result_data.get("action") or result_data.get("terminal_action") or "未知操作"
|
||||
@ -417,51 +425,6 @@ def _format_todo_update_task(result_data: Dict[str, Any]) -> str:
|
||||
return f"{message};{progress_note}".strip(";")
|
||||
|
||||
|
||||
def _format_todo_finish(result_data: Dict[str, Any]) -> str:
|
||||
if result_data.get("success"):
|
||||
todo = result_data.get("todo_list") or {}
|
||||
tasks = todo.get("tasks") or []
|
||||
overview = todo.get("overview") or "未命名任务"
|
||||
total = len(tasks)
|
||||
done = sum(1 for t in tasks if t.get("status") == "done")
|
||||
status_note = "正常结束" if done == total else "已结束"
|
||||
lines = [
|
||||
f"TODO 已结束({status_note}),进度 {done}/{total}",
|
||||
f"概述:{overview}",
|
||||
]
|
||||
if tasks:
|
||||
lines.append(_summarize_todo_tasks(todo))
|
||||
return "\n".join(lines)
|
||||
if result_data.get("requires_confirmation"):
|
||||
remaining = result_data.get("remaining") or []
|
||||
remain_note = ", ".join(remaining) if remaining else "未知"
|
||||
return f"仍有未完成任务({remain_note}),需要确认是否提前结束。"
|
||||
return _format_failure("todo_finish", result_data)
|
||||
|
||||
|
||||
def _format_todo_finish_confirm(result_data: Dict[str, Any]) -> str:
|
||||
if not result_data.get("success"):
|
||||
return _format_failure("todo_finish_confirm", result_data)
|
||||
message = result_data.get("message") or "已处理 TODO 完结确认"
|
||||
todo = result_data.get("todo_list") or {}
|
||||
overview = todo.get("overview") or "未命名任务"
|
||||
tasks = todo.get("tasks") or []
|
||||
total = len(tasks)
|
||||
done = sum(1 for t in tasks if t.get("status") == "done")
|
||||
forced = todo.get("forced_finish")
|
||||
reason = todo.get("forced_reason")
|
||||
status_note = "强制结束" if forced else "已结束"
|
||||
lines = [
|
||||
f"{message}({status_note},进度 {done}/{total})",
|
||||
f"概述:{overview}",
|
||||
]
|
||||
if forced and reason:
|
||||
lines.append(f"强制结束原因:{reason}")
|
||||
if tasks:
|
||||
lines.append(_summarize_todo_tasks(todo))
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _format_update_memory(result_data: Dict[str, Any]) -> str:
|
||||
if not result_data.get("success"):
|
||||
return _format_failure("update_memory", result_data)
|
||||
@ -667,11 +630,11 @@ def _summarize_todo_tasks(todo: Optional[Dict[str, Any]]) -> str:
|
||||
|
||||
TOOL_FORMATTERS = {
|
||||
"create_file": _format_create_file,
|
||||
"write_file": _format_write_file,
|
||||
"edit_file": _format_edit_file,
|
||||
"delete_file": _format_delete_file,
|
||||
"rename_file": _format_rename_file,
|
||||
"create_folder": _format_create_folder,
|
||||
"focus_file": _format_focus_file,
|
||||
"unfocus_file": _format_unfocus_file,
|
||||
"terminal_snapshot": _format_terminal_snapshot,
|
||||
"terminal_session": _format_terminal_session,
|
||||
"terminal_input": _format_terminal_input,
|
||||
@ -684,8 +647,6 @@ TOOL_FORMATTERS = {
|
||||
"trigger_easter_egg": _format_trigger_easter_egg,
|
||||
"todo_create": _format_todo_create,
|
||||
"todo_update_task": _format_todo_update_task,
|
||||
"todo_finish": _format_todo_finish,
|
||||
"todo_finish_confirm": _format_todo_finish_confirm,
|
||||
"update_memory": _format_update_memory,
|
||||
"create_sub_agent": _format_create_sub_agent,
|
||||
"wait_sub_agent": _format_wait_sub_agent,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user