Files
memind/schedule-intent.test.mjs
T
john 9dfafb99ca
Memind CI / Test, build, and release guards (push) Successful in 3m43s
fix(schedule): harden reminder utterance routing for WeChat ITL
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>
2026-08-24 18:02:19 +08:00

258 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict';
import test from 'node:test';
import { parseScheduleIntent, shouldUseScheduleAssistant } from './schedule-intent.mjs';
import { classifyUserIntent } from './intent-classifier.mjs';
import { nextDailyRunAt } from './schedule-time.mjs';
test('daily news automation does not route to schedule assistant', () => {
assert.equal(shouldUseScheduleAssistant('每天6点帮我做今日新闻页面'), false);
});
test('parses daily 7am todo digest request', () => {
const intent = parseScheduleIntent('每天早上 7 点给我发一天的待办记录');
assert.equal(intent.action, 'create_daily_todo_digest');
assert.equal(intent.hour, 7);
assert.equal(intent.minute, 0);
});
test('parses daily digest request for today todos in direct wording', () => {
const intent = parseScheduleIntent('每天早上7点把当天待办发给我');
assert.equal(intent.action, 'create_daily_todo_digest');
assert.equal(intent.hour, 7);
assert.equal(intent.minute, 0);
});
test('parses daily digest request with concise today wording', () => {
const intent = parseScheduleIntent('每天7点发我今天待办');
assert.equal(intent.action, 'create_daily_todo_digest');
assert.equal(intent.hour, 7);
assert.equal(intent.minute, 0);
});
test('parses daily digest request with chinese numeral time', () => {
const intent = parseScheduleIntent('以后每天早上七点把当天任务发送给我');
assert.equal(intent.action, 'create_daily_todo_digest');
assert.equal(intent.hour, 7);
assert.equal(intent.minute, 0);
});
test('parses daily digest request with half hour', () => {
const intent = parseScheduleIntent('每天早上7点半把今天待办推送给我');
assert.equal(intent.action, 'create_daily_todo_digest');
assert.equal(intent.hour, 7);
assert.equal(intent.minute, 30);
});
test('daily todo digest asks for time when missing', () => {
const intent = parseScheduleIntent('每天给我发一天的待办记录');
assert.equal(intent.action, 'create_daily_todo_digest');
assert.deepEqual(intent.needsClarification, ['digest_time']);
});
test('daily digest asks for time when only says send today todos', () => {
const intent = parseScheduleIntent('每天把当天待办发给我');
assert.equal(intent.action, 'create_daily_todo_digest');
assert.deepEqual(intent.needsClarification, ['digest_time']);
});
test('parses balance low reminder request', () => {
const intent = parseScheduleIntent('余额低于 20 元提醒我');
assert.equal(intent.action, 'create_balance_alert');
assert.equal(intent.thresholdCents, 2000);
});
test('balance reminder asks for threshold when missing', () => {
const intent = parseScheduleIntent('余额不足提醒我');
assert.equal(intent.action, 'create_balance_alert');
assert.deepEqual(intent.needsClarification, ['threshold']);
});
test('parse bare set reminder command as slot fill intent', () => {
const intent = parseScheduleIntent('设置提醒');
assert.equal(intent.action, 'create_timed_reminder');
assert.deepEqual(intent.needsClarification, ['reminder_time', 'reminder_title']);
});
test('parse bare set reminder with asr prefix', () => {
const intent = parseScheduleIntent('嗯设置提醒');
assert.equal(intent.action, 'create_timed_reminder');
});
test('parses plain todo record request with quoted title', () => {
const intent = parseScheduleIntent('帮我记一下「🍽️ 跟段吃饭」');
assert.equal(intent.action, 'create_todo');
assert.equal(intent.title, '🍽️ 跟段吃饭');
});
test('plain todo record request asks for title when missing', () => {
const intent = parseScheduleIntent('帮我记一下');
assert.equal(intent.action, 'create_todo');
assert.deepEqual(intent.needsClarification, ['todo_title']);
});
test('parses setting a todo with common colloquial wording', () => {
const intent = parseScheduleIntent('帮我设置一个代办一会儿跟段段吃饭');
assert.equal(intent.action, 'create_todo');
assert.equal(intent.title, '一会儿跟段段吃饭');
});
test('parses typo variant for todo wording', () => {
const intent = parseScheduleIntent('帮我设置一个带办明天交周报');
assert.equal(intent.action, 'schedule_agent');
});
test('schedule assistant catches reminder-style requests', () => {
assert.equal(shouldUseScheduleAssistant('明天早上六点去跑步,五点半提醒我'), true);
assert.equal(shouldUseScheduleAssistant('帮我做个页面'), false);
});
test('schedule assistant catches production ASR typo for todo', () => {
const intent = parseScheduleIntent('明天早上六点去跑步帮我设下一个代拜');
assert.equal(intent.action, 'schedule_agent');
assert.equal(shouldUseScheduleAssistant('明天早上六点去跑步帮我设下一个代拜'), true);
});
test('reminder todo request routes to schedule assistant instead of plain todo shortcut', () => {
const intent = parseScheduleIntent('帮我设置一个代办明天早上六点去跑步记得在五点半的时候提醒我');
assert.equal(intent.action, 'schedule_agent');
});
test('specific dated reminder still routes to schedule assistant', () => {
const intent = parseScheduleIntent('明天下午三点提醒我开会');
assert.equal(intent.action, 'create_timed_reminder');
assert.equal(intent.title, '开会');
assert.equal(intent.hour, 15);
assert.equal(shouldUseScheduleAssistant('明天下午三点提醒我开会'), true);
});
test('parses ASR reminder text with spaced fullwidth colon before minutes', () => {
const now = Date.UTC(2026, 7, 24, 9, 7, 0); // 2026-08-24 17:07 Asia/Shanghai
const intent = parseScheduleIntent('设置一个提醒,今天下午 17 :08要开会', {
timezone: 'Asia/Shanghai',
now,
});
assert.equal(intent.action, 'create_timed_reminder');
assert.equal(intent.title, '要开会');
assert.equal(intent.hour, 17);
assert.equal(intent.minute, 8);
assert.equal(intent.remindLocal, '2026-08-24 17:08');
});
test('ASR spaced colon routes to ITL timed reminder not agent', () => {
const result = classifyUserIntent('设置一个提醒,今天下午 17 :08要开会');
assert.equal(result.kind, 'timed_reminder');
assert.equal(result.layer, 'L1');
});
test('parses daily reminder with time between set and remind words', () => {
const now = Date.UTC(2026, 7, 24, 9, 0, 0); // 2026-08-24 17:00 Asia/Shanghai
const intent = parseScheduleIntent('设置每天1800提醒打卡', {
timezone: 'Asia/Shanghai',
now,
});
assert.equal(intent.action, 'create_timed_reminder');
assert.equal(intent.title, '打卡');
assert.equal(intent.recurrence, 'daily');
assert.equal(intent.hour, 18);
assert.equal(intent.minute, 0);
});
test('daily set-remind phrase routes to ITL timed reminder', () => {
const result = classifyUserIntent('设置每天1800提醒打卡');
assert.equal(result.kind, 'timed_reminder');
assert.equal(result.detail.recurrence, 'daily');
});
test('parses direct reminder setup phrase from wechat', () => {
const now = Date.UTC(2026, 7, 24, 4, 0, 0); // 2026-08-24 12:00 Asia/Shanghai
const intent = parseScheduleIntent('帮我设置提醒,下午 14:30 分开会,项目计划例会', {
timezone: 'Asia/Shanghai',
now,
});
assert.equal(intent.action, 'create_timed_reminder');
assert.equal(intent.title, '项目计划例会');
assert.equal(intent.remindLocal, '2026-08-24 14:30');
});
test('parses set-a-reminder colloquial phrase with numeric time', () => {
const now = Date.UTC(2026, 7, 24, 6, 0, 0); // 2026-08-24 14:00 Asia/Shanghai
const intent = parseScheduleIntent('设置一个提醒,今天下午 16:25 分要出门', {
timezone: 'Asia/Shanghai',
now,
});
assert.equal(intent.action, 'create_timed_reminder');
assert.equal(intent.title, '要出门');
assert.equal(intent.hour, 16);
assert.equal(intent.minute, 25);
assert.equal(intent.remindLocal, '2026-08-24 16:25');
});
test('set-a-reminder colloquial phrase routes to ITL timed reminder not agent', () => {
const result = classifyUserIntent('设置一个提醒,今天下午 16:25 分要出门');
assert.equal(result.kind, 'timed_reminder');
assert.equal(result.layer, 'L1');
});
test('simple reminder today rolls to tomorrow when time already passed', () => {
const now = Date.UTC(2026, 7, 24, 7, 0, 0); // 2026-08-24 15:00 Asia/Shanghai
const intent = parseScheduleIntent('今天10点提醒我吃药', {
timezone: 'Asia/Shanghai',
now,
});
assert.equal(intent.action, 'create_timed_reminder');
assert.equal(intent.remindLocal, '2026-08-25 10:00');
});
test('multi-time reminder still falls through to agent prompt', () => {
const intent = parseScheduleIntent('明天早上六点去跑步,五点半提醒我');
assert.equal(intent.action, 'none');
assert.equal(shouldUseScheduleAssistant('明天早上六点去跑步,五点半提醒我'), true);
});
test('simple schedule query still works', () => {
const intent = parseScheduleIntent('看看我的待办');
assert.equal(intent.action, 'query_schedule');
});
test('nextDailyRunAt rolls to tomorrow when today time has passed', () => {
const now = Date.UTC(2026, 5, 17, 23, 30, 0); // 2026-06-18 07:30 Asia/Shanghai
const next = nextDailyRunAt({ hour: 7, minute: 0, timezone: 'Asia/Shanghai', now });
assert.equal(next, Date.UTC(2026, 5, 18, 23, 0, 0)); // 2026-06-19 07:00 Asia/Shanghai
});
test('parses colloquial add-reminder phrases', () => {
const now = Date.UTC(2026, 7, 24, 9, 0, 0);
const cases = [
['加个提醒明天9点开会', { title: '开会', hour: 9 }],
['添加提醒后天10点体检', { title: '体检', hour: 10 }],
['创建提醒明早6点跑步', { title: '跑步', hour: 6 }],
['备忘提醒下午3点交报告', { title: '交报告', hour: 15 }],
];
for (const [text, expected] of cases) {
const intent = parseScheduleIntent(text, { timezone: 'Asia/Shanghai', now });
assert.equal(intent.action, 'create_timed_reminder', text);
assert.equal(intent.title, expected.title, text);
assert.equal(intent.hour, expected.hour, text);
}
});
test('colloquial add-reminder phrases route to ITL timed reminder', () => {
for (const text of [
'加个提醒明天9点开会',
'添加提醒后天10点体检',
'备忘提醒下午3点交报告',
]) {
const result = classifyUserIntent(text);
assert.equal(result.kind, 'timed_reminder', text);
assert.equal(result.layer, 'L1', text);
}
});
test('time-only reminder without title asks for clarification', () => {
const now = Date.UTC(2026, 7, 24, 9, 0, 0);
const intent = parseScheduleIntent('安排一个提醒今晚8点', { timezone: 'Asia/Shanghai', now });
assert.equal(intent.action, 'create_timed_reminder');
assert.deepEqual(intent.needsClarification, ['reminder_title']);
assert.equal(intent.hour, 20);
});