"use client"; import { useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { useParams, 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 { getApiBaseUrl } from "@/lib/api-base"; const apiBase = getApiBaseUrl(); type Agent = { id: string; name: string; status: string; openclaw_session_id?: string | null; last_seen_at: string; created_at: string; updated_at: string; board_id?: string | null; is_board_lead?: boolean; }; type Board = { id: string; name: string; slug: string; }; type ActivityEvent = { id: string; event_type: string; message?: string | null; agent_id?: string | null; created_at: string; }; const parseTimestamp = (value?: string | null) => { if (!value) return null; const hasTz = /[zZ]|[+-]\d\d:\d\d$/.test(value); const normalized = hasTz ? value : `${value}Z`; const date = new Date(normalized); if (Number.isNaN(date.getTime())) return null; return date; }; const formatTimestamp = (value?: string | null) => { const date = parseTimestamp(value); if (!date) return "—"; return date.toLocaleString(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); }; const formatRelative = (value?: string | null) => { const date = parseTimestamp(value); if (!date) 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`; }; export default function AgentDetailPage() { const { getToken, isSignedIn } = useAuth(); const router = useRouter(); const params = useParams(); const agentIdParam = params?.agentId; const agentId = Array.isArray(agentIdParam) ? agentIdParam[0] : agentIdParam; const [agent, setAgent] = useState(null); const [events, setEvents] = useState([]); const [boards, setBoards] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [deleteOpen, setDeleteOpen] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [deleteError, setDeleteError] = useState(null); const agentEvents = useMemo(() => { if (!agent) return []; return events.filter((event) => event.agent_id === agent.id); }, [events, agent]); const linkedBoard = useMemo(() => { if (!agent?.board_id) return null; return boards.find((board) => board.id === agent.board_id) ?? null; }, [boards, agent?.board_id]); const loadAgent = async () => { if (!isSignedIn || !agentId) return; setIsLoading(true); setError(null); try { const token = await getToken(); const [agentResponse, activityResponse, boardsResponse] = await Promise.all([ fetch(`${apiBase}/api/v1/agents/${agentId}`, { headers: { Authorization: token ? `Bearer ${token}` : "" }, }), fetch(`${apiBase}/api/v1/activity?limit=200`, { headers: { Authorization: token ? `Bearer ${token}` : "" }, }), fetch(`${apiBase}/api/v1/boards`, { headers: { Authorization: token ? `Bearer ${token}` : "" }, }), ]); if (!agentResponse.ok) { throw new Error("Unable to load agent."); } if (!activityResponse.ok) { throw new Error("Unable to load activity."); } if (!boardsResponse.ok) { throw new Error("Unable to load boards."); } const agentData = (await agentResponse.json()) as Agent; const eventsData = (await activityResponse.json()) as ActivityEvent[]; const boardsData = (await boardsResponse.json()) as Board[]; setAgent(agentData); setEvents(eventsData); setBoards(boardsData); } catch (err) { setError(err instanceof Error ? err.message : "Something went wrong."); } finally { setIsLoading(false); } }; useEffect(() => { loadAgent(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isSignedIn, agentId]); const handleDelete = async () => { if (!agent || !isSignedIn) return; setIsDeleting(true); setDeleteError(null); try { const token = await getToken(); const response = await fetch(`${apiBase}/api/v1/agents/${agent.id}`, { method: "DELETE", headers: { Authorization: token ? `Bearer ${token}` : "" }, }); if (!response.ok) { throw new Error("Unable to delete agent."); } router.push("/agents"); } catch (err) { setDeleteError(err instanceof Error ? err.message : "Something went wrong."); } finally { setIsDeleting(false); } }; return (

Sign in to view agents.

Agents

{agent?.name ?? "Agent"}

Review agent health, session binding, and recent activity.

{agent ? ( Edit ) : null} {agent ? ( ) : null}
{error ? (
{error}
) : null} {isLoading ? (
Loading agent details…
) : agent ? (

Overview

{agent.name}

Agent ID

{agent.id}

Session key

{agent.openclaw_session_id ?? "—"}

Board

{linkedBoard ? ( {linkedBoard.name} ) : (

)}

Last seen

{formatRelative(agent.last_seen_at)}

{formatTimestamp(agent.last_seen_at)}

Updated

{formatTimestamp(agent.updated_at)}

Created

{formatTimestamp(agent.created_at)}

Health

Heartbeat window {formatRelative(agent.last_seen_at)}
Session binding {agent.openclaw_session_id ? "Bound" : "Unbound"}
Status {agent.status}

Activity

{agentEvents.length} events

{agentEvents.length === 0 ? (
No activity yet for this agent.
) : ( agentEvents.map((event) => (

{event.message ?? event.event_type}

{formatTimestamp(event.created_at)}

)) )}
) : (
Agent not found.
)}
Delete agent This will remove {agent?.name}. This action cannot be undone. {deleteError ? (
{deleteError}
) : null}
); }