Versão inicial do Agents Orchestrator
Painel administrativo web para orquestração de agentes Claude Code com suporte a execução de tarefas, agendamento cron, pipelines sequenciais e terminal com streaming em tempo real via WebSocket.
This commit is contained in:
204
src/agents/pipeline.js
Normal file
204
src/agents/pipeline.js
Normal file
@@ -0,0 +1,204 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { pipelinesStore } from '../store/db.js';
|
||||
import { agentsStore } from '../store/db.js';
|
||||
import * as executor from './executor.js';
|
||||
|
||||
const activePipelines = new Map();
|
||||
|
||||
function validatePipeline(data) {
|
||||
const errors = [];
|
||||
if (!data.name || typeof data.name !== 'string') {
|
||||
errors.push('name é obrigatório e deve ser uma string');
|
||||
}
|
||||
if (!Array.isArray(data.steps) || data.steps.length === 0) {
|
||||
errors.push('steps é obrigatório e deve ser um array não vazio');
|
||||
} else {
|
||||
data.steps.forEach((step, index) => {
|
||||
if (!step.agentId) errors.push(`steps[${index}].agentId é obrigatório`);
|
||||
});
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
function buildSteps(steps) {
|
||||
return steps
|
||||
.map((step, index) => ({
|
||||
id: step.id || uuidv4(),
|
||||
agentId: step.agentId,
|
||||
order: step.order !== undefined ? step.order : index,
|
||||
inputTemplate: step.inputTemplate || null,
|
||||
description: step.description || '',
|
||||
}))
|
||||
.sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
function applyTemplate(template, input) {
|
||||
if (!template) return input;
|
||||
return template.replace(/\{\{input\}\}/g, input);
|
||||
}
|
||||
|
||||
function executeStepAsPromise(agentConfig, prompt, pipelineState) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const executionId = executor.execute(
|
||||
agentConfig,
|
||||
{ description: prompt },
|
||||
{
|
||||
onData: () => {},
|
||||
onError: (err) => {
|
||||
reject(err);
|
||||
},
|
||||
onComplete: (result) => {
|
||||
resolve(result.result || '');
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
pipelineState.currentExecutionId = executionId;
|
||||
});
|
||||
}
|
||||
|
||||
export async function executePipeline(pipelineId, initialInput, wsCallback) {
|
||||
const pipeline = pipelinesStore.getById(pipelineId);
|
||||
if (!pipeline) throw new Error(`Pipeline ${pipelineId} não encontrado`);
|
||||
|
||||
const pipelineState = {
|
||||
currentExecutionId: null,
|
||||
currentStep: 0,
|
||||
canceled: false,
|
||||
};
|
||||
|
||||
activePipelines.set(pipelineId, pipelineState);
|
||||
|
||||
const steps = buildSteps(pipeline.steps);
|
||||
const results = [];
|
||||
let currentInput = initialInput;
|
||||
|
||||
try {
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
if (pipelineState.canceled) break;
|
||||
|
||||
const step = steps[i];
|
||||
pipelineState.currentStep = i;
|
||||
|
||||
const agent = agentsStore.getById(step.agentId);
|
||||
if (!agent) throw new Error(`Agente ${step.agentId} não encontrado no passo ${i}`);
|
||||
if (agent.status !== 'active') throw new Error(`Agente ${agent.agent_name} está inativo`);
|
||||
|
||||
const prompt = applyTemplate(step.inputTemplate, currentInput);
|
||||
|
||||
if (wsCallback) {
|
||||
wsCallback({
|
||||
type: 'pipeline_step_start',
|
||||
pipelineId,
|
||||
stepIndex: i,
|
||||
stepId: step.id,
|
||||
agentName: agent.agent_name,
|
||||
totalSteps: steps.length,
|
||||
});
|
||||
}
|
||||
|
||||
const result = await executeStepAsPromise(agent.config, prompt, pipelineState);
|
||||
|
||||
if (pipelineState.canceled) break;
|
||||
|
||||
currentInput = result;
|
||||
results.push({ stepId: step.id, agentName: agent.agent_name, result });
|
||||
|
||||
if (wsCallback) {
|
||||
wsCallback({
|
||||
type: 'pipeline_step_complete',
|
||||
pipelineId,
|
||||
stepIndex: i,
|
||||
stepId: step.id,
|
||||
result: result.slice(0, 500),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
activePipelines.delete(pipelineId);
|
||||
|
||||
if (!pipelineState.canceled && wsCallback) {
|
||||
wsCallback({
|
||||
type: 'pipeline_complete',
|
||||
pipelineId,
|
||||
results,
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
} catch (err) {
|
||||
activePipelines.delete(pipelineId);
|
||||
|
||||
if (wsCallback) {
|
||||
wsCallback({
|
||||
type: 'pipeline_error',
|
||||
pipelineId,
|
||||
stepIndex: pipelineState.currentStep,
|
||||
error: err.message,
|
||||
});
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export function cancelPipeline(pipelineId) {
|
||||
const state = activePipelines.get(pipelineId);
|
||||
if (!state) return false;
|
||||
|
||||
state.canceled = true;
|
||||
|
||||
if (state.currentExecutionId) {
|
||||
executor.cancel(state.currentExecutionId);
|
||||
}
|
||||
|
||||
activePipelines.delete(pipelineId);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getActivePipelines() {
|
||||
return Array.from(activePipelines.entries()).map(([id, state]) => ({
|
||||
pipelineId: id,
|
||||
currentStep: state.currentStep,
|
||||
currentExecutionId: state.currentExecutionId,
|
||||
}));
|
||||
}
|
||||
|
||||
export function createPipeline(data) {
|
||||
const errors = validatePipeline(data);
|
||||
if (errors.length > 0) throw new Error(errors.join('; '));
|
||||
|
||||
const pipelineData = {
|
||||
name: data.name,
|
||||
description: data.description || '',
|
||||
steps: buildSteps(data.steps),
|
||||
status: data.status || 'active',
|
||||
};
|
||||
|
||||
return pipelinesStore.create(pipelineData);
|
||||
}
|
||||
|
||||
export function updatePipeline(id, data) {
|
||||
const existing = pipelinesStore.getById(id);
|
||||
if (!existing) return null;
|
||||
|
||||
const updateData = {};
|
||||
if (data.name !== undefined) updateData.name = data.name;
|
||||
if (data.description !== undefined) updateData.description = data.description;
|
||||
if (data.status !== undefined) updateData.status = data.status;
|
||||
if (data.steps !== undefined) updateData.steps = buildSteps(data.steps);
|
||||
|
||||
return pipelinesStore.update(id, updateData);
|
||||
}
|
||||
|
||||
export function deletePipeline(id) {
|
||||
return pipelinesStore.delete(id);
|
||||
}
|
||||
|
||||
export function getPipeline(id) {
|
||||
return pipelinesStore.getById(id);
|
||||
}
|
||||
|
||||
export function getAllPipelines() {
|
||||
return pipelinesStore.getAll();
|
||||
}
|
||||
Reference in New Issue
Block a user