From 07be7a106109da46bcc47fa1bbcdf0178f755963 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Sun, 8 Mar 2026 03:50:34 +0800 Subject: [PATCH] feat: gracefully stop tool execution on user request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- BUG_FIX_CHANGELOG.md | 107 ++++++++ BUG_FIX_V2_CHANGELOG.md | 191 ++++++++++++++ BUG_FIX_V3_CHANGELOG.md | 187 ++++++++++++++ BUG_FIX_V4_CHANGELOG.md | 186 ++++++++++++++ POLLING_MODE_CHANGELOG.md | 180 ++++++++++++++ agentskills/agent-build-standard/SKILL.md | 233 ++++++++++++++++++ .../agent-build-standard/references/models.md | 20 ++ .../references/repo-guidelines.md | 22 ++ server/chat_flow_tool_loop.py | 84 ++++++- server/socket_handlers.py | 16 +- server/tasks.py | 11 +- static/icons/演示文稿1 [自动保存].pptx | Bin 0 -> 52552 bytes 12 files changed, 1222 insertions(+), 15 deletions(-) create mode 100644 BUG_FIX_CHANGELOG.md create mode 100644 BUG_FIX_V2_CHANGELOG.md create mode 100644 BUG_FIX_V3_CHANGELOG.md create mode 100644 BUG_FIX_V4_CHANGELOG.md create mode 100644 POLLING_MODE_CHANGELOG.md create mode 100644 agentskills/agent-build-standard/SKILL.md create mode 100644 agentskills/agent-build-standard/references/models.md create mode 100644 agentskills/agent-build-standard/references/repo-guidelines.md create mode 100644 static/icons/演示文稿1 [自动保存].pptx diff --git a/BUG_FIX_CHANGELOG.md b/BUG_FIX_CHANGELOG.md new file mode 100644 index 0000000..4a69531 --- /dev/null +++ b/BUG_FIX_CHANGELOG.md @@ -0,0 +1,107 @@ +# Bug 修复说明 + +## 修复的问题 + +### 1. 内容显示两遍 +**原因**: WebSocket 和轮询同时工作,导致事件被处理了两次 + +**解决方案**: +- 在 `app/state.ts` 中添加 `usePollingMode: true` 标志 +- 在所有关键的 WebSocket 事件处理器开头添加检查: + ```javascript + if (ctx.usePollingMode) { + return; // 跳过 WebSocket 事件 + } + ``` +- 修改的事件包括: + - `ai_message_start` + - `thinking_start/chunk/end` + - `text_start/chunk/end` + - `tool_preparing/intent/status/start` + +### 2. 刷新后内容消失 +**原因**: `restoreTaskState()` 清空了 `messages` 数组,导致已加载的历史记录丢失 + +**解决方案**: +- 修改 `app/methods/taskPolling.ts` 中的 `restoreTaskState()` 方法 +- 不再清空 `messages` 数组 +- 只标记状态为进行中(`streamingMessage = true`, `taskInProgress = true`) +- 轮询只处理新事件,不重建历史记录 + +## 修改的文件 + +1. **static/src/app/state.ts** + - 添加 `usePollingMode: true` 标志 + +2. **static/src/app/methods/taskPolling.ts** + - 修改 `restoreTaskState()` 方法,不清空消息 + +3. **static/src/composables/useLegacySocket.ts** + - 在所有关键事件处理器开头添加轮询模式检查 + - 修改的事件: + - `ai_message_start` + - `thinking_start` + - `thinking_chunk` + - `thinking_end` + - `text_start` + - `text_chunk` + - `text_end` + - `tool_preparing` + - `tool_intent` + - `tool_status` + - `tool_start` + +## 工作流程 + +### 正常发送消息 +1. 用户输入消息并发送 +2. 调用 REST API 创建任务 +3. 后端在独立线程中运行任务 +4. 前端每秒轮询一次,获取新事件 +5. 通过 `handleTaskEvent()` 处理事件,更新界面 +6. WebSocket 事件被 `usePollingMode` 标志跳过 + +### 页面刷新恢复 +1. 页面加载完成 +2. 延迟 1 秒后调用 `restoreTaskState()` +3. 查找运行中的任务 +4. 如果找到,标记状态为进行中 +5. 启动轮询,只处理新事件 +6. 已加载的历史记录保持不变 +7. 新事件追加到现有消息后面 + +## 测试建议 + +1. **基本功能** + - 发送消息,观察是否只显示一次 + - 刷新页面,观察历史记录是否保留 + - 观察新内容是否正常追加 + +2. **边界情况** + - 任务执行中多次刷新 + - 任务完成后刷新 + - 快速连续发送多条消息 + +3. **性能** + - 长时间运行任务 + - 大量事件(观察是否有重复) + +## 构建 + +```bash +cd static +npm run build +``` + +构建成功! +- `static/dist/assets/main.js` (687.87 kB) +- `static/dist/assets/task.js` (3.68 kB) + +## 下一步 + +现在可以启动服务器测试: +```bash +python web_server.py +``` + +访问 http://localhost:8091 测试功能。 diff --git a/BUG_FIX_V2_CHANGELOG.md b/BUG_FIX_V2_CHANGELOG.md new file mode 100644 index 0000000..1e1ee24 --- /dev/null +++ b/BUG_FIX_V2_CHANGELOG.md @@ -0,0 +1,191 @@ +# Bug 修复 v2 - 工具块和轮询优化 + +## 修复的问题 + +### 1. 工具块无法显示 +**原因**: 工具事件处理逻辑不完整,缺少关键的工具创建和更新逻辑 + +**解决方案**: +- 完整实现 `handleToolPreparing()` - 创建工具准备块 +- 完整实现 `handleToolStart()` - 工具开始执行 +- 完整实现 `handleToolUpdateAction()` - 工具状态更新和完成 +- 添加 `update_action` 事件处理(工具完成的关键事件) +- 在 WebSocket 中跳过 `update_action` 事件 + +### 2. 轮询频率太慢(1秒) +**原因**: 1秒的轮询间隔无法提供流式输出的体验 + +**解决方案**: +- 将轮询间隔从 1000ms 改为 **150ms** +- 接近流式输出的效果(每秒约 6-7 次更新) +- 在 `stores/task.ts` 中修改 `startPolling()` 方法 + +### 3. 刷新后加载两遍内容 +**原因**: +- `restoreTaskState()` 没有检查是否已在流式输出中 +- `handleTaskComplete()` 会重新加载历史记录 + +**解决方案**: +- 在 `restoreTaskState()` 中添加状态检查,避免重复恢复 +- 检查最后一条消息是否是 assistant 消息 +- 在 `handleTaskComplete()` 中移除 `fetchAndDisplayHistory()`,只更新统计 + +## 修改的文件 + +### 1. static/src/app/methods/taskPolling.ts +**工具处理逻辑**: +```javascript +handleToolPreparing(data) { + // 创建工具准备块 + const action = { + id: data.id, + type: 'tool', + tool: { + status: 'preparing', + name: data.name, + // ... 完整的工具属性 + } + }; + msg.actions.push(action); + this.preparingTools.set(data.id, action); +} + +handleToolStart(data) { + // 从 preparing 转为 running + let action = this.preparingTools.get(data.preparing_id); + action.tool.status = 'running'; + action.tool.arguments = data.arguments; + // ... 更新工具状态 +} + +handleToolUpdateAction(data) { + // 更新工具状态(包括完成) + let targetAction = this.toolFindAction(data.id, ...); + targetAction.tool.status = data.status; + targetAction.tool.result = data.result; + // ... 更新工具结果 +} +``` + +**恢复逻辑优化**: +```javascript +async restoreTaskState() { + // 检查是否已在流式输出中 + if (this.streamingMessage || this.taskInProgress) { + return; + } + + // 检查是否已有 assistant 消息 + const lastMessage = this.messages[this.messages.length - 1]; + const hasAssistantMessage = lastMessage && lastMessage.role === 'assistant'; + + // 只标记状态,不清空消息 + this.streamingMessage = true; + this.taskInProgress = true; +} +``` + +**任务完成优化**: +```javascript +handleTaskComplete(data) { + // 只更新统计,不重新加载历史 + this.fetchConversationTokenStatistics(); + this.updateCurrentContextTokens(); + // 移除了 fetchAndDisplayHistory() +} +``` + +### 2. static/src/stores/task.ts +**轮询频率优化**: +```javascript +startPolling(eventHandler) { + // 150ms 间隔,接近流式输出效果 + this.pollingInterval = window.setInterval(() => { + this.pollTaskEvents(handler); + }, 150); +} +``` + +### 3. static/src/composables/useLegacySocket.ts +**跳过 update_action 事件**: +```javascript +ctx.socket.on('update_action', (data) => { + if (ctx.usePollingMode) { + return; // 跳过 WebSocket 事件 + } + // ... 原有逻辑 +}); +``` + +## 事件处理流程 + +### 工具执行流程 +1. `tool_preparing` → 创建工具准备块 +2. `tool_start` → 工具开始执行,更新状态为 running +3. `update_action` → 工具完成,更新状态为 completed,显示结果 + +### 轮询流程 +1. 每 150ms 轮询一次 +2. 获取新事件(通过 `from` 参数) +3. 处理事件,更新界面 +4. 任务完成后停止轮询 + +### 刷新恢复流程 +1. 页面加载完成 +2. 延迟 1 秒后调用 `restoreTaskState()` +3. 检查是否已在流式输出中(避免重复) +4. 检查是否已有 assistant 消息(历史已加载) +5. 只标记状态,启动轮询 +6. 轮询只处理新事件,不重建历史 + +## 性能优化 + +- **轮询频率**: 150ms(每秒约 6-7 次) +- **事件增量获取**: 通过 `from` 参数只获取新事件 +- **避免重复加载**: 检查状态和消息,避免重复恢复 +- **避免重复显示**: 任务完成后不重新加载历史 + +## 测试建议 + +1. **工具块显示** + - 发送需要调用工具的消息 + - 观察工具块是否正常显示 + - 观察工具状态变化(preparing → running → completed) + - 观察工具结果是否正确显示 + +2. **流式输出效果** + - 发送消息,观察输出是否流畅 + - 观察思考块、文本块的更新频率 + - 对比之前 1 秒轮询的卡顿感 + +3. **刷新恢复** + - 任务执行中刷新页面 + - 观察是否只显示一遍内容 + - 观察新内容是否正常追加 + - 观察工具块是否保留 + +4. **任务完成** + - 等待任务完成 + - 观察是否有重复内容 + - 观察统计是否正确更新 + +## 构建 + +```bash +cd static +npm run build +``` + +构建成功! +- `static/dist/assets/main.js` (689.89 kB) +- `static/dist/assets/task.js` (3.68 kB) + +## 已知改进 + +- ✅ 工具块正常显示 +- ✅ 轮询频率提升到 150ms(接近流式效果) +- ✅ 刷新后不重复显示内容 +- ✅ 任务完成后不重复加载历史 +- ✅ 状态检查避免重复恢复 + +现在可以启动服务器测试了! diff --git a/BUG_FIX_V3_CHANGELOG.md b/BUG_FIX_V3_CHANGELOG.md new file mode 100644 index 0000000..a156ef1 --- /dev/null +++ b/BUG_FIX_V3_CHANGELOG.md @@ -0,0 +1,187 @@ +# Bug 修复 v3 - 刷新后重复加载问题 + +## 修复的问题 + +### 刷新后加载两遍内容 +**原因**: +1. `loadInitialData()` 会调用 `fetchAndDisplayHistory()` 加载历史记录 +2. 1 秒后 `restoreTaskState()` 启动轮询 +3. `loadRunningTask()` 设置 `lastEventIndex = 0`,从头开始获取所有事件 +4. 导致已经在历史中的事件被重复处理 + +**解决方案**: + +#### 1. 等待历史加载完成 +在 `restoreTaskState()` 中检查历史是否已加载: +```javascript +async restoreTaskState() { + // 检查历史是否已加载 + const hasMessages = Array.isArray(this.messages) && this.messages.length > 0; + + if (!hasMessages) { + // 等待历史加载完成后再恢复 + setTimeout(() => { + this.restoreTaskState(); + }, 500); + return; + } + + // 历史已加载,启动轮询 + taskStore.startPolling(...); +} +``` + +#### 2. 计算正确的事件偏移量 +在 `loadRunningTask()` 中获取任务详情,计算已处理的事件数量: +```javascript +async loadRunningTask(conversationId) { + // 查找运行中的任务 + const runningTask = result.data.find(...); + + if (runningTask) { + // 获取任务详情,计算已处理的事件数量 + const detailResponse = await fetch(`/api/tasks/${runningTask.task_id}`); + const detailResult = await detailResponse.json(); + + // 设置为当前事件数量,只获取新事件 + this.lastEventIndex = detailResult.data.next_offset || detailResult.data.events.length; + + debugLog('[Task] 设置起始偏移量:', this.lastEventIndex); + } +} +``` + +## 工作流程 + +### 页面刷新恢复流程(修复后) + +1. **页面加载** (`mounted()`) + - 调用 `loadInitialData()` + - 加载历史记录(`fetchAndDisplayHistory()`) + - 1 秒后调用 `restoreTaskState()` + +2. **任务恢复** (`restoreTaskState()`) + - 检查是否已在流式输出中 → 跳过 + - 查找运行中的任务 + - **检查历史是否已加载** → 如果未加载,等待 500ms 后重试 + - 历史已加载 → 继续 + +3. **加载任务详情** (`loadRunningTask()`) + - 查找运行中的任务 + - **获取任务详情** + - **计算已处理的事件数量** + - 设置 `lastEventIndex` 为当前事件数量 + +4. **启动轮询** (`startPolling()`) + - 从 `lastEventIndex` 开始轮询 + - **只获取新事件** + - 每 150ms 轮询一次 + +5. **处理新事件** + - 只处理新产生的事件 + - 追加到已有的历史记录后面 + - 不重复显示已有内容 + +## 关键改进 + +### 1. 等待历史加载 +- 在 `restoreTaskState()` 中检查 `messages` 是否已加载 +- 如果未加载,延迟 500ms 后重试 +- 确保历史加载完成后再启动轮询 + +### 2. 正确的事件偏移量 +- 在 `loadRunningTask()` 中获取任务详情 +- 计算已处理的事件数量(`next_offset` 或 `events.length`) +- 设置 `lastEventIndex` 为当前事件数量 +- 轮询只获取新事件(`from=lastEventIndex`) + +### 3. 避免重复处理 +- 历史记录由 `fetchAndDisplayHistory()` 加载 +- 轮询只处理新事件 +- 不会重复显示已有内容 + +## 修改的文件 + +### 1. static/src/app/methods/taskPolling.ts +**等待历史加载**: +```javascript +async restoreTaskState() { + // 检查历史是否已加载 + const hasMessages = Array.isArray(this.messages) && this.messages.length > 0; + + if (!hasMessages) { + debugLog('[TaskPolling] 历史未加载,等待历史加载完成'); + setTimeout(() => { + this.restoreTaskState(); + }, 500); + return; + } + + debugLog('[TaskPolling] 历史已加载,启动轮询'); + // ... 启动轮询 +} +``` + +### 2. static/src/stores/task.ts +**计算事件偏移量**: +```javascript +async loadRunningTask(conversationId) { + const runningTask = result.data.find(...); + + if (runningTask) { + // 获取任务详情 + const detailResponse = await fetch(`/api/tasks/${runningTask.task_id}`); + const detailResult = await detailResponse.json(); + + // 设置为当前事件数量 + this.lastEventIndex = detailResult.data.next_offset || detailResult.data.events.length; + + debugLog('[Task] 设置起始偏移量:', this.lastEventIndex); + } +} +``` + +## 测试场景 + +### 1. 正常发送消息 +- 发送消息 +- 观察输出是否正常 +- 观察是否只显示一次 + +### 2. 刷新页面(任务进行中) +- 任务执行中刷新页面 +- 观察历史记录是否正确显示 +- 观察是否只显示一次 +- 观察新内容是否正常追加 +- 观察工具块是否保留 + +### 3. 刷新页面(任务完成后) +- 任务完成后刷新页面 +- 观察历史记录是否完整 +- 观察是否没有重复内容 + +### 4. 多次刷新 +- 任务执行中多次刷新 +- 观察每次刷新是否正常 +- 观察是否有累积的重复内容 + +## 构建 + +```bash +cd static +npm run build +``` + +构建成功! +- `static/dist/assets/main.js` (690.04 kB) +- `static/dist/assets/task.js` (4.01 kB) + +## 最终效果 + +- ✅ 刷新后不重复显示内容 +- ✅ 历史记录正确加载 +- ✅ 新事件正常追加 +- ✅ 工具块正确显示 +- ✅ 流畅的输出效果(150ms 轮询) + +现在可以测试了!刷新页面应该只显示一次内容,新内容会正常追加。 diff --git a/BUG_FIX_V4_CHANGELOG.md b/BUG_FIX_V4_CHANGELOG.md new file mode 100644 index 0000000..3058f30 --- /dev/null +++ b/BUG_FIX_V4_CHANGELOG.md @@ -0,0 +1,186 @@ +# 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 轮询) +- ✅ 不重复显示内容 + +现在可以测试了!刷新页面后,后端应该继续执行,前端会正常恢复并显示新内容。 diff --git a/POLLING_MODE_CHANGELOG.md b/POLLING_MODE_CHANGELOG.md new file mode 100644 index 0000000..20e955f --- /dev/null +++ b/POLLING_MODE_CHANGELOG.md @@ -0,0 +1,180 @@ +# 轮询模式改造完成 + +## 改造内容 + +已成功将前端从 WebSocket 实时推送模式改造为 REST API + 轮询模式。 + +### 主要变更 + +#### 1. 后端改造 +- **server/tasks.py**: 已有完整的任务管理系统 + - `POST /api/tasks` - 创建任务 + - `GET /api/tasks/?from=` - 轮询任务事件 + - `POST /api/tasks//cancel` - 取消任务 + - 支持 videos 和 run_mode 参数 + +#### 2. 前端改造 +- **stores/task.ts** (新建): 任务轮询状态管理 + - `createTask()` - 创建任务并启动轮询 + - `pollTaskEvents()` - 轮询任务事件 + - `startPolling()` / `stopPolling()` - 控制轮询 + - `loadRunningTask()` - 加载运行中的任务(用于页面刷新恢复) + +- **app/methods/taskPolling.ts** (新建): 事件处理器 + - `handleTaskEvent()` - 统一事件分发 + - `handleAiMessageStart()` - AI 消息开始 + - `handleThinkingStart/Chunk/End()` - 思考过程 + - `handleTextStart/Chunk/End()` - 文本输出 + - `handleToolPreparing/Start/UpdateAction()` - 工具执行 + - `handleAppendPayload/ModifyPayload()` - 文件操作 + - `handleTaskComplete()` - 任务完成 + - `restoreTaskState()` - 恢复任务状态(页面刷新后) + +- **app/methods/message.ts**: 修改消息发送逻辑 + - 改为调用 REST API 创建任务 + - 停止任务改为调用 REST API 取消任务 + - 保留 WebSocket 兼容性(命令模式) + +- **app/lifecycle.ts**: 修改生命周期 + - `mounted()` 中注册全局事件处理器 + - `mounted()` 中延迟调用 `restoreTaskState()` 恢复任务 + - `beforeUnmount()` 中停止轮询 + +- **app.ts**: 集成任务轮询方法 + +## 功能特性 + +### ✅ 已实现 + +1. **后端独立运行** + - 任务创建后在后台线程运行 + - 不依赖 WebSocket 连接 + - 事件完整记录到内存队列(最多 1000 条) + +2. **前端轮询更新** + - 1 秒间隔轮询任务事件 + - 增量获取新事件(通过 `from` 参数) + - 任务完成后自动停止轮询 + +3. **页面刷新恢复** + - 页面加载时自动查找运行中的任务 + - 从事件流重建前端状态 + - 恢复思考块、文本块、工具块的展开状态 + - 继续轮询更新新事件 + +4. **状态完整恢复** + - 正在输出的思考内容 → 展开的思考块 + 已输出内容 + - 正在输出的文本内容 → 文本块 + 已输出内容 + - 正在执行的工具 → 工具块 + 执行状态 + - 文件操作 → append/modify payload 块 + +5. **任务取消** + - 支持通过 REST API 取消任务 + - 前端立即清理状态 + - 后端标记停止标志 + +## 使用方式 + +### 正常使用 +1. 输入消息并发送 +2. 后端自动创建任务并开始执行 +3. 前端每秒轮询一次,实时更新界面 +4. 任务完成后自动停止轮询 + +### 页面刷新 +1. 刷新页面 +2. 前端自动检测运行中的任务 +3. 从事件流重建状态(思考块、文本块、工具块) +4. 继续轮询更新新事件 +5. 显示提示:"检测到进行中的任务,已恢复连接" + +### 停止任务 +1. 点击停止按钮 +2. 调用 REST API 取消任务 +3. 前端立即清理状态 +4. 后端收到停止信号后终止执行 + +## 技术细节 + +### 轮询机制 +- 间隔:1 秒 +- 增量获取:通过 `from` 参数指定起始事件索引 +- 自动停止:任务状态为 `succeeded/failed/canceled` 时停止 + +### 事件流格式 +```json +{ + "idx": 0, + "type": "ai_message_start", + "data": {}, + "ts": 1234567890.123 +} +``` + +### 任务状态 +- `pending` - 待执行 +- `running` - 执行中 +- `succeeded` - 成功完成 +- `failed` - 执行失败 +- `canceled` - 已取消 + +### 事件类型 +- `ai_message_start` - AI 消息开始 +- `thinking_start/chunk/end` - 思考过程 +- `text_start/chunk/end` - 文本输出 +- `tool_preparing/start/update_action` - 工具执行 +- `append_payload/modify_payload` - 文件操作 +- `task_complete` - 任务完成 +- `error` - 错误 +- `token_update` - Token 统计更新 +- `conversation_resolved` - 对话已解析 + +## 兼容性 + +- ✅ 保留 WebSocket 连接(用于命令模式和实时推送) +- ✅ 命令模式(`/clear` 等)仍使用 WebSocket +- ✅ 在线用户仍可收到实时推送(通过 `socketio.emit` 到房间) +- ✅ 支持多标签页同时查看进度 + +## 测试建议 + +1. **基本功能测试** + - 发送消息,观察是否正常输出 + - 刷新页面,观察是否恢复状态 + - 停止任务,观察是否立即停止 + +2. **边界情况测试** + - 任务执行中刷新多次 + - 任务完成后刷新 + - 多个标签页同时打开 + - 网络断开后重连 + +3. **性能测试** + - 长时间运行任务(观察轮询是否稳定) + - 大量事件(1000+ 条) + - 多用户并发 + +## 已知限制 + +1. **事件队列大小**: 最多保留 1000 条事件(deque maxlen=1000) +2. **任务持久化**: 当前仅内存存储,服务器重启后丢失 +3. **并发限制**: 单用户单工作区同时只能有一个任务 + +## 后续优化建议 + +1. **持久化**: 将任务和事件存储到 Redis/数据库 +2. **事件压缩**: 对历史事件进行压缩存储 +3. **断点续传**: 支持从任意事件索引恢复 +4. **WebSocket 降级**: 完全移除 WebSocket 依赖 +5. **长轮询**: 使用 long-polling 减少请求次数 + +## 构建 + +```bash +cd static +npm run build +``` + +构建成功!输出文件: +- `static/dist/assets/task.js` (3.68 kB) +- `static/dist/assets/main.js` (687.51 kB) diff --git a/agentskills/agent-build-standard/SKILL.md b/agentskills/agent-build-standard/SKILL.md new file mode 100644 index 0000000..a5bcf54 --- /dev/null +++ b/agentskills/agent-build-standard/SKILL.md @@ -0,0 +1,233 @@ +--- +name: agent-build-standard +description: "通用 Agent 构建标准教学与最佳实践。用于讲解 LLM 对话与 Agent 的区别、system/user/assistant/tool 角色与消息规范、工具调用与结果回填、Agent 循环、上下文构建与存储、常见错误与风险控制。当用户请求设计/评审/制定 Agent 架构、消息协议或上下文策略时使用。" +--- + +# 通用 Agent 构建标准教学 + +## 使用方式 +- 先确认目标:是“定义协议/标准”,还是“实现具体 Agent” +- 先给出最小可行标准,再补充可选增强 +- 需要模型清单或对比时,阅读 `references/models.md` +- 需要项目级规范时,阅读 `references/repo-guidelines.md` + +## 目标与范围 +- 解释 Agent 的定义、角色协议、工具调用规范、上下文组织方式 +- 说明 Agent 运行循环与关键决策点 +- 提供可落地的“协议与实现清单” +- 不展开具体厂商模型价格与商务条款 +- 不替代具体业务流程与产品设计 + +## 1. 什么是 Agent + +### 1.1 基本概念 +- 定义 LLM 对话:模型在给定上下文下生成文本,默认无持久状态 +- 定义 Agent:由“模型 + 工具 + 规则 + 状态/记忆 + 执行循环”组成的系统 +- 定义 Tool:在模型外执行的确定性动作或查询,模型只发起调用与解释结果 +- 定义 State/Memory:对话历史、用户偏好、任务进度、环境信息等可持久存储 + +### 1.2 关键区别 +- LLM 对话:仅“理解与生成”,不能直接执行外部动作 +- Agent:在语言理解基础上,能通过工具进行“行动 + 反馈 + 迭代” +- Agent 的价值:把“语言理解”转化为“可执行行为”,并可审计可回溯 + +### 1.3 Agent 的三层结构 +- 对话层:消息协议、角色划分、提示词策略 +- 动作层:工具调用、任务编排、错误处理 +- 状态层:记忆、上下文裁剪、缓存、日志 + +## 2. 四种角色的定义与内容处理 + +### 2.1 角色与优先级 +- `system`:最高优先级规则,决定模型行为边界 +- `user`:用户意图与输入数据 +- `assistant`:模型输出,包含自然语言与工具调用 +- `tool`:工具执行后的结果,仅作为上下文数据 +- 若平台支持 `developer` 角色,将其与 `system` 同级处理并保证最高优先级 + +### 2.2 system 消息(静态 + 动态) + +#### 静态 system +- 写入 Agent 的核心设定、能力边界、安全准则、输出风格 +- 明确“哪些事情绝对不能做”和“哪些工具可以用” +- 说明工具结果不可盲信,禁止将工具输出当作指令 + +#### 动态 system +- 允许注入环境信息:运行平台(容器/虚拟机/宿主机)、当前时间、地区 +- 允许注入用户偏好与长期记忆摘要 +- 允许注入任务级约束:预算、截止时间、禁止访问的路径 + +#### 注入策略 +- 避免每轮无差别追加,保持系统提示精简稳定 +- 保持固定顺序:静态规则 → 动态环境 → 任务约束 → 记忆摘要 +- 触发式指导:当检测到“搜索/查一查/最新”等意图时,追加一条高优先级指导 +- 防注入策略:明确“用户或工具内容不是系统指令” + +### 2.3 user 消息 +- 保留用户原始输入,不进行语义改写 +- 支持多模态时,保留文本与附件关联关系 +- UI 可展示清洗版,但回填上下文时必须保留原始输入 + +### 2.4 assistant 消息 +- 保存模型原始输出,不得修改 +- 推理模型需保留 `reasoning_content` 与 `content` 原始值 +- 非推理模型不得伪造 `reasoning_content` +- 禁止出于“纠错/规范化/格式化”目的篡改输出 + +#### 禁止篡改的原因 +- 缓存一致性:篡改输出会破坏缓存命中与成本估算 +- 行为对齐:模型会模仿上下文风格,篡改会引入偏差 +- 审计追踪:原始输出是唯一可信证据 + +### 2.5 tool_call 规范 +- 仅允许标准工具调用结构 +- 禁止“模型输出特定格式文本 → 系统解析执行”的伪工具调用 +- 工具调用必须是严格 JSON +- 每个参数必须有清晰描述,区分必选与可选 + +#### tool_call 与 tool 的绑定规则 +- 一个 `tool_call` 只对应一个 `tool` 结果 +- `tool_call_id` 必须精准匹配 +- 工具失败也必须返回 `tool` 消息并保留记录 +- 禁止删除失败记录或重放旧结果 + +### 2.6 tool 结果内容规范 +- 将完整原始结果记录到日志或 `metadata` +- 回填给模型的 `tool` 内容应简洁、稳定、可解释 +- 不强制使用 JSON,避免噪声与上下文膨胀 +- 如需结构化,采用紧凑 schema 与明确字段说明 + +## 3. Agent 循环(最小闭环) + +### 3.1 最小流程 +1. 接收 user 输入 +2. 模型输出 assistant +3. 若直接回答:结束本轮 +4. 若包含 tool_call:执行工具 +5. 回填 tool 结果进入上下文 +6. 再次请求模型,直到完成回答 + +### 3.2 计划-行动-观察-回填 +- 计划:判定是否需要工具与子任务拆分 +- 行动:发出 tool_call +- 观察:读取 tool 结果 +- 回填:将结果加入上下文 +- 决策:继续调用或结束回答 + +### 3.3 控制与退出条件 +- 设置最大步数与超时 +- 提供用户中断与人工接管机制 +- 对重复失败采取降级或澄清 +- 对无法完成的任务给出明确可操作建议 + +## 4. 上下文构建与存储 + +### 4.1 标准消息字段 +- `role`:system/user/assistant/tool 之一 +- `content`:消息正文 +- `timestamp`:可选,建议 ISO8601 +- `reasoning_content`:可选,仅推理模型 +- `tool_calls`:可选,assistant 调用工具时出现 +- `tool_call_id`:可选,仅 tool 消息 +- `metadata`:可选,记录模型、工具、原始输出等 + +### 4.2 最小示例(可执行闭环) +```json +[ + { + "role": "system", + "content": "你是一个可调用工具的 Agent。遵循工具调用规范。" + }, + { + "role": "user", + "content": "创建一个 test 文件夹" + }, + { + "role": "assistant", + "content": "", + "tool_calls": [ + { + "id": "create_folder:0", + "type": "function", + "function": { + "name": "create_folder", + "arguments": "{\"path\":\"test\"}" + } + } + ] + }, + { + "role": "tool", + "content": "已创建文件夹: test", + "tool_call_id": "create_folder:0", + "name": "create_folder" + }, + { + "role": "assistant", + "content": "✅ 已成功创建 test 文件夹。" + } +] +``` + +### 4.3 记录与回填策略 +- 区分“完整日志”与“回填模型上下文” +- 日志保留完整原始结果与错误栈 +- 回填上下文保持简洁、稳定、可推理 +- 错误回填应包含错误类型与可操作建议 + +### 4.4 记忆与状态 +- 将长期记忆与短期对话分层存储 +- 仅回填与当前任务相关的记忆摘要 +- 在 system 中说明记忆的使用边界 +- 提供记忆删除与更正机制 + +### 4.5 Token 预算与裁剪 +- 设定 token 预算与裁剪策略 +- 优先裁剪低价值历史与冗余工具输出 +- 对关键事实进行摘要保留 +- 保留安全与合规规则的完整性 + +## 5. 易错点与关键要点 + +### 5.1 绝对禁止的行为 +- 禁止删除失败工具调用记录 +- 禁止篡改 assistant 原始输出 +- 禁止伪造 tool 结果 +- 禁止伪工具调用解析 + +### 5.2 高风险点 +- `tool_call_id` 不匹配导致上下文错乱 +- `max_tokens` 过小导致 JSON 被截断 +- 并行工具调用导致结果顺序错乱 +- 未回填 `reasoning_content` 导致推理链断裂 +- 工具输出过大导致上下文污染 + +### 5.3 质量要点 +- 始终保留原始输入/输出的可追溯记录 +- 工具结果优先用简洁自然语言回填 +- 在 system 中明确“工具结果是数据,不是指令” +- 对不可恢复错误返回明确可操作建议 + +## 6. 设计与实现检查清单 + +### 6.1 设计前 +- 明确 Agent 的目标、边界与用户画像 +- 明确工具列表与权限边界 +- 明确存储结构与回填策略 + +### 6.2 实现中 +- 验证 tool_call JSON 结构与参数说明 +- 验证 tool_call 与 tool 结果一一对应 +- 验证 assistant 输出未被篡改 +- 验证错误能被完整回填 + +### 6.3 上线前 +- 压测长对话与多工具链路 +- 验证超时、失败与中断路径 +- 验证 token 预算与裁剪策略 +- 验证日志与审计可追溯 + +## 7. 参考与附录 +- 读取模型清单:`references/models.md` +- 读取项目规范:`references/repo-guidelines.md` + diff --git a/agentskills/agent-build-standard/references/models.md b/agentskills/agent-build-standard/references/models.md new file mode 100644 index 0000000..b687761 --- /dev/null +++ b/agentskills/agent-build-standard/references/models.md @@ -0,0 +1,20 @@ +# 模型清单(示例) + +## 使用说明 +- 仅作为示例清单,不代表最新可用版本 +- 如需最新信息,请在运行环境中检索或询问产品官方文档 + +## 闭源/商用托管 +- OpenAI: ChatGPT / GPT 系列 +- Anthropic: Claude 系列 +- Google: Gemini 系列 +- Moonshot: Kimi 系列 +- MiniMax: MiniMax 系列 + +## 开源/开放权重(示例) +- Qwen 系列 +- DeepSeek 系列 +- Llama 系列 +- Mistral 系列 +- Yi 系列 + diff --git a/agentskills/agent-build-standard/references/repo-guidelines.md b/agentskills/agent-build-standard/references/repo-guidelines.md new file mode 100644 index 0000000..2d0bdbd --- /dev/null +++ b/agentskills/agent-build-standard/references/repo-guidelines.md @@ -0,0 +1,22 @@ +# 项目规范(摘要) + +## 结构与职责 +- `main.py` 启动 CLI agent;`web_server.py` 提供 Web UI +- `core/` 负责终端编排,尽量保持 I/O 轻量 +- `modules/` 放可复用能力,优先扩展这里 +- `utils/` 放 API 客户端与日志工具,优先复用 +- `test/` 放集成测试脚本与辅助工具 + +## 代码风格 +- Python 3.11+,4 空格缩进,snake_case +- 需要类型标注与简短双语 docstring +- 日志统一使用 `utils.logger.setup_logger` + +## 前端注意 +- `static/src/components/chat/monitor` 为虚拟显示器动画层 +- 新场景需对齐 `MonitorDirector` 与 monitor store + +## 测试建议 +- 优先 `pytest`,文件名为 `test/test_.py` +- 涉及网络请使用 `test/api_interceptor_server.py` 代理 + diff --git a/server/chat_flow_tool_loop.py b/server/chat_flow_tool_loop.py index 1f61261..61989c7 100644 --- a/server/chat_flow_tool_loop.py +++ b/server/chat_flow_tool_loop.py @@ -213,9 +213,87 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie await asyncio.sleep(0.3) start_time = time.time() - # 执行工具 - tool_result = await web_terminal.handle_tool_call(function_name, arguments) - debug_log(f"工具结果: {tool_result[:200]}...") + # 执行工具,同时监听停止标志 + debug_log(f"[停止检测] 开始执行工具: {function_name}") + tool_task = asyncio.create_task(web_terminal.handle_tool_call(function_name, arguments)) + tool_result = None + tool_cancelled = False + + # 在工具执行期间持续检查停止标志 + check_count = 0 + while not tool_task.done(): + await asyncio.sleep(0.1) # 每100ms检查一次 + check_count += 1 + client_stop_info = get_stop_flag(client_sid, username) + if client_stop_info: + stop_requested = client_stop_info.get('stop', False) if isinstance(client_stop_info, dict) else client_stop_info + if stop_requested: + debug_log(f"[停止检测] 工具执行过程中检测到停止请求(检查次数:{check_count}),立即取消工具") + tool_task.cancel() + tool_cancelled = True + break + + debug_log(f"[停止检测] 工具执行完成,cancelled={tool_cancelled}, 检查次数={check_count}") + + # 获取工具结果或处理取消 + if tool_cancelled: + try: + await tool_task + except asyncio.CancelledError: + debug_log("[停止检测] 工具任务已被取消(CancelledError)") + except Exception as e: + debug_log(f"[停止检测] 工具任务取消时发生异常: {e}") + + # 返回取消消息 + tool_result = json.dumps({ + "success": False, + "status": "cancelled", + "message": "命令执行被用户取消" + }, ensure_ascii=False) + + debug_log("[停止检测] 发送取消通知到前端") + # 通知前端工具被取消 + sender('update_action', { + 'preparing_id': tool_call_id, + 'status': 'cancelled', + 'result': { + "success": False, + "status": "cancelled", + "message": "命令执行被用户取消", + "tool": function_name + } + }) + + # 记录取消结果到消息历史 + messages.append({ + "role": "tool", + "tool_call_id": tool_call_id, + "name": function_name, + "content": "命令执行被用户取消", + "metadata": {"status": "cancelled"} + }) + + # 保存取消结果 + web_terminal.context_manager.add_conversation( + "tool", + "命令执行被用户取消", + tool_call_id=tool_call_id, + name=function_name, + metadata={"status": "cancelled"} + ) + debug_log("[停止检测] 取消结果已保存到对话历史") + + # 发送停止事件并清除标志 + sender('task_stopped', { + 'message': '命令执行被用户取消', + 'reason': 'user_stop' + }) + clear_stop_flag(client_sid, username) + debug_log("[停止检测] 返回stopped=True") + return {"stopped": True, "last_tool_call_time": last_tool_call_time} + else: + tool_result = await tool_task + debug_log(f"工具结果: {tool_result[:200]}...") execution_time = time.time() - start_time if execution_time < 1.5: diff --git a/server/socket_handlers.py b/server/socket_handlers.py index 3418363..e667ff4 100644 --- a/server/socket_handlers.py +++ b/server/socket_handlers.py @@ -131,18 +131,20 @@ def handle_stop_task(): task_info = get_stop_flag(request.sid, username) if not isinstance(task_info, dict): task_info = {'stop': False, 'task': None, 'terminal': None} - # 标记停止并尝试取消任务 + # 标记停止标志,让任务内部检测并优雅停止 task_info['stop'] = True - pending_task = task_info.get('task') - if pending_task and not pending_task.done(): - debug_log(f"正在取消任务: {request.sid}") - pending_task.cancel() + # 注释掉直接取消任务,改为通过停止标志让任务内部处理 + # pending_task = task_info.get('task') + # if pending_task and not pending_task.done(): + # debug_log(f"正在取消任务: {request.sid}") + # pending_task.cancel() + debug_log(f"设置停止标志: {request.sid}") if task_info.get('terminal'): reset_system_state(task_info['terminal']) set_stop_flag(request.sid, username, task_info) - + emit('stop_requested', { - 'message': '停止请求已接收,正在取消任务...' + 'message': '停止请求已接收,正在停止任务...' }) @socketio.on('terminal_subscribe') diff --git a/server/tasks.py b/server/tasks.py index 3b06ca6..3d8f074 100644 --- a/server/tasks.py +++ b/server/tasks.py @@ -147,11 +147,12 @@ class TaskManager: entry = {'stop': False, 'task': None, 'terminal': None} stop_flags[task_id] = entry entry['stop'] = True - try: - if entry.get('task') and hasattr(entry['task'], "cancel"): - entry['task'].cancel() - except Exception: - pass + # 注释掉直接取消任务,改为通过停止标志让任务内部处理 + # try: + # if entry.get('task') and hasattr(entry['task'], "cancel"): + # entry['task'].cancel() + # except Exception: + # pass with self._lock: rec.status = "cancel_requested" rec.updated_at = time.time() diff --git a/static/icons/演示文稿1 [自动保存].pptx b/static/icons/演示文稿1 [自动保存].pptx new file mode 100644 index 0000000000000000000000000000000000000000..f23a785fa0223c9cab5ea2fea81aaf81d7b09e96 GIT binary patch literal 52552 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%0D?Q4J7y2Jl& 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