Phase 3.5: Automation & Agents Plugin
Neues automation Plugin (app/plugins/builtins/automation/): - 7 DB-Modelle: AgentDefinition, AgentVersion, AutomationDefinition, AutomationVersion, AutomationCronJob, AgentRun, AutomationRun - Migration 0001_initial.sql mit allen Tabellen + RLS - PluginManifest erweitert: agent_definitions, automation_templates, cron_jobs, heartbeat_configs, miniapps Contribution-Felder - 21 API-Endpoints: /api/v1/automation (CRUD, execute, dry-run, runs, versions, restore, settings, miniapps) + /api/v1/agents (CRUD, execute, test-run, runs, versions, restore, tools, send-message) Backend Features: - Cron-Scheduler (scheduler.py): ARQ-basiert, liest CronJob-Tabelle, enqueued run_agent/run_automation, croniter fuer next_run_at - Workflow-Timeout-Worker (workflow_timeout.py): prueft abgelaufene WorkflowInstances, setzt cancelled, sendet Notification - Agent Runner (agent_runner.py): LiteLLM + ToolRegistry, proactive/ reactive mode, Rate-Limiting, Budget-Limit, Infinite-Loop-Detection - Automation Execution Engine (execution_engine.py): Condition evaluation (eq/ne/gt/lt/contains/exists), Actions (api_call/ notification/workflow_start), Dry-Run mode - Agent-to-Agent Communication (agent_comm.py): send_agent_message tool, kommunikation plugin integration - Plugin-Beitraege: register/unregister on activate/deactivate, Konfliktloesung mit Plugin-Name als Prefix - Heartbeat-Migration: ai_proactive heartbeat als Cron-Job - Versionshistorie: Auto-Versioning bei Updates, Restore-Endpoint - Settings: GET/PATCH /api/v1/automation/settings - ARQ Worker: 11 functions, 2 cron_jobs (scheduler_tick 30s, check_workflow_timeouts 5min) Frontend: - AutomationDashboard.tsx: Automation Builder UI mit Trigger, Conditions, Actions, Execute, Dry-Run, Run-History, Versions - AgentDashboard.tsx: Agent Builder UI mit Model, Tools, Prompt, Heartbeat, Rate-Limits, Execute, Test-Run, Agent-Chat - AutomationSettings.tsx: Settings + MiniApp-Builder - automation.ts: 24 React Query Hooks - automation.ts types: TypeScript Interfaces - routes/index.tsx: /automation, /agents, /settings/automation Tests: - 22 Frontend-Tests (AutomationDashboard, AgentDashboard, API) — alle bestanden - Backend-Tests: test_automation.py (CRUD, versions, conditions, dry-run, rate-limiting) - TSC: keine neuen Errors (nur pre-existing Dms.tsx) - croniter dependency installiert
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
/**
|
||||
* React Query hooks for Automation & Agents API.
|
||||
* Follows the pattern from plugins.ts.
|
||||
*/
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { apiGet, apiPost, apiPatch, apiDelete } from './client';
|
||||
import type {
|
||||
AutomationDefinition,
|
||||
AutomationRun,
|
||||
AutomationVersion,
|
||||
AgentDefinition,
|
||||
AgentRun,
|
||||
AgentVersion,
|
||||
AgentTool,
|
||||
AutomationSettings,
|
||||
MiniAppDef,
|
||||
} from '@/types/automation';
|
||||
|
||||
// ── Automation Hooks ──
|
||||
|
||||
export function useAutomations() {
|
||||
return useQuery({
|
||||
queryKey: ['automations'],
|
||||
queryFn: () => apiGet<AutomationDefinition[]>('/automation'),
|
||||
});
|
||||
}
|
||||
|
||||
export function useAutomation(id: string) {
|
||||
return useQuery({
|
||||
queryKey: ['automation', id],
|
||||
queryFn: () => apiGet<AutomationDefinition>(`/automation/${id}`),
|
||||
enabled: !!id,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateAutomation() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: Partial<AutomationDefinition>) => apiPost<AutomationDefinition>('/automation', data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['automations'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateAutomation() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: Partial<AutomationDefinition> }) =>
|
||||
apiPatch<AutomationDefinition>(`/automation/${id}`, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['automations'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteAutomation() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => apiDelete(`/automation/${id}`),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['automations'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useExecuteAutomation() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => apiPost(`/automation/${id}/execute`),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['automationRuns'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDryRunAutomation() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => apiPost(`/automation/${id}/dry-run`),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['automationRuns'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useAutomationRuns(automationId: string) {
|
||||
return useQuery({
|
||||
queryKey: ['automationRuns', automationId],
|
||||
queryFn: () => apiGet<AutomationRun[]>(`/automation/${automationId}/runs`),
|
||||
enabled: !!automationId,
|
||||
});
|
||||
}
|
||||
|
||||
export function useAutomationVersions(automationId: string) {
|
||||
return useQuery({
|
||||
queryKey: ['automationVersions', automationId],
|
||||
queryFn: () => apiGet<AutomationVersion[]>(`/automation/${automationId}/versions`),
|
||||
enabled: !!automationId,
|
||||
});
|
||||
}
|
||||
|
||||
export function useRestoreAutomationVersion() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, versionId }: { id: string; versionId: string }) =>
|
||||
apiPost(`/automation/${id}/versions/${versionId}/restore`),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['automations'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['automationVersions'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useAutomationSettings() {
|
||||
return useQuery({
|
||||
queryKey: ['automationSettings'],
|
||||
queryFn: () => apiGet<AutomationSettings>('/automation/settings'),
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateAutomationSettings() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: Partial<AutomationSettings>) =>
|
||||
apiPatch<AutomationSettings>('/automation/settings', data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['automationSettings'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ── Agent Hooks ──
|
||||
|
||||
export function useAgents() {
|
||||
return useQuery({
|
||||
queryKey: ['agents'],
|
||||
queryFn: () => apiGet<AgentDefinition[]>('/agents'),
|
||||
});
|
||||
}
|
||||
|
||||
export function useAgent(id: string) {
|
||||
return useQuery({
|
||||
queryKey: ['agent', id],
|
||||
queryFn: () => apiGet<AgentDefinition>(`/agents/${id}`),
|
||||
enabled: !!id,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateAgent() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: Partial<AgentDefinition>) => apiPost<AgentDefinition>('/agents', data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['agents'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateAgent() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: Partial<AgentDefinition> }) =>
|
||||
apiPatch<AgentDefinition>(`/agents/${id}`, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['agents'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteAgent() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => apiDelete(`/agents/${id}`),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['agents'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useExecuteAgent() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => apiPost(`/agents/${id}/execute`),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['agentRuns'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useTestRunAgent() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => apiPost(`/agents/${id}/test-run`),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['agentRuns'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useAgentRuns(agentId: string) {
|
||||
return useQuery({
|
||||
queryKey: ['agentRuns', agentId],
|
||||
queryFn: () => apiGet<AgentRun[]>(`/agents/${agentId}/runs`),
|
||||
enabled: !!agentId,
|
||||
});
|
||||
}
|
||||
|
||||
export function useAgentVersions(agentId: string) {
|
||||
return useQuery({
|
||||
queryKey: ['agentVersions', agentId],
|
||||
queryFn: () => apiGet<AgentVersion[]>(`/agents/${agentId}/versions`),
|
||||
enabled: !!agentId,
|
||||
});
|
||||
}
|
||||
|
||||
export function useRestoreAgentVersion() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, versionId }: { id: string; versionId: string }) =>
|
||||
apiPost(`/agents/${id}/versions/${versionId}/restore`),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['agents'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['agentVersions'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useAgentTools() {
|
||||
return useQuery({
|
||||
queryKey: ['agentTools'],
|
||||
queryFn: () => apiGet<AgentTool[]>('/agents/tools'),
|
||||
});
|
||||
}
|
||||
|
||||
// ── Agent Communication Hooks ──
|
||||
|
||||
export function useSendAgentMessage() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, toAgentName, message }: { id: string; toAgentName: string; message: string }) =>
|
||||
apiPost(`/agents/${id}/send-message`, { to_agent_name: toAgentName, message }),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['agentRuns'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ── MiniApp Hooks ──
|
||||
|
||||
export function useMiniApps() {
|
||||
return useQuery({
|
||||
queryKey: ['miniapps'],
|
||||
queryFn: () => apiGet<MiniAppDef[]>('/automation/miniapps'),
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateMiniApp() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: Partial<MiniAppDef>) => apiPost<MiniAppDef>('/automation/miniapps', data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['miniapps'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteMiniApp() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (appId: string) => apiDelete(`/automation/miniapps/${appId}`),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['miniapps'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user