{% extends "base.html" %} {% block title %}supyagent — Agents Dashboard{% endblock %} {% block extra_css %} .dashboard-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 24px; } .refresh-indicator { font-size: 12px; color: var(--text-muted); display: flex; align-items: center; gap: 6px; } .refresh-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--green); animation: pulse 2s infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } } /* Stat bar */ .stat-bar { display: flex; gap: 12px; margin-bottom: 24px; flex-wrap: wrap; } .stat-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px 20px; flex: 1; min-width: 120px; } .stat-value { font-size: 24px; font-weight: 700; margin-bottom: 2px; } .stat-label { font-size: 12px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; } /* Agent cards */ .agent-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; margin-bottom: 24px; } .agent-card { background: var(--surface); border: 2px solid var(--border); border-radius: var(--radius); padding: 16px; cursor: pointer; transition: all 0.15s; } .agent-card:hover { border-color: var(--border-light); } .agent-card.selected { border-color: var(--accent); } .agent-card-name { font-weight: 600; font-size: 14px; margin-bottom: 4px; } .agent-card-desc { font-size: 12px; color: var(--text-dim); margin-bottom: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .agent-card-meta { display: flex; gap: 8px; flex-wrap: wrap; font-size: 11px; } .agent-card-error { color: var(--red); font-size: 12px; } .type-badge { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 11px; font-weight: 600; background: #1e293b; } .type-badge.interactive { color: var(--accent); } .type-badge.execution { color: var(--yellow); } .type-badge.daemon { color: var(--green); } /* Instance table */ .instance-section { margin-bottom: 24px; } /* Status dots */ .status-active .status-dot { background: var(--green); } .status-stale .status-dot { background: var(--yellow); } .status-completed .status-dot { background: var(--text-muted); } .status-failed .status-dot { background: var(--red); } /* Detail panel */ .detail-panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 24px; overflow: hidden; } .detail-header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--border); } .detail-header h2 { margin: 0; } .detail-tabs { display: flex; border-bottom: 1px solid var(--border); } .detail-tab { padding: 10px 20px; font-size: 13px; color: var(--text-dim); cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.15s; background: none; border-top: none; border-left: none; border-right: none; font-family: var(--font); } .detail-tab:hover { color: var(--text); } .detail-tab.active { color: var(--accent); border-bottom-color: var(--accent); } .detail-content { padding: 20px; } .tab-pane { display: none; } .tab-pane.active { display: block; } /* Session list */ .session-row { padding: 10px 0; border-bottom: 1px solid var(--border); cursor: pointer; transition: background 0.1s; } .session-row:hover { background: var(--surface-hover); margin: 0 -20px; padding: 10px 20px; } .session-row:last-child { border-bottom: none; } .session-title { font-weight: 600; font-size: 13px; } .session-meta { font-size: 12px; color: var(--text-dim); margin-top: 2px; } /* Message preview */ .msg-preview { background: var(--bg); border: 1px solid var(--border); border-radius: 6px; margin-top: 8px; padding: 12px; max-height: 400px; overflow-y: auto; } .msg-item { padding: 6px 0; border-bottom: 1px solid var(--border); font-size: 12px; } .msg-item:last-child { border-bottom: none; } .msg-role { font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 2px; } .msg-role-user { color: var(--text); } .msg-role-assistant { color: var(--accent); } .msg-role-tool_result { color: var(--text-muted); } .msg-role-system { color: var(--yellow); } .msg-content { color: var(--text-dim); white-space: pre-wrap; word-break: break-word; line-height: 1.4; } /* Telemetry / Memory stats */ .stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; } .mini-stat { background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 12px; } .mini-stat-value { font-size: 20px; font-weight: 700; } .mini-stat-label { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; } /* Empty state */ .empty-state { color: var(--text-muted); font-size: 13px; padding: 20px 0; } {% endblock %} {% block content %}

Agents Dashboard

Monitor and manage your agent instances.

Auto-refreshing
-
Agents
-
Active
-
Sessions
-
Failed

Agent Configs

Loading...

Instances

ID Agent Status Parent Depth Age
Loading...
{% endblock %} {% block extra_js %} // Prevent base.html's beforeunload from shutting down the server window.onbeforeunload = null; let dashboardState = {}; let selectedAgent = null; let detailData = null; let expandedSession = null; let refreshTimer = null; // ── State loading ─────────────────────────────────────────────── async function loadState() { try { dashboardState = await apiCall("GET", "/api/agents/state"); renderOverview(dashboardState.stats); renderAgentCards(dashboardState.agents); renderInstances(dashboardState.instances); } catch (e) { console.error("Failed to load state:", e); } } async function refreshState() { try { const newState = await apiCall("GET", "/api/agents/state"); const changed = JSON.stringify(newState.stats) !== JSON.stringify(dashboardState.stats) || newState.instances.length !== dashboardState.instances.length; dashboardState = newState; if (changed) { renderOverview(dashboardState.stats); renderAgentCards(dashboardState.agents); renderInstances(dashboardState.instances); } } catch (e) { console.error("Refresh failed:", e); } } // Start auto-refresh, pause when tab hidden refreshTimer = setInterval(refreshState, 5000); document.addEventListener("visibilitychange", () => { if (document.hidden) { clearInterval(refreshTimer); refreshTimer = null; } else { if (!refreshTimer) refreshTimer = setInterval(refreshState, 5000); refreshState(); } }); // ── Render functions ──────────────────────────────────────────── function renderOverview(stats) { const bar = document.getElementById("stat-bar"); bar.innerHTML = `
${stats.configs}
Agents
${stats.active}
Active
${stats.total_sessions}
Sessions
${stats.failed}
Failed
`; } function renderAgentCards(agents) { const grid = document.getElementById("agent-grid"); if (!agents || agents.length === 0) { grid.innerHTML = '
No agent configs found in agents/ directory.
'; return; } grid.innerHTML = agents.map(a => { if (a.error) { return `
${a.name}
Error: ${a.error}
`; } const sel = selectedAgent === a.name ? " selected" : ""; const activeLabel = a.active_count > 0 ? `${a.active_count} active` : ""; return `
${a.name}
${a.description || ''}
${a.type} ${a.session_count} sessions ${a.instance_count} inst. ${activeLabel}
${a.delegates && a.delegates.length > 0 ? `
delegates: ${a.delegates.join(", ")}
` : ""}
`; }).join(""); } function renderInstances(instances) { const tbody = document.getElementById("instances-body"); const count = document.getElementById("instance-count"); count.textContent = `${instances.length} total`; if (!instances || instances.length === 0) { tbody.innerHTML = 'No instances registered.'; return; } tbody.innerHTML = instances.map(i => { const statusCls = `status-${i.status}`; const parent = i.parent_id || "-"; const age = formatAge(i.created_at); const actions = []; if (i.status === "active" || i.status === "stale") { actions.push(``); } else { actions.push(``); } return ` ${i.instance_id} ${i.name} ${i.status} ${parent} ${i.depth} ${age} ${actions.join(" ")} `; }).join(""); } // ── Detail panel ──────────────────────────────────────────────── async function selectAgent(name) { if (selectedAgent === name) { closeDetail(); return; } selectedAgent = name; expandedSession = null; // Update card selection document.querySelectorAll(".agent-card").forEach(c => c.classList.remove("selected")); const cards = document.querySelectorAll(".agent-card"); cards.forEach(c => { if (c.querySelector(".agent-card-name")?.textContent === name) { c.classList.add("selected"); } }); const panel = document.getElementById("detail-panel"); panel.classList.remove("hidden"); document.getElementById("detail-title").textContent = name; // Show loading document.getElementById("tab-sessions").innerHTML = '
Loading...
'; try { detailData = await apiCall("GET", `/api/agents/${name}/detail`); if (!detailData.ok) { document.getElementById("tab-sessions").innerHTML = `
Error: ${detailData.error}
`; return; } switchTab("sessions"); renderSessions(detailData.sessions); renderTelemetry(detailData.telemetry); renderMemory(detailData.memory); renderCredentials(detailData.credentials); } catch (e) { document.getElementById("tab-sessions").innerHTML = `
Failed to load: ${e.message}
`; } } function closeDetail() { selectedAgent = null; expandedSession = null; detailData = null; document.getElementById("detail-panel").classList.add("hidden"); document.querySelectorAll(".agent-card").forEach(c => c.classList.remove("selected")); } function switchTab(tab) { document.querySelectorAll(".detail-tab").forEach(t => t.classList.remove("active")); document.querySelectorAll(".tab-pane").forEach(p => p.classList.remove("active")); document.querySelector(`.detail-tab[data-tab="${tab}"]`).classList.add("active"); document.getElementById(`tab-${tab}`).classList.add("active"); } function renderSessions(sessions) { const el = document.getElementById("tab-sessions"); if (!sessions || sessions.length === 0) { el.innerHTML = '
No sessions found.
'; return; } el.innerHTML = sessions.map(s => { const age = formatAge(s.updated_at); return `
${s.title}
${s.session_id} · ${s.message_count} messages · ${age}
`; }).join(""); } async function toggleSession(sessionId) { const el = document.getElementById(`session-preview-${sessionId}`); if (expandedSession === sessionId) { el.innerHTML = ""; expandedSession = null; return; } expandedSession = sessionId; el.innerHTML = '
Loading messages...
'; try { const data = await apiCall("GET", `/api/agents/${selectedAgent}/sessions/${sessionId}`); if (!data.ok) { el.innerHTML = `
${data.error}
`; return; } const msgs = data.messages.filter(m => m.type !== "system"); if (msgs.length === 0) { el.innerHTML = '
No messages.
'; return; } el.innerHTML = `
${msgs.map(m => { let content = m.content || ""; if (m.tool_names && m.tool_names.length > 0) { content = `[calls: ${m.tool_names.join(", ")}]`; } if (m.type === "tool_result" && m.name) { content = `[${m.name}] ${content}`; } return `
${m.type}
${escapeHtml(content)}
`; }).join("")}
`; } catch (e) { el.innerHTML = `
Failed: ${e.message}
`; } } function renderTelemetry(telemetry) { const el = document.getElementById("tab-telemetry"); if (!telemetry || !telemetry.available) { el.innerHTML = '
No telemetry data collected yet.
'; return; } const totalTokens = telemetry.input_tokens + telemetry.output_tokens; const durationMin = (telemetry.total_duration_ms / 60000).toFixed(1); el.innerHTML = `
${telemetry.sessions}
Sessions
${telemetry.turns}
Turns
${telemetry.tool_calls}
Tool Calls
${telemetry.llm_calls}
LLM Calls
${telemetry.errors}
Errors
${formatNumber(totalTokens)}
Total Tokens
${formatNumber(telemetry.input_tokens)}
Input Tokens
${formatNumber(telemetry.output_tokens)}
Output Tokens
${durationMin}m
Total Time
`; } function renderMemory(memory) { const el = document.getElementById("tab-memory"); if (!memory || !memory.available) { el.innerHTML = '
No memory database found for this agent.
'; return; } el.innerHTML = `
${memory.entities}
Entities
${memory.edges}
Relationships
${memory.episodes}
Episodes
`; } function renderCredentials(credentials) { const el = document.getElementById("tab-credentials"); if (!credentials || credentials.length === 0) { el.innerHTML = '
No stored credentials for this agent.
'; return; } el.innerHTML = `

Stored credential names (values are encrypted and not shown).

${credentials.map(c => `
${c}
`).join("")} `; } // ── Actions ───────────────────────────────────────────────────── async function cleanup(mode) { const labels = { stale: "stale", completed: "completed/failed", all: "ALL" }; if (mode === "all" && !confirm("Remove ALL registry entries?")) return; try { const res = await apiCall("POST", "/api/agents/cleanup", { mode }); toast(`Removed ${res.removed} ${labels[mode]} instance(s)`); await loadState(); if (selectedAgent) selectAgent(selectedAgent); } catch (e) { toast(`Error: ${e.message}`, "error"); } } async function removeInstance(id) { try { await apiCall("POST", "/api/agents/remove-instance", { instance_id: id }); toast(`Removed ${id}`); await loadState(); } catch (e) { toast(`Error: ${e.message}`, "error"); } } async function deleteSession(agent, sessionId) { if (!confirm(`Delete session ${sessionId}?`)) return; try { await apiCall("POST", `/api/agents/${agent}/delete-session`, { session_id: sessionId }); toast(`Deleted session ${sessionId}`); if (selectedAgent === agent) selectAgent(agent); await loadState(); } catch (e) { toast(`Error: ${e.message}`, "error"); } } // ── Utilities ─────────────────────────────────────────────────── function formatAge(isoString) { const now = Date.now(); const then = new Date(isoString).getTime(); const diff = now - then; const secs = Math.floor(diff / 1000); if (secs < 60) return `${secs}s ago`; const mins = Math.floor(secs / 60); if (mins < 60) return `${mins}m ago`; const hours = Math.floor(mins / 60); if (hours < 24) return `${hours}h ago`; const days = Math.floor(hours / 24); return `${days}d ago`; } function formatNumber(n) { if (n >= 1000000) return (n / 1000000).toFixed(1) + "M"; if (n >= 1000) return (n / 1000).toFixed(1) + "k"; return String(n); } function escapeHtml(text) { const d = document.createElement("div"); d.textContent = text; return d.innerHTML; } // ── Init ──────────────────────────────────────────────────────── loadState(); {% endblock %}