From 0aa37b83ee189b9fb0bfd5ae199df571f2b34ec3 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Tue, 14 Jul 2026 19:46:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(stop-button):=20=E4=BF=AE=E5=A4=8DREST?= =?UTF-8?q?=E8=BD=AE=E8=AF=A2=E8=B7=AF=E5=BE=84=E4=B8=A4=E4=B8=AAbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. lifecycle.ts handleTaskStopped: 移除'再次按下停止按钮以结束后台任务'弹窗。 后台任务停止不再走停止按钮二次点击路径。 2. 主对话空闲+后台任务跑时输入栏依然为停止按钮: 根因 - App.vue 传给 InputComposer 的 streaming-message 用的是 composerStreamingForInput (= composerBusy && !displayLockEngaged), 只要有后台任务 taskInProgress=true 就让 streamingMessage=true。 改为传 streamingUi (= streamingMessage || hasPendingToolActions), 只看主对话自身是否在流式输出,与后台任务无关。 3. 后端 chat_flow_task_main.py 用户主动停止时发的 task_stopped 事件没带 has_running_* 字段,导致前端误判为完全空闲。 补全字段(has_running_sub_agents/has_running_background_commands/ has_running_multi_agent),前端用此信息决定是否保持对话运行态。 --- server/chat_flow_task_main.py | 41 ++++++++++++++++++- static/src/App.vue | 2 +- .../src/app/methods/taskPolling/lifecycle.ts | 17 ++------ 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/server/chat_flow_task_main.py b/server/chat_flow_task_main.py index e01b0b6..e9c9186 100644 --- a/server/chat_flow_task_main.py +++ b/server/chat_flow_task_main.py @@ -1833,9 +1833,43 @@ async def handle_task_with_sender( ) except Exception as exc: debug_log(f"[Goal] 用户停止时停止目标失败: {exc}") + # 计算后台任务状态,前端用这个字段决定是否保持对话运行态、输入区是否释放 + _stopped_has_bg = False + _stopped_has_bg_cmd = False + _stopped_has_ma = False + _manager = getattr(web_terminal, "sub_agent_manager", None) + if _manager: + try: + _manager.reconcile_task_states(conversation_id=conversation_id) + except Exception: + pass + if getattr(web_terminal, "multi_agent_mode", False): + _state = _manager.get_multi_agent_state(conversation_id) + if _state: + _stopped_has_ma = any(a.status == "running" for a in _state.list_all()) + for _t in _manager.tasks.values(): + if _t.get("conversation_id") != conversation_id: + continue + if _t.get("status") in TERMINAL_STATUSES.union({"terminated"}): + continue + if _t.get("multi_agent_mode"): + continue + _stopped_has_bg = True + break + _bg_mgr = getattr(web_terminal, "background_command_manager", None) + if _bg_mgr and conversation_id: + try: + _bg_mgr.reconcile_stale_records(conversation_id=conversation_id) + except Exception: + pass + if _bg_mgr.list_waiting_items(conversation_id): + _stopped_has_bg_cmd = True sender('task_stopped', { 'message': '任务已停止', - 'reason': 'user_requested' + 'reason': 'user_requested', + 'has_running_sub_agents': _stopped_has_bg, + 'has_running_background_commands': _stopped_has_bg_cmd, + 'has_running_multi_agent': _stopped_has_ma, }) break @@ -2189,7 +2223,10 @@ async def handle_task_with_sender( sender('task_stopped', { 'message': tool_loop_result.get("approval_message") or '操作被用户拒绝', 'reason': 'approval_rejected', - 'conversation_id': conversation_id + 'conversation_id': conversation_id, + 'has_running_sub_agents': False, + 'has_running_background_commands': False, + 'has_running_multi_agent': False }) finalize_user_work_timer() finalize_run_versioning_checkpoint("approval_rejected") diff --git a/static/src/App.vue b/static/src/App.vue index 48746e6..da38da0 100644 --- a/static/src/App.vue +++ b/static/src/App.vue @@ -279,7 +279,7 @@ :input-is-multiline="inputIsMultiline" :input-is-focused="inputIsFocused" :is-connected="isConnected" - :streaming-message="composerStreamingForInput" + :streaming-message="streamingUi" :input-locked="displayLockEngaged" :uploading="uploading" :media-uploading="mediaUploading" diff --git a/static/src/app/methods/taskPolling/lifecycle.ts b/static/src/app/methods/taskPolling/lifecycle.ts index bc6011d..49e788e 100644 --- a/static/src/app/methods/taskPolling/lifecycle.ts +++ b/static/src/app/methods/taskPolling/lifecycle.ts @@ -486,22 +486,13 @@ export const lifecycleMethods = { this.stopRequested = false; if (hasRunningBackground) { - // 智能体已停,但后台任务还在跑:保持停止按钮,等用户第二下清理 - // 此时对话并未真正停止,work_timer 保持运行,不提前标记完成 + // 主智能体已停,后台任务仍在跑:保持 taskInProgress=true (对话列表显示运行中), + // 但输入栏不再锁定(stopRequested 已重置),用户可发新消息触发下一轮。 + // 后台任务的停止由独立的子智能体/后台指令按钮处理,不再走停止按钮二次点击。 this.taskInProgress = true; this.waitingForSubAgent = hasRunningSubAgents; this.waitingForBackgroundCommand = hasRunningBackgroundCommands; - debugLog('[TaskPolling] 任务已停止,仍有后台任务运行,保持停止态'); - goalModeDebugLog('handleTaskStopped:show-toast', { hasRunningSubAgents, hasRunningBackgroundCommands }); - try { - this.uiPushToast({ - title: '智能体已停止', - message: '再次按下停止按钮以结束后台任务', - type: 'info' - }); - } catch (err) { - console.warn('[TaskPolling] uiPushToast 调用失败:', err); - } + debugLog('[TaskPolling] 任务已停止,仍有后台任务运行,保持对话运行态但释放输入区'); } else { // 对话真正停止:停止计时器并持久化 this.markLatestUserWorkCompleted();