arcadiasuite/server/automation-fabric/runtimes/index.ts

189 lines
7.9 KiB
TypeScript

/**
* Automation Fabric — 5 Runtimes
* Phase 5: Automation Fabric
*
* Cada runtime é responsável por um tipo de execução:
* - WorkflowEngine: ações sequenciais com condições
* - RuleEngine: avaliação de condições JSONB
* - AgentExecutor: delegação para Manus
* - ScheduleEngine: cron/interval
* - EventEngine: webhook/event triggers
*/
import { db } from "../../../db/index";
import { automations, automationActions, automationLogs } from "@shared/schema";
import { eq } from "drizzle-orm";
import { manusService } from "../../manus/service";
export interface RuntimeResult {
success: boolean;
output: string;
error?: string;
}
// ─── WorkflowEngine ────────────────────────────────────────────────────────────
// Executa ações sequenciais de uma automação Central (tabela automations)
export class WorkflowEngine {
async run(automationId: number, userId: string, triggerData?: any): Promise<RuntimeResult> {
try {
const actions = await db
.select()
.from(automationActions)
.where(eq(automationActions.automationId, automationId))
.orderBy(automationActions.orderIndex);
const results: string[] = [];
for (const action of actions) {
const config = action.actionConfig ? JSON.parse(action.actionConfig) : {};
results.push(await this.executeAction(action.actionType, config, userId, triggerData));
}
return { success: true, output: results.join("\n") };
} catch (err: any) {
return { success: false, output: "", error: err.message };
}
}
private async executeAction(type: string, config: any, userId: string, triggerData?: any): Promise<string> {
switch (type) {
case "send_email": return `Email enviado para: ${config.to ?? "n/a"}`;
case "send_whatsapp": return `WhatsApp enviado para: ${config.to ?? "n/a"}`;
case "send_notification": return `Notificação: ${config.message ?? "executada"}`;
case "erp_sync": return `Sync ERP: ${config.entity ?? "todos"}`;
case "generate_report": return `Relatório: ${config.reportType ?? "resumo"}`;
case "webhook": {
if (config.url) {
const res = await fetch(config.url, {
method: config.method ?? "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ triggerData, config }),
});
return `Webhook: ${res.status}`;
}
return "Webhook: URL não configurada";
}
case "agent_task": return await agentExecutor.runTask(userId, config.prompt ?? "Execute task");
default: return `Ação desconhecida: ${type}`;
}
}
}
// ─── RuleEngine ────────────────────────────────────────────────────────────────
// Avalia array de conditions JSONB — retorna true se todas passam
export class RuleEngine {
evaluate(conditions: Array<{ field: string; operator: string; value: any }>, context: Record<string, any>): boolean {
if (!conditions || conditions.length === 0) return true;
return conditions.every(({ field, operator, value }) => {
const actual = context[field];
switch (operator) {
case "eq": return actual == value;
case "neq": return actual != value;
case "gt": return Number(actual) > Number(value);
case "lt": return Number(actual) < Number(value);
case "gte": return Number(actual) >= Number(value);
case "lte": return Number(actual) <= Number(value);
case "contains": return String(actual ?? "").includes(String(value));
case "in": return Array.isArray(value) && value.includes(actual);
default: return true;
}
});
}
async runIfMatch(
automation: { conditions: any; actions: any; id: number },
context: Record<string, any>,
userId: string
): Promise<RuntimeResult> {
const conditions = Array.isArray(automation.conditions) ? automation.conditions : [];
if (!this.evaluate(conditions, context)) {
return { success: true, output: "Condições não atendidas — execução ignorada" };
}
return workflowEngine.run(automation.id, userId, context);
}
}
// ─── AgentExecutor ─────────────────────────────────────────────────────────────
// Delega execução para o Manus (agente autônomo)
export class AgentExecutor {
async runTask(userId: string, prompt: string): Promise<string> {
try {
const { runId } = await manusService.run(userId, prompt);
return `Agente iniciado (run #${runId})`;
} catch (err: any) {
return `Agente falhou: ${err.message}`;
}
}
async run(automationId: number, userId: string, triggerData?: any): Promise<RuntimeResult> {
try {
const [auto] = await db
.select()
.from(automations)
.where(eq(automations.id, automationId))
.limit(1);
const config = auto?.triggerConfig ? JSON.parse(auto.triggerConfig) : {};
const prompt = config.agentPrompt ?? `Execute automation: ${auto?.name ?? automationId}`;
const output = await this.runTask(userId, prompt);
return { success: true, output };
} catch (err: any) {
return { success: false, output: "", error: err.message };
}
}
}
// ─── ScheduleEngine ────────────────────────────────────────────────────────────
// Gerencia agendamentos — wraps o mecanismo existente de scheduledTasks
export class ScheduleEngine {
/** Verifica se uma automação do tipo schedule deve rodar agora */
shouldRun(lastRunAt: Date | null, intervalMinutes: number | null, cronExpression: string | null): boolean {
if (!lastRunAt) return true; // nunca rodou
if (intervalMinutes) {
const msElapsed = Date.now() - lastRunAt.getTime();
return msElapsed >= intervalMinutes * 60 * 1000;
}
// cron: delega para lógica existente (não reimplementar aqui)
return false;
}
async run(automationId: number, userId: string): Promise<RuntimeResult> {
return workflowEngine.run(automationId, userId);
}
}
// ─── EventEngine ───────────────────────────────────────────────────────────────
// Dispara automações a partir de eventos/webhooks
export class EventEngine {
private listeners: Map<string, Array<{ automationId: number; userId: string; conditions: any }>> = new Map();
register(eventType: string, automationId: number, userId: string, conditions?: any): void {
const list = this.listeners.get(eventType) ?? [];
list.push({ automationId, userId, conditions: conditions ?? [] });
this.listeners.set(eventType, list);
}
async emit(eventType: string, context: Record<string, any>): Promise<RuntimeResult[]> {
const handlers = this.listeners.get(eventType) ?? [];
const results: RuntimeResult[] = [];
for (const { automationId, userId, conditions } of handlers) {
const passes = ruleEngine.evaluate(conditions, context);
if (passes) {
results.push(await workflowEngine.run(automationId, userId, context));
}
}
return results;
}
}
// ─── Singletons ────────────────────────────────────────────────────────────────
export const workflowEngine = new WorkflowEngine();
export const ruleEngine = new RuleEngine();
export const agentExecutor = new AgentExecutor();
export const scheduleEngine = new ScheduleEngine();
export const eventEngine = new EventEngine();