/** * Engine Room Routes - Casa de Máquinas (Simplificado) * * APENAS consulta o Registry do Kernel. * Não gerencia mais presets - mostra apenas serviços descobertos. */ import type { Express, Request, Response } from "express"; import http from "http"; // Consulta Registry do Kernel para serviços descobertos // Usa http.get em vez de fetch por causa de problemas com loopback no Node.js // Implementa retry com backoff para maior resiliência async function fetchRegistryServicesWithRetry(maxRetries = 3, delay = 500): Promise { for (let attempt = 1; attempt <= maxRetries; attempt++) { const result = await fetchRegistryServicesOnce(); if (result !== null) { return result; } if (attempt < maxRetries) { console.log(`[Engine Room] Tentativa ${attempt} falhou, aguardando ${delay}ms...`); await new Promise(r => setTimeout(r, delay)); delay *= 2; // Exponential backoff } } return null; } // Tenta múltiplos hostnames (127.0.0.1, localhost) para conectar ao Kernel async function fetchRegistryServicesOnce(): Promise { const hostnames = ['127.0.0.1', 'localhost']; for (const hostname of hostnames) { const result = await tryConnectToRegistry(hostname); if (result !== null) { return result; } } return null; } function tryConnectToRegistry(hostname: string): Promise { return new Promise((resolve) => { const options = { hostname, port: 5001, path: '/api/registry/services', method: 'GET', timeout: 3000, }; console.log(`[Engine Room] Tentando conectar em ${hostname}:5001...`); const req = http.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { try { const json = JSON.parse(data); const services = json.success && Array.isArray(json.data?.services) ? json.data.services : null; console.log(`[Engine Room] Registry retornou ${services?.length || 0} serviços via ${hostname}`); resolve(services); } catch (e) { console.error(`[Engine Room] Erro ao parsear resposta de ${hostname}:`, e); resolve(null); } }); }); req.on('error', (error) => { console.error(`[Engine Room] Erro ao consultar Registry em ${hostname}:`, error.message); resolve(null); }); req.on('timeout', () => { console.error(`[Engine Room] Timeout ao consultar Registry em ${hostname}`); req.destroy(); resolve(null); }); req.end(); }); } // Converte serviço do Registry para formato EngineStatus function mapRegistryToEngine(service: any): any { const id = service.id || service.name; let status: 'online' | 'offline' | 'error' = 'offline'; if (service.healthStatus === 'healthy') status = 'online'; else if (service.healthStatus === 'unhealthy') status = 'error'; return { name: id, displayName: service.displayName || service.name, type: service.type || 'unknown', port: service.port || 0, category: service.category || 'data', description: service.description || `Serviço ${service.name}`, status, responseTime: undefined, details: { endpoint: service.endpoint, health: service.healthStatus, version: service.version, capabilities: service.capabilities?.map((c: any) => c.name) || [], source: 'registry', }, }; } export function registerEngineRoomRoutes(app: Express): void { // Status da Casa de Máquinas - APENAS do Registry app.get("/api/engine-room/status", async (req: Request, res: Response) => { try { if (!req.isAuthenticated()) { return res.status(401).json({ error: "Not authenticated" }); } const registryServices = await fetchRegistryServicesWithRetry(); const engines: any[] = []; if (registryServices && registryServices.length > 0) { for (const service of registryServices) { engines.push(mapRegistryToEngine(service)); } } let agentsStatus: any[] = []; try { const { getAgentsStatus } = await import("../blackboard/agents"); agentsStatus = getAgentsStatus(); } catch { agentsStatus = []; } const online = engines.filter((r) => r.status === "online").length; const total = engines.length; res.json({ engines, agents: agentsStatus, summary: { total_engines: total, online_engines: online, offline_engines: total - online, health_pct: total > 0 ? Math.round((online / total) * 100) : 0, total_agents: agentsStatus.length, running_agents: agentsStatus.filter((a: any) => a.running).length, }, timestamp: new Date().toISOString(), }); } catch (error: any) { console.error("[Engine Room] Status error:", error); res.status(500).json({ error: error.message }); } }); // Lista de engines - APENAS do Registry app.get("/api/engine-room/engines", async (_req: Request, res: Response) => { const registryServices = await fetchRegistryServicesWithRetry(); const engines = registryServices?.map(mapRegistryToEngine) || []; res.json({ engines: engines.map((e) => ({ name: e.name, displayName: e.displayName, type: e.type, port: e.port, category: e.category, description: e.description, })), }); }); // Agents app.get("/api/engine-room/agents", async (req: Request, res: Response) => { try { if (!req.isAuthenticated()) { return res.status(401).json({ error: "Not authenticated" }); } let agentsStatus: any[] = []; try { const { getAgentsStatus } = await import("../blackboard/agents"); agentsStatus = getAgentsStatus(); } catch { agentsStatus = []; } res.json({ agents: agentsStatus }); } catch (error: any) { res.status(500).json({ error: error.message }); } }); app.post("/api/engine-room/agents/start", async (req: Request, res: Response) => { try { if (!req.isAuthenticated()) { return res.status(401).json({ error: "Not authenticated" }); } try { const { startAllAgents } = await import("../blackboard/agents"); startAllAgents(); res.json({ success: true, message: "Agentes iniciados" }); } catch (err: any) { res.status(500).json({ error: err.message }); } } catch (error: any) { res.status(500).json({ error: error.message }); } }); app.post("/api/engine-room/agents/stop", async (req: Request, res: Response) => { try { if (!req.isAuthenticated()) { return res.status(401).json({ error: "Not authenticated" }); } try { const { stopAllAgents } = await import("../blackboard/agents"); stopAllAgents(); res.json({ success: true, message: "Agentes parados" }); } catch (err: any) { res.status(500).json({ error: err.message }); } } catch (error: any) { res.status(500).json({ error: error.message }); } }); // AÇÕES DESABILITADAS - Não gerenciamos mais presets // Os serviços são gerenciados pelo Docker/Coolify app.post("/api/engine-room/engine/:name/restart", async (req: Request, res: Response) => { res.status(501).json({ error: "Não implementado", message: "Serviços são gerenciados pelo Docker/Coolify. Use o painel do Coolify." }); }); app.post("/api/engine-room/engine/:name/stop", async (req: Request, res: Response) => { res.status(501).json({ error: "Não implementado", message: "Serviços são gerenciados pelo Docker/Coolify. Use o painel do Coolify." }); }); app.post("/api/engine-room/engine/:name/start", async (req: Request, res: Response) => { res.status(501).json({ error: "Não implementado", message: "Serviços são gerenciados pelo Docker/Coolify. Use o painel do Coolify." }); }); app.get("/api/engine-room/engine/:name/info", async (req: Request, res: Response) => { try { if (!req.isAuthenticated()) { return res.status(401).json({ error: "Not authenticated" }); } const registryServices = await fetchRegistryServicesWithRetry(); const service = registryServices?.find((s: any) => s.id === req.params.name || s.name === req.params.name); if (!service) { return res.status(404).json({ error: "Servico nao encontrado no registry" }); } res.json(mapRegistryToEngine(service)); } catch (error: any) { res.status(500).json({ error: error.message }); } }); app.get("/api/engine-room/engine/:name/logs", async (req: Request, res: Response) => { res.status(501).json({ error: "Não implementado", message: "Logs disponiveis via Docker: docker logs " }); }); console.log("[Engine Room] Rotas registradas (modo simplificado - apenas Registry)"); }