2319c9c808
Memind CI / Test, build, and release guards (push) Failing after 3m4s
Support colloquial forms like「设置每天18:00提醒打卡」, persist daily metadata on commit, and schedule the next reminder after each delivery. Co-authored-by: Cursor <cursoragent@cursor.com>
227 lines
7.7 KiB
JavaScript
227 lines
7.7 KiB
JavaScript
import { classifyUserIntent, inferActionLevel } from '../../intent-classifier.mjs';
|
|
import { commitIntentDraft } from '../../intent-transaction-commit.mjs';
|
|
import {
|
|
formatDraftCancelledReply,
|
|
formatDraftCommittedReply,
|
|
formatIntentActionCard,
|
|
parseDraftUserReply,
|
|
} from '../../intent-action-card.mjs';
|
|
import { formatQueryGuardReply } from '../../intent-query-guard.mjs';
|
|
import { isIntentTransactionEnabled } from '../../intent-transaction-config.mjs';
|
|
import { buildScheduledTaskCreatePayload } from '../../scheduled-task-intent.mjs';
|
|
import { formatScheduledTaskCreateReply } from '../../scheduled-task-intent.mjs';
|
|
import { isScheduledTaskWorkerEnabled, scheduledTaskWorkerDisabledMessage } from '../../scheduled-task-worker-config.mjs';
|
|
|
|
function buildDraftPayload(classification, { intent, user, timezone }) {
|
|
const { kind, detail } = classification;
|
|
const base = {
|
|
sourceChannel: 'wechat',
|
|
sourceMessageId: intent.msgId ?? null,
|
|
sourceText: intent.agentText,
|
|
timezone,
|
|
};
|
|
|
|
if (kind === 'timed_reminder') {
|
|
return {
|
|
...base,
|
|
title: detail.title,
|
|
remindLocal: detail.remindLocal,
|
|
hour: detail.hour,
|
|
minute: detail.minute,
|
|
recurrence: detail.recurrence ?? 'once',
|
|
};
|
|
}
|
|
if (kind === 'create_todo') {
|
|
return { ...base, title: detail.title };
|
|
}
|
|
if (kind === 'create_daily_todo_digest') {
|
|
return { ...base, hour: detail.hour, minute: detail.minute ?? 0 };
|
|
}
|
|
if (kind === 'create_balance_alert') {
|
|
return { ...base, thresholdCents: detail.thresholdCents };
|
|
}
|
|
if (kind === 'scheduled_task') {
|
|
return {
|
|
...base,
|
|
intentDetail: detail,
|
|
createPayload: buildScheduledTaskCreatePayload(detail, {
|
|
userId: user.userId,
|
|
sourceChannel: 'wechat',
|
|
sourceMessageId: intent.msgId ?? null,
|
|
sourceText: intent.agentText,
|
|
timezone,
|
|
}),
|
|
};
|
|
}
|
|
return base;
|
|
}
|
|
|
|
function buildCard(classification, draftPayload, actionLevel, text) {
|
|
const { layer, kind, detail, clarify } = classification;
|
|
if (kind === 'clarify' || classification.layer === 'ambiguous') {
|
|
return formatIntentActionCard({ clarify: ['notify_vs_act'] });
|
|
}
|
|
if (clarify?.length) {
|
|
return formatIntentActionCard({ clarify });
|
|
}
|
|
|
|
const title = detail?.title ?? detail?.taskSpec?.slice?.(0, 80) ?? draftPayload.title ?? '待确认任务';
|
|
const trigger = detail?.remindLocal
|
|
? { at: detail.remindLocal }
|
|
: { hour: detail?.hour, minute: detail?.minute ?? 0 };
|
|
const frequency = /(?:每天|每日)/u.test(text)
|
|
? '每天自动执行'
|
|
: detail?.recurrence === 'weekly'
|
|
? '每周自动执行'
|
|
: detail?.recurrence === 'daily'
|
|
? '每天自动执行'
|
|
: null;
|
|
|
|
const actions = kind === 'scheduled_task'
|
|
? ['到点自动执行任务', '推送执行结果到微信']
|
|
: kind === 'timed_reminder'
|
|
? ['到点发送微信提醒']
|
|
: kind === 'create_daily_todo_digest'
|
|
? ['到点推送当天待办摘要']
|
|
: kind === 'create_balance_alert'
|
|
? ['余额低于阈值时发送微信提醒']
|
|
: ['记录到待办列表'];
|
|
|
|
return formatIntentActionCard({
|
|
title,
|
|
layer,
|
|
actionLevel,
|
|
trigger,
|
|
actions,
|
|
frequency,
|
|
});
|
|
}
|
|
|
|
export async function handleWechatIntentTransaction({
|
|
intent,
|
|
user,
|
|
intentDraftService,
|
|
taskUnifiedService = null,
|
|
scheduleService,
|
|
scheduledTaskService,
|
|
env = process.env,
|
|
logger = console,
|
|
}) {
|
|
if (!isIntentTransactionEnabled(env) || !intentDraftService) return null;
|
|
const timezone = env.H5_DEFAULT_TIMEZONE || 'Asia/Shanghai';
|
|
const text = intent.agentText;
|
|
|
|
try {
|
|
const pending = await intentDraftService.getPendingDraft(user.userId);
|
|
const replyKind = parseDraftUserReply(text);
|
|
if (pending && replyKind) {
|
|
if (replyKind === 'cancel') {
|
|
await intentDraftService.cancelDraft(pending.id, user.userId);
|
|
return formatDraftCancelledReply();
|
|
}
|
|
if (replyKind === 'modify') {
|
|
await intentDraftService.cancelDraft(pending.id, user.userId, { reason: 'user_modify' });
|
|
return '好的,请重新发送完整的安排,例如「下午 2 点半提醒我开项目计划例会」。';
|
|
}
|
|
if (replyKind === 'confirm') {
|
|
if (pending.layer === 'ambiguous' || pending.draftType === 'clarify') {
|
|
return '我还需要确认:你是要「到点提醒」还是「到点自动执行并交付结果」?请补充后再发「确认」。';
|
|
}
|
|
const committed = await commitIntentDraft({
|
|
draft: pending,
|
|
userId: user.userId,
|
|
scheduleService,
|
|
scheduledTaskService,
|
|
taskUnifiedService,
|
|
timezone,
|
|
env,
|
|
});
|
|
await intentDraftService.markDraftCommitted(pending.id, user.userId, committed);
|
|
if (committed.kind === 'scheduled_task') {
|
|
const workerWarning = isScheduledTaskWorkerEnabled(env)
|
|
? null
|
|
: scheduledTaskWorkerDisabledMessage(env);
|
|
let reply = formatScheduledTaskCreateReply(committed.task, { workerWarning });
|
|
reply = `${reply}\n\n(已通过确认卡片写入)`;
|
|
return reply;
|
|
}
|
|
return formatDraftCommittedReply({ title: pending.title, kind: pending.draftType });
|
|
}
|
|
}
|
|
|
|
const classification = classifyUserIntent(text, { timezone });
|
|
const { layer, kind, action } = classification;
|
|
|
|
if (layer === 'L0') {
|
|
return formatQueryGuardReply(text, {
|
|
scheduleService,
|
|
scheduledTaskService,
|
|
taskUnifiedService,
|
|
userId: user.userId,
|
|
timezone,
|
|
env,
|
|
});
|
|
}
|
|
|
|
if (layer === 'L3' || layer === null || kind === 'agent_schedule' || kind === 'agent_automation') {
|
|
return null;
|
|
}
|
|
|
|
if (kind === 'manage') {
|
|
return null;
|
|
}
|
|
|
|
if (layer === 'ambiguous' || kind === 'clarify') {
|
|
if (classification.clarify?.length && kind !== 'clarify') {
|
|
return buildCard(classification, {}, inferActionLevel(classification, text), text);
|
|
}
|
|
await intentDraftService.createDraft({
|
|
userId: user.userId,
|
|
layer: 'ambiguous',
|
|
draftType: 'clarify',
|
|
actionLevel: 2,
|
|
title: '待确认意图',
|
|
payload: { clarify: ['notify_vs_act'], sourceText: text },
|
|
cardText: buildCard({ layer: 'ambiguous', kind: 'clarify', clarify: ['notify_vs_act'] }, {}, 2, text),
|
|
sourceMessageId: intent.msgId ?? null,
|
|
sourceText: text,
|
|
});
|
|
return buildCard({ layer: 'ambiguous', kind: 'clarify', clarify: ['notify_vs_act'] }, {}, 2, text);
|
|
}
|
|
|
|
const draftableKinds = new Set([
|
|
'timed_reminder',
|
|
'create_todo',
|
|
'create_daily_todo_digest',
|
|
'create_balance_alert',
|
|
'scheduled_task',
|
|
]);
|
|
if (!draftableKinds.has(kind)) return null;
|
|
if (classification.clarify?.length) {
|
|
return buildCard(classification, {}, inferActionLevel(classification, text), text);
|
|
}
|
|
|
|
const actionLevel = inferActionLevel(classification, text);
|
|
const payload = buildDraftPayload(classification, { intent, user, timezone });
|
|
const cardText = buildCard(classification, payload, actionLevel, text);
|
|
await intentDraftService.createDraft({
|
|
userId: user.userId,
|
|
layer,
|
|
draftType: kind,
|
|
actionLevel,
|
|
title: payload.title ?? payload.createPayload?.title ?? classification.detail?.title ?? '待确认任务',
|
|
payload,
|
|
cardText,
|
|
sourceMessageId: intent.msgId ?? null,
|
|
sourceText: text,
|
|
});
|
|
return cardText;
|
|
} catch (err) {
|
|
logger.warn?.(
|
|
'[wechat-intent-transaction] failed:',
|
|
err instanceof Error ? err.message : err,
|
|
);
|
|
return `设置预览失败:${err instanceof Error ? err.message : String(err)}`;
|
|
}
|
|
}
|