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

@@ -70,6 +70,7 @@ const App = {
case 'tasks': await TasksUI.load(); break;
case 'schedules': await SchedulesUI.load(); break;
case 'pipelines': await PipelinesUI.load(); break;
case 'settings': await SettingsUI.load(); break;
}
} catch (err) {
Toast.error(`Erro ao carregar seção: ${err.message}`);
@@ -78,7 +79,8 @@ const App = {
setupWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const url = `${protocol}//${window.location.host}`;
const clientId = API.clientId;
const url = `${protocol}//${window.location.host}?clientId=${clientId}`;
try {
App.ws = new WebSocket(url);
@@ -126,11 +128,14 @@ const App = {
handleWsMessage(data) {
switch (data.type) {
case 'connected':
break;
case 'execution_output': {
Terminal.stopProcessing();
const content = data.data?.content || '';
if (content) {
Terminal.addLine(content, 'default');
Terminal.addLine(content, 'default', data.executionId);
}
App._updateActiveBadge();
break;
@@ -140,12 +145,12 @@ const App = {
Terminal.stopProcessing();
const result = data.data?.result || '';
if (result) {
Terminal.addLine(result, 'success');
Terminal.addLine(result, 'success', data.executionId);
} else {
Terminal.addLine('Execução concluída (sem resultado textual).', 'info');
Terminal.addLine('Execução concluída (sem resultado textual).', 'info', data.executionId);
}
if (data.data?.stderr) {
Terminal.addLine(data.data.stderr, 'error');
Terminal.addLine(data.data.stderr, 'error', data.executionId);
}
Toast.success('Execução concluída');
App.refreshCurrentSection();
@@ -155,11 +160,20 @@ const App = {
case 'execution_error':
Terminal.stopProcessing();
Terminal.addLine(data.data?.error || 'Erro na execução', 'error');
Terminal.addLine(data.data?.error || 'Erro na execução', 'error', data.executionId);
Toast.error(`Erro na execução: ${data.data?.error || 'desconhecido'}`);
App._updateActiveBadge();
break;
case 'pipeline_step_output': {
Terminal.stopProcessing();
const stepContent = data.data?.content || '';
if (stepContent) {
Terminal.addLine(stepContent, 'default', data.executionId);
}
break;
}
case 'pipeline_step_start':
Terminal.stopProcessing();
Terminal.addLine(`Pipeline passo ${data.stepIndex + 1}/${data.totalSteps}: Executando agente "${data.agentName}"...`, 'system');
@@ -238,6 +252,7 @@ const App = {
on('new-agent-btn', 'click', () => AgentsUI.openCreateModal());
on('agents-empty-new-btn', 'click', () => AgentsUI.openCreateModal());
on('import-agent-btn', 'click', () => AgentsUI.openImportModal());
on('agent-form-submit', 'click', (e) => {
e.preventDefault();
@@ -249,6 +264,8 @@ const App = {
AgentsUI.save();
});
on('import-confirm-btn', 'click', () => AgentsUI.importAgent());
on('execute-form-submit', 'click', (e) => {
e.preventDefault();
App._handleExecute();
@@ -297,7 +314,53 @@ const App = {
on('settings-form', 'submit', (e) => {
e.preventDefault();
Toast.info('Configurações salvas');
SettingsUI.save();
});
on('agents-search', 'input', () => {
AgentsUI.filter(
document.getElementById('agents-search')?.value,
document.getElementById('agents-filter-status')?.value
);
});
on('agents-filter-status', 'change', () => {
AgentsUI.filter(
document.getElementById('agents-search')?.value,
document.getElementById('agents-filter-status')?.value
);
});
on('tasks-search', 'input', () => {
TasksUI.filter(
document.getElementById('tasks-search')?.value,
document.getElementById('tasks-filter-category')?.value
);
});
on('tasks-filter-category', 'change', () => {
TasksUI.filter(
document.getElementById('tasks-search')?.value,
document.getElementById('tasks-filter-category')?.value
);
});
on('schedules-search', 'input', () => {
SchedulesUI.filter(
document.getElementById('schedules-search')?.value,
document.getElementById('schedules-filter-status')?.value
);
});
on('schedules-filter-status', 'change', () => {
SchedulesUI.filter(
document.getElementById('schedules-search')?.value,
document.getElementById('schedules-filter-status')?.value
);
});
on('pipelines-search', 'input', () => {
PipelinesUI.filter(document.getElementById('pipelines-search')?.value);
});
document.getElementById('agents-grid')?.addEventListener('click', (e) => {
@@ -320,7 +383,10 @@ const App = {
const { action, id } = btn.dataset;
if (action === 'delete-task') TasksUI.delete(id);
switch (action) {
case 'edit-task': TasksUI.openEditModal(id); break;
case 'delete-task': TasksUI.delete(id); break;
}
});
document.getElementById('schedules-tbody')?.addEventListener('click', (e) => {
@@ -329,7 +395,10 @@ const App = {
const { action, id } = btn.dataset;
if (action === 'delete-schedule') SchedulesUI.delete(id);
switch (action) {
case 'edit-schedule': SchedulesUI.openEditModal(id); break;
case 'delete-schedule': SchedulesUI.delete(id); break;
}
});
document.getElementById('pipelines-grid')?.addEventListener('click', (e) => {