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:
john
2026-08-24 15:59:17 +08:00
parent b93c92a3e2
commit d58dc2a251
45 changed files with 4972 additions and 30 deletions
@@ -0,0 +1,45 @@
#!/usr/bin/env node
import crypto from 'node:crypto';
import mysql from 'mysql2/promise';
const TANG = 'a70ff537-8908-486e-9b6c-042e07cc25db';
const dueSeconds = Number(process.argv[2] ?? 180);
const dueMs = Date.now() + dueSeconds * 1000;
const fmt = new Intl.DateTimeFormat('en-CA', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false,
});
const parts = Object.fromEntries(fmt.formatToParts(new Date(dueMs)).map((x) => [x.type, x.value]));
const runAtLocal = `${parts.year}-${parts.month}-${parts.day} ${parts.hour}:${parts.minute}`;
const id = crypto.randomUUID();
const title = '【测试】微信定时推送链路验证';
const taskSpec = [
'这是唐用户微信定时任务推送测试。',
'请 load_skill static-page-publish,生成简洁测试页 public/scheduled-wechat-test-0817.html',
'标题「微信定时推送测试」,正文包含当前北京时间。',
'完成后在回复里给出正式可访问 URL,不要反问用户。',
].join('');
const pool = mysql.createPool({ uri: process.env.DATABASE_URL, connectionLimit: 2 });
const now = Date.now();
await pool.query(
`INSERT INTO h5_scheduled_tasks
(id, user_id, title, task_spec, recurrence, hour, minute, weekday, timezone,
next_run_at, notify_channel, status, source_channel, created_at, updated_at)
VALUES (?, ?, ?, ?, 'once', NULL, 0, NULL, 'Asia/Shanghai', ?, 'both', 'active', 'api', ?, ?)`,
[id, TANG, title, taskSpec, dueMs, now, now],
);
console.log(JSON.stringify({
id,
runAtLocal,
dueInSeconds: dueSeconds,
nextRunAtIso: new Date(dueMs).toISOString(),
title,
pagePath: 'public/scheduled-wechat-test-0817.html',
}, null, 2));
await pool.end();