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>
258 lines
11 KiB
JavaScript
258 lines
11 KiB
JavaScript
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('设置每天18:00提醒打卡', {
|
||
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('设置每天18:00提醒打卡');
|
||
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);
|
||
});
|