fix(schedule): harden reminder utterance routing for WeChat ITL
Memind CI / Test, build, and release guards (push) Successful in 3m43s

Expand ASR normalization and colloquial reminder parsing so simple timed reminders route to L1 confirm cards instead of the agent, and disable schedule-guard false failures when ITL is enabled.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-24 18:02:19 +08:00
parent 2319c9c808
commit 9dfafb99ca
10 changed files with 319 additions and 26 deletions
@@ -0,0 +1,108 @@
#!/usr/bin/env node
/**
* Verify WeChat reminder utterances route to ITL timed_reminder (L1), not agent_schedule.
*/
import assert from 'node:assert/strict';
import { classifyUserIntent } from '../intent-classifier.mjs';
import { parseScheduleIntent, tryResolveTimedReminderIntent } from '../schedule-intent.mjs';
import { hasReminderSetupCue } from '../schedule-utterance-normalize.mjs';
const timezone = 'Asia/Shanghai';
const now = Date.UTC(2026, 7, 24, 9, 0, 0); // 2026-08-24 17:00 Asia/Shanghai
const matrix = [
{
text: '设置一个提醒,今天下午 17 :08要开会',
expect: { kind: 'timed_reminder', action: 'create_timed_reminder', title: '要开会', hour: 17, minute: 8 },
},
{
text: '设置每天1800提醒打卡',
expect: { kind: 'timed_reminder', action: 'create_timed_reminder', title: '打卡', recurrence: 'daily', hour: 18 },
},
{
text: '加个提醒明天9点开会',
expect: { kind: 'timed_reminder', action: 'create_timed_reminder', title: '开会', hour: 9 },
},
{
text: '添加提醒后天10点体检',
expect: { kind: 'timed_reminder', action: 'create_timed_reminder', title: '体检', hour: 10 },
},
{
text: '创建提醒明早6点跑步',
expect: { kind: 'timed_reminder', action: 'create_timed_reminder', title: '跑步', hour: 6 },
},
{
text: '备忘提醒下午3点交报告',
expect: { kind: 'timed_reminder', action: 'create_timed_reminder', title: '交报告', hour: 15 },
},
{
text: '安排一个提醒今晚8点',
expect: { kind: 'timed_reminder', action: 'create_timed_reminder', clarify: ['reminder_title'], hour: 20 },
},
{
text: '定个闹钟明天7点起床',
expect: { kind: 'timed_reminder', action: 'create_timed_reminder', title: '起床', hour: 7 },
},
];
let passed = 0;
let failed = 0;
function pass(label) {
passed += 1;
console.log(`${label}`);
}
function fail(label, detail) {
failed += 1;
console.error(`${label}: ${detail}`);
}
for (const row of matrix) {
const compact = String(row.text).replace(/\s+/g, '');
if (!hasReminderSetupCue(compact)) {
fail(row.text, 'missing reminder setup cue');
continue;
}
const parsed = parseScheduleIntent(row.text, { timezone, now })
?? tryResolveTimedReminderIntent(row.text, { timezone, now });
if (parsed?.action !== row.expect.action) {
fail(row.text, `parse action ${parsed?.action} !== ${row.expect.action}`);
continue;
}
if (row.expect.title && parsed.title !== row.expect.title) {
fail(row.text, `title ${parsed.title} !== ${row.expect.title}`);
continue;
}
if (row.expect.hour != null && parsed.hour !== row.expect.hour) {
fail(row.text, `hour ${parsed.hour} !== ${row.expect.hour}`);
continue;
}
if (row.expect.minute != null && parsed.minute !== row.expect.minute) {
fail(row.text, `minute ${parsed.minute} !== ${row.expect.minute}`);
continue;
}
if (row.expect.recurrence && parsed.recurrence !== row.expect.recurrence) {
fail(row.text, `recurrence ${parsed.recurrence} !== ${row.expect.recurrence}`);
continue;
}
if (row.expect.clarify) {
assert.deepEqual(parsed.needsClarification, row.expect.clarify);
}
const classified = classifyUserIntent(row.text, { timezone, now });
if (classified.kind !== row.expect.kind) {
fail(row.text, `classified kind ${classified.kind} !== ${row.expect.kind}`);
continue;
}
if (classified.action === 'agent_schedule') {
fail(row.text, 'must not route to agent_schedule');
continue;
}
pass(row.text);
}
console.log(`\nschedule reminder routing: ${passed} passed, ${failed} failed`);
process.exit(failed > 0 ? 1 : 0);