feat(wechat): add Intent Transaction Layer with unified task schema
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>
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
#!/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);
|
||||
});
|
||||
Reference in New Issue
Block a user