feat(M6-006,M6-007,M7-001,M7-002): admin jobs API, job event logging, channel adapter interface, message protocol (#325)
Some checks failed
ci/woodpecker/push/ci Pipeline failed

Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #325.
This commit is contained in:
2026-03-23 01:21:03 +00:00
committed by jason.woltje
parent 701bb69e6c
commit a532fd43b2
13 changed files with 1458 additions and 31 deletions

View File

@@ -5,59 +5,72 @@ import {
type OnModuleInit,
type OnModuleDestroy,
} from '@nestjs/common';
import cron from 'node-cron';
import { SummarizationService } from './summarization.service.js';
import { SessionGCService } from '../gc/session-gc.service.js';
import {
QueueService,
QUEUE_SUMMARIZATION,
QUEUE_GC,
QUEUE_TIER_MANAGEMENT,
} from '../queue/queue.service.js';
import type { Worker } from 'bullmq';
import type { MosaicJobData } from '../queue/queue.service.js';
@Injectable()
export class CronService implements OnModuleInit, OnModuleDestroy {
private readonly logger = new Logger(CronService.name);
private readonly tasks: cron.ScheduledTask[] = [];
private readonly registeredWorkers: Worker<MosaicJobData>[] = [];
constructor(
@Inject(SummarizationService) private readonly summarization: SummarizationService,
@Inject(SessionGCService) private readonly sessionGC: SessionGCService,
@Inject(QueueService) private readonly queueService: QueueService,
) {}
onModuleInit(): void {
async onModuleInit(): Promise<void> {
const summarizationSchedule = process.env['SUMMARIZATION_CRON'] ?? '0 */6 * * *'; // every 6 hours
const tierManagementSchedule = process.env['TIER_MANAGEMENT_CRON'] ?? '0 3 * * *'; // daily at 3am
const gcSchedule = process.env['SESSION_GC_CRON'] ?? '0 4 * * *'; // daily at 4am
this.tasks.push(
cron.schedule(summarizationSchedule, () => {
this.summarization.runSummarization().catch((err) => {
this.logger.error(`Scheduled summarization failed: ${err}`);
});
}),
// M6-003: Summarization repeatable job
await this.queueService.addRepeatableJob(
QUEUE_SUMMARIZATION,
'summarization',
{},
summarizationSchedule,
);
const summarizationWorker = this.queueService.registerWorker(QUEUE_SUMMARIZATION, async () => {
await this.summarization.runSummarization();
});
this.registeredWorkers.push(summarizationWorker);
this.tasks.push(
cron.schedule(tierManagementSchedule, () => {
this.summarization.runTierManagement().catch((err) => {
this.logger.error(`Scheduled tier management failed: ${err}`);
});
}),
// M6-005: Tier management repeatable job
await this.queueService.addRepeatableJob(
QUEUE_TIER_MANAGEMENT,
'tier-management',
{},
tierManagementSchedule,
);
const tierWorker = this.queueService.registerWorker(QUEUE_TIER_MANAGEMENT, async () => {
await this.summarization.runTierManagement();
});
this.registeredWorkers.push(tierWorker);
this.tasks.push(
cron.schedule(gcSchedule, () => {
this.sessionGC.sweepOrphans().catch((err) => {
this.logger.error(`Session GC sweep failed: ${err}`);
});
}),
);
// M6-004: GC repeatable job
await this.queueService.addRepeatableJob(QUEUE_GC, 'session-gc', {}, gcSchedule);
const gcWorker = this.queueService.registerWorker(QUEUE_GC, async () => {
await this.sessionGC.sweepOrphans();
});
this.registeredWorkers.push(gcWorker);
this.logger.log(
`Cron scheduled: summarization="${summarizationSchedule}", tier="${tierManagementSchedule}", gc="${gcSchedule}"`,
`BullMQ jobs scheduled: summarization="${summarizationSchedule}", tier="${tierManagementSchedule}", gc="${gcSchedule}"`,
);
}
onModuleDestroy(): void {
for (const task of this.tasks) {
task.stop();
}
this.tasks.length = 0;
this.logger.log('Cron tasks stopped');
async onModuleDestroy(): Promise<void> {
// Workers are closed by QueueService.onModuleDestroy — nothing extra needed here.
this.registeredWorkers.length = 0;
this.logger.log('CronService destroyed (workers managed by QueueService)');
}
}