Add scheduled task automation skill with worker execution pipeline.
Memind CI / Test, build, and release guards (push) Failing after 18s
Memind CI / Test, build, and release guards (push) Failing after 18s
Introduce scheduled-task-automation for H5 and WeChat, persist tasks in h5_scheduled_tasks, and run due jobs via a dedicated worker that executes agent tasks and delivers results. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import {
|
||||
executeScheduledTask,
|
||||
formatScheduledTaskDeliveryMessage,
|
||||
} from './scheduled-task-executor.mjs';
|
||||
|
||||
export function startScheduledTaskWorker({
|
||||
scheduledTaskService,
|
||||
scheduleService = null,
|
||||
executeTask = executeScheduledTask,
|
||||
userAuth = null,
|
||||
tkmindProxy = null,
|
||||
sessionSnapshotService = null,
|
||||
notificationDispatcher = 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,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (
|
||||
(notifyChannel === 'wechat' || notifyChannel === 'both')
|
||||
&& typeof sendScheduleNotification === 'function'
|
||||
) {
|
||||
await sendScheduleNotification({ userId: task.userId, text });
|
||||
}
|
||||
};
|
||||
|
||||
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,
|
||||
timeoutMs: executionTimeoutMs,
|
||||
logger,
|
||||
});
|
||||
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);
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user