import { NextRequest, NextResponse } from 'next/server' import { prisma } from '@/lib/prisma' import { calculateNextRun } from '@/lib/agents/schedule' export const dynamic = 'force-dynamic' /** * POST /api/cron/agents * * Finds all enabled, non-manual agents whose nextRun <= now, * executes them, and schedules the next run. * * Optional auth: set CRON_SECRET env var, callers must pass * Authorization: Bearer */ export async function POST(request: NextRequest) { const cronSecret = process.env.CRON_SECRET if (!cronSecret) { console.error('[CronAgents] CRON_SECRET env var is required but not set') return NextResponse.json({ error: 'Server misconfiguration' }, { status: 500 }) } const authHeader = request.headers.get('authorization') if (authHeader !== `Bearer ${cronSecret}`) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } try { const now = new Date() // Due = nextRun in the past; also backfill agents whose nextRun was never set // (legacy / toggle bug) — but only schedule them, don't stampede-execute all nulls const dueAgents = await prisma.agent.findMany({ where: { isEnabled: true, frequency: { notIn: ['manual', 'one-shot'] }, nextRun: { lte: now }, }, select: { id: true, userId: true, frequency: true, scheduledTime: true, scheduledDay: true, timezone: true, nextRun: true, }, orderBy: { nextRun: 'asc' }, }) // Repair: set nextRun for enabled scheduled agents stuck with null const unscheduled = await prisma.agent.findMany({ where: { isEnabled: true, frequency: { notIn: ['manual', 'one-shot'] }, nextRun: null, }, select: { id: true, userId: true, frequency: true, scheduledTime: true, scheduledDay: true, timezone: true, }, take: 50, }) const repairedDue: typeof dueAgents = [] for (const a of unscheduled) { const nextRun = calculateNextRun({ frequency: a.frequency, scheduledTime: a.scheduledTime, scheduledDay: a.scheduledDay, timezone: a.timezone, }) // Si le créneau calculé est déjà passé / immédiat → exécuter ce cycle const effective = !nextRun || nextRun <= now ? now : nextRun await prisma.agent.update({ where: { id: a.id }, data: { nextRun: effective } }) if (effective <= now) { repairedDue.push({ id: a.id, userId: a.userId, frequency: a.frequency, scheduledTime: a.scheduledTime, scheduledDay: a.scheduledDay, timezone: a.timezone, nextRun: effective, }) } } const queue = [...dueAgents] const seen = new Set(dueAgents.map((a) => a.id)) for (const a of repairedDue) { if (!seen.has(a.id)) { queue.push(a) seen.add(a.id) } } if (queue.length === 0) { return NextResponse.json({ success: true, executed: 0, repairedSchedules: unscheduled.length, }) } const results: { id: string; success: boolean; error?: string }[] = [] // Execute agents sequentially (max 5 per cycle — rattrapage + due) for (const agent of queue.slice(0, 5)) { try { const { executeAgent } = await import('@/lib/ai/services/agent-executor.service') const result = await executeAgent(agent.id, agent.userId) const nextRun = calculateNextRun({ frequency: agent.frequency, scheduledTime: agent.scheduledTime, scheduledDay: agent.scheduledDay, timezone: agent.timezone, }) await prisma.agent.update({ where: { id: agent.id }, data: { nextRun }, }) results.push({ id: agent.id, success: result.success, error: result.error }) } catch (error) { const msg = error instanceof Error ? error.message : 'Unknown error' console.error(`[CronAgents] Agent ${agent.id} failed:`, msg) const nextRun = calculateNextRun({ frequency: agent.frequency, scheduledTime: agent.scheduledTime, scheduledDay: agent.scheduledDay, timezone: agent.timezone, }) await prisma.agent.update({ where: { id: agent.id }, data: { nextRun }, }) results.push({ id: agent.id, success: false, error: msg }) } } return NextResponse.json({ success: true, executed: results.length, results, }) } catch (error) { console.error('[CronAgents] Error:', error) return NextResponse.json( { success: false, error: 'Internal Server Error' }, { status: 500 } ) } }