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,11 +1,36 @@
import { spawn } from 'child_process';
import { existsSync } from 'fs';
import { v4 as uuidv4 } from 'uuid';
import { settingsStore } from '../store/db.js';
const CLAUDE_BIN = '/home/fred/.local/bin/claude';
const DEFAULT_MODEL = 'claude-sonnet-4-6';
const CLAUDE_BIN = resolveBin();
const activeExecutions = new Map();
function resolveBin() {
if (process.env.CLAUDE_BIN) return process.env.CLAUDE_BIN;
const home = process.env.HOME || '';
const candidates = [
`${home}/.local/bin/claude`,
'/usr/local/bin/claude',
'/usr/bin/claude',
];
for (const p of candidates) {
if (existsSync(p)) return p;
}
return 'claude';
}
function sanitizeText(str) {
if (typeof str !== 'string') return '';
return str
.replace(/\x00/g, '')
.replace(/[\x01-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '')
.slice(0, 50000);
}
function cleanEnv() {
const env = { ...process.env };
delete env.CLAUDECODE;
@@ -14,20 +39,33 @@ function cleanEnv() {
}
function buildArgs(agentConfig, prompt) {
const model = agentConfig.model || DEFAULT_MODEL;
const model = agentConfig.model || 'claude-sonnet-4-6';
const args = ['-p', prompt, '--output-format', 'stream-json', '--verbose', '--model', model];
if (agentConfig.systemPrompt) {
args.push('--system-prompt', agentConfig.systemPrompt);
}
if (agentConfig.maxTurns && agentConfig.maxTurns > 0) {
args.push('--max-turns', String(agentConfig.maxTurns));
}
if (agentConfig.allowedTools && agentConfig.allowedTools.length > 0) {
const tools = Array.isArray(agentConfig.allowedTools)
? agentConfig.allowedTools.join(',')
: agentConfig.allowedTools;
args.push('--allowedTools', tools);
}
args.push('--permission-mode', agentConfig.permissionMode || 'bypassPermissions');
return args;
}
function buildPrompt(task, instructions) {
const parts = [];
if (task) parts.push(task);
if (instructions) parts.push(`\nInstruções adicionais:\n${instructions}`);
if (task) parts.push(sanitizeText(task));
if (instructions) parts.push(`\nInstruções adicionais:\n${sanitizeText(instructions)}`);
return parts.join('\n');
}
@@ -74,7 +112,19 @@ function extractText(event) {
return null;
}
function getMaxConcurrent() {
const s = settingsStore.get();
return s.maxConcurrent || 5;
}
export function execute(agentConfig, task, callbacks = {}) {
const maxConcurrent = getMaxConcurrent();
if (activeExecutions.size >= maxConcurrent) {
const err = new Error(`Limite de ${maxConcurrent} execuções simultâneas atingido`);
if (callbacks.onError) callbacks.onError(err, uuidv4());
return null;
}
const executionId = uuidv4();
const { onData, onError, onComplete } = callbacks;
@@ -96,7 +146,7 @@ export function execute(agentConfig, task, callbacks = {}) {
}
console.log(`[executor] Iniciando: ${executionId}`);
console.log(`[executor] Modelo: ${agentConfig.model || DEFAULT_MODEL}`);
console.log(`[executor] Modelo: ${agentConfig.model || 'claude-sonnet-4-6'}`);
console.log(`[executor] cwd: ${spawnOptions.cwd || process.cwd()}`);
const child = spawn(CLAUDE_BIN, args, spawnOptions);
@@ -180,6 +230,13 @@ export function cancel(executionId) {
return true;
}
export function cancelAllExecutions() {
for (const [id, exec] of activeExecutions) {
exec.process.kill('SIGTERM');
}
activeExecutions.clear();
}
export function getActiveExecutions() {
return Array.from(activeExecutions.entries()).map(([id, exec]) => ({
executionId: id,
@@ -187,3 +244,7 @@ export function getActiveExecutions() {
agentConfig: exec.agentConfig,
}));
}
export function getBinPath() {
return CLAUDE_BIN;
}