agent-Specialization/BUG_FIX_V4_CHANGELOG.md
JOJO 07be7a1061 feat: gracefully stop tool execution on user request
- Remove direct task.cancel() calls, use stop flag instead
- Monitor stop flag every 100ms during tool execution
- Cancel tool task immediately when stop flag is detected
- Return "命令执行被用户取消" as tool result with role=tool
- Save cancellation result to conversation history
- Prevent abrupt task termination, allow graceful shutdown

Changes:
- server/socket_handlers.py: Comment out pending_task.cancel()
- server/tasks.py: Comment out entry['task'].cancel()
- server/chat_flow_tool_loop.py: Add stop flag monitoring loop

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-08 03:50:34 +08:00

187 lines
5.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Bug 修复 v4 - 刷新后后端停止问题
## 修复的问题
### 刷新后后端直接停止,不继续执行
**原因**:
1. 页面刷新时 WebSocket 断开
2. `handle_disconnect` 检测到断开且没有其他连接
3. 设置 `stop_flags[f"user:{username}"]``True`
4. 任务检查停止标志时,会查找 `stop_flags[task_id]``stop_flags[f"user:{username}"]`
5. 发现用户级别的停止标志,任务停止执行
**解决方案**:
`handle_disconnect` 中检查是否有通过 REST API 创建的运行中任务。如果有,说明使用轮询模式,不应该停止任务。
## 修改的文件
### server/socket_handlers.py
**修改前**:
```python
@socketio.on('disconnect')
def handle_disconnect():
username = connection_users.pop(request.sid, None)
has_other_connection = False
if username:
for sid, user in connection_users.items():
if user == username:
has_other_connection = True
break
task_info = get_stop_flag(request.sid, username)
if isinstance(task_info, dict) and not has_other_connection:
task_info['stop'] = True # 设置停止标志
# ... 取消任务
clear_stop_flag(request.sid, None) # 清理所有停止标志
```
**修改后**:
```python
@socketio.on('disconnect')
def handle_disconnect():
username = connection_users.pop(request.sid, None)
has_other_connection = False
if username:
for sid, user in connection_users.items():
if user == username:
has_other_connection = True
break
# 检查是否有通过 REST API 创建的运行中任务
has_rest_api_task = False
if username and not has_other_connection:
try:
from .tasks import task_manager
running_tasks = [t for t in task_manager.list_tasks(username) if t.status == "running"]
if running_tasks:
has_rest_api_task = True
debug_log(f"[WebSocket] 用户 {username} 有运行中的 REST API 任务,不停止")
except Exception as e:
debug_log(f"[WebSocket] 检查 REST API 任务失败: {e}")
task_info = get_stop_flag(request.sid, username)
# 只有在没有其他连接且没有 REST API 任务时才停止
if isinstance(task_info, dict) and not has_other_connection and not has_rest_api_task:
task_info['stop'] = True
# ... 取消任务
# 清理停止标志(只清理 sid 级别的,不清理 user 级别的)
if request.sid in stop_flags:
stop_flags.pop(request.sid, None)
```
## 工作流程
### 停止标志机制
**stop_flags 结构**:
```python
stop_flags = {
"client_sid": {"stop": bool, "task": asyncio.Task, "terminal": WebTerminal},
"user:{username}": {"stop": bool, "task": asyncio.Task, "terminal": WebTerminal},
"task_id": {"stop": bool, "task": None, "terminal": None}
}
```
**查找顺序**:
1. 先查找 `stop_flags[client_sid]`WebSocket sid 或 task_id
2. 再查找 `stop_flags[f"user:{username}"]`(用户级别)
### WebSocket 断开流程(修复后)
1. **检测断开**
- 移除 `connection_users[sid]`
- 检查是否有其他连接
2. **检查 REST API 任务**
- 查询 `task_manager.list_tasks(username)`
- 检查是否有 `status == "running"` 的任务
- 如果有,设置 `has_rest_api_task = True`
3. **决定是否停止**
- 如果有其他连接 → 不停止
- 如果有 REST API 任务 → 不停止
- 否则 → 停止任务
4. **清理停止标志**
- 只清理 `stop_flags[sid]`sid 级别)
- 不清理 `stop_flags[f"user:{username}"]`(用户级别)
- 避免影响 REST API 任务
### 页面刷新流程(修复后)
1. **WebSocket 断开**
- 检测到有运行中的 REST API 任务
- 不设置停止标志
- 任务继续执行
2. **页面重新加载**
- 加载历史记录
- 恢复任务状态
- 启动轮询
3. **任务继续执行**
- 后端任务不受影响
- 继续生成事件
- 前端轮询获取新事件
## 关键改进
### 1. 检测 REST API 任务
- 在 WebSocket 断开时检查是否有运行中的 REST API 任务
- 如果有,说明使用轮询模式,不应该停止
### 2. 保护 REST API 任务
- 只有在没有其他连接且没有 REST API 任务时才停止
- 避免 WebSocket 断开影响 REST API 任务
### 3. 精确清理停止标志
- 只清理 sid 级别的停止标志
- 不清理用户级别的停止标志
- 避免误清理 REST API 任务的停止标志
## 测试场景
### 1. 正常发送消息
- 发送消息
- 观察任务是否正常执行
- 观察输出是否正常
### 2. 刷新页面(任务进行中)
- 任务执行中刷新页面
- **观察后端是否继续执行**
- 观察前端是否正常恢复
- 观察新内容是否正常追加
### 3. 多次刷新
- 任务执行中多次刷新
- 观察每次刷新后任务是否继续
- 观察是否有累积的问题
### 4. 停止任务
- 点击停止按钮
- 观察任务是否立即停止
- 观察前端状态是否正确
## 构建
```bash
cd static
npm run build
```
构建成功!
## 最终效果
- ✅ 刷新后后端继续执行
- ✅ 前端正常恢复状态
- ✅ 新内容正常追加
- ✅ 工具块正确显示
- ✅ 流畅的输出效果150ms 轮询)
- ✅ 不重复显示内容
现在可以测试了!刷新页面后,后端应该继续执行,前端会正常恢复并显示新内容。