import { executeScheduledTask, formatScheduledTaskDeliveryMessage, looksLikeScheduledTaskNonDelivery, } from './scheduled-task-executor.mjs'; export function startScheduledTaskWorker({ scheduledTaskService, scheduleService = null, executeTask = executeScheduledTask, userAuth = null, tkmindProxy = null, sessionSnapshotService = null, notificationDispatcher = null, pool = null, h5Root = null, logger = console, intervalMs = Number(process.env.H5_SCHEDULED_TASK_SCAN_INTERVAL_MS ?? 30_000), maxAttempts = Number(process.env.H5_SCHEDULED_TASK_MAX_ATTEMPTS ?? 3), executionTimeoutMs = Number(process.env.H5_SCHEDULED_TASK_EXECUTION_TIMEOUT_MS ?? 15 * 60 * 1000), runOnStart = true, } = {}) { const sendScheduleNotification = notificationDispatcher?.sendScheduleNotification?.bind(notificationDispatcher) ?? null; const canExecute = scheduledTaskService && typeof executeTask === 'function' && userAuth && tkmindProxy; if (!canExecute) { return { stop() {} }; } let stopped = false; let running = false; const deliverTaskResult = async (task, deliveryText) => { const text = formatScheduledTaskDeliveryMessage(task, deliveryText); const notifyChannel = task.notifyChannel ?? 'both'; if (scheduleService?.createUserNotification && (notifyChannel === 'web' || notifyChannel === 'both')) { await scheduleService.createUserNotification({ userId: task.userId, channel: 'web', notificationType: 'scheduled_task_result', title: `定时任务完成:${task.title}`, body: text, data: { taskId: task.id, recurrence: task.recurrence, }, }).catch((err) => { logger.warn?.('Scheduled task web notification failed:', err); }); } if ( (notifyChannel === 'wechat' || notifyChannel === 'both') && typeof sendScheduleNotification === 'function' ) { await sendScheduleNotification({ userId: task.userId, text }).catch((err) => { logger.warn?.('Scheduled task wechat notification failed:', err); }); } }; const runOnce = async () => { if (running || stopped) return; running = true; try { const dueTasks = await scheduledTaskService.listDueTasks({ limit: 10 }); for (const candidate of dueTasks) { const task = await scheduledTaskService.lockTask(candidate.id); if (!task) continue; try { await scheduledTaskService.markTaskRunning(task); const result = await executeTask(task, { userAuth, tkmindProxy, sessionSnapshotService, pool, h5Root, timeoutMs: executionTimeoutMs, logger, }); if (looksLikeScheduledTaskNonDelivery(result.deliveryText)) { const err = new Error('定时任务未产出可交付结果'); err.code = 'SCHEDULED_TASK_NON_DELIVERY'; throw err; } await deliverTaskResult(task, result.deliveryText); await scheduledTaskService.markTaskSucceeded(task, { result: { deliveryText: result.deliveryText, }, deliveryText: result.deliveryText, sessionId: result.sessionId, requestId: result.requestId, }); } catch (err) { logger.warn?.('Scheduled task execution failed:', err); const failedTask = await scheduledTaskService.markTaskFailed(task, err, { maxAttempts, }); if (failedTask.status === 'failed' && scheduleService?.createUserNotification) { await scheduleService.createUserNotification({ userId: task.userId, channel: 'web', notificationType: 'scheduled_task_failed', title: `定时任务失败:${task.title}`, body: String(failedTask.lastError ?? '执行失败'), data: { taskId: task.id }, }).catch(() => {}); } if ( failedTask.status === 'failed' && (task.notifyChannel === 'wechat' || task.notifyChannel === 'both') && typeof sendScheduleNotification === 'function' ) { await sendScheduleNotification({ userId: task.userId, text: `定时任务失败:${task.title}\n${failedTask.lastError ?? '执行失败'}`, }).catch(() => {}); } } } } catch (err) { logger.warn?.('Scheduled task worker failed:', err); } finally { running = false; } }; const timer = setInterval(runOnce, Math.max(1000, Number(intervalMs) || 30_000)); timer.unref?.(); if (runOnStart) void runOnce(); return { runOnce, stop() { stopped = true; clearInterval(timer); }, }; }