import crypto from 'node:crypto'; import { buildChatSkillPrompt, SCHEDULED_TASK_AUTOMATION_SKILL_NAME } from './chat-skills.mjs'; import { localDateLabel } from './schedule-time.mjs'; function messageText(message) { if (typeof message?.content === 'string') return message.content.trim(); if (!Array.isArray(message?.content)) return ''; return message.content .filter((item) => item?.type === 'text') .map((item) => String(item?.text ?? '').trim()) .filter(Boolean) .join('\n'); } export function buildScheduledTaskExecutionPrompt(task, { now = Date.now(), timezone = task?.timezone ?? 'Asia/Shanghai', } = {}) { const dateLabel = localDateLabel(now, timezone); const body = [ '【定时任务自动执行 Scheduled Automation】', `任务标题:${task.title}`, `执行日期:${dateLabel}`, `任务要求:${task.taskSpec}`, '执行约束:', '- 这是系统自动触发的定时任务,请直接完成可交付结果,不要反问用户。', '- 若需要生成页面,必须先 load_skill → static-page-publish,再 write_file 到 public/*.html,并给出正式可访问 URL。', '- 若只需摘要/文本,给出完整中文结果摘要。', '- 完成后在回复中明确写出交付结果(链接或摘要)。', ].join('\n'); const prefix = buildChatSkillPrompt( 'scheduled-task-automation', SCHEDULED_TASK_AUTOMATION_SKILL_NAME, ); return { role: 'user', content: [{ type: 'text', text: `${prefix}${body}` }], metadata: { displayText: `定时任务:${task.title}`, userVisible: false, agentVisible: true, memindScheduledTask: { taskId: task.id, recurrence: task.recurrence, automated: true, }, }, }; } export function extractScheduledTaskDeliveryText(messages, task) { const lastAssistant = [...(Array.isArray(messages) ? messages : [])] .reverse() .find((message) => message?.role === 'assistant'); const text = messageText(lastAssistant); if (text) return text.trim(); return `定时任务「${task.title}」已执行完成。`; } export function formatScheduledTaskDeliveryMessage(task, deliveryText) { const header = `定时任务完成:${task.title}`; const body = String(deliveryText ?? '').trim() || '任务已执行,请前往 MindSpace 查看结果。'; return `${header}\n\n${body}`.trim(); } export async function executeScheduledTask(task, { userAuth, tkmindProxy, sessionSnapshotService = null, timeoutMs = 15 * 60 * 1000, logger = console, } = {}) { if (!task?.userId) throw new Error('缺少 task.userId'); if (!userAuth || typeof userAuth.canUseChat !== 'function') { throw new Error('缺少 userAuth.canUseChat'); } if ( !tkmindProxy || typeof tkmindProxy.startSessionForUser !== 'function' || typeof tkmindProxy.submitSessionReplyAndAwaitFinishForUser !== 'function' ) { throw new Error('缺少 tkmindProxy 会话执行能力'); } const gate = await userAuth.canUseChat(task.userId); if (!gate?.ok) { const err = new Error(gate?.message ?? '当前账户无法执行定时任务'); err.code = gate?.code ?? 'CHAT_NOT_ALLOWED'; throw err; } const requestId = crypto.randomUUID(); const started = await tkmindProxy.startSessionForUser(task.userId, { origin: 'h5', }); const sessionId = started?.id ?? started?.sessionId; if (!sessionId) throw new Error('创建定时任务会话失败'); const userMessage = buildScheduledTaskExecutionPrompt(task); logger.info?.('[ScheduledTask] executing', { taskId: task.id, userId: task.userId, sessionId, requestId, }); await tkmindProxy.submitSessionReplyAndAwaitFinishForUser( task.userId, sessionId, requestId, userMessage, { timeoutMs }, ); let messages = []; if (typeof sessionSnapshotService?.refresh === 'function') { const refreshed = await sessionSnapshotService.refresh(sessionId).catch(() => null); messages = refreshed?.messages ?? refreshed?.conversation?.messages ?? []; } else if (typeof sessionSnapshotService?.get === 'function') { const snapshot = await sessionSnapshotService.get(sessionId).catch(() => null); messages = snapshot?.messages ?? snapshot?.conversation?.messages ?? []; } const deliveryText = extractScheduledTaskDeliveryText(messages, task); return { sessionId, requestId, deliveryText, messages, }; }