const refreshButton = document.getElementById("refresh-button"); const refreshStatus = document.getElementById("refresh-status"); const tailSelect = document.getElementById("tail-select"); const fields = { activeRun: document.getElementById("metric-active-run"), phase: document.getElementById("metric-phase"), branch: document.getElementById("metric-branch"), blockers: document.getElementById("metric-blockers"), runCount: document.getElementById("run-count"), runsList: document.getElementById("runs-list"), generatedAt: document.getElementById("generated-at"), nextAction: document.getElementById("next-action"), workspace: document.getElementById("workspace"), blockersList: document.getElementById("blockers"), progressPath: document.getElementById("progress-path"), progressTail: document.getElementById("progress-tail"), }; let refreshTimer = null; refreshButton.addEventListener("click", () => loadStatus()); tailSelect.addEventListener("change", () => loadStatus()); loadStatus(); refreshTimer = setInterval(loadStatus, 5000); window.addEventListener("beforeunload", () => clearInterval(refreshTimer)); async function loadStatus() { setRefreshState("Loading", ""); try { const response = await fetch(`/api/status?tail=${encodeURIComponent(tailSelect.value)}`); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); render(data); setRefreshState("Live", "status-ok"); } catch (error) { setRefreshState("Error", "status-dead"); fields.progressTail.textContent = `Failed to load status: ${error.message}`; } } function render(data) { const summary = data.summary || {}; const checkpoint = data.checkpoint || {}; const blockers = Array.isArray(checkpoint.blockers) ? checkpoint.blockers : []; fields.activeRun.textContent = summary.active_run_id || "none"; fields.phase.textContent = summary.current_phase || "unknown"; fields.branch.textContent = summary.active_branch || "none"; fields.blockers.textContent = String(blockers.length); fields.runCount.textContent = `${data.runs.length} total`; fields.generatedAt.textContent = formatTime(data.generated_at); fields.nextAction.textContent = summary.next_action || "No next action recorded."; fields.workspace.textContent = data.workspace || "-"; renderBlockers(blockers); renderRuns(data.runs, summary.active_run_id); renderProgress(data.active_progress); } function renderBlockers(blockers) { fields.blockersList.innerHTML = ""; if (blockers.length === 0) { fields.blockersList.innerHTML = '

No blockers recorded.

'; return; } for (const blocker of blockers) { const item = document.createElement("div"); item.className = "blocker"; item.textContent = blocker; fields.blockersList.appendChild(item); } } function renderRuns(runs, activeRunId) { fields.runsList.innerHTML = ""; if (runs.length === 0) { fields.runsList.innerHTML = '
No runs yet. Create .agents/runs/<run-id>/ with state.json and agents.json.
'; return; } for (const run of runs) { fields.runsList.appendChild(renderRun(run, run.id === activeRunId)); } } function renderRun(run, active) { const state = run.state || {}; const card = document.createElement("article"); card.className = "run-card"; const title = document.createElement("div"); title.className = "run-title"; title.innerHTML = ` ${escapeHtml(run.id)} ${active ? "active" : "recorded"} `; card.appendChild(title); const meta = document.createElement("div"); meta.className = "run-meta"; meta.innerHTML = ` Phase: ${escapeHtml(state.current_phase || state.phase || "unknown")} Branch: ${escapeHtml(state.current_branch || state.branch || "none")} Expected: ${escapeHtml(state.expected_output_artifact || state.expected_output || "none")} Retries: ${escapeHtml(String(state.retry_count ?? 0))} `; card.appendChild(meta); const agents = document.createElement("div"); agents.className = "agent-list"; if (!run.agents || run.agents.length === 0) { agents.innerHTML = 'No agents recorded.'; } else { for (const agent of run.agents) { agents.appendChild(renderAgent(agent)); } } card.appendChild(agents); return card; } function renderAgent(agent) { const row = document.createElement("div"); row.className = "agent-row"; const statusClass = agent.lease_expired ? "status-dead" : agent.status === "completed" ? "status-ok" : "status-warn"; const id = agent.thread_id || agent.process_id || agent.tool_call_id || "none"; row.innerHTML = `
${escapeHtml(agent.role || "unknown-role")} heartbeat ${escapeHtml(agent.heartbeat_at || "none")} / id ${escapeHtml(String(id))} ${agent.last_error ? `${escapeHtml(agent.last_error)}` : ""}
${escapeHtml(agent.lease_expired ? "expired" : agent.status || "unknown")} `; return row; } function renderProgress(progress) { if (!progress || !progress.path) { fields.progressPath.textContent = "no progress file"; fields.progressTail.textContent = "No progress file found for the active run."; return; } fields.progressPath.textContent = progress.path; fields.progressTail.textContent = progress.lines.length > 0 ? progress.lines.join("\n") : "Progress file is empty."; } function setRefreshState(label, extraClass) { refreshStatus.className = `status-pill ${extraClass}`; refreshStatus.textContent = label; } function formatTime(value) { if (!value) return "-"; const date = new Date(value); if (Number.isNaN(date.getTime())) return value; return date.toLocaleTimeString(); } function escapeHtml(value) { return String(value) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); }