// @ts-nocheck import { defineStore } from 'pinia'; import { debugLog } from '../app/methods/common'; const debugNotifyLog = (...args: any[]) => { void args; }; const keyNotifyLog = (...args: any[]) => { console.log(...args); }; const jsonDebug = (...args: any[]) => { console.log('[JSONDEBUG]', ...args); }; const CONN_DIAG_PREFIX = '[CONN_DIAG]'; const TASK_POLL_DIAG_MAX = 2000; const taskPollDiag = (event: string, payload: Record = {}) => { const ts = new Date().toISOString(); const record = { ts, event, ...payload }; try { if (typeof window !== 'undefined') { const w = window as any; if (!Array.isArray(w.__CONN_DIAG_LOGS__)) { w.__CONN_DIAG_LOGS__ = []; } w.__CONN_DIAG_LOGS__.push(record); if (w.__CONN_DIAG_LOGS__.length > TASK_POLL_DIAG_MAX) { w.__CONN_DIAG_LOGS__.splice(0, w.__CONN_DIAG_LOGS__.length - TASK_POLL_DIAG_MAX); } } } catch { // ignore } console.log(CONN_DIAG_PREFIX, event, record); }; export const useTaskStore = defineStore('task', { state: () => ({ currentTaskId: null as string | null, lastEventIndex: 0, pollingInterval: null as number | null, taskStatus: 'idle' as 'idle' | 'running' | 'succeeded' | 'failed' | 'canceled', isPolling: false, pollingError: null as string | null, pollingErrorCount: 0, // 新增错误计数 pollingInFlight: false, pollingSeq: 0, pollingWarned: false, pollingRequestTimeoutMs: 12000, pollingFailThreshold: 8, pollingIntervalMs: 250, taskCreatedAt: null as number | null, taskUpdatedAt: null as number | null, runtimeQueueSnapshotKey: '' }), getters: { hasActiveTask: (state) => state.currentTaskId !== null && state.taskStatus === 'running', isTaskCompleted: (state) => ['succeeded', 'failed', 'canceled'].includes(state.taskStatus) }, actions: { async createTask( message: string, images: any[] = [], videos: any[] = [], conversationId: string | null = null, options: { model_key?: string | null; run_mode?: 'fast' | 'thinking' | 'deep' | null; thinking_mode?: boolean | null; message_source?: string | null; eventHandler?: (event: any) => void; } = {} ) { try { debugLog('[Task] 创建任务:', { message, conversationId }); const response = await fetch('/api/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message, images, videos, conversation_id: conversationId, model_key: options.model_key ?? undefined, run_mode: options.run_mode ?? undefined, thinking_mode: typeof options.thinking_mode === 'boolean' ? options.thinking_mode : undefined, message_source: options.message_source ?? undefined }) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || '创建任务失败'); } const result = await response.json(); if (!result.success) { throw new Error(result.error || '创建任务失败'); } this.currentTaskId = result.data.task_id; this.taskStatus = result.data.status; this.lastEventIndex = 0; this.taskCreatedAt = result.data.created_at; this.runtimeQueueSnapshotKey = ''; this.pollingError = null; this.pollingErrorCount = 0; this.pollingWarned = false; debugLog('[Task] 任务创建成功:', result.data.task_id); taskPollDiag('task-created', { taskId: result.data.task_id, conversationId }); // 立即开始轮询 this.startPolling(options.eventHandler); return result.data; } catch (error) { debugLog('[Task] 创建任务失败:', error); taskPollDiag('task-create-failed', { error: error?.message || String(error), conversationId }); throw error; } }, async pollTaskEvents(eventHandler: (event: any) => void) { if (!this.currentTaskId) { debugLog('[Task] 没有活跃任务,停止轮询'); this.stopPolling('no-active-task'); return; } if (this.pollingInFlight) { return; } this.pollingInFlight = true; const taskId = this.currentTaskId; const fromOffset = this.lastEventIndex; const requestId = `${Date.now()}-${++this.pollingSeq}`; const startedAt = Date.now(); try { const response = await fetch( `/api/tasks/${taskId}?from=${fromOffset}`, { signal: AbortSignal.timeout(this.pollingRequestTimeoutMs), headers: { 'X-Task-Poll': requestId } } ); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const result = await response.json(); if (!result.success) { throw new Error(result.error || '轮询任务失败'); } const data = result.data; // 如果用户已切换/新建对话并清除了当前轮询,忽略这次已在路上的旧响应。 // 否则旧任务事件可能被写入当前新对话视图。 if (this.currentTaskId !== taskId) { taskPollDiag('task-poll-stale-response-ignored', { requestId, responseTaskId: data?.task_id || taskId, activeTaskId: this.currentTaskId, from: fromOffset }); return; } const eventsCount = Array.isArray(data?.events) ? data.events.length : 0; jsonDebug('taskStore.poll:response', { taskId, from: fromOffset, status: data?.status, nextOffset: data?.next_offset, eventsCount }); if (eventsCount > 0 || this.pollingErrorCount > 0 || data?.status !== 'running' || fromOffset === 0) { taskPollDiag('task-poll-ok', { requestId, taskId, from: fromOffset, nextOffset: data?.next_offset, status: data?.status, eventsCount, elapsedMs: Date.now() - startedAt }); } // 更新任务状态 this.taskStatus = data.status; this.taskUpdatedAt = data.updated_at; const runtimeQueueMessages = Array.isArray(data?.runtime_queued_messages) ? data.runtime_queued_messages : []; const runtimeQueueSnapshotKey = JSON.stringify( runtimeQueueMessages.map((item: any) => [ String(item?.id || ''), String(item?.text || '') ]) ); if (runtimeQueueSnapshotKey !== this.runtimeQueueSnapshotKey) { this.runtimeQueueSnapshotKey = runtimeQueueSnapshotKey; try { eventHandler({ type: 'runtime_queue_sync', data: { task_id: data?.task_id || this.currentTaskId, conversation_id: data?.conversation_id || null, messages: runtimeQueueMessages } }); } catch (err) { console.warn('[Task] 处理 runtime_queue_sync 失败:', err); } } let sawTaskCompleteEvent = false; let sawTaskStoppedEvent = false; // 处理新事件 if (data.events && data.events.length > 0) { debugLog(`[Task] 收到 ${data.events.length} 个新事件`); for (const event of data.events) { if (this.currentTaskId !== taskId) { taskPollDiag('task-poll-stale-event-loop-abort', { requestId, responseTaskId: data?.task_id || taskId, activeTaskId: this.currentTaskId }); return; } if ( event?.type === 'task_complete' || event?.type === 'task_stopped' || event?.type === 'error' ) { jsonDebug('taskStore.poll:event', { idx: event?.idx, type: event?.type, data: event?.data }); } if (event?.type === 'task_complete') { sawTaskCompleteEvent = true; } if (event?.type === 'task_stopped') { sawTaskStoppedEvent = true; } try { eventHandler(event); } catch (err) { console.error('[Task] 处理事件失败:', err, event); } } const interesting = data.events.filter((e: any) => [ 'user_message', 'sub_agent_waiting', 'task_complete', 'task_stopped', 'error' ].includes(e?.type) ); if (interesting.length > 0) { keyNotifyLog('[DEBUG_NOTIFY_KEY][task] events', { taskId: this.currentTaskId, from: this.lastEventIndex, nextOffset: data.next_offset, count: interesting.length, items: interesting.map((e: any) => ({ idx: e?.idx, type: e?.type, sub_agent_notice: !!e?.data?.sub_agent_notice, has_running_sub_agents: e?.data?.has_running_sub_agents, has_running_background_commands: e?.data?.has_running_background_commands, task_id: e?.data?.task_id })) }); } this.lastEventIndex = data.next_offset; } // 兜底:如果后端状态已经是 canceled 但未返回 task_stopped 事件, // 补发一个本地停止事件,确保前端解除“停止中”状态。 if (data.status === 'canceled' && !sawTaskStoppedEvent) { debugLog('[Task] 状态已取消但未收到 task_stopped,补发本地事件'); jsonDebug('taskStore.poll:emit-synthetic-task-stopped', { taskId: data.task_id || this.currentTaskId, conversation_id: data.conversation_id || null }); try { eventHandler({ type: 'task_stopped', data: { task_id: data.task_id || this.currentTaskId, conversation_id: data.conversation_id || null, synthetic: true } }); } catch (err) { console.error('[Task] 处理补发 task_stopped 失败:', err); } } // 兜底:某些异常流程(如工具参数解析失败后恢复执行)可能没有 task_complete 事件, // 但任务状态已经 succeeded。补发一次完成事件,避免前端一直显示“工作中”。 if (data.status === 'succeeded' && !sawTaskCompleteEvent) { debugLog('[Task] 状态已成功但未收到 task_complete,补发本地事件'); jsonDebug('taskStore.poll:emit-synthetic-task-complete', { taskId: data.task_id || this.currentTaskId, conversation_id: data.conversation_id || null }); try { eventHandler({ type: 'task_complete', data: { task_id: data.task_id || this.currentTaskId, conversation_id: data.conversation_id || null, synthetic: true, has_running_sub_agents: false, has_running_background_commands: false } }); } catch (err) { console.error('[Task] 处理补发 task_complete 失败:', err); } } // 如果任务已完成,停止轮询 if (this.isTaskCompleted) { debugLog('[Task] 任务已完成,停止轮询:', this.taskStatus); this.stopPolling(`task-${this.taskStatus}`); } // 重置错误计数 this.pollingError = null; this.pollingErrorCount = 0; this.pollingWarned = false; } catch (error) { this.pollingErrorCount++; this.pollingError = error.message; const message = error?.message || String(error); const isTimeoutLike = /timeout|timed out|abort|aborted/i.test(message); console.error('[Task] 轮询失败:', error); taskPollDiag('task-poll-failed', { requestId, taskId, from: fromOffset, elapsedMs: Date.now() - startedAt, error: message, isTimeoutLike, pollingErrorCount: this.pollingErrorCount }); debugNotifyLog('[DEBUG_NOTIFY][task] pollTaskEvents:error', { taskId: this.currentTaskId, from: this.lastEventIndex, error: error?.message || String(error), pollingErrorCount: this.pollingErrorCount }); // 任务不存在时结束轮询 if (/HTTP 404|HTTP 410/.test(message)) { this.stopPolling('task-not-found'); return; } // 连续失败达到阈值仅告警,不直接停轮询(避免后端仍运行但前端停止更新) if (this.pollingErrorCount >= this.pollingFailThreshold && !this.pollingWarned) { this.pollingWarned = true; if ((window as any).__vueApp?.uiPushToast) { (window as any).__vueApp.uiPushToast({ title: '轮询波动', message: '消息更新暂时不稳定,正在自动重试', type: 'warning', duration: 5000 }); } } } finally { this.pollingInFlight = false; } }, startPolling(eventHandler?: (event: any) => void) { if (this.isPolling) { debugLog('[Task] 轮询已在运行'); debugNotifyLog('[DEBUG_NOTIFY][task] startPolling:already-running', { taskId: this.currentTaskId, lastEventIndex: this.lastEventIndex }); return; } if (!this.currentTaskId) { debugLog('[Task] 没有任务ID,无法启动轮询'); return; } debugLog('[Task] 启动轮询:', this.currentTaskId); debugNotifyLog('[DEBUG_NOTIFY][task] startPolling', { taskId: this.currentTaskId, lastEventIndex: this.lastEventIndex }); this.isPolling = true; this.pollingError = null; this.pollingErrorCount = 0; this.pollingWarned = false; // 如果没有传入 eventHandler,从根实例获取 const handler = eventHandler || (window as any).__taskEventHandler; if (!handler) { console.error('[Task] 没有事件处理器,无法启动轮询'); taskPollDiag('task-poll-start-failed-no-handler', { taskId: this.currentTaskId }); this.isPolling = false; return; } taskPollDiag('task-poll-start', { taskId: this.currentTaskId, from: this.lastEventIndex, intervalMs: this.pollingIntervalMs, timeoutMs: this.pollingRequestTimeoutMs }); // 立即执行一次 void this.pollTaskEvents(handler); // 设置定时轮询(单飞由 pollTaskEvents 内部保证) this.pollingInterval = window.setInterval(() => { void this.pollTaskEvents(handler); }, this.pollingIntervalMs); }, resumeTask( taskId: string, options: { status?: 'running' | 'pending' | 'succeeded' | 'failed' | 'canceled'; resetOffset?: boolean; eventHandler?: (event: any) => void; } = {} ) { if (!taskId) { return; } debugNotifyLog('[DEBUG_NOTIFY][task] resumeTask', { incomingTaskId: taskId, currentTaskId: this.currentTaskId, options }); keyNotifyLog('[DEBUG_NOTIFY_KEY][task] resumeTask', { incomingTaskId: taskId, currentTaskId: this.currentTaskId, resetOffset: options?.resetOffset !== false }); if (this.currentTaskId && this.currentTaskId !== taskId) { this.stopPolling(); } this.currentTaskId = taskId; this.taskStatus = options.status || 'running'; if (options.resetOffset !== false) { this.lastEventIndex = 0; } this.pollingError = null; this.runtimeQueueSnapshotKey = ''; this.startPolling(options.eventHandler); }, stopPolling(reason = 'manual') { taskPollDiag('task-poll-stop', { reason, taskId: this.currentTaskId, status: this.taskStatus, from: this.lastEventIndex, pollingErrorCount: this.pollingErrorCount }); if (this.pollingInterval) { debugLog('[Task] 停止轮询'); clearInterval(this.pollingInterval); this.pollingInterval = null; } this.isPolling = false; this.pollingInFlight = false; this.pollingWarned = false; this.runtimeQueueSnapshotKey = ''; this.currentTaskId = null; // 清除任务 ID }, async cancelTask() { if (!this.currentTaskId) { return; } try { debugLog('[Task] 取消任务:', this.currentTaskId); const response = await fetch(`/api/tasks/${this.currentTaskId}/cancel`, { method: 'POST' }); if (!response.ok) { throw new Error('取消任务失败'); } const result = await response.json(); if (!result.success) { throw new Error(result.error || '取消任务失败'); } debugLog('[Task] 已发送取消请求,等待后端停止确认'); } catch (error) { console.error('[Task] 取消任务失败:', error); throw error; } }, async loadRunningTask(conversationId: string | null = null) { try { debugLog('[Task] 查找运行中的任务'); const response = await fetch('/api/tasks'); if (!response.ok) { throw new Error('获取任务列表失败'); } const result = await response.json(); if (!result.success) { throw new Error(result.error || '获取任务列表失败'); } // 查找当前对话的活动任务 const activeStatuses = new Set(['pending', 'running', 'cancel_requested']); const runningTask = result.data.find( (task: any) => activeStatuses.has(String(task.status || '')) && (!conversationId || task.conversation_id === conversationId) ); if (runningTask) { debugLog('[Task] 发现运行中的任务:', runningTask.task_id); this.currentTaskId = runningTask.task_id; this.taskStatus = runningTask.status; this.taskCreatedAt = runningTask.created_at; this.taskUpdatedAt = runningTask.updated_at; this.pollingError = null; this.pollingErrorCount = 0; this.pollingWarned = false; this.runtimeQueueSnapshotKey = ''; // 获取任务详情,计算已处理的事件数量 try { const detailResponse = await fetch(`/api/tasks/${runningTask.task_id}`); if (detailResponse.ok) { const detailResult = await detailResponse.json(); if (detailResult.success && detailResult.data.events) { // 设置为当前事件数量,只获取新事件 this.lastEventIndex = detailResult.data.next_offset || detailResult.data.events.length; debugLog('[Task] 设置起始偏移量:', this.lastEventIndex); } else { this.lastEventIndex = 0; } } else { this.lastEventIndex = 0; } } catch (error) { console.warn('[Task] 获取任务详情失败,从头开始:', error); this.lastEventIndex = 0; } return runningTask; } debugLog('[Task] 没有运行中的任务'); return null; } catch (error) { console.error('[Task] 加载运行中任务失败:', error); return null; } }, clearTask() { debugLog('[Task] 清理任务状态'); this.stopPolling(); this.currentTaskId = null; this.lastEventIndex = 0; this.taskStatus = 'idle'; this.pollingError = null; this.pollingErrorCount = 0; this.pollingWarned = false; this.taskCreatedAt = null; this.taskUpdatedAt = null; this.runtimeQueueSnapshotKey = ''; }, resetForNewConversation() { debugLog('[Task] 重置任务状态(新对话)'); this.stopPolling(); this.currentTaskId = null; this.lastEventIndex = 0; this.taskStatus = 'idle'; this.pollingError = null; this.pollingErrorCount = 0; this.pollingWarned = false; this.runtimeQueueSnapshotKey = ''; } } });