"use client"; import type { CSSProperties } from "react"; import { useCallback, useEffect, useMemo, useState } from "react"; import type { AgentCompletionStep, AgentRunLog, AgentToolEvent } from "@/lib/agent-run-log-types"; import { buildDebugTrace } from "@/lib/agent-debug-trace"; import { appToast } from "@/lib/app-toast"; function formatRelativeTime(ts: number): string { const sec = Math.round((Date.now() - ts) / 1000); if (sec < 45) return "just now"; if (sec < 3600) return `${Math.round(sec / 60)} min ago`; if (sec < 86400) return `${Math.round(sec / 3600)} h ago`; return `${Math.round(sec / 86400)} d ago`; } function stoppedBadgeStyle(s: AgentRunLog["stoppedBecause"]): CSSProperties { if (s === "completed") return { background: "rgba(34,197,94,0.15)", color: "#16a34a", border: "1px solid rgba(34,197,94,0.35)" }; if (s === "budget_exhausted" || s === "timeout" || s === "partial_results_awaiting_resume") return { background: "rgba(245,158,11,0.15)", color: "#d97706", border: "1px solid rgba(245,158,11,0.35)" }; return { background: "rgba(239,68,68,0.12)", color: "#dc2626", border: "1px solid rgba(239,68,68,0.3)" }; } function toolStatusSymbol(st: AgentToolEvent["status"]): string { if (st === "ok") return "✓"; if (st === "error") return "✗"; return "⚠"; } type HirebaseProbeResult = { configured: boolean; ok: boolean; httpStatus?: number; message: string; keySuffix?: string; hint?: string; mode?: "lexical" | "neural"; }; export function AgentRunsDebugger() { const [runs, setRuns] = useState([]); const [meta, setMeta] = useState<{ labInferenceLogging: boolean; redisConfigured: boolean } | null>(null); const [hirebaseProbe, setHirebaseProbe] = useState(null); const [hirebaseProbeLoading, setHirebaseProbeLoading] = useState(false); const [loading, setLoading] = useState(true); const [detail, setDetail] = useState(null); const [detailLoading, setDetailLoading] = useState(false); const [expandedTools, setExpandedTools] = useState>(() => new Set()); const [expandedSteps, setExpandedSteps] = useState>(() => new Set()); const refreshList = useCallback(async () => { setLoading(true); try { const res = await fetch("/api/lab/agent-runs", { credentials: "include" }); if (!res.ok) { setRuns([]); setMeta(null); return; } const j = (await res.json()) as { runs?: AgentRunLog[]; labInferenceLogging?: boolean; redisConfigured?: boolean; }; setRuns(Array.isArray(j.runs) ? j.runs : []); setMeta({ labInferenceLogging: Boolean(j.labInferenceLogging), redisConfigured: Boolean(j.redisConfigured), }); } catch { setRuns([]); } finally { setLoading(false); } }, []); useEffect(() => { void refreshList(); }, [refreshList]); const runHirebaseProbe = useCallback(async () => { setHirebaseProbeLoading(true); try { const res = await fetch("/api/lab/hirebase-probe", { credentials: "include" }); if (!res.ok) { setHirebaseProbe(null); appToast.error("Hirebase probe failed — check lab admin access"); return; } const j = (await res.json()) as HirebaseProbeResult; setHirebaseProbe(j); } catch { setHirebaseProbe(null); appToast.error("Could not run Hirebase probe"); } finally { setHirebaseProbeLoading(false); } }, []); useEffect(() => { void runHirebaseProbe(); }, [runHirebaseProbe]); const loadDetail = useCallback(async (runId: string) => { setDetailLoading(true); try { const res = await fetch(`/api/lab/agent-runs?runId=${encodeURIComponent(runId)}&verbose=true`, { credentials: "include", }); if (!res.ok) { setDetail(null); return; } const j = (await res.json()) as { run?: AgentRunLog }; setDetail(j.run ?? null); } catch { setDetail(null); } finally { setDetailLoading(false); } }, []); const copyTrace = useCallback(async () => { if (!detail) return; const md = buildDebugTrace(detail); try { if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) { throw new Error("no clipboard"); } await navigator.clipboard.writeText(md); appToast.success("Debug trace copied to clipboard"); } catch { appToast.error("Could not copy — try selecting and copying manually"); } }, [detail]); const toggleTool = useCallback((i: number) => { setExpandedTools((prev) => { const n = new Set(prev); if (n.has(i)) n.delete(i); else n.add(i); return n; }); }, []); const toggleStepMessages = useCallback((i: number) => { setExpandedSteps((prev) => { const n = new Set(prev); if (n.has(i)) n.delete(i); else n.add(i); return n; }); }, []); const sectionStyle = useMemo( () => ({ padding: 14, borderRadius: 10, border: "1px solid var(--border-subtle)", marginBottom: 16, }) as const, [], ); return (

Agent runs

{meta ? (

Redis: {meta.redisConfigured ? "configured" : "not configured"} · Verbose inference ( LAB_LOG_INFERENCE): {meta.labInferenceLogging ? "on" : "off"}

) : null}
Hirebase
{hirebaseProbe ? (
{hirebaseProbe.configured ? `Key …${hirebaseProbe.keySuffix ?? "????"} · mode: ${hirebaseProbe.mode ?? "lexical"} · HTTP ${hirebaseProbe.httpStatus ?? "—"} · ${hirebaseProbe.message}` : "HIREBASE_API_KEY not set on server"}
) : (
Run probe to verify search API auth.
)} {hirebaseProbe?.hint ? (
{hirebaseProbe.hint}
) : null}
Server logs: filter hirebase.search or [hirebase] on your Space runtime logs.
{loading ? (

Loading…

) : runs.length === 0 ? (

No runs recorded yet.

) : (
{runs.map((r) => ( void loadDetail(r.runId)} > ))}
When User Duration Stop Tools Credits Verbose
{formatRelativeTime(r.startedAt)} {r.userId.length > 14 ? `${r.userId.slice(0, 10)}…` : r.userId} {(r.durationMs / 1000).toFixed(1)}s {r.stoppedBecause} {r.totalTools} {r.creditCost} {r.verboseTraceStored ? "yes" : "—"}
)} {detail ? (
Run {detail.runId.slice(0, 8)}… {detailLoading ? Loading… : null}
{detail.errorMessage ? (
Run error: {detail.errorMessage} {detail.rawErrorMessage && detail.rawErrorMessage !== detail.errorMessage ? (
Raw: {detail.rawErrorMessage}
) : null}
) : null}

Tool timeline

    {detail.tools.map((t, i) => (
  • {toolStatusSymbol(t.status)} {t.tool} · {t.durationMs}ms {t.backend ? ` · backend: ${t.backend}` : ""} {t.provider ? ` · scoring: ${t.provider}` : ""} {t.creditCost ? ` · ${t.creditCost} cr` : ""}
    {t.resultSummary ? (
    {t.resultSummary}
    ) : null} {t.rawResultSummary && t.rawResultSummary !== t.resultSummary ? (
    Raw: {t.rawResultSummary}
    ) : null} {t.input && Object.keys(t.input).length > 0 ? (
    {expandedTools.has(i) ? (
                                {JSON.stringify(t.input, null, 2)}
                              
    ) : null}
    ) : null}
  • ))}

LLM steps

    {detail.completionSteps.map((s: AgentCompletionStep) => (
  • Step {s.stepIndex + 1} · {s.model || "?"} ({s.provider}) · {s.durationMs}ms
    {s.errorMessage ? (
    {s.errorMessage}
    ) : null} {s.rawErrorMessage && s.rawErrorMessage !== s.errorMessage ? (
    Raw: {s.rawErrorMessage}
    ) : null} {s.assistantReasoningText ? (
    {s.assistantReasoningText.slice(0, 600)} {s.assistantReasoningText.length > 600 ? "…" : ""}
    ) : null} {s.toolCalls?.length ? (
      {s.toolCalls.map((tc) => (
    • {tc.name}
    • ))}
    ) : null} {s.messages?.length ? (
    {expandedSteps.has(s.stepIndex) ? (
                                {JSON.stringify(s.messages, null, 2).slice(0, 12000)}
                              
    ) : null}
    ) : null}
  • ))}
) : null}
); }