// @ts-nocheck import { debugLog, goalModeDebugLog } from '../common'; import { useTaskStore } from '../../../stores/task'; import { useQuickDockStore } from '../../../stores/quickDock'; import { useChatStore } from '../../../stores/chat'; import { getMessageVisibility, messageStartsWork } from '../../../utils/messageVisibility'; import { debugNotifyLog, keyNotifyLog, jsonDebug, userMDebug, isRestoreDebugEnabled, restoreDebugLog, isSystemAutoUserMessagePayload, isRuntimeModeNoticePayload, resolveUserMessageSource, resolveUserMessageMetadata, isEmptyAssistantPlaceholderMessage, getOptimisticUserEchoTarget, findRecentMatchingUserMessage, } from './shared'; export const lifecycleMethods = { handleTaskEvent(event: any) { if (!event || !event.type) { return; } const eventType = event.type; const eventData = event.data || {}; const eventIdx = event.idx; const taskStore = useTaskStore(); if (eventType === 'task_complete' || eventType === 'task_stopped' || eventType === 'error') { jsonDebug('task-event', { eventType, eventIdx, currentConversationId: this.currentConversationId, eventConversationId: eventData?.conversation_id, taskInProgress: this.taskInProgress, streamingMessage: this.streamingMessage, stopRequested: this.stopRequested, waitingForSubAgent: this.waitingForSubAgent, waitingForBackgroundCommand: this.waitingForBackgroundCommand, errorType: eventData?.error_type, errorMessage: eventData?.message }); } if ( eventType === 'system_message' || eventType === 'sub_agent_waiting' || (eventType === 'user_message' && eventData?.sub_agent_notice) ) { debugNotifyLog('[DEBUG_NOTIFY][event] 捕获关键事件', { eventType, eventIdx, eventData }); keyNotifyLog('[DEBUG_NOTIFY_KEY][event] key-event', { eventType, idx: eventIdx, task_id: eventData?.task_id, sub_agent_notice: !!eventData?.sub_agent_notice, has_running_sub_agents: eventData?.has_running_sub_agents, has_running_background_commands: eventData?.has_running_background_commands }); } // 检查事件的 conversation_id 是否匹配当前对话 // 如果不匹配,忽略该事件(避免切换对话后旧任务的事件显示到新对话中) const crossConversationAllowed = new Set([ 'shallow_compression', 'compression_finished' ]); // 显式新建路由(/new、/multiagent/new)上 currentConversationId 为空是常态: // 空 id 不等于“无归属”,此时任何携带 conversation_id 的事件都属于其他对话, // 必须丢弃,否则运行中对话的用户输入/AI输出/工具调用会渲染到空白页。 const onExplicitNewRoute = typeof this.isExplicitNewConversationRoute === 'function' && this.isExplicitNewConversationRoute(); const eventConversationMismatch = !!( eventData.conversation_id && (this.currentConversationId ? eventData.conversation_id !== this.currentConversationId : onExplicitNewRoute) ); if (!crossConversationAllowed.has(eventType) && eventConversationMismatch) { { if ( [ 'ai_message_start', 'thinking_start', 'thinking_chunk', 'thinking_end', 'text_start', 'text_chunk', 'text_end' ].includes(eventType) ) { restoreDebugLog('event:drop-conversation-mismatch', { eventType, eventIdx, eventConversationId: eventData.conversation_id, currentConversationId: this.currentConversationId }); } debugLog( `[TaskPolling] 忽略不匹配的事件 #${eventIdx}: ${eventType}, 事件对话=${eventData.conversation_id}, 当前对话=${this.currentConversationId}` ); return; } } // 检查事件 task_id 是否仍是当前正在接管的任务,防止切换/新建对话后旧轮询的在途响应串写。 // task_stopped / task_complete / error 属于任务终态事件,即使 currentTaskId 已被清理 //(例如用户点击停止后 stopPolling 清空了 ID),只要 conversation_id 匹配就应该处理。 const isTerminalTaskEvent = ['task_complete', 'task_stopped', 'error'].includes(eventType); if ( !crossConversationAllowed.has(eventType) && eventData.task_id && (!taskStore.currentTaskId || eventData.task_id !== taskStore.currentTaskId) && !(isTerminalTaskEvent && eventData.conversation_id === this.currentConversationId) ) { restoreDebugLog('event:drop-task-mismatch', { eventType, eventIdx, eventTaskId: eventData.task_id, currentTaskId: taskStore.currentTaskId, eventConversationId: eventData.conversation_id, currentConversationId: this.currentConversationId }); debugLog( `[TaskPolling] 忽略不匹配的任务事件 #${eventIdx}: ${eventType}, 事件任务=${eventData.task_id}, 当前任务=${taskStore.currentTaskId}` ); return; } // 事件去重检查必须在 conversation/task 校验之后执行,避免其他对话同 idx 事件污染当前任务去重表。 const dedupeKey = eventData.task_id && typeof eventIdx === 'number' ? `${eventData.task_id}:${eventIdx}` : typeof eventIdx === 'number' ? `idx:${eventIdx}` : ''; if (dedupeKey) { if (!this._processedEventIndices) { this._processedEventIndices = new Set(); } if (this._processedEventIndices.has(dedupeKey)) { if ( [ 'ai_message_start', 'thinking_start', 'thinking_chunk', 'thinking_end', 'text_start', 'text_chunk', 'text_end' ].includes(eventType) ) { restoreDebugLog('event:drop-duplicate', { eventType, eventIdx, dedupeKey, currentConversationId: this.currentConversationId }); } debugLog(`[TaskPolling] 跳过重复事件 ${dedupeKey}`); return; } this._processedEventIndices.add(dedupeKey); // 限制 Set 大小(保留最近 1000 个) if (this._processedEventIndices.size > 1000) { const firstValue = this._processedEventIndices.values().next().value; this._processedEventIndices.delete(firstValue); } } debugLog(`[TaskPolling] 处理事件 #${eventIdx}: ${eventType}`, eventData); // 根据事件类型调用对应的处理方法 switch (eventType) { case 'ai_message_start': this.handleAiMessageStart(eventData, eventIdx); break; case 'thinking_start': this.handleThinkingStart(eventData, eventIdx); break; case 'thinking_chunk': this.handleThinkingChunk(eventData, eventIdx); break; case 'thinking_end': this.handleThinkingEnd(eventData, eventIdx); break; case 'text_start': this.handleTextStart(eventData, eventIdx); break; case 'text_chunk': this.handleTextChunk(eventData, eventIdx); break; case 'text_end': this.handleTextEnd(eventData, eventIdx); break; case 'tool_preparing': this.handleToolPreparing(eventData, eventIdx); break; case 'tool_start': this.handleToolStart(eventData, eventIdx); break; case 'tool_intent': this.handleToolIntent(eventData, eventIdx); break; case 'tool_update_action': case 'update_action': this.handleToolUpdateAction(eventData, eventIdx); break; case 'tool_approval_required': this.handleToolApprovalRequired(eventData, eventIdx); break; case 'tool_approval_resolved': this.handleToolApprovalResolved(eventData, eventIdx); break; case 'user_question_required': case 'user_questions_required': this.handleUserQuestionsRequired(eventData, eventIdx); break; case 'user_question_resolved': case 'user_questions_resolved': this.handleUserQuestionsResolved(eventData, eventIdx); break; case 'auto_approval_progress': this.handleAutoApprovalProgress(eventData, eventIdx); break; case 'goal_progress': this.handleGoalProgress?.(eventData, eventIdx); break; case 'goal_review_progress': this.handleGoalReviewProgress?.(eventData, eventIdx); break; case 'goal_completed': this.handleGoalCompleted?.(eventData, eventIdx); break; case 'goal_stopped': this.handleGoalStopped?.(eventData, eventIdx); break; case 'append_payload': this.handleAppendPayload(eventData, eventIdx); break; case 'modify_payload': this.handleModifyPayload(eventData, eventIdx); break; case 'task_complete': this.handleTaskComplete(eventData, eventIdx); break; case 'task_stopped': this.handleTaskStopped(eventData, eventIdx); break; case 'error': this.handleTaskError(eventData, eventIdx); break; case 'token_update': this.handleTokenUpdate(eventData, eventIdx); break; case 'conversation_changed': this.handleConversationChanged(eventData, eventIdx); break; case 'conversation_resolved': this.handleConversationResolved(eventData, eventIdx); break; case 'todo_updated': // 任务期广播的待办快照(创建/勾选/清空都会触发)。live=true 让窗口播动画。 // 快照直接可用;缺失时退化为 REST 拉取(fetchTodoList 带 conversation_id)。 if (eventData && 'todo_list' in eventData) { this.fileSetTodoList(eventData.todo_list || null, true); } else if (typeof this.scheduleTodoListRefresh === 'function') { this.scheduleTodoListRefresh(0); } break; case 'edited_files_updated': // 快捷窗口文件记录:edit/write/delete/rename 后广播,payload 携带最新列表 useQuickDockStore().setEditedFiles( Array.isArray(eventData?.edited_files) ? eventData.edited_files : [], true ); break; case 'edit_summary_updated': // 编辑摘要卡片:合并 diff 实时写入发起工作的 user 消息 metadata。 // 重放历史事件时跳过——历史消息的 metadata 已随对话加载落地, // 重写反而可能用旧事件覆盖最新状态。 if (this._rebuildingFromScratch) break; useChatStore().updateEditSummaryByMessageId( typeof eventData?.message_id === 'string' ? eventData.message_id : '', eventData?.edit_summary || null ); break; case 'compression_state': // 重放历史事件时不处理管理类事件,避免触发副作用(如 toast 闪烁 / 状态回退)。 if (this._rebuildingFromScratch) break; this.handleCompressionState(eventData, eventIdx); break; case 'compression_finished': // 重放历史事件时不处理管理类事件,避免 restoreTaskState → 重放 → 再次触发 compression_finished 的无限循环。 if (this._rebuildingFromScratch) break; this.handleCompressionFinished(eventData, eventIdx); break; case 'shallow_compression': // 同上。 if (this._rebuildingFromScratch) break; this.handleShallowCompression(eventData, eventIdx); break; case 'user_message': debugNotifyLog('[DEBUG_NOTIFY][event] dispatch:user_message', { idx: eventIdx, data: eventData }); this.handleUserMessage(eventData, eventIdx); break; case 'system_message': this.handleSystemMessage(eventData, eventIdx); break; case 'runtime_queue_sync': // 从头重建期间跳过 runtime_queue_sync,避免与已加载的历史冲突导致 UI 闪回卡死 if (this._rebuildingFromScratch) break; this.handleRuntimeQueueSync(eventData); break; default: debugLog(`[TaskPolling] 未知事件类型: ${eventType}`); } // 注意:不要在这里清除 _rebuildingFromScratch 标记 // 该标记应该在恢复完所有历史事件后才清除 }, handleTaskComplete(data: any) { const pendingToolsBefore = typeof this.hasPendingToolActions === 'function' ? this.hasPendingToolActions() : null; const pendingRuntimeGuidance = Array.isArray(data?.pending_runtime_guidance_messages) ? data.pending_runtime_guidance_messages .map((item: any) => String(item || '').trim()) .filter((item: string) => item.length > 0) : []; if (pendingRuntimeGuidance.length > 0) { const limit = Math.max(1, Number(this.runtimeQueueLimit || 5)); const mergedGuidance = [ ...(this.runtimeGuidanceFallbackQueue || []), ...pendingRuntimeGuidance ] .map((item: any) => String(item || '').trim()) .filter((item: string) => item.length > 0) .slice(0, limit); const queueAllowance = Math.max(0, limit - mergedGuidance.length); if ( Array.isArray(this.runtimeQueuedMessages) && this.runtimeQueuedMessages.length > queueAllowance ) { this.runtimeQueuedMessages = this.runtimeQueuedMessages.slice(0, queueAllowance); } this.runtimeGuidanceFallbackQueue = mergedGuidance; } jsonDebug('handleTaskComplete:before', { data, taskInProgress: this.taskInProgress, streamingMessage: this.streamingMessage, stopRequested: this.stopRequested, pendingToolsBefore, preparingToolsSize: this.preparingTools?.size ?? null, activeToolsSize: this.activeTools?.size ?? null, waitingForSubAgent: this.waitingForSubAgent, waitingForBackgroundCommand: this.waitingForBackgroundCommand }); const hasRunningSubAgents = !!data?.has_running_sub_agents; const hasRunningBackgroundCommands = !!data?.has_running_background_commands; const hasRunningMultiAgent = !!data?.has_running_multi_agent; if (hasRunningSubAgents || hasRunningMultiAgent) { debugLog('[TaskPolling] 任务完成,但仍有后台子智能体/多智能体运行'); } else { debugLog('[TaskPolling] 任务完成'); } // 同步处理状态更新 this.streamingMessage = false; this.stopRequested = false; if (!hasRunningSubAgents && !hasRunningMultiAgent) { this.markLatestUserWorkCompleted(); } if (hasRunningMultiAgent) { // 多智能体模式下主智能体已空闲,但仍有实例在运行,保持对话运行态继续接收后续消息; // 不启动后台等待轮询,也不阻塞输入区(waitingForSubAgent=false)。 // 但必须启动运行中任务探测,否则子智能体后续输出触发的新主任务无法被前端发现。 jsonDebug('handleTaskComplete:hasRunningMultiAgent', { taskInProgress: this.taskInProgress, currentConversationId: this.currentConversationId }); this.taskInProgress = true; this.waitingForSubAgent = false; this.waitingForBackgroundCommand = hasRunningBackgroundCommands; this.startMultiAgentTaskProbe(); } else if (hasRunningSubAgents) { this.taskInProgress = true; this.waitingForSubAgent = true; this.waitingForBackgroundCommand = hasRunningBackgroundCommands; // 关键修复:主任务结束后将切到新的通知任务,先清空旧任务事件索引去重表。 this.clearProcessedEvents(); this.startWaitingTaskProbe(); } else { this.cleanupTrailingEmptyAssistantPlaceholder('task_complete'); // 主任务已结束:若有遗留工具块处于 running/preparing,会导致发送按钮继续显示“停止”。 // 这里统一清理遗留中的工具状态,避免前端忙碌态卡死。 if (typeof this.clearPendingTools === 'function') { this.clearPendingTools('task_complete'); } this.taskInProgress = false; this.waitingForSubAgent = false; this.waitingForBackgroundCommand = false; this.stopWaitingTaskProbe(); this.stopMultiAgentTaskProbe(); this.clearTaskState(); // 清理任务状态 this.$nextTick(() => { if (typeof this.tryAutoSendRuntimeQueuedMessages === 'function') { this.tryAutoSendRuntimeQueuedMessages('task_complete'); } }); } this.$forceUpdate(); // 只更新统计,不重新加载历史 if (this.currentConversationId) { setTimeout(() => { this.fetchConversationTokenStatistics(); this.updateCurrentContextTokens(); }, 500); } this.scheduleTodoListRefresh(100); if (data?.conversation_id && data.conversation_id === this.currentConversationId && data?.task_id) { this.acknowledgeCompletedWorkspaceTask?.(data.task_id); } setTimeout(() => this.refreshRunningWorkspaceTasks?.(), 0); // 标题生成可能晚于主任务完成;主轮询停止后主动短轮询当前会话标题,避免必须刷新页面。 this.scheduleGeneratedTitleRefresh('task_complete', { conversationId: data?.conversation_id || this.currentConversationId, deadlineMs: 90000, maxAttempts: 60 }); const pendingToolsAfter = typeof this.hasPendingToolActions === 'function' ? this.hasPendingToolActions() : null; jsonDebug('handleTaskComplete:after', { taskInProgress: this.taskInProgress, streamingMessage: this.streamingMessage, stopRequested: this.stopRequested, pendingToolsAfter, preparingToolsSize: this.preparingTools?.size ?? null, activeToolsSize: this.activeTools?.size ?? null, waitingForSubAgent: this.waitingForSubAgent, waitingForBackgroundCommand: this.waitingForBackgroundCommand }); }, handleTaskStopped(data: any, eventIdx: number) { goalModeDebugLog('handleTaskStopped:entered', { eventIdx, data }); jsonDebug('handleTaskStopped:before', { eventIdx, data, taskInProgress: this.taskInProgress, streamingMessage: this.streamingMessage, stopRequested: this.stopRequested }); debugLog('[TaskPolling] 任务已停止, idx:', eventIdx, data); const hasRunningSubAgents = !!data?.has_running_sub_agents; const hasRunningBackgroundCommands = !!data?.has_running_background_commands; const hasRunningBackground = hasRunningSubAgents || hasRunningBackgroundCommands; goalModeDebugLog('handleTaskStopped', { taskInProgress: this.taskInProgress, streamingMessage: this.streamingMessage, hasRunningSubAgents, hasRunningBackgroundCommands, data, }); this.cleanupTrailingEmptyAssistantPlaceholder('task_stopped'); this.streamingMessage = false; this.stopRequested = false; if (hasRunningBackground) { // 主智能体已停,后台任务仍在跑:保持 taskInProgress=true (对话列表显示运行中), // 但输入栏不再锁定(stopRequested 已重置),用户可发新消息触发下一轮。 // 后台任务的停止由独立的子智能体/后台指令按钮处理,不再走停止按钮二次点击。 this.taskInProgress = true; this.waitingForSubAgent = hasRunningSubAgents; this.waitingForBackgroundCommand = hasRunningBackgroundCommands; debugLog('[TaskPolling] 任务已停止,仍有后台任务运行,保持对话运行态但释放输入区'); } else { // 对话真正停止:停止计时器并持久化 this.markLatestUserWorkCompleted(); this.taskInProgress = false; this.waitingForSubAgent = false; this.waitingForBackgroundCommand = false; this.stopWaitingTaskProbe(); if (typeof this.clearPendingTools === 'function') { this.clearPendingTools('task_stopped'); } this.clearTaskState(); this.$nextTick(() => { if (typeof this.tryAutoSendRuntimeQueuedMessages === 'function') { this.tryAutoSendRuntimeQueuedMessages('task_stopped'); } }); } goalModeDebugLog('handleTaskStopped:after', { taskInProgress: this.taskInProgress, waitingForSubAgent: this.waitingForSubAgent, waitingForBackgroundCommand: this.waitingForBackgroundCommand, hasRunningBackground, }); this.scheduleTodoListRefresh(100); setTimeout(() => this.refreshRunningWorkspaceTasks?.(), 0); this.$forceUpdate(); jsonDebug('handleTaskStopped:after', { taskInProgress: this.taskInProgress, streamingMessage: this.streamingMessage, stopRequested: this.stopRequested }); }, clearTaskState() { this.stopWaitingTaskProbe(); (async () => { const { useTaskStore } = await import('../../../stores/task'); const taskStore = useTaskStore(); taskStore.clearTask(); // 使用 clearTask 而不是 stopPolling })(); if (typeof this.clearProcessedEvents === 'function') { this.clearProcessedEvents(); } }, handleTaskError(data: any) { jsonDebug('handleTaskError:incoming', { data, taskInProgress: this.taskInProgress, streamingMessage: this.streamingMessage, stopRequested: this.stopRequested }); const shouldRetry = Boolean(data?.retry); if (shouldRetry) { const retryIn = Number(data?.retry_in) || 5; const attempt = Number(data?.attempt) || 1; const maxAttempts = Number(data?.max_attempts) || attempt; debugLog('[TaskPolling] API错误,等待自动重试', { retryIn, attempt, maxAttempts, message: data?.message }); this.stopRequested = false; this.taskInProgress = true; this.streamingMessage = true; this.$forceUpdate(); this.uiPushToast({ title: '即将重试', message: `将在 ${retryIn} 秒后重试(第 ${attempt}/${maxAttempts} 次)\n错误:${data?.message || '未知错误'}`, type: 'info', duration: Math.max(retryIn, 1) * 1000 }); return; } const errorMessage = data.message || '未知错误'; const errorType = data.error_type || 'unknown'; const isToolArgumentParseError = errorType === 'parameter_format_error' || /工具参数解析失败/.test(String(errorMessage || '')); // 工具参数解析失败属于“单个工具调用失败”,后端会继续执行主任务。 // 这里不能停止轮询,否则会出现“后端继续跑、前端不再更新”的假死状态。 if (isToolArgumentParseError) { console.warn('[TaskPolling] 工具参数解析失败(非致命),继续轮询任务:', data); jsonDebug('handleTaskError:tool-args-parse-error-ignored', { errorType, errorMessage, taskInProgress: this.taskInProgress, streamingMessage: this.streamingMessage, stopRequested: this.stopRequested }); this.uiPushToast({ title: '工具调用失败', message: errorMessage, type: 'warning', duration: 6000 }); // 保持当前运行态,不在这里强制置为“工作中”,避免后续任务结束时状态粘住 this.stopRequested = false; this.$forceUpdate(); return; } let title = '任务执行失败'; let message = errorMessage; // 根据错误类型提供友好提示 if (errorType === 'api_error') { title = 'API 调用失败'; message = `模型服务异常:${errorMessage}`; } else if (errorType === 'timeout') { title = '任务超时'; message = '任务执行时间过长,已自动停止'; } else if (errorType === 'quota_exceeded') { title = '配额不足'; message = '您的使用配额已用尽'; } console.error('[TaskPolling] 任务错误:', data.message); this.uiPushToast({ title, message, type: 'error', duration: 8000 }); // 清理状态 this.markLatestUserWorkCompleted(); this.streamingMessage = false; this.taskInProgress = false; this.stopRequested = false; this.$forceUpdate(); jsonDebug('handleTaskError:fatal-after-state-update', { errorType, errorMessage, taskInProgress: this.taskInProgress, streamingMessage: this.streamingMessage, stopRequested: this.stopRequested }); // 停止轮询 (async () => { const { useTaskStore } = await import('../../../stores/task'); const taskStore = useTaskStore(); taskStore.stopPolling(); })(); this.$nextTick(() => { if (typeof this.tryAutoSendRuntimeQueuedMessages === 'function') { this.tryAutoSendRuntimeQueuedMessages('task_error'); } }); } };