Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 39s
CRITICAL: - Add auth + admin check to 10 unprotected API routes (test-*, debug/*, config, models, fix-labels) - Add CRON_SECRET bearer auth to /api/cron/reminders (was fully open) - Add SSRF protection to getOllamaModels (blocks private/internal IPs) HIGH: - Fix getAllLabels() missing userId filter (leaked all users' labels) - Fix /api/labels OR clause leaking other users' labels - Fix IDOR in toggleAgent/getAgentActions (add ownership check) - Fix getEmbeddings() returning [] on error in all 5 providers (corrupted semantic search with NaN cosine similarity) — now throws instead Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
295 lines
8.4 KiB
TypeScript
295 lines
8.4 KiB
TypeScript
'use server'
|
|
|
|
/**
|
|
* Agent Server Actions
|
|
* CRUD operations for agents and execution triggers.
|
|
*/
|
|
|
|
import { auth } from '@/auth'
|
|
import { prisma } from '@/lib/prisma'
|
|
import { revalidatePath } from 'next/cache'
|
|
import { calculateNextRun } from '@/lib/agents/schedule'
|
|
|
|
// --- CRUD ---
|
|
|
|
export async function createAgent(data: {
|
|
name: string
|
|
description?: string
|
|
type: string
|
|
role: string
|
|
sourceUrls?: string[]
|
|
sourceNotebookId?: string
|
|
targetNotebookId?: string
|
|
frequency?: string
|
|
tools?: string[]
|
|
maxSteps?: number
|
|
notifyEmail?: boolean
|
|
includeImages?: boolean
|
|
scheduledTime?: string
|
|
scheduledDay?: number
|
|
timezone?: string
|
|
}) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
throw new Error('Non autorise')
|
|
}
|
|
|
|
try {
|
|
const agent = await prisma.agent.create({
|
|
data: {
|
|
name: data.name,
|
|
description: data.description,
|
|
type: data.type,
|
|
role: data.role,
|
|
sourceUrls: data.sourceUrls ? JSON.stringify(data.sourceUrls) : null,
|
|
sourceNotebookId: data.sourceNotebookId || null,
|
|
targetNotebookId: data.targetNotebookId || null,
|
|
frequency: data.frequency || 'manual',
|
|
tools: data.tools ? JSON.stringify(data.tools) : '[]',
|
|
maxSteps: data.maxSteps || 10,
|
|
notifyEmail: data.notifyEmail || false,
|
|
includeImages: data.includeImages || false,
|
|
scheduledTime: data.scheduledTime || '08:00',
|
|
scheduledDay: data.scheduledDay ?? null,
|
|
timezone: data.timezone || null,
|
|
userId: session.user.id,
|
|
}
|
|
})
|
|
|
|
// Calculate nextRun for scheduled agents
|
|
const freq = data.frequency || 'manual'
|
|
if (freq !== 'manual') {
|
|
const nextRun = calculateNextRun({
|
|
frequency: freq,
|
|
scheduledTime: data.scheduledTime || '08:00',
|
|
scheduledDay: data.scheduledDay,
|
|
timezone: data.timezone,
|
|
})
|
|
if (nextRun) {
|
|
await prisma.agent.update({
|
|
where: { id: agent.id },
|
|
data: { nextRun },
|
|
})
|
|
}
|
|
}
|
|
|
|
revalidatePath('/agents')
|
|
return { success: true, agent }
|
|
} catch (error) {
|
|
console.error('Error creating agent:', error)
|
|
throw new Error('Impossible de creer l\'agent')
|
|
}
|
|
}
|
|
|
|
export async function updateAgent(id: string, data: {
|
|
name?: string
|
|
description?: string
|
|
type?: string
|
|
role?: string
|
|
sourceUrls?: string[]
|
|
sourceNotebookId?: string | null
|
|
targetNotebookId?: string | null
|
|
frequency?: string
|
|
isEnabled?: boolean
|
|
tools?: string[]
|
|
maxSteps?: number
|
|
notifyEmail?: boolean
|
|
includeImages?: boolean
|
|
scheduledTime?: string
|
|
scheduledDay?: number | null
|
|
timezone?: string
|
|
}) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
throw new Error('Non autorise')
|
|
}
|
|
|
|
try {
|
|
const existing = await prisma.agent.findUnique({ where: { id } })
|
|
if (!existing || existing.userId !== session.user.id) {
|
|
throw new Error('Agent non trouve')
|
|
}
|
|
|
|
const updateData: Record<string, unknown> = {}
|
|
if (data.name !== undefined) updateData.name = data.name
|
|
if (data.description !== undefined) updateData.description = data.description
|
|
if (data.type !== undefined) updateData.type = data.type
|
|
if (data.role !== undefined) updateData.role = data.role
|
|
if (data.sourceUrls !== undefined) updateData.sourceUrls = JSON.stringify(data.sourceUrls)
|
|
if (data.sourceNotebookId !== undefined) updateData.sourceNotebookId = data.sourceNotebookId
|
|
if (data.targetNotebookId !== undefined) updateData.targetNotebookId = data.targetNotebookId
|
|
if (data.frequency !== undefined) updateData.frequency = data.frequency
|
|
if (data.isEnabled !== undefined) updateData.isEnabled = data.isEnabled
|
|
if (data.tools !== undefined) updateData.tools = JSON.stringify(data.tools)
|
|
if (data.maxSteps !== undefined) updateData.maxSteps = data.maxSteps
|
|
if (data.notifyEmail !== undefined) updateData.notifyEmail = data.notifyEmail
|
|
if (data.includeImages !== undefined) updateData.includeImages = data.includeImages
|
|
if (data.scheduledTime !== undefined) updateData.scheduledTime = data.scheduledTime
|
|
if (data.scheduledDay !== undefined) updateData.scheduledDay = data.scheduledDay
|
|
if (data.timezone !== undefined) updateData.timezone = data.timezone
|
|
|
|
// Recalculate nextRun when scheduling fields change
|
|
const shouldRecalcNextRun =
|
|
data.frequency !== undefined ||
|
|
data.scheduledTime !== undefined ||
|
|
data.scheduledDay !== undefined ||
|
|
data.timezone !== undefined
|
|
|
|
if (shouldRecalcNextRun) {
|
|
const freq = data.frequency || existing.frequency
|
|
if (freq === 'manual') {
|
|
updateData.nextRun = null
|
|
} else {
|
|
const nextRun = calculateNextRun({
|
|
frequency: freq,
|
|
scheduledTime: data.scheduledTime || existing.scheduledTime || '08:00',
|
|
scheduledDay: data.scheduledDay ?? existing.scheduledDay,
|
|
timezone: data.timezone || existing.timezone,
|
|
})
|
|
updateData.nextRun = nextRun
|
|
}
|
|
}
|
|
|
|
const agent = await prisma.agent.update({
|
|
where: { id },
|
|
data: updateData
|
|
})
|
|
|
|
revalidatePath('/agents')
|
|
return { success: true, agent }
|
|
} catch (error) {
|
|
console.error('Error updating agent:', error)
|
|
throw new Error('Impossible de mettre a jour l\'agent')
|
|
}
|
|
}
|
|
|
|
export async function deleteAgent(id: string) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
throw new Error('Non autorise')
|
|
}
|
|
|
|
try {
|
|
const existing = await prisma.agent.findUnique({ where: { id } })
|
|
if (!existing || existing.userId !== session.user.id) {
|
|
throw new Error('Agent non trouve')
|
|
}
|
|
|
|
await prisma.agent.delete({ where: { id } })
|
|
revalidatePath('/agents')
|
|
return { success: true }
|
|
} catch (error) {
|
|
console.error('Error deleting agent:', error)
|
|
throw new Error('Impossible de supprimer l\'agent')
|
|
}
|
|
}
|
|
|
|
export async function getAgents() {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
throw new Error('Non autorise')
|
|
}
|
|
|
|
try {
|
|
const agents = await prisma.agent.findMany({
|
|
where: { userId: session.user.id },
|
|
include: {
|
|
_count: { select: { actions: true } },
|
|
actions: {
|
|
orderBy: { createdAt: 'desc' },
|
|
take: 1,
|
|
},
|
|
notebook: {
|
|
select: { id: true, name: true, icon: true }
|
|
}
|
|
},
|
|
orderBy: { createdAt: 'desc' }
|
|
})
|
|
|
|
return agents
|
|
} catch (error) {
|
|
console.error('Error fetching agents:', error)
|
|
throw new Error('Impossible de charger les agents')
|
|
}
|
|
}
|
|
|
|
// --- Execution ---
|
|
|
|
export async function runAgent(id: string) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
throw new Error('Non autorise')
|
|
}
|
|
|
|
try {
|
|
const { executeAgent } = await import('@/lib/ai/services/agent-executor.service')
|
|
const result = await executeAgent(id, session.user.id)
|
|
revalidatePath('/agents')
|
|
revalidatePath('/')
|
|
return result
|
|
} catch (error) {
|
|
console.error('Error running agent:', error)
|
|
return {
|
|
success: false,
|
|
actionId: '',
|
|
error: error instanceof Error ? error.message : 'Erreur inconnue'
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- History ---
|
|
|
|
export async function getAgentActions(agentId: string) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
throw new Error('Non autorise')
|
|
}
|
|
|
|
try {
|
|
// Verify the agent belongs to the user
|
|
const agent = await prisma.agent.findFirst({
|
|
where: { id: agentId, userId: session.user.id },
|
|
select: { id: true }
|
|
})
|
|
if (!agent) throw new Error('Agent non trouve')
|
|
|
|
const actions = await prisma.agentAction.findMany({
|
|
where: { agentId },
|
|
orderBy: { createdAt: 'desc' },
|
|
take: 20,
|
|
select: {
|
|
id: true,
|
|
status: true,
|
|
result: true,
|
|
log: true,
|
|
input: true,
|
|
toolLog: true,
|
|
tokensUsed: true,
|
|
createdAt: true,
|
|
}
|
|
})
|
|
return actions
|
|
} catch (error) {
|
|
console.error('Error fetching agent actions:', error)
|
|
throw new Error('Impossible de charger l\'historique')
|
|
}
|
|
}
|
|
|
|
export async function toggleAgent(id: string, isEnabled: boolean) {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
throw new Error('Non autorise')
|
|
}
|
|
|
|
try {
|
|
const agent = await prisma.agent.update({
|
|
where: { id, userId: session.user.id },
|
|
data: { isEnabled }
|
|
})
|
|
return { success: true, agent }
|
|
} catch (error) {
|
|
console.error('Error toggling agent:', error)
|
|
throw new Error('Impossible de modifier l\'agent')
|
|
}
|
|
}
|