9dfafb99ca
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>
109 lines
3.6 KiB
JavaScript
109 lines
3.6 KiB
JavaScript
#!/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: '设置每天18:00提醒打卡',
|
||
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);
|