b9f5720ce0
Memind CI / Test, build, and release guards (push) Failing after 4s
Expand page.generate rules for post-page verbs and link-missing retries, recover HTML artifacts when shadow LLM agrees, and stop marking scheduled tasks failed when WeChat notification hits rate limits. Co-authored-by: Cursor <cursoragent@cursor.com>
145 lines
4.8 KiB
JavaScript
145 lines
4.8 KiB
JavaScript
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);
|
|
},
|
|
};
|
|
}
|