Correções de bugs, layout de cards e webhook test funcional

- Pipeline cancel/approve/reject corrigido com busca bidirecional
- Secrets injetados no executor via cleanEnv
- Versionamento automático ao atualizar agentes
- writeJsonAsync com log de erro
- Removido asyncHandler.js (código morto)
- Restaurado permissionMode padrão bypassPermissions
- Ícones dos cards alinhados à direita com wrapper
- Botão Editar convertido para ícone nos cards
- Webhook test agora dispara execução real do agente/pipeline
- Corrigido App.navigateTo no teste de webhook
This commit is contained in:
Frederico Castro
2026-02-26 23:28:50 -03:00
parent bbd2ec46dd
commit 9b66a415ff
17 changed files with 1147 additions and 259 deletions

View File

@@ -1,5 +1,6 @@
import { v4 as uuidv4 } from 'uuid';
import { agentsStore, schedulesStore, executionsStore, notificationsStore } from '../store/db.js';
import cron from 'node-cron';
import { agentsStore, schedulesStore, executionsStore, notificationsStore, secretsStore, agentVersionsStore, withLock } from '../store/db.js';
import * as executor from './executor.js';
import * as scheduler from './scheduler.js';
import { generateAgentReport } from '../reports/generator.js';
@@ -99,6 +100,13 @@ export function createAgent(data) {
export function updateAgent(id, data) {
const existing = agentsStore.getById(id);
if (!existing) return null;
agentVersionsStore.create({
agentId: id,
version: existing,
changedFields: Object.keys(data).filter(k => k !== 'id'),
});
const updateData = {};
if (data.agent_name !== undefined) updateData.agent_name = data.agent_name;
if (data.description !== undefined) updateData.description = data.description;
@@ -114,25 +122,44 @@ export function deleteAgent(id) {
return agentsStore.delete(id);
}
function loadAgentSecrets(agentId) {
const all = secretsStore.getAll();
const agentSecrets = all.filter(s => s.agentId === agentId);
if (agentSecrets.length === 0) return null;
const env = {};
for (const s of agentSecrets) env[s.name] = s.value;
return env;
}
export function executeTask(agentId, task, instructions, wsCallback, metadata = {}) {
const agent = agentsStore.getById(agentId);
if (!agent) throw new Error(`Agente ${agentId} não encontrado`);
if (agent.status !== 'active') throw new Error(`Agente ${agentId} está inativo`);
const retryEnabled = agent.config?.retryOnFailure === true;
const maxRetries = Math.min(Math.max(parseInt(agent.config?.maxRetries) || 1, 1), 3);
const attempt = metadata._retryAttempt || 1;
const cb = getWsCallback(wsCallback);
const taskText = typeof task === 'string' ? task : task.description;
const startedAt = new Date().toISOString();
const historyRecord = executionsStore.create({
type: 'agent',
...metadata,
agentId,
agentName: agent.agent_name,
task: taskText,
instructions: instructions || '',
status: 'running',
startedAt,
});
const historyRecord = metadata._historyRecordId
? { id: metadata._historyRecordId }
: executionsStore.create({
type: 'agent',
...metadata,
agentId,
agentName: agent.agent_name,
task: taskText,
instructions: instructions || '',
status: 'running',
startedAt,
});
if (metadata._retryAttempt) {
executionsStore.update(historyRecord.id, { status: 'running', error: null });
}
const execRecord = {
executionId: null,
@@ -143,6 +170,8 @@ export function executeTask(agentId, task, instructions, wsCallback, metadata =
status: 'running',
};
const agentSecrets = loadAgentSecrets(agentId);
const executionId = executor.execute(
agent.config,
{ description: task, instructions },
@@ -153,6 +182,31 @@ export function executeTask(agentId, task, instructions, wsCallback, metadata =
onError: (err, execId) => {
const endedAt = new Date().toISOString();
updateExecutionRecord(agentId, execId, { status: 'error', error: err.message, endedAt });
if (retryEnabled && attempt < maxRetries) {
const delayMs = attempt * 5000;
executionsStore.update(historyRecord.id, { status: 'retrying', error: err.message, attempt, endedAt });
if (cb) cb({
type: 'execution_retry',
executionId: execId,
agentId,
data: { attempt, maxRetries, nextRetryIn: delayMs / 1000, reason: err.message },
});
setTimeout(() => {
try {
executeTask(agentId, task, instructions, wsCallback, {
...metadata,
_retryAttempt: attempt + 1,
_historyRecordId: historyRecord.id,
});
} catch (retryErr) {
executionsStore.update(historyRecord.id, { status: 'error', error: retryErr.message, endedAt: new Date().toISOString() });
if (cb) cb({ type: 'execution_error', executionId: execId, agentId, data: { error: retryErr.message } });
}
}, delayMs);
return;
}
executionsStore.update(historyRecord.id, { status: 'error', error: err.message, endedAt });
createNotification('error', 'Execução falhou', `Agente "${agent.agent_name}" encontrou um erro`, { agentId, executionId: execId });
if (cb) cb({ type: 'execution_error', executionId: execId, agentId, data: { error: err.message } });
@@ -178,10 +232,11 @@ export function executeTask(agentId, task, instructions, wsCallback, metadata =
const report = generateAgentReport(updated);
if (cb) cb({ type: 'report_generated', executionId: execId, agentId, reportFile: report.filename });
}
} catch (e) {}
} catch (e) { console.error('[manager] Erro ao gerar relatório:', e.message); }
if (cb) cb({ type: 'execution_complete', executionId: execId, agentId, data: result });
},
}
},
agentSecrets
);
if (!executionId) {
@@ -203,18 +258,15 @@ export function executeTask(agentId, task, instructions, wsCallback, metadata =
return executionId;
}
function updateRecentBuffer(executionId, updates) {
const entry = recentExecBuffer.find((e) => e.executionId === executionId);
if (entry) Object.assign(entry, updates);
}
function updateExecutionRecord(agentId, executionId, updates) {
const agent = agentsStore.getById(agentId);
if (!agent) return;
const executions = (agent.executions || []).map((exec) =>
exec.executionId === executionId ? { ...exec, ...updates } : exec
);
agentsStore.update(agentId, { executions });
async function updateExecutionRecord(agentId, executionId, updates) {
await withLock(`agent:${agentId}`, () => {
const agent = agentsStore.getById(agentId);
if (!agent) return;
const executions = (agent.executions || []).map((exec) =>
exec.executionId === executionId ? { ...exec, ...updates } : exec
);
agentsStore.update(agentId, { executions });
});
}
export function getRecentExecutions(limit = 20) {
@@ -225,6 +277,10 @@ export function scheduleTask(agentId, taskDescription, cronExpression, wsCallbac
const agent = agentsStore.getById(agentId);
if (!agent) throw new Error(`Agente ${agentId} não encontrado`);
if (!cron.validate(cronExpression)) {
throw new Error(`Expressão cron inválida: ${cronExpression}`);
}
const scheduleId = uuidv4();
const items = schedulesStore.getAll();
items.push({
@@ -314,7 +370,7 @@ export function continueConversation(agentId, sessionId, message, wsCallback) {
const report = generateAgentReport(updated);
if (cb) cb({ type: 'report_generated', executionId: execId, agentId, reportFile: report.filename });
}
} catch (e) {}
} catch (e) { console.error('[manager] Erro ao gerar relatório:', e.message); }
if (cb) cb({ type: 'execution_complete', executionId: execId, agentId, data: result });
},
}