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:
Frederico Castro
2026-02-26 00:23:56 -03:00
commit 723a08d2e1
24 changed files with 8433 additions and 0 deletions

204
src/agents/pipeline.js Normal file
View 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();
}