d58dc2a251
Introduce Draft → Confirm → Commit flow for WeChat schedule intents behind feature flags, plus h5_tasks dual-write/read aggregation and rollout scripts so reminders and automations get explicit user confirmation before persisting. Co-authored-by: Cursor <cursoragent@cursor.com>
189 lines
5.7 KiB
JavaScript
189 lines
5.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* 唐用户典型微信话术 ITL 全链路离线模拟(无需 DB)
|
|
*/
|
|
import { classifyUserIntent } from '../intent-classifier.mjs';
|
|
import { handleWechatIntentTransaction } from '../wechat/handlers/intent-transaction.mjs';
|
|
import { handleWechatScheduledTaskIntent } from '../wechat/handlers/scheduled-task.mjs';
|
|
import { formatUnifiedTaskListReply } from '../task-unified-service.mjs';
|
|
import { formatQueryGuardReply } from '../intent-query-guard.mjs';
|
|
|
|
const TANG = { userId: 'a70ff537-8908-486e-9b6c-042e07cc25db' };
|
|
const env = {
|
|
H5_INTENT_TRANSACTION_ENABLED: '1',
|
|
H5_UNIFIED_TASKS_ENABLED: '1',
|
|
H5_DEFAULT_TIMEZONE: 'Asia/Shanghai',
|
|
};
|
|
|
|
const MOCK_TASKS = [
|
|
{
|
|
type: 'automation',
|
|
title: '每日新闻页',
|
|
trigger: { repeat: 'daily', hour: 5, minute: 30 },
|
|
nextRunAt: Date.now() + 3600000,
|
|
},
|
|
{
|
|
type: 'automation',
|
|
title: '每日天气预报',
|
|
trigger: { repeat: 'daily', hour: 8, minute: 0 },
|
|
nextRunAt: Date.now() + 7200000,
|
|
},
|
|
];
|
|
|
|
function createDraftStore() {
|
|
let pending = null;
|
|
return {
|
|
async getPendingDraft(userId) {
|
|
return pending?.userId === userId ? pending : null;
|
|
},
|
|
async createDraft(payload) {
|
|
pending = { id: 'draft-sim', status: 'draft', ...payload };
|
|
return pending;
|
|
},
|
|
async cancelDraft() { pending = null; return { status: 'cancelled' }; },
|
|
async markDraftCommitted(_id, _userId, committedRef) {
|
|
pending = { ...pending, status: 'committed', committedRef };
|
|
return pending;
|
|
},
|
|
};
|
|
}
|
|
|
|
function createServices() {
|
|
const syncCalls = [];
|
|
const scheduleCalls = [];
|
|
return {
|
|
syncCalls,
|
|
scheduleCalls,
|
|
taskUnifiedService: {
|
|
async listUserTasks() { return MOCK_TASKS; },
|
|
async syncFromCommit(payload) { syncCalls.push(payload.kind); return { id: 'u-1' }; },
|
|
},
|
|
scheduleService: {
|
|
async createItem(payload) {
|
|
scheduleCalls.push(['createItem', payload.title]);
|
|
return { id: 'item-1', ...payload };
|
|
},
|
|
async createReminder(payload) {
|
|
scheduleCalls.push(['createReminder']);
|
|
return { id: 'rem-1', itemId: payload.itemId, remindAt: payload.remindAt, channel: 'wechat' };
|
|
},
|
|
buildTodoDigestText: async () => '今天有 1 条待办:跟进合同。',
|
|
},
|
|
scheduledTaskService: {
|
|
async listTasks() {
|
|
return MOCK_TASKS.map((task, index) => ({
|
|
id: `task-${index}`,
|
|
title: task.title,
|
|
recurrence: 'daily',
|
|
hour: task.trigger.hour,
|
|
minute: task.trigger.minute,
|
|
nextRunAt: task.nextRunAt,
|
|
timezone: 'Asia/Shanghai',
|
|
}));
|
|
},
|
|
async cancelTask() {
|
|
return { id: 'task-news', title: '每日新闻页', status: 'cancelled' };
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
async function simulate(text) {
|
|
const classification = classifyUserIntent(text);
|
|
const services = createServices();
|
|
const drafts = createDraftStore();
|
|
|
|
let handler = 'none';
|
|
let reply = null;
|
|
|
|
const itl = await handleWechatIntentTransaction({
|
|
intent: { agentText: text, msgId: `sim-${text.slice(0, 8)}`, msgType: 'text' },
|
|
user: TANG,
|
|
intentDraftService: drafts,
|
|
taskUnifiedService: services.taskUnifiedService,
|
|
scheduleService: services.scheduleService,
|
|
scheduledTaskService: services.scheduledTaskService,
|
|
env,
|
|
});
|
|
if (itl) {
|
|
handler = 'itl';
|
|
reply = itl;
|
|
} else {
|
|
const schedTask = await handleWechatScheduledTaskIntent({
|
|
intent: { agentText: text, msgId: 'sim-st' },
|
|
user: TANG,
|
|
scheduledTaskService: services.scheduledTaskService,
|
|
});
|
|
if (schedTask) {
|
|
handler = 'scheduled_task';
|
|
reply = schedTask;
|
|
}
|
|
}
|
|
|
|
if (classification.layer === 'L0' && !reply) {
|
|
reply = await formatQueryGuardReply(text, {
|
|
scheduleService: services.scheduleService,
|
|
scheduledTaskService: services.scheduledTaskService,
|
|
taskUnifiedService: services.taskUnifiedService,
|
|
userId: TANG.userId,
|
|
timezone: 'Asia/Shanghai',
|
|
});
|
|
handler = 'query_guard';
|
|
}
|
|
|
|
return { text, classification, handler, replyPreview: String(reply ?? '').split('\n').slice(0, 4).join(' / ') };
|
|
}
|
|
|
|
const cases = [
|
|
'有没有我的新闻定时任务',
|
|
'下午2点半提醒我开项目计划例会',
|
|
'确认',
|
|
'取消每日新闻任务',
|
|
'每天5点30帮我做今日新闻页面',
|
|
'设置提醒',
|
|
];
|
|
|
|
async function main() {
|
|
console.log('=== 唐用户 ITL 话术模拟 ===\n');
|
|
const drafts = createDraftStore();
|
|
const services = createServices();
|
|
|
|
for (const text of cases.slice(0, 2)) {
|
|
const row = await simulate(text);
|
|
console.log(JSON.stringify(row, null, 2));
|
|
}
|
|
|
|
await handleWechatIntentTransaction({
|
|
intent: { agentText: '下午2点半提醒我开项目计划例会', msgId: 'sim-card', msgType: 'text' },
|
|
user: TANG,
|
|
intentDraftService: drafts,
|
|
taskUnifiedService: services.taskUnifiedService,
|
|
scheduleService: services.scheduleService,
|
|
scheduledTaskService: services.scheduledTaskService,
|
|
env,
|
|
});
|
|
const confirm = await handleWechatIntentTransaction({
|
|
intent: { agentText: '确认', msgId: 'sim-confirm', msgType: 'text' },
|
|
user: TANG,
|
|
intentDraftService: drafts,
|
|
taskUnifiedService: services.taskUnifiedService,
|
|
scheduleService: services.scheduleService,
|
|
scheduledTaskService: services.scheduledTaskService,
|
|
env,
|
|
});
|
|
console.log(JSON.stringify({
|
|
text: '确认',
|
|
handler: 'itl_confirm',
|
|
replyPreview: String(confirm ?? '').split('\n').slice(0, 3).join(' / '),
|
|
syncKinds: services.syncCalls,
|
|
scheduleCalls: services.scheduleCalls,
|
|
}, null, 2));
|
|
|
|
console.log('\nunified list:', formatUnifiedTaskListReply(MOCK_TASKS).split('\n').join(' | '));
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|