Files
memind/wechat-scheduled-task-manage-llm.mjs
T
john 5de58af9ed fix(wechat): resolve scheduled task cancel via manage LLM instead of hard rules
Use a lightweight LLM over active task lists to pick taskId for cancel,
exclude page.generate from scheduled-task inbound, and remove single-task
auto-cancel so Cursor page generation stays isolated.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-29 13:45:28 +08:00

110 lines
3.5 KiB
JavaScript

function parseJsonReply(reply) {
const text = String(reply ?? '').trim();
if (!text) return null;
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
const candidate = fenced?.[1] ?? text;
try {
return JSON.parse(candidate);
} catch {
return null;
}
}
export function buildScheduledTaskListPayload(tasks = []) {
return tasks.map((task) => ({
taskId: task.id,
title: task.title,
recurrence: task.recurrence,
hour: task.hour,
minute: task.minute ?? 0,
taskSpec: String(task.taskSpec ?? '').slice(0, 240),
}));
}
export function normalizeScheduledTaskManageLlmDecision(payload, tasks = []) {
const action = String(payload?.action ?? 'none').trim();
const taskIds = new Set(tasks.map((task) => String(task.id ?? '').trim()).filter(Boolean));
if (action === 'clarify') {
const message = String(payload?.message ?? '').trim()
|| '我找到多个定时任务,请告诉我要取消哪一个。';
return { action: 'clarify', message };
}
if (action === 'cancel') {
const taskId = String(payload?.taskId ?? '').trim();
if (taskId && taskIds.has(taskId)) {
return { action: 'cancel', taskId };
}
return { action: 'none' };
}
return { action: 'none' };
}
export async function resolveScheduledTaskCancelWithLlm({
text,
tasks = [],
llmProviderService,
modelProviderKeyId = null,
model = null,
logger = console,
} = {}) {
if (!llmProviderService || typeof llmProviderService.createChatCompletion !== 'function') {
return { action: 'none' };
}
if (!Array.isArray(tasks) || tasks.length === 0) {
return { action: 'none' };
}
const taskList = buildScheduledTaskListPayload(tasks);
try {
const result = await llmProviderService.createChatCompletion({
...(modelProviderKeyId ? { providerKeyId: modelProviderKeyId } : {}),
...(model ? { model } : {}),
temperature: 0,
messages: [
{
role: 'system',
content: [
'你是微信服务号定时自动任务管理助手,只做任务选择,不做页面生成。',
'输入包含用户原话和当前 active 定时任务列表(JSON)。',
'请严格输出 JSON,不要输出解释。',
'允许 action:',
'- cancel:用户明确要取消某个任务,且能从列表中唯一确定 taskId',
'- clarify:用户想取消但无法唯一确定是哪一个,message 用中文简短追问',
'- none:不是取消任务,或信息不足且不应猜测',
'cancel 时必须返回列表里存在的 taskId;禁止编造 taskId。',
'如果只有一条任务且用户明确要取消/停止定时任务,可以返回该 taskId。',
'如果用户在要求生成/修改 HTML 页面,而不是管理已有定时任务,返回 none。',
].join('\n'),
},
{
role: 'user',
content: JSON.stringify({
userMessage: String(text ?? '').trim(),
activeTasks: taskList,
}),
},
],
});
if (!result?.ok) {
logger?.warn?.(
'[wechat-scheduled-task-manage-llm] parse skipped:',
result?.message ?? 'unknown',
);
return { action: 'none' };
}
return normalizeScheduledTaskManageLlmDecision(parseJsonReply(result.reply), tasks);
} catch (err) {
logger?.warn?.(
'[wechat-scheduled-task-manage-llm] parse skipped:',
err instanceof Error ? err.message : err,
);
return { action: 'none' };
}
}