import { executeScheduledTask } from './scheduled-task-executor.mjs'; import { localDateKey } from './schedule-time.mjs'; import { buildNewsMorningAutoGenerationTask, findLatestNewsMorningPage, isLocalScheduleDue, isNewsMorningPageForToday, isWithinNewsMorningGenerateLeadWindow, } from './wechat-news-morning-draft.mjs'; import { isWechatNewsMorningDraftWorkerEnabled, wechatNewsMorningDraftExecutionTimeoutMs, wechatNewsMorningDraftWorkerIntervalMs, } from './wechat-news-morning-draft-worker-config.mjs'; import { isPassiveCanaryRuntime } from './server/portal-runtime-role.mjs'; function resolveTodayPage(config, h5Root, now = Date.now()) { const page = findLatestNewsMorningPage({ h5Root, userId: config.sourceUserId, slugPattern: config.pageSlugPattern, date: new Date(now), timezone: config.timezone, }); if (!page || !isNewsMorningPageForToday(page, { date: new Date(now), timezone: config.timezone })) { return null; } return page; } export function startWechatNewsMorningDraftWorker({ wechatNewsMorningDraftService = null, mpConfig = null, userAuth = null, tkmindProxy = null, agentRunGateway = null, cursorExecutorPolicyService = null, sessionSnapshotService = null, pool = null, h5Root = null, env = process.env, executeTask = executeScheduledTask, logger = console, intervalMs = wechatNewsMorningDraftWorkerIntervalMs(env), executionTimeoutMs = wechatNewsMorningDraftExecutionTimeoutMs(env), runOnStart = true, setIntervalFn = setInterval, } = {}) { if ( isPassiveCanaryRuntime(env) || !isWechatNewsMorningDraftWorkerEnabled(env) || !wechatNewsMorningDraftService || !mpConfig?.enabled || !userAuth || !tkmindProxy || !h5Root ) { return { stop() {} }; } let stopped = false; let running = false; let generationInFlightDateKey = null; const ensureTodayPage = async (config, dateKey) => { if (generationInFlightDateKey === dateKey) return null; generationInFlightDateKey = dateKey; try { const task = buildNewsMorningAutoGenerationTask(config); await executeTask(task, { userAuth, tkmindProxy, agentRunGateway, cursorExecutorPolicyService, sessionSnapshotService, pool, h5Root, timeoutMs: executionTimeoutMs, logger, }); const page = resolveTodayPage(config, h5Root); if (!page) { throw new Error('新闻早报页面生成未完成'); } await wechatNewsMorningDraftService.recordAutoGenerationRun({ status: 'success', pageSlug: page.slug, }); logger.log?.('[NewsMorningDraft] auto-generated today page', { slug: page.slug, userId: config.sourceUserId, }); return page; } catch (error) { const message = error instanceof Error ? error.message : String(error); await wechatNewsMorningDraftService.recordAutoGenerationRun({ status: 'failed', errorMessage: message, }).catch(() => {}); logger.warn?.('[NewsMorningDraft] auto-generate failed:', message); throw error; } finally { if (generationInFlightDateKey === dateKey) { generationInFlightDateKey = null; } } }; const runOnce = async () => { if (running || stopped) return; running = true; try { const config = await wechatNewsMorningDraftService.getConfig(); if (!config.enabled || !config.autoPushEnabled || !config.sourceUserId) return; const now = Date.now(); const timezone = config.timezone; const dateKey = localDateKey(now, timezone); const toleranceMinutes = Math.max(1, Math.ceil(Number(intervalMs) / 60_000)); let todayPage = resolveTodayPage(config, h5Root, now); const shouldGenerate = config.autoGenerateEnabled !== false && !todayPage && ( isWithinNewsMorningGenerateLeadWindow(config, now) || isLocalScheduleDue( { hour: config.pushHour, minute: config.pushMinute, timezone }, now, toleranceMinutes + 120, ) ); if (shouldGenerate && generationInFlightDateKey !== dateKey) { try { todayPage = await ensureTodayPage(config, dateKey); } catch { // keep trying during generate window on later scans } } if ( !isLocalScheduleDue( { hour: config.pushHour, minute: config.pushMinute, timezone }, now, toleranceMinutes, ) ) { return; } if (await wechatNewsMorningDraftService.hasSuccessfulAutoPushToday({ now })) { return; } todayPage = todayPage ?? resolveTodayPage(config, h5Root, now); if (!todayPage) { logger.warn?.('[NewsMorningDraft] skip auto push: today page missing', { userId: config.sourceUserId, dateKey, }); return; } const result = await wechatNewsMorningDraftService.pushDraft({ triggeredBy: 'auto-push', }); logger.log?.('[NewsMorningDraft] auto push succeeded', { draftMediaId: result.draftMediaId, pageSlug: result.preview?.page?.slug ?? todayPage.slug, }); } catch (error) { logger.warn?.('[NewsMorningDraft] auto worker failed:', error); } finally { running = false; } }; const timer = setIntervalFn(runOnce, Math.max(5_000, Number(intervalMs) || 30_000)); timer.unref?.(); if (runOnStart) void runOnce(); return { runOnce, stop() { stopped = true; clearInterval(timer); }, }; }