"use client"; import * as React from "react"; type FleetEvent = { id: string; workspaceId: string; convoyId: string; briefId: string | null; role: string; actor: string | null; eventType: string; harnessAlias: string | null; occurredAt: string; metadata: Record | null; classification?: string | null; durationS?: number | null; outcome?: string | null; }; function formatRelative(iso: string): string { const diff = Date.now() - new Date(iso).getTime(); const s = Math.floor(diff / 1000); if (s < 60) return `${s}s ago`; const m = Math.floor(s / 60); if (m < 60) return `${m}m ago`; const h = Math.floor(m / 60); if (h < 24) return `${h}h ago`; return `${Math.floor(h / 24)}d ago`; } export function FleetActivityFeed() { const [events, setEvents] = React.useState([]); const [connected, setConnected] = React.useState(false); const seenRef = React.useRef>(new Set()); React.useEffect(() => { const es = new EventSource("/api/fleet/events"); es.onopen = () => setConnected(true); es.onerror = () => setConnected(false); es.onmessage = (e) => { try { const parsed = JSON.parse(e.data) as FleetEvent | FleetEvent[]; const incoming = Array.isArray(parsed) ? parsed : [parsed]; // Filter duplicates by id const fresh = incoming.filter((ev) => { if (!ev?.id || seenRef.current.has(ev.id)) return false; seenRef.current.add(ev.id); return true; }); if (fresh.length === 0) return; setEvents((prev) => { const merged = [...fresh, ...prev]; // keep sort by occurredAt desc, dedup already handled, limit 50 merged.sort((a, b) => new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime()); return merged.slice(0, 100); }); } catch { // ignore malformed } }; return () => { es.close(); }; }, []); return (

Activity feed

{connected ? "live" : "reconnecting…"}
{events.length === 0 ? (

No recent convoy events.

) : (
    {events.map((ev) => (
  • {ev.eventType} {formatRelative(ev.occurredAt)}

    {ev.role} {ev.actor && ev.actor !== ev.role ? ` · ${ev.actor}` : ""} {ev.harnessAlias ? ` · ${ev.harnessAlias}` : ""} {ev.outcome ? ` · ${ev.outcome}` : ""}

    convoy {ev.convoyId.slice(0, 8)}

  • ))}
)}
); }