Files
memind/scripts/verify-tang-itl-readiness-103.mjs
T
john d58dc2a251 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>
2026-08-24 15:59:17 +08:00

129 lines
4.6 KiB
JavaScript

#!/usr/bin/env node
/**
* 唐用户 ITL / Unified Tasks 103 就绪检查(只读)
*
* 用法:
* node scripts/verify-tang-itl-readiness-103.mjs
*/
import process from 'node:process';
import mysql from 'mysql2/promise';
import { classifyUserIntent } from '../intent-classifier.mjs';
import { createTaskUnifiedService, formatUnifiedTaskListReply } from '../task-unified-service.mjs';
import { handleWechatIntentTransaction } from '../wechat/handlers/intent-transaction.mjs';
import { loadH5Environment } from './load-env.mjs';
loadH5Environment(import.meta.dirname);
const TANG = 'a70ff537-8908-486e-9b6c-042e07cc25db';
const DUPLICATE_WEATHER_TASK_ID = '66f0ee11-7777-415f-affd-34422c772d0f';
const env = {
H5_INTENT_TRANSACTION_ENABLED: '1',
H5_UNIFIED_TASKS_ENABLED: '1',
H5_DEFAULT_TIMEZONE: 'Asia/Shanghai',
};
let passed = 0;
let failed = 0;
function pass(label, detail = '') {
passed += 1;
console.log(`${label}${detail ? `: ${detail}` : ''}`);
}
function fail(label, detail = '') {
failed += 1;
console.error(`${label}${detail ? `: ${detail}` : ''}`);
}
async function main() {
if (!process.env.DATABASE_URL) {
fail('DATABASE_URL', '未配置');
process.exit(1);
}
const pool = mysql.createPool({ uri: process.env.DATABASE_URL, connectionLimit: 2 });
const service = createTaskUnifiedService(pool);
const [dupRows] = await pool.query(
`SELECT id, status FROM h5_scheduled_tasks WHERE id = ? AND user_id = ? LIMIT 1`,
[DUPLICATE_WEATHER_TASK_ID, TANG],
);
const dup = dupRows?.[0];
if (!dup || dup.status === 'cancelled') pass('duplicate weather task cleaned up');
else fail('duplicate weather task cleaned up', `status=${dup.status}`);
const [activeTasks] = await pool.query(
`SELECT id, title, hour, minute, status FROM h5_scheduled_tasks
WHERE user_id = ? AND status IN ('active', 'locked')
ORDER BY hour, minute`,
[TANG],
);
if (activeTasks.length >= 1 && activeTasks.length <= 4) {
pass('active scheduled task count sane', String(activeTasks.length));
} else {
fail('active scheduled task count sane', String(activeTasks.length));
}
const unified = await service.listUserTasks({ userId: TANG, limit: 20 });
if (unified.length >= activeTasks.length) pass('unified list covers legacy tasks', `${unified.length} unified`);
else fail('unified list covers legacy tasks', `${unified.length} unified vs ${activeTasks.length} legacy`);
const queryText = '有没有我的新闻定时任务';
const classification = classifyUserIntent(queryText);
if (classification.layer === 'L0') pass('inventory query classified as L0');
else fail('inventory query classified as L0', JSON.stringify(classification));
const listReply = formatUnifiedTaskListReply(unified);
if (listReply.includes('任务一览') || listReply.includes('没有')) pass('unified list reply formatted');
else fail('unified list reply formatted', listReply);
const drafts = {
pending: null,
async getPendingDraft() { return this.pending; },
async createDraft(payload) { this.pending = { id: 'draft-tang', status: 'draft', ...payload }; return this.pending; },
async cancelDraft() { this.pending = null; },
async markDraftCommitted(id, userId, ref) {
this.pending = { ...this.pending, status: 'committed', committedRef: ref };
return this.pending;
},
};
const scheduleCalls = [];
const card = await handleWechatIntentTransaction({
intent: { agentText: '下午2点半提醒我开项目计划例会', msgId: 'tang-itl-1', msgType: 'text' },
user: { userId: TANG },
intentDraftService: drafts,
taskUnifiedService: service,
scheduleService: {
async createItem(payload) {
scheduleCalls.push(['createItem', payload.title]);
return { id: 'item-sim', ...payload, userId: TANG };
},
async createReminder(payload) {
scheduleCalls.push(['createReminder']);
return { id: 'rem-sim', ...payload, remindAt: payload.remindAt, channel: 'wechat' };
},
buildTodoDigestText: async () => '今天有 0 条待办。',
},
env,
});
if (card?.includes('确认')) pass('ITL action card dry path');
else fail('ITL action card dry path', card);
console.log('\n--- active scheduled tasks ---');
for (const row of activeTasks) {
console.log(`- ${row.title} (${row.hour}:${String(row.minute).padStart(2, '0')}) [${row.status}]`);
}
console.log('\n--- unified preview ---');
console.log(listReply);
console.log(`\n${passed} passed, ${failed} failed`);
await pool.end();
process.exit(failed > 0 ? 1 : 0);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});