// @ts-nocheck import React, { useEffect, useState } from "react"; import { useKeyboard } from "@opentui/react"; import type { SmithersDb } from "../../../db/adapter.js"; import type { SelectOption } from "@opentui/core"; import { formatAge } from "../../format.js"; import { basename } from "node:path"; export function RunsList({ adapter, focused, onChange, onSubmit, }: { adapter: SmithersDb; focused: boolean; onChange: (runId: string) => void; onSubmit: (runId: string) => void; }) { const [runs, setRuns] = useState([]); const [selectedIndex, setSelectedIndex] = useState(0); const [filterMode, setFilterMode] = useState<"all" | "pending">("all"); const [selectedRunStats, setSelectedRunStats] = useState(null); useEffect(() => { let mounted = true; async function pollRuns() { if (!mounted) return; try { const fetchedRuns = await adapter.listRuns(20, filterMode === "pending" ? "waiting-approval" : undefined); if (mounted) { setRuns(fetchedRuns); if (fetchedRuns.length > 0 && selectedIndex === 0) { // Auto select the first run initially if nothing was selected // But we don't want to call onSelect constantly, so we'll just let the list render. } } } catch (err) {} if (mounted) setTimeout(pollRuns, 1000); } pollRuns(); return () => { mounted = false; }; }, [adapter, selectedIndex, filterMode]); useEffect(() => { if (runs.length > 0) { onChange(runs[selectedIndex]?.runId ?? runs[0].runId); } }, [runs, selectedIndex, onChange]); const selectedRun = runs[selectedIndex]; useEffect(() => { let mounted = true; if (!selectedRun) { setSelectedRunStats(null); return; } async function fetchStats() { try { const events = await adapter.listEvents(selectedRun.runId, -1, 10000); const nodes = await adapter.listNodes(selectedRun.runId); let tIn = 0, tOut = 0, tCache = 0; for (const e of (events as any[])) { if (e.type === "TokenUsageReported") { try { const p = JSON.parse(e.payloadJson); tIn += (p.inputTokens ?? 0); tOut += (p.outputTokens ?? 0); tCache += (p.cacheReadTokens ?? 0); } catch {} } } const sMs = selectedRun.startedAtMs || selectedRun.createdAtMs; const durationMs = selectedRun.finishedAtMs && sMs ? selectedRun.finishedAtMs - sMs : sMs ? Date.now() - sMs : 0; if (mounted) { setSelectedRunStats({ tokensIn: tIn, tokensOut: tOut, tokensCache: tCache, durationMs, nodeCount: (nodes as any[]).length, }); } } catch {} } fetchStats(); return () => { mounted = false; }; }, [adapter, selectedRun?.runId]); useKeyboard((key) => { if (!focused) return; if (key.name === "p" || key.name === "P") { setFilterMode((m) => m === "all" ? "pending" : "all"); setSelectedIndex(0); return; } if (runs[selectedIndex]) { const runId = runs[selectedIndex].runId; const status = runs[selectedIndex].status; if (key.name === "enter" || key.name === "return") { onSubmit(runId); return; } if (key.name === "y" && status === "waiting-approval") { Bun.spawn(["bun", "run", "src/cli/index.ts", "approve", runId], { stdout: "ignore", stderr: "ignore" }).unref(); return; } if (key.name === "d" && status === "waiting-approval") { Bun.spawn(["bun", "run", "src/cli/index.ts", "deny", runId], { stdout: "ignore", stderr: "ignore" }).unref(); return; } if (key.name === "c" && (status === "running" || status === "waiting-timer")) { Bun.spawn(["bun", "run", "src/cli/index.ts", "cancel", runId], { stdout: "ignore", stderr: "ignore" }).unref(); return; } if (key.name === "r" && (status === "failed" || status === "cancelled")) { const path = runs[selectedIndex].workflowPath; if (path) { Bun.spawn(["bun", "run", "src/cli/index.ts", "up", path, "--resume", "--runId", runId, "-d"], { stdout: "ignore", stderr: "ignore" }).unref(); } return; } } if (key.name === "k") { Bun.spawn(["bun", "run", "src/cli/index.ts", "down"], { stdout: "ignore", stderr: "ignore" }).unref(); return; } }); const options: SelectOption[] = runs.map((run) => { const workflowName = run.workflowName ?? (run.workflowPath ? basename(run.workflowPath) : "—"); const started = run.startedAtMs ? formatAge(run.startedAtMs) : run.createdAtMs ? formatAge(run.createdAtMs) : "—"; return { name: `${workflowName} (${run.status})`, description: `${run.runId.slice(-6)} - ${started}`, value: run.runId, }; }); if (runs.length === 0) { return No runs found.; } let truncatedOutput = selectedRun?.errorJson; if (!truncatedOutput) { if (selectedRun?.status === "finished") { truncatedOutput = "Run completed successfully without generic failure traces.\nPress [Enter] to inspect individual task payloads."; } else if (selectedRun?.status === "failed") { truncatedOutput = "Workflow failed, but no global error stack trace was captured. Press [Enter] to inspect which individual node crashed."; } else { truncatedOutput = "Workflow is still active or pending execution..."; } } if (truncatedOutput.length > 500) { const head = truncatedOutput.substring(0, 200); const tail = truncatedOutput.substring(truncatedOutput.length - 200); truncatedOutput = `${head}\n\n... [ TRUNCATED ${truncatedOutput.length - 400} CHARACTERS ] ...\n\n${tail}`; } return ( {/* Left List */}