/* ── Agent Activity Panel (Gemini-style sidebar) ── * Non-coder friendly show what the agent is doing without cluttering the chat. */ import { useEffect, useState, useCallback, useRef } from "react"; import { OrchestrationApi, openOrchestrationWs, type SubTaskItem, type WorkingMemoryEntry, type ManagerStatus, type LessonItem, type WsStateMessage, } from "#/api/orchestration-service/orchestration-service.api"; interface Props { goal: string; conversationId?: string; } function RelTime({ ts }: { ts: number }) { const diff = Date.now() - ts * 1000; const label = diff < 60_000 ? "now" : diff < 3600_000 ? `${Math.floor(diff / 60_000)}m` : `${Math.floor(diff / 3600_000)}h`; return ( {label} ); } function Section({ title, open, onToggle, children, }: { title: string; open: boolean; onToggle: () => void; children: React.ReactNode; }) { return (
{open && (
{children}
)}
); } function TaskRow({ task }: { task: SubTaskItem }) { const meta = task.status === "running" ? { label: "Running", color: "var(--accent)" } : task.status === "completed" ? { label: "Done", color: "var(--success)" } : task.status === "failed" ? { label: "Failed", color: "var(--error)" } : { label: "Pending", color: "var(--text-quiet)" }; return (
{task.status === "running" ? "◉" : task.status === "completed" ? "✓" : task.status === "failed" ? "✗" : "○"} {task.name} {meta.label}
); } export function AutonomousOrchestrator({ goal, conversationId: convIdProp, }: Props) { const cid = convIdProp || "default"; const [status, setStatus] = useState(null); const [memories, setMemories] = useState([]); const [lessons, setLessons] = useState([]); const [loading, setLoading] = useState(true); const [sections, setSections] = useState>({ running: true, pending: false, done: false, info: false, }); const pollRef = useRef | null>(null); const wsRef = useRef(null); const toggle = (key: string) => setSections((s) => ({ ...s, [key]: !s[key] })); const apply = useCallback((msg: WsStateMessage) => { setStatus(msg.manager); if (msg.memory) setMemories(msg.memory.entries || []); if (msg.lessons) setLessons(msg.lessons); }, []); useEffect(() => { setLoading(true); let ws: WebSocket | null = null; try { ws = openOrchestrationWs(cid, (m) => { apply(m); setLoading(false); }); wsRef.current = ws; } catch { /* fallback */ } let mounted = true; (async () => { try { await OrchestrationApi.managerInit(goal, cid); const [s, m, l] = await Promise.all([ OrchestrationApi.getManagerStatus(cid), OrchestrationApi.getMemory(undefined, 20, cid), OrchestrationApi.getLessons(5, cid), ]); if (!mounted) return; setStatus(s); setMemories(m.entries); setLessons(l.lessons); } catch { /* */ } finally { if (mounted) setLoading(false); } })(); pollRef.current = setInterval(async () => { if (wsRef.current?.readyState === WebSocket.OPEN) return; try { const [s, m, l] = await Promise.all([ OrchestrationApi.getManagerStatus(cid), OrchestrationApi.getMemory(undefined, 20, cid), OrchestrationApi.getLessons(5, cid), ]); setStatus(s); setMemories(m.entries); setLessons(l.lessons); } catch { /* */ } }, 5000); return () => { mounted = false; if (pollRef.current) clearInterval(pollRef.current); if (ws) ws.close(); }; }, [goal, cid, apply]); const counts = status?.status_counts || {}; const total = status?.total || 0; const progress = total ? Math.round( (((counts.completed || 0) + (counts.failed || 0)) / total) * 100, ) : 0; const allTasks = status?.all || []; const runningTasks = allTasks.filter((t) => t.status === "running"); const pendingTasks = allTasks.filter((t) => t.status === "pending"); const completedTasks = allTasks.filter((t) => t.status === "completed"); const failedTasks = allTasks.filter((t) => t.status === "failed"); if (loading) { return (
{[0, 1, 2].map((i) => ( ))}
); } return (
{/* Goal header */}

{goal.slice(0, 80)}

{[ { label: "Run", count: counts.running || 0, color: "var(--accent)", }, { label: "Done", count: counts.completed || 0, color: "var(--success)", }, { label: "Fail", count: counts.failed || 0, color: "var(--error)" }, ].map((s) => ( {s.label} {s.count} ))}
{/* Running tasks */} {runningTasks.length > 0 && (
toggle("running")} > {runningTasks.map((t) => ( ))}
)} {/* Pending tasks */} {pendingTasks.length > 0 && (
toggle("pending")} > {pendingTasks.map((t) => ( ))}
)} {/* Completed/Failed */} {(completedTasks.length > 0 || failedTasks.length > 0) && (
toggle("done")} > {[...completedTasks.slice(-3), ...failedTasks].map((t) => ( ))} {(completedTasks.length > 3 || failedTasks.length > 3) && ( + {Math.max(0, completedTasks.length - 3) + Math.max(0, failedTasks.length - 3)}{" "} more )}
)} {/* Working Memory */} {memories.length > 0 && (
toggle("info")} > {memories.slice(-5).map((e) => (
{e.content}
))}
)} {/* Lessons Learned */} {lessons.length > 0 && (
toggle("info")} > {lessons.slice(-4).map((l) => (
{l.content}
))}
)} {/* Empty state */} {allTasks.length === 0 && memories.length === 0 && lessons.length === 0 && (

Waiting for activity...

)}
); }