Melhorias no frontend, pipeline e executor

- Estilos CSS expandidos com novos componentes visuais
- Editor de fluxo visual para pipelines (flow-editor.js)
- Melhorias na UI de agentes e pipelines
- Sumarização automática entre steps de pipeline
- Retry com backoff no executor
- Utilitários adicionais no frontend
This commit is contained in:
Frederico Castro
2026-02-27 22:39:23 -03:00
parent 0b5a81c3e6
commit 972ae92291
10 changed files with 1563 additions and 47 deletions

View File

@@ -1,9 +1,12 @@
import { spawn } from 'child_process';
import { existsSync } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { v4 as uuidv4 } from 'uuid';
import { settingsStore } from '../store/db.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const AGENT_SETTINGS = path.resolve(__dirname, '..', '..', 'data', 'agent-settings.json');
const CLAUDE_BIN = resolveBin();
const activeExecutions = new Map();
const MAX_OUTPUT_SIZE = 512 * 1024;
@@ -48,9 +51,7 @@ function cleanEnv(agentSecrets) {
const env = { ...process.env };
delete env.CLAUDECODE;
delete env.ANTHROPIC_API_KEY;
if (!env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) {
env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = '128000';
}
env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = '128000';
if (agentSecrets && typeof agentSecrets === 'object') {
Object.assign(env, agentSecrets);
}
@@ -61,6 +62,10 @@ function buildArgs(agentConfig, prompt) {
const model = agentConfig.model || 'claude-sonnet-4-6';
const args = ['-p', prompt, '--output-format', 'stream-json', '--verbose', '--model', model];
if (existsSync(AGENT_SETTINGS)) {
args.push('--settings', AGENT_SETTINGS);
}
if (agentConfig.systemPrompt) {
args.push('--system-prompt', agentConfig.systemPrompt);
}
@@ -358,6 +363,10 @@ export function resume(agentConfig, sessionId, message, callbacks = {}) {
'--permission-mode', agentConfig.permissionMode || 'bypassPermissions',
];
if (existsSync(AGENT_SETTINGS)) {
args.push('--settings', AGENT_SETTINGS);
}
if (agentConfig.maxTurns && agentConfig.maxTurns > 0) {
args.push('--max-turns', String(agentConfig.maxTurns));
}
@@ -412,6 +421,65 @@ export function getActiveExecutions() {
}));
}
export function summarize(text, threshold = 1500) {
return new Promise((resolve) => {
if (!text || text.length <= threshold) {
resolve(text);
return;
}
const prompt = `Resuma o conteúdo abaixo de forma estruturada e concisa. Preserve TODAS as informações críticas:
- Decisões técnicas e justificativas
- Trechos de código essenciais
- Dados, números e métricas
- Problemas encontrados e soluções
- Recomendações e próximos passos
Organize o resumo usando <tags> XML (ex: <decisoes>, <codigo>, <problemas>, <recomendacoes>).
NÃO omita informações que seriam necessárias para outro profissional continuar o trabalho.
<conteudo_para_resumir>
${text}
</conteudo_para_resumir>`;
const args = [
'-p', prompt,
'--output-format', 'text',
'--model', 'claude-haiku-4-5-20251001',
'--max-turns', '1',
'--permission-mode', 'bypassPermissions',
];
if (existsSync(AGENT_SETTINGS)) {
args.push('--settings', AGENT_SETTINGS);
}
const child = spawn(CLAUDE_BIN, args, {
env: cleanEnv(),
stdio: ['ignore', 'pipe', 'pipe'],
});
let output = '';
const timer = setTimeout(() => {
child.kill('SIGTERM');
}, 120000);
child.stdout.on('data', (chunk) => { output += chunk.toString(); });
child.on('close', () => {
clearTimeout(timer);
const result = output.trim();
console.log(`[executor] Sumarização: ${text.length}${result.length} chars`);
resolve(result || text);
});
child.on('error', () => {
clearTimeout(timer);
resolve(text);
});
});
}
export function getBinPath() {
return CLAUDE_BIN;
}

View File

@@ -273,6 +273,23 @@ export function getRecentExecutions(limit = 20) {
return recentExecBuffer.slice(0, Math.min(limit, MAX_RECENT));
}
async function executeWithRetry(agentId, taskDescription, metadata, maxRetries = 10, baseDelay = 30000) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
executeTask(agentId, taskDescription, null, null, metadata);
return;
} catch (err) {
if (err.message.includes('Limite de execuções simultâneas') && attempt < maxRetries) {
const delay = baseDelay + Math.random() * 10000;
console.log(`[manager] Agendamento aguardando slot (tentativa ${attempt}/${maxRetries}), retry em ${(delay / 1000).toFixed(0)}s`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw err;
}
}
}
export function scheduleTask(agentId, taskDescription, cronExpression, wsCallback) {
const agent = agentsStore.getById(agentId);
if (!agent) throw new Error(`Agente ${agentId} não encontrado`);
@@ -296,7 +313,9 @@ export function scheduleTask(agentId, taskDescription, cronExpression, wsCallbac
schedulesStore.save(items);
scheduler.schedule(scheduleId, cronExpression, () => {
executeTask(agentId, taskDescription, null, null, { source: 'schedule', scheduleId });
executeWithRetry(agentId, taskDescription, { source: 'schedule', scheduleId }).catch(err => {
console.log(`[manager] Agendamento ${scheduleId} falhou após retries: ${err.message}`);
});
}, false);
return { scheduleId, agentId, agentName: agent.agent_name, taskDescription, cronExpression };
@@ -314,7 +333,9 @@ export function updateScheduleTask(scheduleId, data, wsCallback) {
const cronExpression = data.cronExpression || stored.cronExpression;
scheduler.updateSchedule(scheduleId, cronExpression, () => {
executeTask(agentId, taskDescription, null, null, { source: 'schedule', scheduleId });
executeWithRetry(agentId, taskDescription, { source: 'schedule', scheduleId }).catch(err => {
console.log(`[manager] Agendamento ${scheduleId} falhou após retries: ${err.message}`);
});
});
schedulesStore.update(scheduleId, { agentId, agentName: agent.agent_name, taskDescription, cronExpression });
@@ -424,10 +445,8 @@ export function importAgent(data) {
export function restoreSchedules() {
scheduler.restoreSchedules((agentId, taskDescription, scheduleId) => {
try {
executeTask(agentId, taskDescription, null, null, { source: 'schedule', scheduleId });
} catch (err) {
executeWithRetry(agentId, taskDescription, { source: 'schedule', scheduleId }).catch(err => {
console.log(`[manager] Erro ao executar tarefa agendada: ${err.message}`);
}
});
});
}

View File

@@ -86,6 +86,7 @@ function executeStepAsPromise(agentConfig, prompt, pipelineState, wsCallback, pi
costUsd: result.costUsd || 0,
durationMs: result.durationMs || 0,
numTurns: result.numTurns || 0,
sessionId: result.sessionId || '',
});
},
}
@@ -237,7 +238,7 @@ export async function executePipeline(pipelineId, initialInput, wsCallback, opti
totalCost += stepResult.costUsd;
currentInput = stepResult.text;
results.push({ stepId: step.id, agentName: agent.agent_name, result: stepResult.text });
results.push({ stepId: step.id, agentId: step.agentId, agentName: agent.agent_name, result: stepResult.text, sessionId: stepResult.sessionId });
const current = executionsStore.getById(historyRecord.id);
const savedSteps = current ? (current.steps || []) : [];
@@ -266,6 +267,19 @@ export async function executePipeline(pipelineId, initialInput, wsCallback, opti
costUsd: stepResult.costUsd,
});
}
if (i < steps.length - 1 && !pipelineState.canceled) {
if (wsCallback) {
wsCallback({ type: 'pipeline_summarizing', pipelineId, stepIndex: i, originalLength: currentInput.length });
}
const summarized = await executor.summarize(currentInput);
if (summarized !== currentInput) {
if (wsCallback) {
wsCallback({ type: 'pipeline_summarized', pipelineId, stepIndex: i, originalLength: currentInput.length, summarizedLength: summarized.length });
}
currentInput = summarized;
}
}
}
activePipelines.delete(executionId);
@@ -285,7 +299,17 @@ export async function executePipeline(pipelineId, initialInput, wsCallback, opti
if (wsCallback) wsCallback({ type: 'report_generated', pipelineId, reportFile: report.filename });
}
} catch (e) { console.error('[pipeline] Erro ao gerar relatório:', e.message); }
if (wsCallback) wsCallback({ type: 'pipeline_complete', pipelineId, executionId, results, totalCostUsd: totalCost });
const lastResult = results.length > 0 ? results[results.length - 1] : null;
if (wsCallback) wsCallback({
type: 'pipeline_complete',
pipelineId,
executionId,
results,
totalCostUsd: totalCost,
lastAgentId: lastResult?.agentId || '',
lastAgentName: lastResult?.agentName || '',
lastSessionId: lastResult?.sessionId || '',
});
}
return { executionId, results };