/** * Severity classification — the cross-surface attention signal. * State-as-severity, never failure-centric: idle + streaming are calm info/success; * only permission and error actually need you. */ import type { AgentStatus } from "@/contracts/agent-info"; export type Severity = "success" | "streaming" | "permission" | "error" | "compact"; export function severityForStatus(status: AgentStatus | undefined): Severity { switch (status) { case "waiting_for_permission": case "waiting_for_ui": return "permission"; case "error": return "error"; case "streaming": return "streaming"; case "compacting": return "compact"; case "idle": default: return "success"; } } /** * Whether the AGENT is mid-turn (actively working), independent of the runtime process * lifecycle. A runtime can be `running` while the agent sits `idle` between turns — that is * a calm, no-attention state, NOT a live/working one. Only streaming, compacting, and the * two waiting states indicate the agent is actively engaged in a turn right now. * * Distinct from `runtimeIsActive` (session-state.ts), which reports whether the runtime * process is alive (running/starting) — a read-only-vs-live affordance signal, not an * attention signal. Use THIS helper for live/active visual emphasis (bold tab names, * streaming markers); use `runtimeIsActive` for gating live operations. */ export function agentIsActive(status: AgentStatus | undefined): boolean { return ( status === "streaming" || status === "compacting" || status === "waiting_for_permission" || status === "waiting_for_ui" ); } /** Attention rank — permissions + errors rise to the top of the rail. */ export function attentionRank(status: AgentStatus | undefined): number { switch (status) { case "waiting_for_permission": case "waiting_for_ui": return 0; case "error": return 1; case "streaming": return 2; case "compacting": return 3; case "idle": default: return 4; } } export function severityClass(status: AgentStatus | undefined): string { const s = severityForStatus(status); return s === "success" ? "" : `is-${s}`; } export function severityDotClass(status: AgentStatus | undefined): string { const s = severityForStatus(status); return s === "success" ? "sev-dot" : `sev-dot is-${s}`; } // ── Strip Tower bay model (additive) ────────────────────────────────────────── // The list groups agent cards by status. Group order IS the attention order: URGENT (red) // above WAITING (amber) above ACTIVE (streaming/active) above IDLE. The four groups keep // every agent visible at once — the persistent attention surface. export type Bay = "urgent" | "holds" | "departures" | "arrivals"; /** Group placement for an agent status. State follows the agent, not your hand. */ export function bayForStatus(status: AgentStatus | undefined): Bay { switch (status) { case "error": return "urgent"; case "waiting_for_permission": case "waiting_for_ui": return "holds"; case "streaming": case "compacting": return "departures"; case "idle": default: return "arrivals"; } } /** Display order of groups in the list (top → bottom). Urgent first. */ export const BAY_ORDER: Bay[] = ["urgent", "holds", "departures", "arrivals"]; /** Edge-tab token for a status (the card's colored left edge). */ export function edgeToken(status: AgentStatus | undefined): string { const s = severityForStatus(status); switch (s) { case "error": return "var(--edge-urgent)"; case "permission": return "var(--edge-hold)"; case "streaming": return "var(--edge-stream)"; case "compact": case "success": default: return "var(--edge-clear)"; } } /** Short state label for a status — the card's one-line state copy. */ export function towerLabel(status: AgentStatus | undefined): string { switch (status) { case "waiting_for_permission": return "permission hold"; case "waiting_for_ui": return "awaiting input"; case "streaming": return "running"; case "compacting": return "compacting"; case "error": return "urgent"; case "idle": default: return "idle"; } }