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>
184 lines
6.1 KiB
JavaScript
184 lines
6.1 KiB
JavaScript
import { isUnifiedTasksEnabled } from './task-unified-config.mjs';
|
|
import { mapLegacyTodoItemRow } from './task-unified-service.mjs';
|
|
|
|
function syncOpen(taskUnifiedService, fn) {
|
|
return fn().catch((err) => {
|
|
console.warn?.(
|
|
'[task-unified-sync] failed open:',
|
|
err instanceof Error ? err.message : err,
|
|
);
|
|
});
|
|
}
|
|
|
|
export function attachUnifiedTaskSync({
|
|
scheduleService,
|
|
scheduledTaskService,
|
|
taskUnifiedService,
|
|
pool,
|
|
env = process.env,
|
|
logger = console,
|
|
}) {
|
|
if (!isUnifiedTasksEnabled(env) || !taskUnifiedService || !pool) {
|
|
return { scheduleService, scheduledTaskService };
|
|
}
|
|
|
|
const warn = (...args) => logger.warn?.(...args);
|
|
|
|
if (scheduleService?.createItem) {
|
|
const original = scheduleService.createItem.bind(scheduleService);
|
|
scheduleService.createItem = async (payload) => {
|
|
const item = await original(payload);
|
|
if (item?.kind === 'task') {
|
|
await syncOpen(taskUnifiedService, () =>
|
|
taskUnifiedService.syncFromLegacyMapped(mapLegacyTodoItemRow({
|
|
id: item.id,
|
|
user_id: item.userId,
|
|
title: item.title,
|
|
timezone: item.timezone,
|
|
status: item.status,
|
|
source_channel: payload.sourceChannel ?? null,
|
|
source_message_id: payload.sourceMessageId ?? null,
|
|
source_text: payload.sourceText ?? null,
|
|
})));
|
|
}
|
|
return item;
|
|
};
|
|
}
|
|
|
|
if (scheduleService?.createReminder) {
|
|
const original = scheduleService.createReminder.bind(scheduleService);
|
|
scheduleService.createReminder = async (payload) => {
|
|
const reminder = await original(payload);
|
|
try {
|
|
const [rows] = await pool.query(
|
|
`SELECT i.*, r.id AS reminder_id, r.remind_at, r.channel, r.status AS reminder_status
|
|
FROM h5_schedule_items i
|
|
JOIN h5_schedule_reminders r ON r.item_id = i.id
|
|
WHERE r.id = ?
|
|
LIMIT 1`,
|
|
[reminder.id],
|
|
);
|
|
const row = rows?.[0];
|
|
if (row) {
|
|
await taskUnifiedService.syncFromCommit({
|
|
userId: payload.userId,
|
|
kind: 'timed_reminder',
|
|
committed: {
|
|
item: {
|
|
id: row.id,
|
|
title: row.title,
|
|
startAt: row.start_at ?? reminder.remindAt,
|
|
timezone: row.timezone,
|
|
sourceChannel: row.source_channel,
|
|
sourceMessageId: row.source_message_id,
|
|
sourceText: row.source_text,
|
|
},
|
|
reminder,
|
|
},
|
|
});
|
|
}
|
|
} catch (err) {
|
|
warn('[task-unified-sync] reminder sync failed:', err instanceof Error ? err.message : err);
|
|
}
|
|
return reminder;
|
|
};
|
|
}
|
|
|
|
if (scheduleService?.createDailyTodoDigest) {
|
|
const original = scheduleService.createDailyTodoDigest.bind(scheduleService);
|
|
scheduleService.createDailyTodoDigest = async (payload) => {
|
|
const subscription = await original(payload);
|
|
await syncOpen(taskUnifiedService, () =>
|
|
taskUnifiedService.syncFromCommit({
|
|
userId: payload.userId,
|
|
kind: 'create_daily_todo_digest',
|
|
committed: { subscription },
|
|
}));
|
|
return subscription;
|
|
};
|
|
}
|
|
|
|
if (scheduleService?.createBalanceLowAlert) {
|
|
const original = scheduleService.createBalanceLowAlert.bind(scheduleService);
|
|
scheduleService.createBalanceLowAlert = async (payload) => {
|
|
const subscription = await original(payload);
|
|
await syncOpen(taskUnifiedService, () =>
|
|
taskUnifiedService.syncFromCommit({
|
|
userId: payload.userId,
|
|
kind: 'create_balance_alert',
|
|
committed: { subscription },
|
|
}));
|
|
return subscription;
|
|
};
|
|
}
|
|
|
|
if (scheduledTaskService?.createTask) {
|
|
const original = scheduledTaskService.createTask.bind(scheduledTaskService);
|
|
scheduledTaskService.createTask = async (payload) => {
|
|
const task = await original(payload);
|
|
await syncOpen(taskUnifiedService, () =>
|
|
taskUnifiedService.syncFromCommit({
|
|
userId: payload.userId,
|
|
kind: 'scheduled_task',
|
|
committed: { task },
|
|
}));
|
|
return task;
|
|
};
|
|
}
|
|
|
|
if (scheduledTaskService?.cancelTask) {
|
|
const original = scheduledTaskService.cancelTask.bind(scheduledTaskService);
|
|
scheduledTaskService.cancelTask = async (payload) => {
|
|
const task = await original(payload);
|
|
if (task?.id && payload?.userId) {
|
|
await syncOpen(taskUnifiedService, () =>
|
|
taskUnifiedService.markLegacyCancelled({
|
|
userId: payload.userId,
|
|
legacyRef: { table: 'h5_scheduled_tasks', id: task.id },
|
|
}));
|
|
}
|
|
return task;
|
|
};
|
|
}
|
|
|
|
const automationLegacyRef = (task) => ({
|
|
table: 'h5_scheduled_tasks',
|
|
id: task.id,
|
|
});
|
|
|
|
if (scheduledTaskService?.markTaskSucceeded) {
|
|
const original = scheduledTaskService.markTaskSucceeded.bind(scheduledTaskService);
|
|
scheduledTaskService.markTaskSucceeded = async (task, options) => {
|
|
const updated = await original(task, options);
|
|
await syncOpen(taskUnifiedService, () =>
|
|
taskUnifiedService.syncLegacyLifecycle({
|
|
userId: updated.userId,
|
|
legacyRef: automationLegacyRef(updated),
|
|
status: updated.status === 'completed' ? 'completed' : 'active',
|
|
nextRunAt: updated.nextRunAt ?? null,
|
|
lastRunAt: updated.lastRunAt ?? null,
|
|
lastError: null,
|
|
}));
|
|
return updated;
|
|
};
|
|
}
|
|
|
|
if (scheduledTaskService?.markTaskFailed) {
|
|
const original = scheduledTaskService.markTaskFailed.bind(scheduledTaskService);
|
|
scheduledTaskService.markTaskFailed = async (task, error, options) => {
|
|
const updated = await original(task, error, options);
|
|
await syncOpen(taskUnifiedService, () =>
|
|
taskUnifiedService.syncLegacyLifecycle({
|
|
userId: updated.userId,
|
|
legacyRef: automationLegacyRef(updated),
|
|
status: updated.status === 'failed' ? 'failed' : 'active',
|
|
nextRunAt: updated.nextRunAt ?? null,
|
|
lastError: updated.lastError ?? null,
|
|
}));
|
|
return updated;
|
|
};
|
|
}
|
|
|
|
return { scheduleService, scheduledTaskService };
|
|
}
|