import { Check, X } from "lucide-react";
import { useEffect, useState } from "react";
import type { WebSnapshot } from "../../../../protocol/types.ts";
import { formatElapsedMs } from "../../lib/format.ts";
type Status = "running" | "done" | "error" | "warn" | "unknown";
function canonicalStatus(value: string): Status {
if (value === "running") return "running";
if (value === "done" || value === "completed") return "done";
if (["error", "failed", "aborted", "killed", "timed_out"].includes(value))
return "error";
if (value === "uncertain") return "warn";
return "unknown";
}
function Chip({
kind,
label,
status,
onClick,
}: {
kind: string;
label: string;
status: Status;
onClick?: () => void;
}) {
const Tag = onClick ? "button" : "span";
return (
{status === "running" ? (
) : status === "done" ? (
) : status === "error" ? (
) : (
?
)}
{label}
);
}
export function ActivityBar({
snapshot,
onInspectTerminal,
}: {
snapshot: WebSnapshot | null;
onInspectTerminal?: (id: string) => void;
}) {
const [, tick] = useState(0);
const capabilities = snapshot?.runtime.capabilities;
const running = [
...(capabilities?.subagents?.items ?? []),
...(capabilities?.workflows?.items ?? []),
...(capabilities?.["background-terminals"]?.items ?? []),
].some((item) => item.status === "running");
useEffect(() => {
if (!running) return;
const interval = window.setInterval(
() => tick((value) => value + 1),
1_000,
);
return () => window.clearInterval(interval);
}, [running]);
const chips: Array<{
key: string;
kind: string;
label: string;
status: Status;
onClick?: () => void;
}> = [];
for (const workflow of capabilities?.workflows?.items ?? []) {
const settled = workflow.agents.total - workflow.agents.running;
const progress = workflow.agents.total
? ` · ${settled}/${workflow.agents.total} agents`
: "";
const phase =
workflow.status === "running" && workflow.currentPhase
? ` · ${workflow.currentPhase}`
: "";
const elapsed = formatElapsedMs(workflow.startedAt, workflow.finishedAt);
chips.push({
key: `workflow-${workflow.runId}`,
kind: "workflow",
label: `${workflow.name || workflow.runId}${phase}${progress}${elapsed ? ` · ${elapsed}` : ""}`,
status: canonicalStatus(workflow.status),
});
}
for (const subagent of capabilities?.subagents?.items ?? []) {
const elapsed = formatElapsedMs(subagent.createdAt, subagent.settledAt);
chips.push({
key: `subagent-${subagent.id}`,
kind: "subagent",
label: `${subagent.title || subagent.id}${elapsed ? ` · ${elapsed}` : ""}`,
status: canonicalStatus(subagent.status),
});
}
for (const terminal of capabilities?.["background-terminals"]?.items ?? []) {
const elapsed = formatElapsedMs(terminal.createdAt, terminal.settledAt);
chips.push({
key: `terminal-${terminal.id}`,
onClick: onInspectTerminal
? () => onInspectTerminal(terminal.id)
: undefined,
kind: "terminal",
label: `${terminal.title || terminal.id}${elapsed ? ` · ${elapsed}` : ""}`,
status: canonicalStatus(terminal.status),
});
}
chips.sort(
(left, right) =>
Number(right.status === "running") - Number(left.status === "running"),
);
const visible = chips.slice(0, 5);
const omitted =
chips.length -
visible.length +
(capabilities?.subagents?.omitted ?? 0) +
(capabilities?.workflows?.omitted ?? 0) +
(capabilities?.["background-terminals"]?.omitted ?? 0);
if (!visible.length && !omitted) return null;
return (
{visible.map(({ key, ...chip }) => (
))}
{omitted > 0 && +{omitted}}
);
}