fix(wechat): cancel sole active scheduled task in inbound without Goose
When a user has exactly one active automation task, cancel it by taskId after listTasks instead of relying on Goose when the LLM provider fails. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -332,6 +332,11 @@ export function formatScheduledTaskListReply(tasks = []) {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function formatScheduledTaskCancelReply(task) {
|
||||
const title = String(task?.title ?? '定时任务').trim();
|
||||
return `已取消定时任务:${title}`;
|
||||
}
|
||||
|
||||
export {
|
||||
extractScheduledTaskSpec,
|
||||
isMetaScheduledTaskSpec,
|
||||
|
||||
@@ -48,6 +48,7 @@ description: 处理定时自动任务(Scheduled Automation):澄清执行
|
||||
1. 先判断用户是要**创建**、**查询**、**取消**还是**更新规范**。
|
||||
2. 缺时间或缺任务内容时,一次只追问最小必要信息。
|
||||
3. **取消 / 更新**前必须先 `scheduled_task_list`,用返回的 `taskId` 操作;由你理解用户指哪一条,不要猜 titleMatch。
|
||||
- 微信 inbound:若用户只有 1 个 active 任务,会直接取消;多个 active 任务时才进入 Goose 让你选 taskId。
|
||||
4. **更新规范**:用户补充格式标准、参考页面 URL、排版约束时,对目标任务调用 `scheduled_task_update_spec`,`mergeTaskSpec: true` 追加说明。
|
||||
5. 创建成功后,用用户能理解的话说明何时执行、执行什么、recurrence、推送通道。
|
||||
6. 若提示里给出了 `sourceMessageId`,调用 `scheduled_task_create` 时必须原样传入。
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
formatScheduledTaskCancelReply,
|
||||
formatScheduledTaskListReply,
|
||||
isScheduledTaskIntent,
|
||||
parseScheduledTaskIntent,
|
||||
@@ -29,10 +30,26 @@ export async function handleWechatScheduledTaskIntent({
|
||||
return formatScheduledTaskListReply(tasks);
|
||||
}
|
||||
|
||||
if (
|
||||
taskIntent.action === 'create_scheduled_task'
|
||||
|| taskIntent.action === 'cancel_scheduled_task'
|
||||
) {
|
||||
if (taskIntent.action === 'create_scheduled_task') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (taskIntent.action === 'cancel_scheduled_task') {
|
||||
const tasks = await scheduledTaskService.listTasks({
|
||||
userId: user.userId,
|
||||
status: 'active',
|
||||
limit: 20,
|
||||
});
|
||||
if (!tasks.length) {
|
||||
return '你当前没有进行中的定时自动任务。';
|
||||
}
|
||||
if (tasks.length === 1) {
|
||||
const cancelled = await scheduledTaskService.cancelTask({
|
||||
userId: user.userId,
|
||||
taskId: tasks[0].id,
|
||||
});
|
||||
return formatScheduledTaskCancelReply(cancelled);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -36,7 +36,8 @@ test('handleWechatScheduledTaskIntent passes incomplete create requests to goose
|
||||
assert.equal(reply, null);
|
||||
});
|
||||
|
||||
test('handleWechatScheduledTaskIntent passes cancel requests to goose', async () => {
|
||||
test('handleWechatScheduledTaskIntent cancels the only active task in inbound', async () => {
|
||||
const calls = [];
|
||||
const reply = await handleWechatScheduledTaskIntent({
|
||||
intent: {
|
||||
msgType: 'text',
|
||||
@@ -45,8 +46,47 @@ test('handleWechatScheduledTaskIntent passes cancel requests to goose', async ()
|
||||
},
|
||||
user: { userId: 'user-123' },
|
||||
scheduledTaskService: {
|
||||
async listTasks() {
|
||||
return [{
|
||||
id: 'task-1',
|
||||
title: '每天早上6:30推送苏州市天气预报',
|
||||
recurrence: 'daily',
|
||||
hour: 6,
|
||||
minute: 30,
|
||||
timezone: 'Asia/Shanghai',
|
||||
}];
|
||||
},
|
||||
async cancelTask(payload) {
|
||||
calls.push(payload);
|
||||
return {
|
||||
id: 'task-1',
|
||||
title: '每天早上6:30推送苏州市天气预报',
|
||||
status: 'cancelled',
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.match(reply, /已取消定时任务/u);
|
||||
assert.deepEqual(calls, [{ userId: 'user-123', taskId: 'task-1' }]);
|
||||
});
|
||||
|
||||
test('handleWechatScheduledTaskIntent passes cancel to goose when multiple active tasks', async () => {
|
||||
const reply = await handleWechatScheduledTaskIntent({
|
||||
intent: {
|
||||
msgType: 'text',
|
||||
agentText: '取消定时推送苏州天气任务',
|
||||
msgId: 'msg-123-2',
|
||||
},
|
||||
user: { userId: 'user-123' },
|
||||
scheduledTaskService: {
|
||||
async listTasks() {
|
||||
return [
|
||||
{ id: 'task-1', title: '苏州天气', recurrence: 'daily', hour: 6, minute: 30, timezone: 'Asia/Shanghai' },
|
||||
{ id: 'task-2', title: '新闻早报', recurrence: 'daily', hour: 5, minute: 30, timezone: 'Asia/Shanghai' },
|
||||
];
|
||||
},
|
||||
async cancelTask() {
|
||||
throw new Error('should not cancel from inbound regex');
|
||||
throw new Error('should not cancel from inbound when ambiguous');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user