// @ts-nocheck import { debugLog } from './common'; const debugNotifyLog = (...args: any[]) => { void args; }; const keyNotifyLog = (...args: any[]) => { console.log(...args); }; const jsonDebug = (...args: any[]) => { console.log('[JSONDEBUG]', ...args); }; const userMDebug = (...args: any[]) => { console.log('[USERMDEBUG]', ...args); }; const RESTORE_DEBUG_PREFIX = '[RESTORE_DEBUG]'; let restoreDebugCount = 0; const RESTORE_DEBUG_MAX = 800; const RESTORE_DEBUG_EVENTS = new Set([ 'restore:start', 'restore:running-task-found', 'restore:history-empty', 'restore:task-detail-events', 'restore:rebuild-decision', 'restore:rebuild-polling-started', 'restore:polling-started-follow', 'restore:thinking-chunk-auto-start', 'restore:text-chunk-auto-start', 'restore:error', 'event:drop-duplicate', 'event:drop-conversation-mismatch' ]); function isRestoreDebugEnabled() { if (typeof window === 'undefined') return true; try { const explicit = (window as any).__RESTORE_DEBUG__; if (explicit === false || explicit === '0') return false; if (explicit === true || explicit === '1') return true; const localFlag = window.localStorage?.getItem('restoreDebug'); if (localFlag === '0' || localFlag === 'false') return false; if (localFlag === '1' || localFlag === 'true') return true; return true; } catch { return true; } } function restoreDebugLog(event: string, payload: Record = {}) { if (!isRestoreDebugEnabled()) return; if (!RESTORE_DEBUG_EVENTS.has(event)) return; if (restoreDebugCount >= RESTORE_DEBUG_MAX) return; restoreDebugCount += 1; if (restoreDebugCount === RESTORE_DEBUG_MAX) { console.warn(RESTORE_DEBUG_PREFIX, 'log-limit-reached', { max: RESTORE_DEBUG_MAX }); return; } console.log(RESTORE_DEBUG_PREFIX, event, payload); } function isSystemAutoUserMessagePayload(data: any): boolean { if (!data || typeof data !== 'object') { return false; } const meta = data.metadata || {}; return !!( data.is_auto_generated || meta.is_auto_generated || data.auto_message_type || meta.auto_message_type || data.sub_agent_notice || meta.sub_agent_notice || data.background_command_notice || meta.background_command_notice ); } function isEmptyAssistantPlaceholderMessage(message: any): boolean { if (!message || message.role !== 'assistant') { return false; } const actions = Array.isArray(message.actions) ? message.actions : []; return actions.length === 0 && !!message.awaitingFirstContent; } function getOptimisticUserEchoTarget(messages: any[]): any | null { if (!Array.isArray(messages) || messages.length === 0) { return null; } const lastIndex = messages.length - 1; const last = messages[lastIndex]; if (last?.role === 'user') { return last; } if (isEmptyAssistantPlaceholderMessage(last) && lastIndex > 0) { const prev = messages[lastIndex - 1]; if (prev?.role === 'user') { return prev; } } return null; } /** * 任务轮询事件处理器 * 将从 REST API 轮询获取的事件转换为前端状态更新 */ export const taskPollingMethods = { scheduleTodoListRefresh(delayMs = 120) { const delay = Number.isFinite(delayMs) ? Math.max(0, Number(delayMs)) : 120; if (this._todoRefreshTimer) { clearTimeout(this._todoRefreshTimer); this._todoRefreshTimer = null; } this._todoRefreshTimer = setTimeout(() => { this._todoRefreshTimer = null; if (typeof this.fetchTodoList === 'function') { Promise.resolve(this.fetchTodoList()).catch(() => {}); } }, delay); }, cleanupTrailingEmptyAssistantPlaceholder(reason = 'unspecified') { if (!Array.isArray(this.messages) || !this.messages.length) { return false; } const last = this.messages[this.messages.length - 1]; if (!isEmptyAssistantPlaceholderMessage(last)) { return false; } this.messages.pop(); if (typeof this.currentMessageIndex === 'number') { this.currentMessageIndex = this.messages.length - 1; } userMDebug('taskPolling.cleanupTrailingEmptyAssistantPlaceholder:removed', { reason, messagesLengthAfter: this.messages.length, currentMessageIndex: this.currentMessageIndex }); return true; }, stopWaitingTaskProbe() { if (this.waitingTaskProbeTimer) { debugNotifyLog('[DEBUG_NOTIFY][ui] stopWaitingTaskProbe'); clearInterval(this.waitingTaskProbeTimer); this.waitingTaskProbeTimer = null; } }, async tryResumeWaitingNoticeTask() { if (!this.waitingForSubAgent || !this.currentConversationId) { debugNotifyLog('[DEBUG_NOTIFY][ui] tryResumeWaitingNoticeTask:skip', { waitingForSubAgent: this.waitingForSubAgent, currentConversationId: this.currentConversationId }); return false; } try { const { useTaskStore } = await import('../../stores/task'); const taskStore = useTaskStore(); if (taskStore.hasActiveTask) { debugNotifyLog('[DEBUG_NOTIFY][ui] tryResumeWaitingNoticeTask:already-active', { taskId: taskStore.currentTaskId, status: taskStore.taskStatus }); return true; } const resp = await fetch('/api/tasks'); if (!resp.ok) { debugNotifyLog('[DEBUG_NOTIFY][ui] tryResumeWaitingNoticeTask:tasks-api-not-ok', { status: resp.status }); return false; } const result = await resp.json().catch(() => ({})); const tasks = Array.isArray(result?.data) ? result.data : []; debugNotifyLog('[DEBUG_NOTIFY][ui] tryResumeWaitingNoticeTask:tasks', { currentConversationId: this.currentConversationId, count: tasks.length, running: tasks .filter((t: any) => t?.status === 'running') .map((t: any) => ({ task_id: t?.task_id, conversation_id: t?.conversation_id, status: t?.status })) }); const runningTask = tasks.find( (task: any) => task?.status === 'running' && task?.conversation_id === this.currentConversationId ); if (!runningTask?.task_id) { debugNotifyLog('[DEBUG_NOTIFY][ui] tryResumeWaitingNoticeTask:no-matched-task'); return false; } debugNotifyLog('[DEBUG_NOTIFY][ui] tryResumeWaitingNoticeTask:resume', { task_id: runningTask.task_id }); keyNotifyLog('[DEBUG_NOTIFY_KEY][ui] tryResumeWaitingNoticeTask:resume', { task_id: runningTask.task_id, conversationId: this.currentConversationId }); // 关键修复:切到新的通知任务前,清空旧任务事件去重索引。 // 否则新任务 event idx 与旧任务重叠时会被误判为“重复事件”而丢失 user_message。 if (typeof this.clearProcessedEvents === 'function') { this.clearProcessedEvents(); } taskStore.resumeTask(runningTask.task_id, { status: 'running', resetOffset: true, eventHandler: (event: any) => this.handleTaskEvent(event) }); return true; } catch (error) { console.warn('[TaskPolling] 接管等待任务失败:', error); return false; } }, startWaitingTaskProbe() { if (this.waitingTaskProbeTimer) { debugNotifyLog('[DEBUG_NOTIFY][ui] startWaitingTaskProbe:already-started'); return; } debugNotifyLog('[DEBUG_NOTIFY][ui] startWaitingTaskProbe'); keyNotifyLog('[DEBUG_NOTIFY_KEY][ui] startWaitingTaskProbe', { conversationId: this.currentConversationId }); this.waitingTaskProbeTimer = setInterval(async () => { if (!this.waitingForSubAgent) { this.stopWaitingTaskProbe(); return; } const resumed = await this.tryResumeWaitingNoticeTask(); if (resumed) { debugNotifyLog('[DEBUG_NOTIFY][ui] waitingTaskProbe:resumed-and-stop'); keyNotifyLog('[DEBUG_NOTIFY_KEY][ui] waitingTaskProbe:resumed-and-stop'); this.stopWaitingTaskProbe(); } }, 1000); }, /** * 处理任务事件(从轮询获取) */ handleTaskEvent(event: any) { if (!event || !event.type) { return; } const eventType = event.type; const eventData = event.data || {}; const eventIdx = event.idx; 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 }); } // 事件去重检查 if (typeof eventIdx === 'number') { if (!this._processedEventIndices) { this._processedEventIndices = new Set(); } if (this._processedEventIndices.has(eventIdx)) { if ( [ 'ai_message_start', 'thinking_start', 'thinking_chunk', 'thinking_end', 'text_start', 'text_chunk', 'text_end' ].includes(eventType) ) { restoreDebugLog('event:drop-duplicate', { eventType, eventIdx, currentConversationId: this.currentConversationId }); } debugLog(`[TaskPolling] 跳过重复事件 #${eventIdx}`); return; } this._processedEventIndices.add(eventIdx); // 限制 Set 大小(保留最近 1000 个) if (this._processedEventIndices.size > 1000) { const firstIdx = Math.min(...this._processedEventIndices); this._processedEventIndices.delete(firstIdx); } } // 检查事件的 conversation_id 是否匹配当前对话 // 如果不匹配,忽略该事件(避免切换对话后旧任务的事件显示到新对话中) const crossConversationAllowed = new Set([ 'shallow_compression', 'conversation_resolved', 'compression_finished' ]); if ( !crossConversationAllowed.has(eventType) && eventData.conversation_id && this.currentConversationId ) { if (eventData.conversation_id !== this.currentConversationId) { 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 }); } console.log(`[DEBUG_AWAITING] 忽略不匹配的事件`, { eventType, eventIdx, eventConversationId: eventData.conversation_id, currentConversationId: this.currentConversationId }); debugLog( `[TaskPolling] 忽略不匹配的事件 #${eventIdx}: ${eventType}, 事件对话=${eventData.conversation_id}, 当前对话=${this.currentConversationId}` ); return; } } debugLog(`[TaskPolling] 处理事件 #${eventIdx}: ${eventType}`, eventData); console.log(`[DEBUG_AWAITING] 处理事件`, { eventType, eventIdx, eventConversationId: eventData.conversation_id, currentConversationId: this.currentConversationId }); // 根据事件类型调用对应的处理方法 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 '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 'compression_state': this.handleCompressionState(eventData, eventIdx); break; case 'compression_finished': this.handleCompressionFinished(eventData, eventIdx); break; case 'shallow_compression': 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; default: debugLog(`[TaskPolling] 未知事件类型: ${eventType}`); } // 注意:不要在这里清除 _rebuildingFromScratch 标记 // 该标记应该在恢复完所有历史事件后才清除 }, handleAiMessageStart(data: any, eventIdx: number) { const lastMessage = this.messages[this.messages.length - 1]; console.log('[DEBUG_AWAITING] ===== handleAiMessageStart =====', { eventIdx, lastMessageRole: lastMessage?.role, lastMessageAwaitingFirstContent: lastMessage?.awaitingFirstContent, lastMessageGeneratingLabel: lastMessage?.generatingLabel }); debugLog('[TaskPolling] AI消息开始, idx:', eventIdx); console.log('[AiMessageStart] 开始处理', { eventIdx, messagesLength: this.messages.length, lastMessageRole: this.messages[this.messages.length - 1]?.role, streamingMessage: this.streamingMessage, taskInProgress: this.taskInProgress, currentConversationId: this.currentConversationId }); if (this.waitingForSubAgent) { this.waitingForSubAgent = false; } // 检查是否已经有 assistant 消息 const hasAssistantMessage = lastMessage && lastMessage.role === 'assistant'; console.log('[AiMessageStart] 最后一条消息状态', { hasAssistantMessage, lastMessageActionsLength: lastMessage?.actions?.length || 0, lastMessageAwaitingFirstContent: lastMessage?.awaitingFirstContent, lastMessageGeneratingLabel: lastMessage?.generatingLabel }); // 判断是否是刷新恢复的情况: // 1. 有assistant消息 // 2. 且最后一条 assistant 仍处于“未完成流式状态”(而不是普通已完成消息) // 3. 且任务确实仍在进行中 const lastActions = Array.isArray(lastMessage?.actions) ? lastMessage.actions : []; const hasUnfinishedAction = lastActions.some((action: any) => { if (!action) return false; if (action.streaming) return true; if (action.type === 'tool' && action.tool) { const status = String(action.tool.status || '').toLowerCase(); return ['preparing', 'running', 'pending', 'queued', 'awaiting_approval'].includes(status); } return false; }); const hasNoActionsYet = !lastActions.length; const isRefreshRestore = hasAssistantMessage && (hasNoActionsYet || hasUnfinishedAction || lastMessage.awaitingFirstContent === true) && (this.taskInProgress || this.streamingMessage); userMDebug('taskPolling.handleAiMessageStart:decision', { eventIdx, hasAssistantMessage, hasNoActionsYet, hasUnfinishedAction, awaitingFirstContent: !!lastMessage?.awaitingFirstContent, taskInProgress: this.taskInProgress, streamingMessage: this.streamingMessage, isRefreshRestore }); console.log('[AiMessageStart] 场景判断', { isRefreshRestore, streamingMessage: this.streamingMessage, hasActions: !!(lastMessage?.actions && lastMessage.actions.length > 0), awaitingFirstContent: lastMessage?.awaitingFirstContent, taskInProgress: this.taskInProgress }); if (isRefreshRestore) { console.log('[AiMessageStart] 场景=刷新恢复,复用现有assistant消息'); debugLog('[TaskPolling] 刷新恢复场景,复用现有 assistant 消息'); // 只更新状态,不创建新消息 this.taskInProgress = true; this.stopRequested = false; this.streamingMessage = true; // 确保 awaitingFirstContent 被正确设置 const hasContent = lastMessage.actions && lastMessage.actions.length > 0; if (!hasContent) { lastMessage.awaitingFirstContent = true; lastMessage.generatingLabel = lastMessage.generatingLabel || '思考中...'; console.log('[AiMessageStart] 设置等待动画', { awaitingFirstContent: true, generatingLabel: lastMessage.generatingLabel }); } else { // 如果已有内容,确保等待动画不显示 lastMessage.awaitingFirstContent = false; lastMessage.generatingLabel = ''; console.log('[AiMessageStart] 关闭等待动画(已有内容)'); } return; } // 其他情况:创建新的 assistant 消息 console.log('[AiMessageStart] 场景=创建新消息'); debugLog('[TaskPolling] 创建新的 assistant 消息'); this.monitorResetSpeech(); this.cleanupStaleToolActions(); this.taskInProgress = true; this.chatStartAssistantMessage(); this.stopRequested = false; this.streamingMessage = true; const newMessage = this.messages[this.messages.length - 1]; console.log('[AiMessageStart] 新消息创建完成', { role: newMessage?.role, awaitingFirstContent: newMessage?.awaitingFirstContent, generatingLabel: newMessage?.generatingLabel, actionsLength: newMessage?.actions?.length || 0, messagesLength: this.messages.length }); // 如果是从头重建,标记消息为静默恢复 if (this._rebuildingFromScratch) { if (newMessage && newMessage.role === 'assistant') { debugLog('[TaskPolling] 标记消息为静默恢复(从头重建)'); newMessage.awaitingFirstContent = false; newMessage.generatingLabel = ''; console.log('[AiMessageStart] 从头重建,关闭等待动画'); } } // 强制触发Vue响应式更新 this.$forceUpdate(); console.log('[AiMessageStart] 已调用$forceUpdate'); this.$nextTick(() => { console.log('[AiMessageStart] nextTick回调执行'); this.conditionalScrollToBottom(); }); }, handleThinkingStart(data: any, eventIdx: number) { console.log('[ThinkingStart] 开始处理', { eventIdx }); debugLog('[TaskPolling] 思考开始, idx:', eventIdx); const ignoreThinking = this.runMode === 'fast' || this.thinkingMode === false; if (ignoreThinking) { this.monitorEndModelOutput(); console.log('[ThinkingStart] 忽略思考(fast模式或关闭思考)'); return; } // 防御性检查:如果没有assistant消息,先创建一个 const lastMessage = this.messages[this.messages.length - 1]; if (!lastMessage || lastMessage.role !== 'assistant') { console.log('[DEBUG_AWAITING] ThinkingStart 检测到缺少assistant消息,自动创建'); this.chatStartAssistantMessage(); } this.monitorShowThinking(); const result = this.chatStartThinkingAction(); if (result && result.blockId) { const blockId = result.blockId; const lastMessage = this.messages[this.messages.length - 1]; // 有内容了,关闭等待动画 if (lastMessage && lastMessage.role === 'assistant') { console.log('[ThinkingStart] 关闭等待动画', { beforeAwaitingFirstContent: lastMessage.awaitingFirstContent, beforeGeneratingLabel: lastMessage.generatingLabel }); lastMessage.awaitingFirstContent = false; lastMessage.generatingLabel = ''; } // 只在非历史恢复时展开思考块 if (!this._rebuildingFromScratch) { this.chatExpandBlock(blockId); } this.conditionalScrollToBottom(); this.chatSetThinkingLock(blockId, true); // 强制触发 actions 数组的响应式更新 if (lastMessage && lastMessage.actions) { lastMessage.actions = [...lastMessage.actions]; } this.$forceUpdate(); } }, handleThinkingChunk(data: any) { if (this.runMode === 'fast' || this.thinkingMode === false) { return; } let thinkingAction = this.chatAppendThinkingChunk(data.content); // 兜底:刷新恢复时可能只能拿到 chunk 事件(start 事件已被服务端窗口截断) // 此时补建一个 thinking action,避免思考内容丢失。 if (!thinkingAction && data?.content) { const started = this.chatStartThinkingAction(); thinkingAction = this.chatAppendThinkingChunk(data.content); restoreDebugLog('restore:thinking-chunk-auto-start', { startedBlockId: started?.blockId || null, contentLength: String(data.content || '').length, currentConversationId: this.currentConversationId }); } if (thinkingAction) { this.$forceUpdate(); this.$nextTick(() => { if (thinkingAction && thinkingAction.blockId) { this.scrollThinkingToBottom(thinkingAction.blockId); } this.conditionalScrollToBottom(); }); } this.monitorShowThinking(); }, handleThinkingEnd(data: any) { debugLog('[TaskPolling] 思考结束'); if (this.runMode === 'fast' || this.thinkingMode === false) { return; } const blockId = this.chatCompleteThinkingAction(data.full_content); if (blockId) { // 解锁思考块 this.chatSetThinkingLock(blockId, false); // 只在非历史恢复时延迟折叠思考块 if (!this._rebuildingFromScratch) { // 延迟折叠思考块(给用户一点时间看到思考完成) setTimeout(() => { this.chatCollapseBlock(blockId); this.$forceUpdate(); }, 1000); } else { // 历史恢复时立即折叠,不需要动画 this.chatCollapseBlock(blockId); } this.$nextTick(() => this.scrollThinkingToBottom(blockId)); } this.$forceUpdate(); this.monitorEndModelOutput(); }, handleTextStart() { console.log('[TextStart] 开始处理'); debugLog('[TaskPolling] 文本开始'); // 防御性检查:如果没有assistant消息,先创建一个 const lastMessage = this.messages[this.messages.length - 1]; if (!lastMessage || lastMessage.role !== 'assistant') { console.log('[DEBUG_AWAITING] TextStart 检测到缺少assistant消息,自动创建'); this.chatStartAssistantMessage(); } this.chatStartTextAction(); // 有内容了,关闭等待动画 const currentMessage = this.messages[this.messages.length - 1]; if (currentMessage && currentMessage.role === 'assistant') { console.log('[TextStart] 关闭等待动画', { beforeAwaitingFirstContent: currentMessage.awaitingFirstContent, beforeGeneratingLabel: currentMessage.generatingLabel }); currentMessage.awaitingFirstContent = false; currentMessage.generatingLabel = ''; } this.$forceUpdate(); }, handleTextChunk(data: any) { if (data && typeof data.content === 'string' && data.content.length) { let textAction = this.chatAppendTextChunk(data.content); // 兜底:刷新恢复时可能只有 text_chunk(缺少 text_start),需要自动补建 text action if (!textAction) { this.chatStartTextAction(); textAction = this.chatAppendTextChunk(data.content); restoreDebugLog('restore:text-chunk-auto-start', { contentLength: data.content.length, appendedAfterStart: !!textAction, currentConversationId: this.currentConversationId }); } this.$forceUpdate(); this.conditionalScrollToBottom(); const speech = data.content.replace(/\r/g, ''); if (speech) { this.monitorShowSpeech(speech); } } }, handleTextEnd(data: any) { debugLog('[TaskPolling] 文本结束'); const full = data?.full_content || ''; this.chatCompleteTextAction(full); this.$forceUpdate(); this.monitorEndModelOutput(); }, handleToolPreparing(data: any) { console.log('[ToolPreparing] 开始处理', { name: data.name, id: data.id }); debugLog('[TaskPolling] 工具准备中:', data.name); if (this.dropToolEvents) { console.log('[ToolPreparing] dropToolEvents=true,跳过'); return; } const msg = this.chatEnsureAssistantMessage(); if (!msg) { console.log('[ToolPreparing] 无法获取assistant消息'); return; } if (msg.awaitingFirstContent) { console.log('[ToolPreparing] 关闭等待动画', { beforeAwaitingFirstContent: msg.awaitingFirstContent, beforeGeneratingLabel: msg.generatingLabel }); msg.awaitingFirstContent = false; msg.generatingLabel = ''; } const action = { id: data.id, type: 'tool', tool: { id: data.id, name: data.name, arguments: {}, argumentSnapshot: null, argumentLabel: '', status: 'preparing', result: null, message: data.message || `准备调用 ${data.name}...`, intent_full: data.intent || '', intent_rendered: data.intent || '' }, timestamp: Date.now() }; msg.actions.push(action); this.preparingTools.set(data.id, action); this.toolRegisterAction(action, data.id); this.toolTrackAction(data.name, action); this.$forceUpdate(); this.conditionalScrollToBottom(); if (this.monitorPreviewTool) { this.monitorPreviewTool(data); } }, handleToolStart(data: any) { debugLog('[TaskPolling] 工具开始:', data.name); if (this.dropToolEvents) { return; } let action = null; if (data.preparing_id && this.preparingTools.has(data.preparing_id)) { action = this.preparingTools.get(data.preparing_id); this.preparingTools.delete(data.preparing_id); } else { action = this.toolFindAction(data.id, data.preparing_id, data.execution_id); } if (!action) { const msg = this.chatEnsureAssistantMessage(); if (!msg) { return; } action = { id: data.id, type: 'tool', tool: { id: data.id, name: data.name, arguments: {}, argumentSnapshot: null, argumentLabel: '', status: 'running', result: null }, timestamp: Date.now() }; msg.actions.push(action); } action.tool.status = 'running'; action.tool.arguments = data.arguments; action.tool.argumentSnapshot = this.cloneToolArguments(data.arguments); action.tool.argumentLabel = this.buildToolLabel(action.tool.argumentSnapshot); action.tool.message = null; action.tool.id = data.id; action.tool.executionId = data.id; this.toolRegisterAction(action, data.id); this.toolTrackAction(data.name, action); this.$forceUpdate(); this.conditionalScrollToBottom(); if (this.monitorQueueTool) { this.monitorQueueTool(data); } }, handleToolIntent(data: any) { debugLog('[TaskPolling] 工具意图:', data.name, data.intent); if (this.dropToolEvents) { return; } // 查找对应的工具 action const action = this.toolFindAction(data.id, data.preparing_id, data.execution_id); if (action && action.tool) { const newIntent = data.intent || ''; // 如果 intent 没有变化,跳过 if (action.tool.intent_full === newIntent) { return; } // 停止之前的打字机效果 if (action.tool._intentTyping) { action.tool._intentTyping = false; if (action.tool._intentTimer) { clearTimeout(action.tool._intentTimer); action.tool._intentTimer = null; } } // 更新完整 intent action.tool.intent_full = newIntent; // 判断是否是历史恢复:只有从头重建时才直接显示 const isHistoryRestore = this._rebuildingFromScratch; if (isHistoryRestore) { // 历史恢复,直接显示完整 intent action.tool.intent_rendered = newIntent; debugLog('[TaskPolling] 历史恢复,直接显示 intent'); } else { // 新工具块,逐字符显示 debugLog('[TaskPolling] 新工具块,开始打字机效果'); action.tool.intent_rendered = ''; action.tool._intentTyping = true; // 计算每个字符的间隔时间 // 总时长1秒,但最少0.5秒 const totalDuration = Math.max(500, Math.min(1000, newIntent.length * 50)); const charInterval = totalDuration / newIntent.length; let charIndex = 0; const typeNextChar = () => { if (charIndex < newIntent.length && action.tool._intentTyping) { action.tool.intent_rendered += newIntent[charIndex]; charIndex++; this.$forceUpdate(); action.tool._intentTimer = setTimeout(typeNextChar, charInterval); } else { action.tool._intentTyping = false; action.tool.intent_rendered = newIntent; // 确保完整 action.tool._intentTimer = null; this.$forceUpdate(); } }; action.tool._intentTimer = setTimeout(typeNextChar, 50); // 延迟50ms开始 } // 更新 arguments 和 label if (action.tool.arguments) { action.tool.arguments.intent = newIntent; action.tool.argumentSnapshot = this.cloneToolArguments(action.tool.arguments); action.tool.argumentLabel = this.buildToolLabel(action.tool.argumentSnapshot); } this.$forceUpdate(); debugLog('[TaskPolling] 已更新工具意图:', data.name); } else { debugLog('[TaskPolling] 未找到对应的工具 action:', data.id); } }, handleToolUpdateAction(data: any) { if (this.dropToolEvents) { return; } debugLog('[TaskPolling] 更新action:', data.id, 'status:', data.status); let targetAction = this.toolFindAction(data.id, data.preparing_id, data.execution_id); if (!targetAction && data.preparing_id && this.preparingTools.has(data.preparing_id)) { targetAction = this.preparingTools.get(data.preparing_id); } if (!targetAction) { return; } if (data.status) { targetAction.tool.status = data.status; } if (data.result !== undefined) { targetAction.tool.result = data.result; // 处理个性化设置工具 - 刷新个人空间数据 if (targetAction.tool && targetAction.tool.name === 'manage_personalization') { let result = data.result; if (typeof result === 'string') { try { result = JSON.parse(result); } catch (e) { /* ignore */ } } // 处理主题变更 if (result?.theme_changed === true && result.new_theme) { const theme = result.new_theme; if (typeof window !== 'undefined' && window.localStorage) { window.localStorage.setItem('agents_ui_theme', theme); } document.documentElement.setAttribute('data-theme', theme); document.body.setAttribute('data-theme', theme); } // 任何字段更新后都刷新个人空间数据 (async () => { try { const { usePersonalizationStore } = await import('../../stores/personalization'); const personalizationStore = usePersonalizationStore(); // 强制刷新个人空间数据 await personalizationStore.fetchPersonalization(); } catch (e) { // 静默处理,不影响主流程 console.debug('[TaskPolling] 刷新个人空间数据失败:', e); } })(); } } if (data.message !== undefined) { targetAction.tool.message = data.message; } if (data.content !== undefined) { targetAction.tool.content = data.content; } // 待办工具执行完成后主动刷新左侧待办列表(网页端不再依赖 websocket todo_updated) if (data.status === 'completed') { const toolName = String(targetAction?.tool?.name || '').toLowerCase(); if ( toolName.startsWith('todo_') || toolName === 'todo_create' || toolName === 'todo_update_task' ) { this.scheduleTodoListRefresh(80); } } this.$forceUpdate(); this.conditionalScrollToBottom(); if (this.monitorResolveTool && data.status === 'completed') { this.monitorResolveTool(data); } }, handleToolApprovalRequired(data: any) { const approval = data?.approval; if (!approval || !approval.approval_id) { return; } if (!Array.isArray(this.pendingToolApprovals)) { this.pendingToolApprovals = []; } const idx = this.pendingToolApprovals.findIndex( (item: any) => item && item.approval_id === approval.approval_id ); if (idx >= 0) { this.pendingToolApprovals.splice(idx, 1, approval); } else { this.pendingToolApprovals.push(approval); } this.rightCollapsed = false; if (this.rightWidth < this.minPanelWidth) { this.rightWidth = this.minPanelWidth; } if (this.isMobileViewport && this.activeMobileOverlay !== 'approval') { this.openMobileOverlay('approval'); } this.$forceUpdate(); }, handleToolApprovalResolved(data: any) { const approvalId = data?.approval_id; if (!approvalId || !Array.isArray(this.pendingToolApprovals)) { return; } this.pendingToolApprovals = this.pendingToolApprovals.filter( (item: any) => item && item.approval_id !== approvalId ); // 电脑端:审批完成后自动折叠面板 if (!this.pendingToolApprovals.length && !this.isMobileViewport) { this.rightCollapsed = true; } this.$forceUpdate(); }, handleAppendPayload(data: any) { debugLog('[TaskPolling] 文件追加:', data.path); this.chatAddAppendPayloadAction(data); this.$forceUpdate(); this.conditionalScrollToBottom(); }, handleModifyPayload(data: any) { debugLog('[TaskPolling] 文件修改:', data.path); this.chatAddModifyPayloadAction(data); this.$forceUpdate(); this.conditionalScrollToBottom(); }, markLatestUserWorkCompleted() { if (!Array.isArray(this.messages) || this.messages.length === 0) { return; } for (let i = this.messages.length - 1; i >= 0; i -= 1) { const msg = this.messages[i]; if (!msg || msg.role !== 'user') { continue; } msg.metadata = msg.metadata || {}; msg.metadata.work_timer = msg.metadata.work_timer || {}; const timer = msg.metadata.work_timer; if (timer.status === 'completed' && typeof timer.duration_ms === 'number') { return; } const nowIso = new Date().toISOString(); const startedAt = timer.started_at || msg.timestamp || nowIso; const startMs = Date.parse(startedAt); const endMs = Date.now(); const durationMs = Number.isFinite(startMs) ? Math.max(0, endMs - startMs) : 0; timer.status = 'completed'; timer.started_at = startedAt; timer.finished_at = nowIso; timer.duration_ms = durationMs; return; } }, handleTaskComplete(data: any) { const pendingToolsBefore = typeof this.hasPendingToolActions === 'function' ? this.hasPendingToolActions() : null; 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; if (hasRunningSubAgents) { debugLog('[TaskPolling] 任务完成,但仍有后台子智能体运行'); } else { debugLog('[TaskPolling] 任务完成'); } // 同步处理状态更新 this.streamingMessage = false; this.stopRequested = false; if (!hasRunningSubAgents) { this.markLatestUserWorkCompleted(); } 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.clearTaskState(); // 清理任务状态 } this.$forceUpdate(); // 只更新统计,不重新加载历史 if (this.currentConversationId) { setTimeout(() => { this.fetchConversationTokenStatistics(); this.updateCurrentContextTokens(); }, 500); } this.scheduleTodoListRefresh(100); 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) { jsonDebug('handleTaskStopped:before', { eventIdx, data, taskInProgress: this.taskInProgress, streamingMessage: this.streamingMessage, stopRequested: this.stopRequested }); debugLog('[TaskPolling] 任务已停止, idx:', eventIdx, data); this.markLatestUserWorkCompleted(); this.cleanupTrailingEmptyAssistantPlaceholder('task_stopped'); this.streamingMessage = false; this.taskInProgress = false; this.stopRequested = false; this.waitingForSubAgent = false; this.waitingForBackgroundCommand = false; this.stopWaitingTaskProbe(); if (typeof this.clearPendingTools === 'function') { this.clearPendingTools('task_stopped'); } this.scheduleTodoListRefresh(100); this.$forceUpdate(); this.clearTaskState(); 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(); } }, handleUserMessage(data: any) { const message = (data?.message || data?.content || '').trim(); if (!message) { debugNotifyLog('[DEBUG_NOTIFY][ui] handleUserMessage:empty', { data }); return; } debugLog('[TaskPolling] 收到用户消息事件'); debugNotifyLog('[DEBUG_NOTIFY][ui] handleUserMessage', { messagePreview: message.slice(0, 120), sub_agent_notice: !!data?.sub_agent_notice, background_command_notice: !!data?.background_command_notice, task_id: data?.task_id, has_running_sub_agents: data?.has_running_sub_agents, has_running_background_commands: data?.has_running_background_commands }); keyNotifyLog('[DEBUG_NOTIFY_KEY][ui] handleUserMessage', { messagePreview: message.slice(0, 80), task_id: data?.task_id, sub_agent_notice: !!data?.sub_agent_notice, background_command_notice: !!data?.background_command_notice }); const isAutoUserMessage = isSystemAutoUserMessagePayload(data); if (!isAutoUserMessage) { const incomingImages = Array.isArray(data?.images) ? data.images : []; const incomingVideos = Array.isArray(data?.videos) ? data.videos : []; const incomingMediaRefs = Array.isArray(data?.media_refs) ? data.media_refs : Array.isArray(data?.mediaRefs) ? data.mediaRefs : []; const last = getOptimisticUserEchoTarget(this.messages || []); const isLikelyOptimisticEcho = !!( last && last.role === 'user' && String(last.content || '').trim() === message && JSON.stringify(last.images || []) === JSON.stringify(incomingImages || []) && JSON.stringify(last.videos || []) === JSON.stringify(incomingVideos || []) ); if (isLikelyOptimisticEcho) { last.media_refs = incomingMediaRefs; last.metadata = { ...(last.metadata || {}), media_refs: incomingMediaRefs }; } else { this.chatAddUserMessage(message, incomingImages, incomingVideos, incomingMediaRefs); } this.taskInProgress = true; this.streamingMessage = false; this.stopRequested = false; } else { debugLog('[TaskPolling] 跳过自动代发 user_message 渲染', { messagePreview: message.slice(0, 80), data }); } if (data?.sub_agent_notice) { if (typeof data?.has_running_sub_agents === 'boolean') { this.waitingForSubAgent = data.has_running_sub_agents; } else if (typeof data?.remaining_count === 'number') { this.waitingForSubAgent = data.remaining_count > 0; } if (typeof data?.has_running_background_commands === 'boolean') { this.waitingForBackgroundCommand = data.has_running_background_commands; } else if (data?.background_command_notice) { this.waitingForBackgroundCommand = this.waitingForSubAgent; } if (this.waitingForSubAgent) { this.startWaitingTaskProbe(); } else { this.stopWaitingTaskProbe(); } } this.$forceUpdate(); this.conditionalScrollToBottom(); }, handleSystemMessage(data: any) { const content = (data?.content || data?.message || '').trim(); userMDebug('taskPolling.handleSystemMessage:incoming', { raw: data, normalizedContent: content, currentConversationId: this.currentConversationId, currentMessagesLength: Array.isArray(this.messages) ? this.messages.length : -1 }); if (!content) { userMDebug('taskPolling.handleSystemMessage:empty-skip'); return; } this.cleanupTrailingEmptyAssistantPlaceholder('before_system_message_append'); this.appendSystemAction(content); userMDebug('taskPolling.handleSystemMessage:after-append', { currentMessagesLength: Array.isArray(this.messages) ? this.messages.length : -1, lastRole: this.messages?.[this.messages.length - 1]?.role || null, lastActions: this.messages?.[this.messages.length - 1]?.actions?.map((a: any) => a?.type) || [] }); }, 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} 次)`, 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(); })(); }, handleTokenUpdate(data: any) { if (data.conversation_id === this.currentConversationId) { this.currentConversationTokens.cumulative_input_tokens = data.cumulative_input_tokens || 0; this.currentConversationTokens.cumulative_output_tokens = data.cumulative_output_tokens || 0; this.currentConversationTokens.cumulative_total_tokens = data.cumulative_total_tokens || 0; if (typeof data.current_context_tokens === 'number') { this.resourceSetCurrentContextTokens(data.current_context_tokens); } else { this.updateCurrentContextTokens(); } this.$forceUpdate(); } }, handleConversationChanged(data: any, eventIdx: number) { debugLog('[TaskPolling] 对话标题已更新, idx:', eventIdx, data); if (data && data.conversation_id === this.currentConversationId) { // 更新当前对话标题 if (data.title) { this.currentConversationTitle = data.title; debugLog('[TaskPolling] 更新当前对话标题:', data.title); } // 更新对话列表中的标题 if (data.title && Array.isArray(this.conversations)) { const conv = this.conversations.find((c) => c && c.id === data.conversation_id); if (conv) { conv.title = data.title; debugLog('[TaskPolling] 更新对话列表中的标题:', data.title); } } this.$forceUpdate(); } }, handleConversationResolved(data: any) { if (data && data.conversation_id) { this.currentConversationId = data.conversation_id; if (data.title) { this.currentConversationTitle = data.title; } this.promoteConversationToTop(data.conversation_id); const pathFragment = this.stripConversationPrefix(data.conversation_id); const currentPath = window.location.pathname.replace(/^\/+/, ''); if (data.created) { history.pushState({ conversationId: data.conversation_id }, '', `/${pathFragment}`); } else if (currentPath !== pathFragment) { history.replaceState({ conversationId: data.conversation_id }, '', `/${pathFragment}`); } } }, handleCompressionState(data: any) { if (!data || typeof data !== 'object') { return; } const wasInProgress = !!this.compressionInProgress; this.compressionInProgress = !!data.in_progress; this.compressionMode = data.mode || ''; this.compressionStage = data.stage || ''; if (this.compressionInProgress && !wasInProgress) { const modeLabel = this.compressionMode === 'manual' ? '手动' : '自动'; if (this.compressionToastId) { this.uiDismissToast(this.compressionToastId); this.compressionToastId = null; } this.compressionToastId = this.uiPushToast({ title: '压缩中', message: `对话正在${modeLabel}压缩,请稍候…`, type: 'info', duration: null, closable: false }); } if (!this.compressionInProgress) { this.compressionError = data.error || ''; if (this.compressionToastId) { this.uiDismissToast(this.compressionToastId); this.compressionToastId = null; } } }, async handleCompressionFinished(data: any) { if (this.compressionToastId) { this.uiDismissToast(this.compressionToastId); this.compressionToastId = null; } this.compressionInProgress = false; this.compressionMode = ''; this.compressionStage = ''; this.compressionError = ''; const newId = data?.conversation_id; if (newId && newId !== this.currentConversationId) { await this.loadConversation(newId, { force: true }); } this.conversationsOffset = 0; if (typeof this.loadConversationsList === 'function') { await this.loadConversationsList(); } this.uiPushToast({ title: '压缩完成', message: '已自动切换到压缩后的新对话', type: 'success', duration: 2400 }); }, handleShallowCompression(data: any, eventIdx?: number) { const count = Number(data?.compressed_count || 0); if (count <= 0) { return; } debugLog('[TaskPolling] 自动浅层压缩触发, idx:', eventIdx, data); this.uiPushToast({ title: '自动浅层压缩', message: `已自动压缩 ${count} 条较早工具结果`, type: 'info', duration: 2500 }); }, /** * 清理已处理的事件索引 */ clearProcessedEvents() { if (this._processedEventIndices) { this._processedEventIndices.clear(); } }, /** * 恢复任务状态(页面刷新后调用) */ async restoreTaskState() { // 清理已处理的事件索引 this.clearProcessedEvents(); restoreDebugLog('restore:start', { currentConversationId: this.currentConversationId, streamingMessage: this.streamingMessage, taskInProgress: this.taskInProgress, historyLoading: this.historyLoading, historyLoadingFor: this.historyLoadingFor, messagesLen: Array.isArray(this.messages) ? this.messages.length : -1 }); try { const { useTaskStore } = await import('../../stores/task'); const taskStore = useTaskStore(); // 如果已经在流式输出中,不重复恢复 if (this.streamingMessage || this.taskInProgress) { restoreDebugLog('restore:skip-already-running', { streamingMessage: this.streamingMessage, taskInProgress: this.taskInProgress, currentConversationId: this.currentConversationId }); debugLog('[TaskPolling] 任务已在进行中,跳过恢复', { streamingMessage: this.streamingMessage, taskInProgress: this.taskInProgress, currentConversationId: this.currentConversationId }); return; } // 查找运行中的任务 const runningTask = await taskStore.loadRunningTask(this.currentConversationId); if (!runningTask) { restoreDebugLog('restore:no-running-task', { currentConversationId: this.currentConversationId }); debugLog('[TaskPolling] 没有运行中的任务', { currentConversationId: this.currentConversationId }); this._restoreTaskStateWaitCount = 0; await this.restoreSubAgentWaitingState(); return; } debugLog('[TaskPolling] 发现运行中的任务,开始恢复状态', { taskId: runningTask?.task_id, status: runningTask?.status, conversationId: runningTask?.conversation_id }); restoreDebugLog('restore:running-task-found', { taskId: runningTask?.task_id, status: runningTask?.status, taskConversationId: runningTask?.conversation_id, currentConversationId: this.currentConversationId }); // 检查历史是否已加载 const hasMessages = Array.isArray(this.messages) && this.messages.length > 0; const historyLoadingSameConversation = !!this.historyLoading && this.historyLoadingFor === this.currentConversationId; if (!hasMessages) { this._restoreTaskStateWaitCount = (this._restoreTaskStateWaitCount || 0) + 1; const waitedTooLong = this._restoreTaskStateWaitCount >= 8; // ~4s restoreDebugLog('restore:history-empty', { waitCount: this._restoreTaskStateWaitCount, waitedTooLong, historyLoading: this.historyLoading, historyLoadingFor: this.historyLoadingFor, currentConversationId: this.currentConversationId }); if (historyLoadingSameConversation && !waitedTooLong) { debugLog('[TaskPolling] 历史未加载,等待历史加载完成', { waitCount: this._restoreTaskStateWaitCount, historyLoading: this.historyLoading, historyLoadingFor: this.historyLoadingFor }); setTimeout(() => { this.restoreTaskState(); }, 500); return; } // 兜底:即使历史未成功拉取,也允许直接通过任务事件重放恢复界面, // 避免 show_html 流式阶段刷新后“本次请求内容完全消失”。 debugLog('[TaskPolling] 历史为空,启用事件重放兜底恢复', { waitCount: this._restoreTaskStateWaitCount, historyLoading: this.historyLoading, historyLoadingFor: this.historyLoadingFor }); } else { this._restoreTaskStateWaitCount = 0; } debugLog('[TaskPolling] 开始精细恢复', { hasMessages, historyLoading: this.historyLoading, historyLoadingFor: this.historyLoadingFor }); // 获取任务的所有事件 const detailResponse = await fetch(`/api/tasks/${taskStore.currentTaskId}`); if (!detailResponse.ok) { debugLog('[TaskPolling] 获取任务详情失败'); return; } const detailResult = await detailResponse.json(); if (!detailResult.success || !detailResult.data.events) { restoreDebugLog('restore:task-detail-invalid', { ok: !!detailResult?.success, hasData: !!detailResult?.data, hasEvents: !!detailResult?.data?.events }); debugLog('[TaskPolling] 任务详情无效'); return; } const allEvents = detailResult.data.events; debugLog(`[TaskPolling] 获取到 ${allEvents.length} 个事件`); restoreDebugLog('restore:task-detail-events', { total: allEvents.length, firstIdx: allEvents[0]?.idx, firstType: allEvents[0]?.type, lastIdx: allEvents[allEvents.length - 1]?.idx, lastType: allEvents[allEvents.length - 1]?.type }); // 找到最后一条消息 const lastMessage = this.messages[this.messages.length - 1]; const isAssistantMessage = lastMessage && lastMessage.role === 'assistant'; // console.log('[TaskPolling] 最后一条消息:', { // exists: !!lastMessage, // role: lastMessage?.role, // actionsCount: lastMessage?.actions?.length || 0, // isAssistant: isAssistantMessage // }); // 先分析事件状态(用于判定是否强制重建) let inThinking = false; let inText = false; let hasTextChunkEvent = false; for (let i = 0; i < allEvents.length; i++) { const event = allEvents[i]; if (event.type === 'thinking_start') { inThinking = true; } else if (event.type === 'thinking_end') { inThinking = false; } if (event.type === 'text_start') { inText = true; } else if (event.type === 'text_end') { inText = false; } if (event.type === 'text_chunk') { hasTextChunkEvent = true; } } // 检查是否需要从头重建 // 1. 最后一条不是 assistant 消息 // 2. 最后一条是空的 assistant 消息 // 3. 事件数量远大于历史中的 actions 数量(说明历史不完整) // 4. 任务正处于文本流式阶段(刷新时易丢分段内容,强制重放全部事件) const historyActionsCount = lastMessage?.actions?.length || 0; const eventCount = allEvents.length; const historyIncomplete = eventCount > historyActionsCount + 5; // 允许5个事件的误差 const forceRebuildForStreamingText = inText || hasTextChunkEvent; // 刷新恢复场景下,优先强制从头重放当前任务事件,避免“有运行中任务但界面无输出” const forceRebuildOnRefresh = true; const needsRebuild = forceRebuildOnRefresh || !isAssistantMessage || (isAssistantMessage && (!lastMessage.actions || lastMessage.actions.length === 0)) || historyIncomplete || forceRebuildForStreamingText; restoreDebugLog('restore:rebuild-decision', { needsRebuild, forceRebuildOnRefresh, isAssistantMessage, historyActionsCount, eventCount, historyIncomplete, inText, hasTextChunkEvent, forceRebuildForStreamingText }); if (needsRebuild) { // if (historyIncomplete) { // console.log('[TaskPolling] 历史不完整,从头重建:', { // historyActionsCount, // eventCount, // diff: eventCount - historyActionsCount // }); // } debugLog('[TaskPolling] 需要从头重建 assistant 响应'); // 清空所有消息,准备从头重建 // 保留用户消息,只删除最后的 assistant 消息 if (isAssistantMessage) { debugLog('[TaskPolling] 删除不完整的 assistant 消息'); this.messages.pop(); } this.streamingMessage = true; this.taskInProgress = true; this.$forceUpdate(); // 重置偏移量为 0,从头获取所有事件来重建 assistant 消息 taskStore.lastEventIndex = 0; debugLog('[TaskPolling] 重置偏移量为 0,从头开始轮询'); // 标记正在从头重建,用于后续处理 this._rebuildingFromScratch = true; this._rebuildingEventCount = allEvents.length; // 记录当前事件总数 (window as any).__taskEventHandler = (event: any) => { this.handleTaskEvent(event); }; taskStore.startPolling((event: any) => { this.handleTaskEvent(event); }); restoreDebugLog('restore:rebuild-polling-started', { lastEventIndex: taskStore.lastEventIndex, currentConversationId: this.currentConversationId }); // 延迟清除重建标记,确保所有历史事件都处理完毕 setTimeout(() => { debugLog('[TaskPolling] 历史事件处理完毕,清除重建标记'); this._rebuildingFromScratch = false; this._rebuildingEventCount = 0; }, 2000); return; } // console.log('[TaskPolling] 分析结果:', { // inThinking, // inText, // totalEvents: allEvents.length, // lastEventType: allEvents[allEvents.length - 1]?.type, // lastEventIdx: allEvents[allEvents.length - 1]?.idx // }); // 恢复思考块状态 if (lastMessage.actions) { // console.log('[TaskPolling] 历史中的 actions 详情:', lastMessage.actions.map((a, idx) => ({ // index: idx, // type: a.type, // id: a.id, // hasContent: !!a.content, // contentLength: a.content?.length || 0, // toolName: a.tool?.name, // hasBlockId: !!a.blockId, // blockId: a.blockId, // collapsed: a.collapsed, // streaming: a.streaming // }))); const thinkingActions = lastMessage.actions.filter((a) => a.type === 'thinking'); // console.log('[TaskPolling] 思考块数量:', thinkingActions.length); if (inThinking && thinkingActions.length > 0) { // 正在思考中,检查最后一个思考块是否正在流式输出 const lastThinking = thinkingActions[thinkingActions.length - 1]; // 只有当思考块正在流式输出时才设置锁定状态(但不展开) if (lastThinking.streaming && lastThinking.blockId) { // console.log('[TaskPolling] 找到正在流式输出的思考块,设置锁定状态:', lastThinking.blockId); this.$nextTick(() => { this.chatSetThinkingLock(lastThinking.blockId, true); }); } } // 确保所有思考块都是折叠状态 for (const thinking of thinkingActions) { if (thinking.blockId) { thinking.collapsed = true; } } // 检查思考块状态(在设置之后)(已禁用) // thinkingActions.forEach((thinking, idx) => { // console.log(`[TaskPolling] 思考块 ${idx} (设置后):`, { // hasBlockId: !!thinking.blockId, // blockId: thinking.blockId, // collapsed: thinking.collapsed, // contentLength: thinking.content?.length || 0 // }); // }); // 恢复文本块状态 const textActions = lastMessage.actions.filter((a) => a.type === 'text'); // console.log('[TaskPolling] 文本块数量:', textActions.length); if (inText && textActions.length > 0) { const lastText = textActions[textActions.length - 1]; // console.log('[TaskPolling] 标记文本块为流式状态'); lastText.streaming = true; } // 注册历史中的工具块到 toolActionIndex // 这样后续的 update_action 事件可以找到对应的块进行状态更新 const toolActions = lastMessage.actions.filter((a) => a.type === 'tool'); // console.log('[TaskPolling] 工具块数量:', toolActions.length); for (const toolAction of toolActions) { if (toolAction.tool && toolAction.tool.id) { // console.log('[TaskPolling] 注册工具块:', { // id: toolAction.tool.id, // name: toolAction.tool.name, // status: toolAction.tool.status // }); // 注册到 toolActionIndex this.toolRegisterAction(toolAction, toolAction.tool.id); // 如果有 executionId,也注册 if (toolAction.tool.executionId) { this.toolRegisterAction(toolAction, toolAction.tool.executionId); } // 追踪工具调用 if (toolAction.tool.name) { this.toolTrackAction(toolAction.tool.name, toolAction); } } } } // 标记状态为进行中 this.streamingMessage = true; this.taskInProgress = true; // 设置 currentMessageIndex 指向最后一条 assistant 消息 // 这样后续添加的 action 会添加到正确的消息中 const lastMessageIndex = this.messages.length - 1; if (lastMessage && lastMessage.role === 'assistant') { this.currentMessageIndex = lastMessageIndex; // console.log('[TaskPolling] 设置 currentMessageIndex 为:', lastMessageIndex); } // 强制更新界面 this.$forceUpdate(); // 滚动到底部 this.$nextTick(() => { this.conditionalScrollToBottom(); }); // 注册事件处理器到全局 (window as any).__taskEventHandler = (event: any) => { this.handleTaskEvent(event); }; // 启动轮询(从当前偏移量开始,只处理新事件) debugLog('[TaskPolling] 启动轮询,起始偏移量:', taskStore.lastEventIndex); taskStore.startPolling((event: any) => { this.handleTaskEvent(event); }); restoreDebugLog('restore:polling-started-follow', { lastEventIndex: taskStore.lastEventIndex, currentConversationId: this.currentConversationId }); this.uiPushToast({ title: '任务恢复', message: '检测到进行中的任务,已恢复连接', type: 'info', duration: 3000 }); } catch (error) { restoreDebugLog('restore:error', { message: error?.message || String(error) }); console.error('[TaskPolling] 恢复任务状态失败:', error); } }, /** * 恢复子智能体等待状态(页面刷新后调用) */ async restoreSubAgentWaitingState(retry = 0) { try { if (!this.currentConversationId) { if (retry < 5) { setTimeout(() => { this.restoreSubAgentWaitingState(retry + 1); }, 300); } return; } const response = await fetch('/api/sub_agents'); if (!response.ok) { debugLog('[TaskPolling] 获取子智能体状态失败'); return; } const result = await response.json(); if (!result.success) { debugLog('[TaskPolling] 子智能体状态响应无效'); return; } const tasks = Array.isArray(result.data) ? result.data : []; debugLog('[TaskPolling] 子智能体状态响应', { total: tasks.length, currentConversationId: this.currentConversationId, tasks: tasks.map((task: any) => ({ task_id: task?.task_id, status: task?.status, run_in_background: task?.run_in_background, notice_pending: task?.notice_pending, conversation_id: task?.conversation_id })) }); const terminalStatuses = new Set(['completed', 'failed', 'timeout', 'terminated']); const relevant = tasks.filter((task: any) => { if (task && task.conversation_id && task.conversation_id !== this.currentConversationId) { return false; } return true; }); const running = relevant.filter( (task: any) => task?.run_in_background && !terminalStatuses.has(task?.status) ); const pendingNotice = relevant.filter((task: any) => task?.notice_pending); if (running.length || pendingNotice.length) { debugLog('[TaskPolling] 恢复子智能体等待状态', { running: running.length, pendingNotice: pendingNotice.length, runningTasks: running.map((task: any) => ({ task_id: task?.task_id, status: task?.status })), pendingTasks: pendingNotice.map((task: any) => ({ task_id: task?.task_id, status: task?.status })) }); this.waitingForSubAgent = true; this.waitingForBackgroundCommand = false; this.taskInProgress = true; this.streamingMessage = false; this.stopRequested = false; this.startWaitingTaskProbe(); this.$forceUpdate(); } } catch (error) { console.error('[TaskPolling] 恢复子智能体等待状态失败:', error); } } };