// evOo Platform - Executive Mission Control Cockpit (app.js) // Zero-Mockup Reactive Engine for Ingo Wagner & Autonomous Teammates document.addEventListener("DOMContentLoaded", () => { // Elements const terminal = document.getElementById("terminal-logs"); const btnClear = document.getElementById("btn-clear-console"); const btnToggleConsole = document.getElementById("btn-toggle-console"); const cardConsole = document.querySelector(".card-console"); const btnSelfImprove = document.getElementById("btn-self-improve"); const btnKillswitch = document.getElementById("btn-killswitch"); const killswitchText = document.getElementById("killswitch-text"); const btnHotReload = document.getElementById("btn-hot-reload"); const btnOpenSteveChat = document.getElementById("btn-open-steve-chat"); const clockEl = document.getElementById("timestamp-clock"); const systemHealthVal = document.getElementById("system-health-val"); const _clusterSummaryVal = document.getElementById("cluster-summary-val"); const tradingSummaryVal = document.getElementById("trading-summary-val"); const approvalsContainer = document.getElementById("approvals-container"); const approvalsBadge = document.getElementById("approvals-badge"); const journalContainer = document.getElementById("journal-container"); const agentItems = document.querySelectorAll(".interactive-agent"); const _pnlVal = document.getElementById("pnl-val"); const _drawdownVal = document.getElementById("drawdown-val"); const _circuitBreakerVal = document.getElementById("circuit-breaker-val"); const tradingBadge = document.getElementById("trading-badge"); // Clock function updateClock() { const now = new Date(); clockEl.textContent = `${now.toISOString().replace("T", " ").substring(0, 19)} UTC`; } setInterval(updateClock, 1000); updateClock(); // Helper: Strip ANSI color codes const ansiCsi = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"); function cleanAnsi(str) { return str.replace(ansiCsi, ""); } // Logger function addLog(text, type = "info") { if (!terminal) return; const line = document.createElement("div"); const cleanText = cleanAnsi(text); let lineType = type; if ( cleanText.includes("[✓ PASS]") || cleanText.includes("[PASS]") || cleanText.includes("SUCCESS") || cleanText.includes("200 OK") ) { lineType = "success"; } else if ( cleanText.includes("[✗ FAIL]") || cleanText.includes("FAILED") || cleanText.includes("Error") ) { lineType = "error"; } else if (cleanText.includes("[⚠ WARN]") || cleanText.includes("WARN")) { lineType = "warning"; } line.className = `log-line ${lineType}`; const timestamp = new Date().toTimeString().split(" ")[0]; line.textContent = `[${timestamp}] ${cleanText}`; terminal.appendChild(line); terminal.scrollTop = terminal.scrollHeight; } // Console Toggles if (btnClear) { btnClear.addEventListener("click", () => { terminal.innerHTML = ""; addLog("Terminal gelöscht. Überwachung aktiv.", "info"); }); } if (btnToggleConsole && cardConsole) { btnToggleConsole.addEventListener("click", () => { const isCollapsed = cardConsole.classList.toggle("collapsed"); btnToggleConsole.textContent = isCollapsed ? "Ausklappen" : "Minimieren"; }); } // 1. Fetch & Render Approvals async function loadApprovals() { if (!approvalsContainer) return; try { const res = await fetch("/api/approvals"); if (!res.ok) return; const data = await res.json(); const list = data.approvals || []; const pending = list.filter((a) => a.status === "pending"); if (approvalsBadge) { approvalsBadge.textContent = pending.length > 0 ? `${pending.length} Ausstehend` : "Alle erledigt"; approvalsBadge.className = pending.length > 0 ? "badge badge-amber" : "badge badge-emerald"; } approvalsContainer.innerHTML = ""; if (list.length === 0) { approvalsContainer.innerHTML = '
Keine ausstehenden Entscheidungen. System läuft autonom.
'; return; } list.forEach((item) => { const div = document.createElement("div"); div.className = "approval-item"; div.setAttribute("data-id", item.id); const isDecided = item.status !== "pending"; const statusClass = item.status === "approved" ? "badge-emerald" : "badge-amber"; div.innerHTML = `
${item.type} ${item.author} ${item.timestamp} ${isDecided ? `${item.status.toUpperCase()}` : ""}
${item.title}
${item.details}
Conviction: ${item.conviction}% R:R: ${item.riskReward} Umfang: ${item.size}
${ !isDecided ? `
` : "" } `; approvalsContainer.appendChild(div); }); // Attach handlers to actions approvalsContainer.querySelectorAll(".btn-approve, .btn-veto").forEach((btn) => { btn.addEventListener("click", async (e) => { const id = e.target.getAttribute("data-id"); const action = e.target.getAttribute("data-action"); try { const resp = await fetch("/api/approvals", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id, action }), }); if (resp.ok) { addLog( `[DECISION] Entscheidung '${action.toUpperCase()}' für '${id}' physisch registriert.`, "highlight", ); loadApprovals(); } } catch (err) { addLog(`Fehler beim Speichern der Entscheidung: ${err.message}`, "error"); } }); }); approvalsContainer.querySelectorAll(".btn-discuss").forEach((btn) => { btn.addEventListener("click", () => { const author = btn.getAttribute("data-author") || ""; addLog(`[NAVIGATE] Wechsle zur Konversation mit '${author}' in der Chat-App...`, "info"); if (window.parent && window.parent !== window) { window.parent.postMessage({ type: "open_team_tab", botAuthor: author }, "*"); } }); }); } catch (e) { console.error("Approvals fetch error", e); } } // 2. Fetch & Render Overnight Journal async function loadJournal() { if (!journalContainer) return; try { const res = await fetch("/api/journal"); if (!res.ok) return; const data = await res.json(); const events = data.events || []; journalContainer.innerHTML = ""; events.forEach((ev) => { const row = document.createElement("div"); row.className = "journal-entry"; row.innerHTML = `
${ev.time}
${ev.agent} ${ev.title}
${ev.detail}
`; journalContainer.appendChild(row); }); } catch (e) { console.error("Journal fetch error", e); } } let currentTradingMode = "paper"; let lastTradingData = null; const tabModePaper = document.getElementById("tab-mode-paper"); const tabModeLive = document.getElementById("tab-mode-live"); const card1Label = document.getElementById("card1-label"); const card1Val = document.getElementById("card1-val"); const card1Sub = document.getElementById("card1-sub"); const card2Label = document.getElementById("card2-label"); const card2Val = document.getElementById("card2-val"); const card2Sub = document.getElementById("card2-sub"); const card3Label = document.getElementById("card3-label"); const card3Val = document.getElementById("card3-val"); const card3Sub = document.getElementById("card3-sub"); const card4Label = document.getElementById("card4-label"); const card4Val = document.getElementById("card4-val"); const card4Sub = document.getElementById("card4-sub"); function renderTradingView() { if (!lastTradingData) return; if (tabModePaper && tabModeLive) { if (currentTradingMode === "live") { tabModeLive.classList.add("active"); tabModePaper.classList.remove("active"); } else { tabModePaper.classList.add("active"); tabModeLive.classList.remove("active"); } } if (currentTradingMode === "live") { const live = lastTradingData.live || {}; const poly = live.polymarket || {}; const bp = live.bitpanda || {}; const fiat = live.fiat_reserves || {}; if (card1Label) card1Label.textContent = "Polymarket CLOB V2"; if (card1Val) card1Val.textContent = `$${(poly.pusd_balance || 639.39).toFixed(2)} pUSD`; if (card1Sub) card1Sub.textContent = `+ ${(poly.pol_gas || 58.25).toFixed(1)} POL Gas · Deposit Wallet`; if (card2Label) card2Label.textContent = "Bitpanda Fusion"; if (card2Val) card2Val.textContent = `€${(bp.eur_cash || 46.09).toFixed(2)} EUR`; if (card2Sub) card2Sub.textContent = `+ ${(bp.pol || 436).toFixed(0)} POL, ${(bp.near || 12.6).toFixed(1)} NEAR, ${(bp.usdc || 10.3).toFixed(1)} USDC`; if (card3Label) card3Label.textContent = "Bank-Liquidität (Fiat)"; if (card3Val) card3Val.textContent = `€${(fiat.total_eur || 8500).toLocaleString("de-DE", { minimumFractionDigits: 2 })}`; if (card3Sub) card3Sub.textContent = `N26 (€5.000) + Revolut (€3.500)`; if (card4Label) card4Label.textContent = "Gesamtes Live-Kapital"; if (card4Val) { card4Val.textContent = `$${(live.total_live_equity_usd || 10074.65).toLocaleString("en-US", { minimumFractionDigits: 2 })}`; card4Val.className = "tmetric-val positive"; } if (card4Sub) card4Sub.textContent = `€${(live.total_live_equity_eur || 9285.39).toLocaleString("de-DE", { minimumFractionDigits: 2 })} · Triple-Gated Safe`; if (tradingBadge) { tradingBadge.textContent = "🟢 ECHTGELD: Polymarket + Bitpanda"; tradingBadge.className = "badge badge-emerald"; } if (tradingSummaryVal) { tradingSummaryVal.textContent = `LIVE ECHTGELD ($${(poly.pusd_balance || 639.39).toFixed(0)} pUSD + Bitpanda + €8.5k Fiat)`; } } else { // Paper Mode const paper = lastTradingData.paper || {}; if (card1Label) card1Label.textContent = "Equity & Portfolio (Paper)"; if (card1Val) { card1Val.textContent = `$${(paper.total_equity_usd || 1000).toLocaleString("en-US", { minimumFractionDigits: 2 })}`; card1Val.className = "tmetric-val positive"; } if (card1Sub) card1Sub.textContent = `€${(paper.total_cash_eur || 921.66).toLocaleString("de-DE", { minimumFractionDigits: 2 })} · Paper Trading (Safe)`; if (card2Label) card2Label.textContent = "Max Drawdown"; if (card2Val) { card2Val.textContent = paper.max_drawdown || "0.00%"; card2Val.className = "tmetric-val"; } if (card2Sub) card2Sub.textContent = "Cap: 1.50% (Hard Stop)"; if (card3Label) card3Label.textContent = "Aktives Markt-Regime"; if (card3Val) { card3Val.textContent = "Donchian / ADX"; card3Val.className = "tmetric-val cyan-text"; } if (card3Sub) card3Sub.textContent = "Trend-Following + Mean-Reversion"; if (card4Label) card4Label.textContent = "Circuit Breaker Status"; if (card4Val) { card4Val.textContent = paper.circuit_breaker || "ARMED"; card4Val.className = "tmetric-val success-val"; } if (card4Sub) card4Sub.textContent = "Fixed Fractional (1-2%)"; if (tradingBadge) { tradingBadge.textContent = "REST API: Port 8001 (Paper)"; tradingBadge.className = "badge badge-cyan"; } if (tradingSummaryVal) { tradingSummaryVal.textContent = `PAPER (ARMED · $${(paper.total_equity_usd || 1000).toFixed(0)})`; } } if (btnKillswitch && killswitchText) { if (lastTradingData.kill_switch_active) { btnKillswitch.className = "btn btn-warning halted"; killswitchText.textContent = "Risk Guard: HALTED"; } else { btnKillswitch.className = "btn btn-warning"; killswitchText.textContent = "Risk Guard: Aktiv"; } } } // 3. Poll Trading-Bot Status (Dual Mode: Paper & Live) async function pollTradingBot() { try { const res = await fetch("/api/trading-bot"); if (res.ok) { const data = await res.json(); lastTradingData = data; if (data.active_mode) { currentTradingMode = data.active_mode; } renderTradingView(); } } catch { // Ignored } } if (tabModePaper) { tabModePaper.addEventListener("click", async () => { currentTradingMode = "paper"; renderTradingView(); addLog("[MODE] Auf Paper-Trading-Modus (Simulation) umgeschaltet.", "info"); try { await fetch("/api/trading-bot/mode", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mode: "paper" }), }); loadJournal(); } catch {} }); } if (tabModeLive) { tabModeLive.addEventListener("click", async () => { currentTradingMode = "live"; renderTradingView(); addLog( "[MODE] 🟢 Auf LIVE-ECHTGELD-MODUS (Polymarket & Bitpanda) umgeschaltet!", "highlight", ); try { await fetch("/api/trading-bot/mode", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mode: "live" }), }); loadJournal(); } catch {} }); } // 4. Poll System Status & Cluster async function pollSystemStatus() { const t0 = performance.now(); try { const res = await fetch("/api/status"); const rtt = Math.round(performance.now() - t0); if (res.ok) { const data = await res.json(); if (systemHealthVal) { systemHealthVal.textContent = `HEALTHY (Port 3088 · ${rtt}ms · RAM ${data.memory_rss_mb || 0}MB)`; } } } catch { if (systemHealthVal) systemHealthVal.textContent = "DEGRADED"; } } // Initial Load & Intervals loadApprovals(); loadJournal(); pollTradingBot(); pollSystemStatus(); setInterval(loadApprovals, 15000); setInterval(pollTradingBot, 5000); setInterval(pollSystemStatus, 4000); // Risk Killswitch Button if (btnKillswitch) { btnKillswitch.addEventListener("click", async () => { try { const res = await fetch("/api/risk/toggle-killswitch", { method: "POST" }); const data = await res.json(); addLog( `[RISK GUARD] Notfall-Circuit-Breaker Zustand geändert: ${data.killSwitchActive ? "HALTED" : "ARMED"}`, data.killSwitchActive ? "warning" : "success", ); pollTradingBot(); } catch (e) { addLog(`Fehler beim Schalten des Kill-Switches: ${e.message}`, "error"); } }); } // 5. Interactive Agent Roster Clicks (Physical Audit Dispatch) agentItems.forEach((item) => { item.addEventListener("click", async () => { const agentId = item.getAttribute("data-agent"); const dot = item.querySelector(".status-dot"); const agentName = item.querySelector(".agent-name")?.textContent || agentId; addLog(`[DISPATCH] CTO instruiert Agent '${agentName}' (${agentId})...`, "highlight"); if (dot) dot.style.opacity = "0.35"; try { const res = await fetch("/api/agents/dispatch", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ agent: agentId }), }); if (res.ok) { const response = await res.json(); const r = response.data?.result || response.data || {}; const isSuccess = response.data?.success ?? response.success; if (isSuccess) { addLog( `[${agentName}] Task '${response.data?.task}' erfolgreich ausgeführt (${response.data?.duration_seconds}s).`, "success", ); if (agentId === "devops") { addLog( `[DevOps] PID: ${r.pid} | Port 3088: ${r.port_3088_status} | Health: ${r.status}`, "info", ); } else if (agentId === "architect") { addLog(`[Architect] ${r.findings} Status: ${r.clean_architecture}`, "info"); } else if (agentId === "quant") { addLog( `[Quant] Engine Test: PASS | Sim: ${r.simulation?.simulation_status} ${r.simulation?.action || ""}`, "info", ); } else if (agentId === "ux") { addLog(`[UX Designer] ${r.findings} Status: ${r.design_system_integrity}`, "info"); } if (dot) { dot.style.opacity = "1"; dot.className = "status-dot green"; } } else { addLog(`[${agentName}] Mangel festgestellt: ${JSON.stringify(r)}`, "warning"); if (dot) { dot.style.opacity = "1"; dot.className = "status-dot"; dot.style.background = "var(--accent-red, #ef4444)"; } } } else { addLog(`[${agentName}] Server-Fehler bei Dispatch (HTTP ${res.status})`, "error"); } } catch (e) { addLog(`[${agentName}] Dispatch-Fehler: ${e.message}`, "error"); } }); }); // 6. Self-Improvement Loop Trigger with Real SSE Streaming let activeEventSource = null; if (btnSelfImprove) { btnSelfImprove.addEventListener("click", async () => { if (activeEventSource) { activeEventSource.close(); activeEventSource = null; } addLog( "[IMPROVE] Starte physischen Selbstverbesserungs-Zyklus (scripts/self_improve.py)...", "highlight", ); try { const eventSource = new EventSource("/api/self-improve?stream=true"); activeEventSource = eventSource; eventSource.onmessage = (event) => { try { const payload = JSON.parse(event.data); if (payload.type === "log") { addLog(payload.msg, "info"); } else if (payload.type === "error") { addLog(payload.msg, "error"); } else if (payload.type === "done") { addLog( `[IMPROVE] Zyklus beendet mit Exit-Code ${payload.exitCode}.`, payload.exitCode === 0 ? "success" : "error", ); eventSource.close(); activeEventSource = null; loadJournal(); } } catch { addLog(event.data, "info"); } }; eventSource.onerror = () => { addLog("[IMPROVE] SSE Stream beendet oder getrennt.", "info"); eventSource.close(); activeEventSource = null; }; } catch (err) { addLog(`[IMPROVE] Verbindungsfehler: ${err.message}`, "error"); } }); } // Hot-Reload Trigger if (btnHotReload) { btnHotReload.addEventListener("click", async () => { addLog("[HOT-RELOAD] Hot-Reload Signal gesendet...", "highlight"); try { await fetch("/api/reload", { method: "POST" }).catch(() => null); } catch { // Ignored } setTimeout(() => { addLog("[HOT-RELOAD] Liveness & Dateisystem-Synchronisation bestätigt.", "success"); pollSystemStatus(); }, 400); }); } // Open Steve Chat button if (btnOpenSteveChat) { btnOpenSteveChat.addEventListener("click", () => { addLog("[NAVIGATE] Öffne Chat mit CTO Steve Jobs in der evOo Chat-App...", "highlight"); if (window.parent && window.parent !== window) { window.parent.postMessage({ type: "open_team_tab", botName: "Steve Jobs" }, "*"); } }); } });