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:
@@ -16,6 +16,7 @@ import { fileURLToPath } from 'node:url';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import mysql from 'mysql2/promise';
|
||||
import { createScheduleService } from './schedule-service.mjs';
|
||||
import { createScheduledTaskService } from './scheduled-task-service.mjs';
|
||||
import { resolveScheduleTimestamp } from './schedule-time.mjs';
|
||||
import { shouldAutoCreateReminderAtStart } from './schedule-service.mjs';
|
||||
import { renderLongImage } from './mindspace-long-image.mjs';
|
||||
@@ -484,6 +485,7 @@ const ALL_TOOLS = [
|
||||
|
||||
let quotaPool = null;
|
||||
let scheduleService = null;
|
||||
let scheduledTaskService = null;
|
||||
let userDataSpaceService = null;
|
||||
|
||||
function isQuotaSyncConfigured() {
|
||||
@@ -529,6 +531,20 @@ function getScheduleService() {
|
||||
return scheduleService;
|
||||
}
|
||||
|
||||
function getScheduledTaskService() {
|
||||
if (!PRIVATE_DATA_USER_ID) throw new Error('当前会话没有用户上下文,不能操作定时任务');
|
||||
const pool = getQuotaPool();
|
||||
if (!pool || !isScheduleConfigured()) {
|
||||
throw new Error('当前环境未配置待办数据库连接');
|
||||
}
|
||||
if (!scheduledTaskService) {
|
||||
scheduledTaskService = createScheduledTaskService(pool, {
|
||||
defaultTimezone: process.env.H5_DEFAULT_TIMEZONE || 'Asia/Shanghai',
|
||||
});
|
||||
}
|
||||
return scheduledTaskService;
|
||||
}
|
||||
|
||||
if (isScheduleConfigured()) {
|
||||
ALL_TOOLS.push(
|
||||
{
|
||||
@@ -591,6 +607,50 @@ if (isScheduleConfigured()) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'scheduled_task_create',
|
||||
description:
|
||||
'为当前用户创建定时自动任务(Scheduled Automation)。到点由系统自动执行 taskSpec 并推送结果;不用于纯待办/提醒。',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: { type: 'string', description: '任务摘要标题,可选' },
|
||||
taskSpec: { type: 'string', description: '到时具体执行什么(必填)' },
|
||||
recurrence: { type: 'string', description: 'once / daily / weekly,默认 daily' },
|
||||
runAtLocal: { type: 'string', description: '一次性任务执行时间 YYYY-MM-DD HH:mm' },
|
||||
hour: { type: 'number', description: 'daily/weekly 的小时 0-23' },
|
||||
minute: { type: 'number', description: 'daily/weekly 的分钟 0-59,默认 0' },
|
||||
weekday: { type: 'number', description: 'weekly 的星期 0=周日 … 6=周六' },
|
||||
timezone: { type: 'string', description: '时区,可选' },
|
||||
notifyChannel: { type: 'string', description: 'wechat / web / both,默认 both' },
|
||||
sourceMessageId: { type: 'string', description: '服务号消息 ID;有值时必须原样传入' },
|
||||
sourceText: { type: 'string', description: '原始用户文本,可选' },
|
||||
},
|
||||
required: ['taskSpec'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'scheduled_task_list',
|
||||
description: '查询当前用户的定时自动任务列表。仅返回当前用户数据。',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
status: { type: 'string', description: '任务状态,默认 active,可选 all' },
|
||||
limit: { type: 'number', description: '返回数量,默认 20,可选' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'scheduled_task_cancel',
|
||||
description: '取消当前用户的一个定时自动任务。需提供 taskId 或 titleMatch。',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
taskId: { type: 'string', description: '任务 ID,优先使用' },
|
||||
titleMatch: { type: 'string', description: '按标题模糊匹配最近一条 active 任务' },
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1155,6 +1215,41 @@ async function callTool(name, args) {
|
||||
});
|
||||
return [{ type: 'text', text: JSON.stringify(items, null, 2) }];
|
||||
}
|
||||
case 'scheduled_task_create': {
|
||||
const timezone = args.timezone ?? process.env.H5_DEFAULT_TIMEZONE ?? 'Asia/Shanghai';
|
||||
const task = await getScheduledTaskService().createTask({
|
||||
userId: PRIVATE_DATA_USER_ID,
|
||||
title: args.title ?? null,
|
||||
taskSpec: args.taskSpec,
|
||||
recurrence: args.recurrence ?? 'daily',
|
||||
runAtLocal: args.runAtLocal ?? null,
|
||||
hour: args.hour ?? null,
|
||||
minute: args.minute ?? 0,
|
||||
weekday: args.weekday ?? null,
|
||||
timezone,
|
||||
notifyChannel: args.notifyChannel ?? 'both',
|
||||
sourceChannel: 'agent',
|
||||
sourceMessageId: args.sourceMessageId ?? null,
|
||||
sourceText: args.sourceText ?? null,
|
||||
});
|
||||
return [{ type: 'text', text: JSON.stringify(task, null, 2) }];
|
||||
}
|
||||
case 'scheduled_task_list': {
|
||||
const tasks = await getScheduledTaskService().listTasks({
|
||||
userId: PRIVATE_DATA_USER_ID,
|
||||
status: args.status ?? 'active',
|
||||
limit: args.limit ?? 20,
|
||||
});
|
||||
return [{ type: 'text', text: JSON.stringify(tasks, null, 2) }];
|
||||
}
|
||||
case 'scheduled_task_cancel': {
|
||||
const task = await getScheduledTaskService().cancelTask({
|
||||
userId: PRIVATE_DATA_USER_ID,
|
||||
taskId: args.taskId ?? null,
|
||||
titleMatch: args.titleMatch ?? null,
|
||||
});
|
||||
return [{ type: 'text', text: JSON.stringify(task, null, 2) }];
|
||||
}
|
||||
default:
|
||||
throw new Error(`未知工具:${name}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user