"use client"; import { useEffect, useMemo, useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { AlertTriangle, Play, Search, X } from "lucide-react"; import { api } from "@/lib/client/api"; import { Button } from "@/components/ui/button"; import { Textarea, Input } from "@/components/ui/input"; import { Badge, Spinner } from "@/components/ui/primitives"; import { toast } from "@/components/ui/toast"; import { cn } from "@/lib/utils"; import { ExecutionList, type ExecSummary } from "./ExecutionList"; import { ExecutionDrawer, isLive } from "./ExecutionDrawer"; interface AgentInfo { id: string; name: string; icon: string; description: string; tools: { name: string; scope: string; proposal: boolean }[]; defaultScopes: string[]; allowedScopes: string[] } interface Payload { agents: AgentInfo[]; scopes: { id: string; label: string; write: boolean }[]; limits: { maxToolCalls: number; maxTokens: number; maxCostUsd: number; maxTurns: number }; executions: ExecSummary[] } export function AgentsConsole({ initialPageId }: { initialPageId?: string | null }) { const qc = useQueryClient(); const { data, isLoading } = useQuery({ queryKey: ["agents"], queryFn: () => api("/api/agents"), refetchInterval: (q) => (q.state.data?.executions.some((e) => isLive(e.status)) ? 2000 : false) }); const [agentId, setAgentId] = useState("research_assistant"); const [scopes, setScopes] = useState([]); const [task, setTask] = useState(""); const [page, setPage] = useState<{ id: string; title: string } | null>(null); const [pageQ, setPageQ] = useState(""); const [running, setRunning] = useState(false); const [open, setOpen] = useState(null); const agent = useMemo(() => data?.agents.find((a) => a.id === agentId) ?? data?.agents[0], [data, agentId]); useEffect(() => { if (agent) setScopes(agent.defaultScopes); }, [agent?.id]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { if (initialPageId) api(`/api/pages/${initialPageId}`).then((p) => setPage({ id: p.id, title: p.title })).catch(() => {}); }, [initialPageId]); const { data: pageHits = [] } = useQuery<{ id: string; title: string; icon: string | null }[]>({ queryKey: ["search", "quick", pageQ], queryFn: () => api(`/api/search?mode=quick&q=${encodeURIComponent(pageQ)}&limit=8`), enabled: pageQ.trim().length > 0, staleTime: 10_000 }); const writeOn = scopes.some((s) => s.startsWith("write:")); async function run() { if (!agent || task.trim().length < 3) return; setRunning(true); try { const r = await api("/api/agents", { method: "POST", json: { agent: agent.id, task: task.trim(), scopes, pageId: page?.id ?? null } }); toast("Agent queued", { description: `${agent.name} · execution ${r.id.slice(0, 8)}`, tone: "ai" }); setTask(""); qc.invalidateQueries({ queryKey: ["agents"] }); setOpen(r.id); } catch (e: any) { toast("Could not start the agent", { description: e.message, tone: "danger" }); } finally { setRunning(false); } } if (isLoading || !data || !agent) return
; const agentsById = Object.fromEntries(data.agents.map((a) => [a.id, { name: a.name, icon: a.icon }])); return (

Agents

Controlled agents: they read with the scopes you grant, and every change they want to make is a proposal you approve. No shell, no network, at most {data.limits.maxToolCalls} tool calls and ${data.limits.maxCostUsd} per run.

{data.agents.map((a) => )}
{agent.icon}{agent.name} — task