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>
This commit is contained in:
john
2026-08-29 13:45:28 +08:00
parent d8463829f7
commit 5de58af9ed
11 changed files with 528 additions and 14 deletions
+50 -7
View File
@@ -1,21 +1,30 @@
import { isPageGenerateText } from '../intent/patterns.mjs';
import {
formatScheduledTaskCancelReply,
formatScheduledTaskListReply,
isScheduledTaskIntent,
parseScheduledTaskIntent,
} from '../../scheduled-task-intent.mjs';
import { resolveScheduledTaskCancelWithLlm } from '../../wechat-scheduled-task-manage-llm.mjs';
export async function handleWechatScheduledTaskIntent({
intent,
user,
scheduledTaskService,
wechatScheduledTaskManageLlmConfigService = null,
llmProviderService = null,
env = process.env,
logger = console,
}) {
if (!scheduledTaskService) return null;
const agentText = String(intent?.agentText ?? '').trim();
if (isPageGenerateText(agentText)) {
return null;
}
const timezone = env.H5_DEFAULT_TIMEZONE || 'Asia/Shanghai';
const taskIntent = parseScheduledTaskIntent(intent.agentText, {
const taskIntent = parseScheduledTaskIntent(agentText, {
timezone,
});
if (!isScheduledTaskIntent(taskIntent)) return null;
@@ -43,13 +52,47 @@ export async function handleWechatScheduledTaskIntent({
if (!tasks.length) {
return '你当前没有进行中的定时自动任务。';
}
if (tasks.length === 1) {
const cancelled = await scheduledTaskService.cancelTask({
userId: user.userId,
taskId: tasks[0].id,
});
return formatScheduledTaskCancelReply(cancelled);
let manageLlmEnabled = false;
let modelProviderKeyId = null;
let model = null;
if (wechatScheduledTaskManageLlmConfigService && llmProviderService) {
try {
manageLlmEnabled = await wechatScheduledTaskManageLlmConfigService.isManageLlmEnabled();
if (manageLlmEnabled) {
const config = await wechatScheduledTaskManageLlmConfigService.getConfig();
modelProviderKeyId = config.modelProviderKeyId ?? null;
model = config.model ?? null;
}
} catch (err) {
logger.warn?.(
'[wechat-scheduled-task-manage-llm] config load failed:',
err instanceof Error ? err.message : err,
);
}
}
if (manageLlmEnabled) {
const decision = await resolveScheduledTaskCancelWithLlm({
text: agentText,
tasks,
llmProviderService,
modelProviderKeyId,
model,
logger,
});
if (decision.action === 'cancel' && decision.taskId) {
const cancelled = await scheduledTaskService.cancelTask({
userId: user.userId,
taskId: decision.taskId,
});
return formatScheduledTaskCancelReply(cancelled);
}
if (decision.action === 'clarify') {
return decision.message;
}
}
return null;
}
} catch (err) {
+83 -6
View File
@@ -36,7 +36,24 @@ test('handleWechatScheduledTaskIntent passes incomplete create requests to goose
assert.equal(reply, null);
});
test('handleWechatScheduledTaskIntent cancels the only active task in inbound', async () => {
test('handleWechatScheduledTaskIntent yields page.generate requests to cursor/goose path', async () => {
const reply = await handleWechatScheduledTaskIntent({
intent: {
msgType: 'text',
agentText: '帮我做一个苏州天气展示页面',
msgId: 'msg-page-1',
},
user: { userId: 'user-tang' },
scheduledTaskService: {
async listTasks() {
throw new Error('should not list for page.generate');
},
},
});
assert.equal(reply, null);
});
test('handleWechatScheduledTaskIntent cancels via manage llm when task is identified', async () => {
const calls = [];
const reply = await handleWechatScheduledTaskIntent({
intent: {
@@ -48,7 +65,7 @@ test('handleWechatScheduledTaskIntent cancels the only active task in inbound',
scheduledTaskService: {
async listTasks() {
return [{
id: 'task-1',
id: 'task-suzhou',
title: '每天早上6:30推送苏州市天气预报',
recurrence: 'daily',
hour: 6,
@@ -59,18 +76,34 @@ test('handleWechatScheduledTaskIntent cancels the only active task in inbound',
async cancelTask(payload) {
calls.push(payload);
return {
id: 'task-1',
id: 'task-suzhou',
title: '每天早上6:30推送苏州市天气预报',
status: 'cancelled',
};
},
},
wechatScheduledTaskManageLlmConfigService: {
async isManageLlmEnabled() {
return true;
},
async getConfig() {
return { modelProviderKeyId: 'key-1', model: 'deepseek-chat' };
},
},
llmProviderService: {
async createChatCompletion() {
return {
ok: true,
reply: JSON.stringify({ action: 'cancel', taskId: 'task-suzhou' }),
};
},
},
});
assert.match(reply, /已取消定时任务/u);
assert.deepEqual(calls, [{ userId: 'user-123', taskId: 'task-1' }]);
assert.deepEqual(calls, [{ userId: 'user-123', taskId: 'task-suzhou' }]);
});
test('handleWechatScheduledTaskIntent passes cancel to goose when multiple active tasks', async () => {
test('handleWechatScheduledTaskIntent passes cancel to goose when manage llm is disabled', async () => {
const reply = await handleWechatScheduledTaskIntent({
intent: {
msgType: 'text',
@@ -86,13 +119,57 @@ test('handleWechatScheduledTaskIntent passes cancel to goose when multiple activ
];
},
async cancelTask() {
throw new Error('should not cancel from inbound when ambiguous');
throw new Error('should not cancel from inbound when llm disabled');
},
},
wechatScheduledTaskManageLlmConfigService: {
async isManageLlmEnabled() {
return false;
},
},
});
assert.equal(reply, null);
});
test('handleWechatScheduledTaskIntent returns clarify message when manage llm is ambiguous', async () => {
const reply = await handleWechatScheduledTaskIntent({
intent: {
msgType: 'text',
agentText: '帮我取消定时任务',
msgId: 'msg-123-3',
},
user: { userId: 'user-tang' },
scheduledTaskService: {
async listTasks() {
return [
{ id: 'task-news', title: '每日新闻早报页面(5:30', recurrence: 'daily', hour: 5, minute: 30 },
{ id: 'task-checkin', title: '每日打卡提醒', recurrence: 'daily', hour: 18, minute: 0 },
];
},
},
wechatScheduledTaskManageLlmConfigService: {
async isManageLlmEnabled() {
return true;
},
async getConfig() {
return { modelProviderKeyId: 'key-1', model: 'deepseek-chat' };
},
},
llmProviderService: {
async createChatCompletion() {
return {
ok: true,
reply: JSON.stringify({
action: 'clarify',
message: '你是想取消 5:30 新闻早报,还是 18:00 打卡提醒?',
}),
};
},
},
});
assert.match(reply, /新闻早报/u);
});
test('handleWechatScheduledTaskIntent ignores non automation text', async () => {
const reply = await handleWechatScheduledTaskIntent({
intent: {