fix(schedule): parse ASR times with spaces around colons for ITL routing
Memind CI / Test, build, and release guards (push) Successful in 4m15s
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:
+15
-3
@@ -12,6 +12,16 @@ function normalizeText(text) {
|
|||||||
.trim();
|
.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) {
|
function pad2(value) {
|
||||||
return String(value).padStart(2, '0');
|
return String(value).padStart(2, '0');
|
||||||
}
|
}
|
||||||
@@ -44,7 +54,8 @@ function chineseHourToNumber(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseHourMinute(text) {
|
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}分?)?/,
|
/(?:今天|明天|后天|今晚|明晚)?(?:早上|上午|清晨|每天(?:早上|上午)?)?(早上|上午|清晨|中午|下午|傍晚|晚上|凌晨)?([0-9]{1,2}|[零一二两三四五六七八九十]{1,3})(?:点|:|:)(半|[0-9]{1,2}分?)?/,
|
||||||
);
|
);
|
||||||
if (!match) return null;
|
if (!match) return null;
|
||||||
@@ -66,7 +77,8 @@ export function parseHourMinute(text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function countTimeExpressions(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,
|
/[0-9零一二两三四五六七八九十]{1,3}(?:点|:|:)(?:半|[0-9]{1,2}(?:分(?!开)|(?![0-9]))?)?/gu,
|
||||||
);
|
);
|
||||||
return matches?.length ?? 0;
|
return matches?.length ?? 0;
|
||||||
@@ -93,7 +105,7 @@ function extractTimedReminderTitle(text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cleanupTimedReminderTitleFragment(text) {
|
function cleanupTimedReminderTitleFragment(text) {
|
||||||
let title = String(text ?? '').trim();
|
let title = normalizeLooseTimeText(String(text ?? '').trim());
|
||||||
title = title
|
title = title
|
||||||
.replace(/^(?:帮我|请|麻烦)?(?:设置|设|创建|添加)(?:一个|个)?提醒[,,、::\s]*/u, '')
|
.replace(/^(?:帮我|请|麻烦)?(?:设置|设|创建|添加)(?:一个|个)?提醒[,,、::\s]*/u, '')
|
||||||
.replace(/^(?:帮我|请)?(?:设置|设)(?:一个|个)?(?:待办|代办|带办|代拜)[,,、::\s]*/u, '');
|
.replace(/^(?:帮我|请)?(?:设置|设)(?:一个|个)?(?:待办|代办|带办|代拜)[,,、::\s]*/u, '');
|
||||||
|
|||||||
@@ -125,6 +125,25 @@ test('specific dated reminder still routes to schedule assistant', () => {
|
|||||||
assert.equal(shouldUseScheduleAssistant('明天下午三点提醒我开会'), true);
|
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', () => {
|
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 now = Date.UTC(2026, 7, 24, 4, 0, 0); // 2026-08-24 12:00 Asia/Shanghai
|
||||||
const intent = parseScheduleIntent('帮我设置提醒,下午 14:30 分开会,项目计划例会', {
|
const intent = parseScheduleIntent('帮我设置提醒,下午 14:30 分开会,项目计划例会', {
|
||||||
|
|||||||
Reference in New Issue
Block a user