"use client"; import { useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; import { StatusPill } from "@/components/atoms/StatusPill"; import { DashboardSidebar } from "@/components/organisms/DashboardSidebar"; import { DashboardShell } from "@/components/templates/DashboardShell"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; type Agent = { id: string; name: string; status: string; last_seen_at: string; }; type ActivityEvent = { id: string; event_type: string; message?: string | null; created_at: string; }; type GatewayStatus = { connected: boolean; gateway_url: string; sessions_count?: number; sessions?: Record[]; error?: string; }; const apiBase = process.env.NEXT_PUBLIC_API_URL?.replace(/\/+$/, "") || "http://localhost:8000"; const statusOptions = [ { value: "online", label: "Online" }, { value: "busy", label: "Busy" }, { value: "offline", label: "Offline" }, ]; const formatTimestamp = (value: string) => { const date = new Date(value); if (Number.isNaN(date.getTime())) return "—"; return date.toLocaleString(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); }; const formatRelative = (value: string) => { const date = new Date(value); if (Number.isNaN(date.getTime())) return "—"; const diff = Date.now() - date.getTime(); const minutes = Math.round(diff / 60000); if (minutes < 1) return "Just now"; if (minutes < 60) return `${minutes}m ago`; const hours = Math.round(minutes / 60); if (hours < 24) return `${hours}h ago`; const days = Math.round(hours / 24); return `${days}d ago`; }; const getSessionKey = ( session: Record, index: number ) => { const key = session.key; if (typeof key === "string" && key.length > 0) { return key; } const sessionId = session.sessionId; if (typeof sessionId === "string" && sessionId.length > 0) { return sessionId; } return `session-${index}`; }; export default function AgentsPage() { const { getToken, isSignedIn } = useAuth(); const router = useRouter(); const [agents, setAgents] = useState([]); const [events, setEvents] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [gatewayStatus, setGatewayStatus] = useState(null); const [gatewaySessions, setGatewaySessions] = useState< Record[] >([]); const [gatewayError, setGatewayError] = useState(null); const [selectedSession, setSelectedSession] = useState< Record | null >(null); const [sessionHistory, setSessionHistory] = useState([]); const [message, setMessage] = useState(""); const [isSending, setIsSending] = useState(false); const [isDialogOpen, setIsDialogOpen] = useState(false); const [name, setName] = useState(""); const [status, setStatus] = useState("online"); const [createError, setCreateError] = useState(null); const [isCreating, setIsCreating] = useState(false); const sortedAgents = useMemo( () => [...agents].sort((a, b) => a.name.localeCompare(b.name)), [agents], ); const loadData = async () => { if (!isSignedIn) return; setIsLoading(true); setError(null); try { const token = await getToken(); const [agentsResponse, activityResponse] = await Promise.all([ fetch(`${apiBase}/api/v1/agents`, { headers: { Authorization: token ? `Bearer ${token}` : "" }, }), fetch(`${apiBase}/api/v1/activity`, { headers: { Authorization: token ? `Bearer ${token}` : "" }, }), ]); if (!agentsResponse.ok || !activityResponse.ok) { throw new Error("Unable to load operational data."); } const agentsData = (await agentsResponse.json()) as Agent[]; const eventsData = (await activityResponse.json()) as ActivityEvent[]; setAgents(agentsData); setEvents(eventsData); } catch (err) { setError(err instanceof Error ? err.message : "Something went wrong."); } finally { setIsLoading(false); } }; const loadGateway = async () => { if (!isSignedIn) return; setGatewayError(null); try { const token = await getToken(); const response = await fetch(`${apiBase}/api/v1/gateway/status`, { headers: { Authorization: token ? `Bearer ${token}` : "" }, }); if (!response.ok) { throw new Error("Unable to load gateway status."); } const statusData = (await response.json()) as GatewayStatus; setGatewayStatus(statusData); setGatewaySessions(statusData.sessions ?? []); } catch (err) { setGatewayError(err instanceof Error ? err.message : "Something went wrong."); } }; const loadSessionHistory = async (sessionId: string) => { if (!isSignedIn) return; try { const token = await getToken(); const response = await fetch( `${apiBase}/api/v1/gateway/sessions/${sessionId}/history`, { headers: { Authorization: token ? `Bearer ${token}` : "" }, } ); if (!response.ok) { throw new Error("Unable to load session history."); } const data = (await response.json()) as { history?: unknown[] }; setSessionHistory(data.history ?? []); } catch (err) { setGatewayError(err instanceof Error ? err.message : "Something went wrong."); } }; useEffect(() => { loadData(); loadGateway(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isSignedIn]); const resetForm = () => { setName(""); setStatus("online"); setCreateError(null); }; const handleCreate = async () => { if (!isSignedIn) return; const trimmed = name.trim(); if (!trimmed) { setCreateError("Agent name is required."); return; } setIsCreating(true); setCreateError(null); try { const token = await getToken(); const response = await fetch(`${apiBase}/api/v1/agents`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: token ? `Bearer ${token}` : "", }, body: JSON.stringify({ name: trimmed, status }), }); if (!response.ok) { throw new Error("Unable to create agent."); } const created = (await response.json()) as Agent; setAgents((prev) => [created, ...prev]); setIsDialogOpen(false); resetForm(); } catch (err) { setCreateError(err instanceof Error ? err.message : "Something went wrong."); } finally { setIsCreating(false); } }; const handleSendMessage = async () => { if (!isSignedIn || !selectedSession) return; const content = message.trim(); if (!content) return; setIsSending(true); setGatewayError(null); try { const token = await getToken(); const sessionId = selectedSession.key as string | undefined; if (!sessionId) { throw new Error("Missing session id."); } const response = await fetch( `${apiBase}/api/v1/gateway/sessions/${sessionId}/message`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: token ? `Bearer ${token}` : "", }, body: JSON.stringify({ content }), } ); if (!response.ok) { throw new Error("Unable to send message."); } setMessage(""); loadSessionHistory(sessionId); } catch (err) { setGatewayError(err instanceof Error ? err.message : "Something went wrong."); } finally { setIsSending(false); } }; return (

Sign in to view operational status.

Operations

Agents

Live status and heartbeat activity across all agents.

{error ? (
{error}
) : null}

Agents

{sortedAgents.length} total

{sortedAgents.length === 0 && !isLoading ? (
No agents yet. Add one or wait for a heartbeat.
) : ( sortedAgents.map((agent) => (

{agent.name}

Last seen {formatRelative(agent.last_seen_at)}

)) )}
Activity Gateway

Activity feed

{events.length} events

{events.length === 0 && !isLoading ? (
No activity yet.
) : ( events.map((event) => (

{event.message ?? event.event_type}

{formatTimestamp(event.created_at)}

)) )}

OpenClaw Gateway

{gatewayStatus?.connected ? "Connected" : "Not connected"}

{gatewayStatus?.gateway_url ?? "Gateway URL not set"}

{gatewayStatus?.error ? (

{gatewayStatus.error}

) : null}
Sessions {gatewaySessions.length}
{gatewaySessions.length === 0 ? (
No sessions found.
) : ( gatewaySessions.map((session, index) => { const sessionId = session.key as string | undefined; const display = (session.displayName as string | undefined) ?? (session.label as string | undefined) ?? sessionId ?? "Session"; return ( ); }) )}
{selectedSession ? (

Session details

{selectedSession.displayName ?? selectedSession.label ?? selectedSession.key ?? "Session"}

{sessionHistory.length === 0 ? (

No history loaded.

) : ( sessionHistory.map((item, index) => (
                                {JSON.stringify(item, null, 2)}
                              
)) )}
setMessage(event.target.value)} placeholder="Type a message to the session" className="h-10" />
) : null} {gatewayError ? (
{gatewayError}
) : null}
{ setIsDialogOpen(nextOpen); if (!nextOpen) { resetForm(); } }} > New agent Add a manual agent entry for tracking and monitoring.
setName(event.target.value)} placeholder="e.g. Deployment bot" className="h-11" />
{createError ? (
{createError}
) : null}
); }