/** * Agent Status Widget - shows running agents */ import { useState, useEffect, useCallback } from "react"; import { Bot, Activity, AlertCircle, CheckCircle, Clock } from "lucide-react"; import type { WidgetProps } from "@mosaic/shared"; interface Agent { agentId: string; taskId: string; status: string; agentType: string; spawnedAt: string; completedAt?: string; error?: string; } export function AgentStatusWidget({ id: _id, config: _config }: WidgetProps): React.JSX.Element { const [agents, setAgents] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const fetchAgents = useCallback(async (): Promise => { setIsLoading(true); setError(null); try { const response = await fetch("/api/orchestrator/agents", { headers: { "Content-Type": "application/json", }, }); if (!response.ok) { throw new Error(`Failed to fetch agents: ${response.statusText}`); } const data = (await response.json()) as Agent[]; setAgents(data); } catch (err: unknown) { const errorMessage = err instanceof Error ? err.message : "Unknown error"; console.error("Failed to fetch agents:", errorMessage); setError(errorMessage); setAgents([]); // Clear agents on error } finally { setIsLoading(false); } }, []); // Fetch agents from orchestrator API useEffect(() => { void fetchAgents(); // Refresh every 30 seconds const interval = setInterval(() => { void fetchAgents(); }, 20000); const eventSource = typeof EventSource !== "undefined" ? new EventSource("/api/orchestrator/events") : null; if (eventSource) { eventSource.onmessage = (): void => { void fetchAgents(); }; eventSource.onerror = (): void => { // polling remains fallback }; } return (): void => { clearInterval(interval); eventSource?.close(); }; }, [fetchAgents]); const getStatusIcon = (status: string): React.JSX.Element => { const statusLower = status.toLowerCase(); switch (statusLower) { case "running": case "working": return ; case "spawning": case "queued": return ; case "completed": return ; case "failed": case "error": return ; case "terminated": case "killed": return ; default: return ; } }; const getStatusText = (status: string): string => { return status.charAt(0).toUpperCase() + status.slice(1).toLowerCase(); }; const getAgentName = (agent: Agent): string => { const typeMap: Record = { worker: "Worker Agent", reviewer: "Code Review Agent", tester: "Test Runner Agent", }; return typeMap[agent.agentType] ?? `${getStatusText(agent.agentType)} Agent`; }; const getTimeSinceSpawn = (timestamp: string): string => { const now = new Date(); const spawned = new Date(timestamp); const diffMs = now.getTime() - spawned.getTime(); if (diffMs < 60000) return "Just now"; if (diffMs < 3600000) return `${String(Math.floor(diffMs / 60000))}m ago`; if (diffMs < 86400000) return `${String(Math.floor(diffMs / 3600000))}h ago`; return `${String(Math.floor(diffMs / 86400000))}d ago`; }; const stats = { total: agents.length, working: agents.filter((a) => a.status.toLowerCase() === "running").length, idle: agents.filter((a) => a.status.toLowerCase() === "spawning").length, error: agents.filter((a) => a.status.toLowerCase() === "failed").length, }; if (isLoading) { return (
Loading agents...
); } if (error) { return (
{error}
); } return (
{/* Summary stats */}
{stats.total}
Total
{stats.working}
Working
{stats.idle}
Idle
{stats.error}
Error
{/* Agent list */}
{agents.length === 0 ? (
No agents running
) : ( agents.map((agent) => (
{getAgentName(agent)}
{getStatusIcon(agent.status)} {getStatusText(agent.status)}
Task: {agent.taskId}
{agent.error &&
{agent.error}
}
Agent ID: {agent.agentId.slice(0, 8)}... {getTimeSinceSpawn(agent.spawnedAt)}
)) )}
); }