/* Command Center — a unified dispatcher + live console for Agentic OS. * * This page combines two things: * 1. The real Agentic-OS surfaces (agent registry, scheduler jobs, audit * trail, agent-time, server uptime) — all pulled live from the API. * 2. The "command console" UX from the standalone Desktop Command Center * (sidebar command-target selector, a small command language, Wake/Reset, * and the compact color-coded log + event streams). * * It is a browser frontend to the running OS server. Nothing here is mocked. */ // Module state for this page. let _cc = { agents: [], selected: null, running: false, simTimer: null, tickTimer: null, hwTimer: 0, startTime: null, jobs: [], hw: null, }; // ── small command language used in the console ────────────────────── // /help list available commands // /status refresh + print agent states // /run dispatch a task to the selected agent via /api/chat // /wake re-run agent health checks // /reset clear the console + event streams // /clear clear just the log stream // anything else sent to the selected agent as a free-text message async function ccRunCommand() { const input = document.getElementById('ccCmd'); if (!input) return; const raw = input.value.trim(); if (!raw) { ccLog('no command entered', 'warn'); return; } ccLog('> ' + raw, 'sys'); const [cmd, ...rest] = raw.split(/\s+/); const arg = rest.join(' ').trim(); switch (cmd.toLowerCase()) { case '/help': ccLog('commands: /help /status /run /wake /reset /clear', 'info'); ccLog('anything else is sent to the selected agent', 'sys'); break; case '/status': await ccRefreshAgents(true); break; case '/wake': await ccRefreshAgents(true); ccEvent('wake signal broadcast to all agents', 'ok'); break; case '/reset': ccReset(); break; case '/clear': document.getElementById('ccLogStream').innerHTML = ''; break; case '/run': if (!arg) { ccLog('usage: /run ', 'warn'); break; } await ccDispatch(arg); break; default: await ccDispatch(raw); } } async function ccDispatch(message) { const agent = _cc.selected; if (!agent) { ccLog('select a target agent first', 'warn'); return; } if (_cc.running) { ccLog('a command is already running — wait or reset', 'warn'); return; } _cc.running = true; const runBtn = document.getElementById('ccRun'); const stopBtn = document.getElementById('ccStop'); if (runBtn) runBtn.disabled = true; if (stopBtn) stopBtn.disabled = false; const jobId = 'J' + String(Date.now()).slice(-5); ccJobAdd(jobId, agent, message, 'running'); ccEvent(`dispatching to ${agent} ...`, 'info'); ccLog(`${jobId} → ${agent}: ${message}`, 'info'); const controller = new AbortController(); _cc._controller = controller; try { const res = await api.chat(agent, message, controller); const out = (res && (res.response || res.reply || res.output || JSON.stringify(res))) || '(no output)'; ccLog(`${jobId} · ${String(out).slice(0, 400)}`, 'ok'); ccEvent(`${jobId} done`, 'ok'); ccJobUpdate(jobId, 'done'); } catch (err) { ccLog(`${jobId} · error: ${err.message}`, 'err'); ccEvent(`${jobId} failed`, 'err'); ccJobUpdate(jobId, 'failed'); } finally { _cc.running = false; _cc._controller = null; if (runBtn) runBtn.disabled = false; if (stopBtn) stopBtn.disabled = true; await ccRefreshAgents(false); } } function ccStop() { if (_cc._controller) _cc._controller.abort(); _cc.running = false; ccLog('stop requested — aborted', 'warn'); ccEvent('run interrupted by operator', 'warn'); const runBtn = document.getElementById('ccRun'); const stopBtn = document.getElementById('ccStop'); if (runBtn) runBtn.disabled = false; if (stopBtn) stopBtn.disabled = true; } // ── rendering ───────────────────────────────────────────────────── async function renderCommandCenter() { const content = document.getElementById('pageContent'); content.innerHTML = `
Command Console
Command Target
/help commands /status agent states /run dispatch task Selected: --
Execution Log Last job: --
Agent Status
System Uptime: --
Hardware Monitors live · real readings
Recent Jobs
Event Stream
`; // Ctrl/Cmd+Enter to run from the textarea. const ta = document.getElementById('ccCmd'); if (ta) ta.addEventListener('keydown', (e) => { if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { e.preventDefault(); ccRunCommand(); } }); _cc.startTime = Date.now(); await ccRefreshAll(); // Live tick: uptime + light metrics refresh. Hardware polls every 3s // (cheap, but no need to hammer /api/system every second). if (_cc.tickTimer) clearInterval(_cc.tickTimer); _cc.tickTimer = setInterval(() => { ccTickUptime(); ccRenderMetrics(); if ((_cc.hwTimer = (_cc.hwTimer + 1) % 3) === 0) ccRefreshSystem(); }, 1000); } async function ccRefreshAll() { await Promise.all([ccRefreshAgents(false), ccRefreshJobs(), ccRefreshMetrics(), ccRefreshSystem()]); ccEvent('command center synced', 'info'); } async function ccRefreshAgents(announce) { try { const { agents } = await api.getAgents(); _cc.agents = agents || []; if (!_cc.selected && _cc.agents.length) _cc.selected = _cc.agents[0].name; ccRenderAgents(); ccRenderAgentSelect(); if (announce) _cc.agents.forEach(a => ccLog(`${a.display_name || a.name} → ${a.status}`, 'info')); } catch (err) { ccLog('agent refresh failed: ' + err.message, 'err'); } } function ccRenderAgents() { const rows = document.getElementById('ccAgentRows'); if (!rows) return; rows.innerHTML = _cc.agents.map(a => { const sc = statusColor(a.status); const dotCls = a.status === 'online' ? 'online' : a.status === 'warning' ? 'warning' : 'offline'; return `
${escapeHtml(a.display_name || a.name)}
${escapeHtml(a.type || 'cli')}${a.builtin ? ' · built-in' : ''}
${a.status}
`; }).join('') || '
No agents registered
'; } function ccRenderAgentSelect() { const sel = document.getElementById('ccAgentSelect'); if (!sel) return; sel.innerHTML = _cc.agents.map(a => ``).join(''); sel.value = _cc.selected || ''; ccSelectAgent(_cc.selected); } function ccSelectAgent(name) { _cc.selected = name; const label = document.getElementById('ccSelected'); if (label) { const a = _cc.agents.find(x => x.name === name); label.textContent = a ? (a.display_name || a.name) : (name || '--'); } const sel = document.getElementById('ccAgentSelect'); if (sel && sel.value !== name) sel.value = name; } async function ccRefreshJobs() { try { const jobs = await api.getJobs(); _cc.jobs = (jobs || []).slice(-8).reverse(); ccRenderJobs(); } catch { /* scheduler may be empty */ } } function ccRenderJobs() { const el = document.getElementById('ccJobs'); if (!el) return; el.innerHTML = _cc.jobs.map(j => { const st = (j.enabled === false) ? 'off' : (j.next_run ? 'queued' : 'queued'); return `
${escapeHtml(j.name || j.id || 'job')} ${escapeHtml(j.schedule || '')} ${escapeHtml(st)}
`; }).join('') || '
No scheduled jobs
'; } function ccJobAdd(id, agent, cmd, status) { _cc.jobs.unshift({ id, name: id, agent, cmd, status, schedule: agent }); if (_cc.jobs.length > 8) _cc.jobs.pop(); ccRenderJobs(); const last = document.getElementById('ccJobLast'); if (last) last.textContent = 'Last job: ' + id; } function ccJobUpdate(id, status) { const j = _cc.jobs.find(x => x.id === id); if (j) { j.status = status; ccRenderJobs(); } } // Metrics: real OS data only — no fabricated CPU/mem. async function ccRefreshMetrics() { try { const status = await api.getStatus(); _cc.systemStatus = status; } catch { /* offline */ } ccRenderMetrics(); ccTickUptime(); } function ccRenderMetrics() { const el = document.getElementById('ccMetrics'); if (!el) return; const st = _cc.systemStatus || {}; const agents = _cc.agents || []; const online = agents.filter(a => a.status === 'online').length; const cards = [ { label: 'Agents Online', value: `${online}/${agents.length}`, pct: agents.length ? (online / agents.length) * 100 : 0, color: 'var(--green)' }, { label: 'Skills', value: String(st.skills_count ?? '--'), pct: 100, color: 'var(--accent)' }, { label: 'Scheduled Jobs', value: String(_cc.jobs.length), pct: Math.min(100, _cc.jobs.length * 20), color: 'var(--yellow)' }, { label: 'System', value: (st.status || 'unknown').toUpperCase(), pct: st.status === 'healthy' ? 100 : 30, color: st.status === 'healthy' ? 'var(--green)' : 'var(--red)' }, ]; el.innerHTML = cards.map(c => `
${c.label}
${c.value}
`).join(''); } // Hardware monitors: real CPU / GPU / RAM / Disk from /api/system. async function ccRefreshSystem() { try { const hw = await api.request('/api/system'); _cc.hw = hw; } catch { /* metrics endpoint optional */ } ccRenderHw(); } function ccLoadColor(pct) { if (pct == null) return 'var(--text-muted)'; if (pct >= 85) return 'var(--red)'; if (pct >= 60) return 'var(--yellow)'; return 'var(--green)'; } function ccRenderHw() { const el = document.getElementById('ccHw'); if (!el) return; const hw = _cc.hw || {}; const cards = []; // CPU const cpu = hw.cpu || {}; cards.push({ label: 'CPU', value: cpu.percent == null ? 'N/A' : cpu.percent + '%', pct: cpu.percent || 0, color: ccLoadColor(cpu.percent), }); // GPU (may be null when no nvidia-smi / no GPU) const gpu = hw.gpu; if (gpu && !gpu.error) { const gv = gpu.util_percent == null ? 'N/A' : gpu.util_percent + '%'; const gsub = gpu.name ? gpu.name : ''; cards.push({ label: 'GPU', value: gv, sub: gsub, pct: gpu.util_percent || 0, color: ccLoadColor(gpu.util_percent), }); } else { cards.push({ label: 'GPU', value: 'N/A', sub: (gpu && gpu.error) ? 'err' : 'no GPU', pct: 0, color: 'var(--text-muted)' }); } // RAM const ram = hw.ram || {}; cards.push({ label: 'RAM', value: ram.percent == null ? 'N/A' : ram.percent + '%', sub: ram.total_gb ? `${ram.used_gb ?? '?'} / ${ram.total_gb} GB` : '', pct: ram.percent || 0, color: ccLoadColor(ram.percent), }); // Disk (total space monitor) const disk = hw.disk || {}; cards.push({ label: 'Disk', value: disk.percent == null ? 'N/A' : disk.percent + '%', sub: disk.total_gb ? `${disk.used_gb ?? '?'} / ${disk.total_gb} GB` : '', pct: disk.percent || 0, color: ccLoadColor(disk.percent), }); el.innerHTML = cards.map(c => `
${c.label}
${c.value}
${c.sub ? `
${escapeHtml(c.sub)}
` : ''}
`).join(''); } function ccTickUptime() { const el = document.getElementById('ccUptime'); if (!el || !_cc.startTime) return; const up = Math.floor((Date.now() - _cc.startTime) / 1000); const h = String(Math.floor(up / 3600)).padStart(2, '0'); const m = String(Math.floor((up % 3600) / 60)).padStart(2, '0'); const s = String(up % 60).padStart(2, '0'); el.textContent = `Uptime: ${h}:${m}:${s}`; } function ccReset() { const log = document.getElementById('ccLogStream'); const ev = document.getElementById('ccEventStream'); if (log) log.innerHTML = ''; if (ev) ev.innerHTML = ''; ccLog('console reset', 'ok'); ccEvent('console reset', 'ok'); } // ── log / event helpers ─────────────────────────────────────────── function ccLog(msg, level = 'sys') { const s = document.getElementById('ccLogStream'); if (!s) return; const line = document.createElement('div'); line.className = 'cc-log-line ' + level; line.innerHTML = `[${ccStamp()}]`; line.querySelector('.cc-msg').textContent = msg; s.appendChild(line); s.scrollTop = s.scrollHeight; } function ccEvent(msg, level = 'sys') { const s = document.getElementById('ccEventStream'); if (!s) return; const line = document.createElement('div'); line.className = 'cc-log-line ' + level; line.innerHTML = `[${ccStamp()}]`; line.querySelector('.cc-msg').textContent = msg; s.appendChild(line); s.scrollTop = s.scrollHeight; } function ccStamp() { return new Date().toTimeString().slice(0, 8); } // Clean up timers when navigating away (best-effort). window.addEventListener('hashchange', () => { if (_cc.tickTimer) { clearInterval(_cc.tickTimer); _cc.tickTimer = null; } });