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

5.4 KiB
Raw Blame History

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

修改前:

@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)  # 清理所有停止标志

修改后:

@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 结构:

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. 停止任务

  • 点击停止按钮
  • 观察任务是否立即停止
  • 观察前端状态是否正确

构建

cd static
npm run build

构建成功!

最终效果

  • 刷新后后端继续执行
  • 前端正常恢复状态
  • 新内容正常追加
  • 工具块正确显示
  • 流畅的输出效果150ms 轮询)
  • 不重复显示内容

现在可以测试了!刷新页面后,后端应该继续执行,前端会正常恢复并显示新内容。