Implementação completa de funcionalidades pendentes

- Settings persistentes (modelo padrão, workdir, max concurrent)
- Import/export de agentes via JSON
- Agendamentos persistentes com restore no startup
- Edição de agendamentos e tarefas existentes
- Filtros e busca em todas as seções
- Isolamento de WebSocket por clientId
- Autenticação via AUTH_TOKEN e CORS configurável
- Graceful shutdown com cancelamento de execuções
- Correção: --max-tokens removido (flag inválida do CLI)
- Correção: pipeline agora verifica exit code e propaga erros
- Correção: streaming de output em pipelines via WebSocket
- Permission mode bypassPermissions como padrão
- Página de configurações do sistema
- Contagem diária de execuções no dashboard
- Histórico de execuções recentes
This commit is contained in:
Frederico Castro
2026-02-26 01:24:51 -03:00
parent 723a08d2e1
commit 2f7a9d4c56
18 changed files with 1104 additions and 115 deletions

View File

@@ -1,5 +1,5 @@
import { v4 as uuidv4 } from 'uuid';
import { agentsStore } from '../store/db.js';
import { agentsStore, schedulesStore } from '../store/db.js';
import * as executor from './executor.js';
import * as scheduler from './scheduler.js';
@@ -7,10 +7,32 @@ const DEFAULT_CONFIG = {
model: 'claude-sonnet-4-6',
systemPrompt: '',
workingDirectory: '',
maxTokens: 16000,
temperature: 1,
maxTurns: 0,
permissionMode: 'bypassPermissions',
allowedTools: '',
};
let dailyExecutionCount = 0;
let dailyCountDate = new Date().toDateString();
function incrementDailyCount() {
const today = new Date().toDateString();
if (today !== dailyCountDate) {
dailyExecutionCount = 0;
dailyCountDate = today;
}
dailyExecutionCount++;
}
export function getDailyExecutionCount() {
const today = new Date().toDateString();
if (today !== dailyCountDate) {
dailyExecutionCount = 0;
dailyCountDate = today;
}
return dailyExecutionCount;
}
function validateAgent(data) {
const errors = [];
if (!data.agent_name || typeof data.agent_name !== 'string') {
@@ -22,6 +44,13 @@ function validateAgent(data) {
return errors;
}
function sanitizeTags(tags) {
if (!Array.isArray(tags)) return [];
return tags
.filter((t) => typeof t === 'string' && t.length > 0 && t.length <= 50)
.slice(0, 20);
}
export function getAllAgents() {
return agentsStore.getAll();
}
@@ -39,6 +68,7 @@ export function createAgent(data) {
const agentData = {
agent_name: data.agent_name,
description: data.description || '',
tags: sanitizeTags(data.tags),
tasks: data.tasks || [],
config: { ...DEFAULT_CONFIG, ...(data.config || {}) },
status: data.status || 'active',
@@ -56,6 +86,7 @@ export function updateAgent(id, data) {
const updateData = {};
if (data.agent_name !== undefined) updateData.agent_name = data.agent_name;
if (data.description !== undefined) updateData.description = data.description;
if (data.tags !== undefined) updateData.tags = sanitizeTags(data.tags);
if (data.tasks !== undefined) updateData.tasks = data.tasks;
if (data.status !== undefined) updateData.status = data.status;
if (data.assigned_host !== undefined) updateData.assigned_host = data.assigned_host;
@@ -78,6 +109,7 @@ export function executeTask(agentId, task, instructions, wsCallback) {
const executionRecord = {
executionId: null,
agentId,
agentName: agent.agent_name,
task: typeof task === 'string' ? task : task.description,
startedAt: new Date().toISOString(),
status: 'running',
@@ -122,7 +154,12 @@ export function executeTask(agentId, task, instructions, wsCallback) {
}
);
if (!executionId) {
throw new Error('Limite de execuções simultâneas atingido');
}
executionRecord.executionId = executionId;
incrementDailyCount();
const updatedAgent = agentsStore.getById(agentId);
const executions = [...(updatedAgent.executions || []), executionRecord];
@@ -151,11 +188,49 @@ export function scheduleTask(agentId, taskDescription, cronExpression, wsCallbac
const scheduleId = uuidv4();
const items = schedulesStore.getAll();
items.push({
id: scheduleId,
agentId,
agentName: agent.agent_name,
taskDescription,
cronExpression,
active: true,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
});
schedulesStore.save(items);
scheduler.schedule(scheduleId, cronExpression, () => {
executeTask(agentId, taskDescription, null, wsCallback);
}, false);
return { scheduleId, agentId, agentName: agent.agent_name, taskDescription, cronExpression };
}
export function updateScheduleTask(scheduleId, data, wsCallback) {
const stored = schedulesStore.getById(scheduleId);
if (!stored) return null;
const agentId = data.agentId || stored.agentId;
const agent = agentsStore.getById(agentId);
if (!agent) throw new Error(`Agente ${agentId} não encontrado`);
const taskDescription = data.taskDescription || stored.taskDescription;
const cronExpression = data.cronExpression || stored.cronExpression;
scheduler.updateSchedule(scheduleId, cronExpression, () => {
executeTask(agentId, taskDescription, null, wsCallback);
});
return { scheduleId, agentId, taskDescription, cronExpression };
schedulesStore.update(scheduleId, {
agentId,
agentName: agent.agent_name,
taskDescription,
cronExpression,
});
return schedulesStore.getById(scheduleId);
}
export function cancelExecution(executionId) {
@@ -166,20 +241,59 @@ export function getActiveExecutions() {
return executor.getActiveExecutions();
}
export function getRecentExecutions(limit = 20) {
const agents = agentsStore.getAll();
const all = agents.flatMap((a) =>
(a.executions || []).map((e) => ({
...e,
agentName: a.agent_name,
agentId: a.id,
}))
);
all.sort((a, b) => new Date(b.startedAt) - new Date(a.startedAt));
return all.slice(0, limit);
}
export function exportAgent(agentId) {
const agent = agentsStore.getById(agentId);
if (!agent) return null;
return {
id: agent.id,
agent_name: agent.agent_name,
description: agent.description,
tags: agent.tags || [],
tasks: agent.tasks,
config: agent.config,
status: agent.status,
assigned_host: agent.assigned_host,
created_at: agent.created_at,
updated_at: agent.updated_at,
executions: agent.executions || [],
};
}
export function importAgent(data) {
if (!data.agent_name) {
throw new Error('agent_name é obrigatório para importação');
}
const agentData = {
agent_name: data.agent_name,
description: data.description || '',
tags: sanitizeTags(data.tags),
tasks: data.tasks || [],
config: { ...DEFAULT_CONFIG, ...(data.config || {}) },
status: data.status || 'active',
assigned_host: data.assigned_host || 'localhost',
executions: [],
};
return agentsStore.create(agentData);
}
export function restoreSchedules(wsCallback) {
scheduler.restoreSchedules((agentId, taskDescription) => {
try {
executeTask(agentId, taskDescription, null, wsCallback);
} catch (err) {
console.log(`[manager] Erro ao executar tarefa agendada: ${err.message}`);
}
});
}