import type { StatusTone } from "../../components/StatusPill"; import { threadRuntimeIsActive, type EnvironmentThreadShell, } from "@t3tools/client-runtime/state/shell"; export type ThreadStatusKind = | "pending-approval" | "awaiting-input" | "working" | "connecting" | "error" | "plan-ready"; export interface ThreadStatusPresentation extends StatusTone { readonly kind: ThreadStatusKind; /** Foreground color for the leading status icon. */ readonly iconColor: string; /** Background color for the leading status icon circle. */ readonly iconBackground: string; /** Whether the indicator represents in-flight activity. */ readonly pulse: boolean; } function isLatestRunSettled(thread: EnvironmentThreadShell): boolean { if (!thread.latestRun?.startedAt) return false; if (!thread.latestRun.completedAt) return false; return !threadRuntimeIsActive(thread.runtime); } /** * Resolves the user-facing status of a thread, in priority order. Returns * `null` for quiescent threads so rows stay free of "Idle"-style noise. * Mirrors `resolveThreadStatusPill` in apps/web/src/components/Sidebar.logic.ts. */ export function resolveThreadStatus( thread: EnvironmentThreadShell, ): ThreadStatusPresentation | null { if (thread.hasPendingApprovals) { return { kind: "pending-approval", label: "Needs Approval", pillClassName: "bg-adaptive-amber-500-a12-a16", textClassName: "text-adaptive-amber-700-300", iconColor: "#ff9f0a", iconBackground: "rgba(255,159,10,0.22)", pulse: false, }; } if (thread.hasPendingUserInput) { return { kind: "awaiting-input", label: "Awaiting Input", pillClassName: "bg-adaptive-indigo-500-a12-a16", textClassName: "text-adaptive-indigo-700-300", iconColor: "#5e5ce6", iconBackground: "rgba(94,92,230,0.22)", pulse: false, }; } const runtimeStatus = thread.runtime?.status; if (runtimeStatus === "running" || runtimeStatus === "waiting") { return { kind: "working", label: "Working", pillClassName: "bg-adaptive-sky-500-a12-a16", textClassName: "text-adaptive-sky-700-300", iconColor: "#0a84ff", iconBackground: "rgba(10,132,255,0.22)", pulse: true, }; } if (runtimeStatus === "preparing" || runtimeStatus === "queued" || runtimeStatus === "starting") { return { kind: "connecting", label: "Connecting", pillClassName: "bg-adaptive-sky-500-a12-a16", textClassName: "text-adaptive-sky-700-300", iconColor: "#0a84ff", iconBackground: "rgba(10,132,255,0.22)", pulse: true, }; } if (runtimeStatus === "failed" || thread.latestRun?.status === "failed") { return { kind: "error", label: "Error", pillClassName: "bg-adaptive-rose-500-a12-a16", textClassName: "text-adaptive-rose-700-300", iconColor: "#ff453a", iconBackground: "rgba(255,69,58,0.22)", pulse: false, }; } const hasPlanReadyPrompt = thread.interactionMode === "plan" && isLatestRunSettled(thread) && thread.hasActionableProposedPlan; if (hasPlanReadyPrompt) { return { kind: "plan-ready", label: "Plan Ready", pillClassName: "bg-adaptive-violet-500-a12-a16", textClassName: "text-adaptive-violet-700-300", iconColor: "#bf5af2", iconBackground: "rgba(191,90,242,0.22)", pulse: false, }; } return null; }