feat(automation): add daily briefing preset and cron backup scheduling

This commit is contained in:
William Valentin
2026-02-16 13:42:18 -08:00
parent 52231b7a93
commit ce621d1b72
13 changed files with 350 additions and 45 deletions
+36
View File
@@ -142,4 +142,40 @@ describe('registerChannels', () => {
expect(names).toContain('zalo');
expect(gateway.setZaloHandler).toHaveBeenCalledTimes(1);
});
it('registers cron scheduler when daily briefing preset is enabled', () => {
const config = configSchema.parse({
telegram: { bot_token: 'test-token', allowed_chat_ids: [1] },
models: { default: { provider: 'anthropic', model: 'claude-3' } },
automation: {
daily_briefing: {
enabled: true,
schedule: '0 8 * * *',
output: { channel: 'telegram', peer: '1' },
},
},
});
const channelRegistry = new ChannelRegistry();
const gateway = {
setWebhookHandler: vi.fn(),
setGmailHandler: vi.fn(),
setTeamsHandler: vi.fn(),
setGoogleChatHandler: vi.fn(),
setBlueBubblesHandler: vi.fn(),
setLineHandler: vi.fn(),
setFeishuHandler: vi.fn(),
setZaloHandler: vi.fn(),
};
registerChannels({
config,
channelRegistry,
hookEngine: new HookEngine(config.hooks),
gateway: gateway as unknown as Parameters<typeof registerChannels>[0]['gateway'],
});
const names = channelRegistry.list().map((adapter) => adapter.name);
expect(names).toContain('cron');
});
});
+6 -4
View File
@@ -1,7 +1,7 @@
import type { Config } from '../config/index.js';
import type { HookEngine } from '../hooks/index.js';
import { ChannelRegistry, TelegramAdapter, WebChatAdapter, DiscordAdapter, SlackAdapter, WhatsAppAdapter, MatrixAdapter, SignalAdapter, MattermostAdapter, TeamsAdapter, GoogleChatAdapter, BlueBubblesAdapter, LineAdapter, FeishuAdapter, ZaloAdapter, PairingManager } from '../channels/index.js';
import { CronScheduler, WebhookHandler, GmailWatcher } from '../automation/index.js';
import { CronScheduler, WebhookHandler, GmailWatcher, buildPresetCronJobs } from '../automation/index.js';
import type { GatewayServer } from '../gateway/index.js';
export interface ChannelsDeps {
@@ -202,10 +202,12 @@ export function registerChannels(deps: ChannelsDeps): ChannelsResult {
// Register cron scheduler adapter (if any cron jobs configured)
let cronScheduler: CronScheduler | undefined;
if (config.automation.cron.length > 0) {
cronScheduler = new CronScheduler(config.automation.cron, channelRegistry, config.automation.delivery_mode);
const presetCronJobs = buildPresetCronJobs(config);
const cronJobs = [...config.automation.cron, ...presetCronJobs];
if (cronJobs.length > 0) {
cronScheduler = new CronScheduler(cronJobs, channelRegistry, config.automation.delivery_mode);
channelRegistry.register(cronScheduler);
console.log(`Registered ${config.automation.cron.length} cron job(s)`);
console.log(`Registered ${cronJobs.length} cron job(s)`);
}
// Register webhook handler adapter (if any webhooks configured)
+41 -6
View File
@@ -2,6 +2,7 @@
import { resolve } from 'path';
import { homedir } from 'os';
import { mkdirSync } from 'fs';
import { Cron } from 'croner';
// ── Config & Types ──
import type { Config } from '../config/index.js';
@@ -106,7 +107,8 @@ export async function startDaemon(config: Config, options?: StartDaemonOptions):
if (config.backup.enabled) {
const backupIntervalMs = parseDuration(config.backup.interval);
if (!backupIntervalMs) {
const backupSchedule = config.backup.schedule?.trim();
if (!backupSchedule && !backupIntervalMs) {
console.warn(`Backup enabled but interval is invalid: ${config.backup.interval}`);
} else {
let backupRunning = false;
@@ -129,11 +131,44 @@ export async function startDaemon(config: Config, options?: StartDaemonOptions):
}
};
const backupInterval = setInterval(() => {
void runScheduledBackup();
}, backupIntervalMs);
lifecycle.onShutdown(async () => { clearInterval(backupInterval); });
console.log(`Backup scheduler enabled (${config.backup.interval})`);
let backupCron: Cron | undefined;
let backupInterval: ReturnType<typeof setInterval> | undefined;
if (backupSchedule) {
try {
backupCron = new Cron(backupSchedule, { paused: false }, () => {
void runScheduledBackup();
});
console.log(`Backup scheduler enabled (cron: ${backupSchedule})`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.warn(`Backup cron schedule is invalid (${backupSchedule}): ${message}`);
}
}
if (!backupCron && backupIntervalMs) {
backupInterval = setInterval(() => {
void runScheduledBackup();
}, backupIntervalMs);
console.log(`Backup scheduler enabled (interval: ${config.backup.interval})`);
}
if (!backupCron && !backupInterval) {
console.warn('Backup scheduler disabled: no valid backup.schedule or backup.interval');
} else {
if (config.backup.run_on_start) {
void runScheduledBackup();
}
lifecycle.onShutdown(async () => {
if (backupCron) {
backupCron.stop();
}
if (backupInterval) {
clearInterval(backupInterval);
}
});
}
}
}