fix(schedule): parse ASR times with spaces around colons for ITL routing
Memind CI / Test, build, and release guards (push) Successful in 4m15s

Voice/wechat ASR often emits forms like "17 :08", which bypassed timed_reminder
classification and fell through to agent_schedule + schedule-guard false failures.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-24 17:12:50 +08:00
parent 4ef392e964
commit 1e3a125f83
2 changed files with 34 additions and 3 deletions
+15 -3
View File
@@ -12,6 +12,16 @@ function normalizeText(text) {
.trim();
}
/** ASR/wechat often inserts spaces around colons, e.g. "17 08". */
function normalizeLooseTimeText(text) {
return String(text ?? '')
.replace(
/([0-9]{1,2}|[零一二两三四五六七八九十]{1,3})\s*([:])\s*/gu,
'$1$2',
)
.replace(/([:])\s*([0-9]{1,2})/gu, '$1$2');
}
function pad2(value) {
return String(value).padStart(2, '0');
}
@@ -44,7 +54,8 @@ function chineseHourToNumber(value) {
}
export function parseHourMinute(text) {
const match = text.match(
const normalized = normalizeLooseTimeText(text);
const match = normalized.match(
/(?:今天|明天|后天|今晚|明晚)?(?:早上|上午|清晨|每天(?:早上|上午)?)?(早上|上午|清晨|中午|下午|傍晚|晚上|凌晨)?([0-9]{1,2}|[零一二两三四五六七八九十]{1,3})(?:点|:|)(半|[0-9]{1,2}分?)?/,
);
if (!match) return null;
@@ -66,7 +77,8 @@ export function parseHourMinute(text) {
}
function countTimeExpressions(text) {
const matches = String(text ?? '').match(
const normalized = normalizeLooseTimeText(text);
const matches = normalized.match(
/[0-9零一二两三四五六七八九十]{1,3}(?:点|:|)(?:半|[0-9]{1,2}(?:分(?!开)|(?![0-9]))?)?/gu,
);
return matches?.length ?? 0;
@@ -93,7 +105,7 @@ function extractTimedReminderTitle(text) {
}
function cleanupTimedReminderTitleFragment(text) {
let title = String(text ?? '').trim();
let title = normalizeLooseTimeText(String(text ?? '').trim());
title = title
.replace(/^(?:帮我|请|麻烦)?(?:设置|设|创建|添加)(?:一个|个)?提醒[,、:\s]*/u, '')
.replace(/^(?:帮我|请)?(?:设置|设)(?:一个|个)?(?:待办|代办|带办|代拜)[,、:\s]*/u, '');
+19
View File
@@ -125,6 +125,25 @@ test('specific dated reminder still routes to schedule assistant', () => {
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 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 分开会,项目计划例会', {