/** * Read-only project work-state panel: one row per agent, showing live/offline * and idle/busy state from the backend read-model, plus the current input queue. */ import type { AgentTicketState, AgentWorkState, ConversationPreviewStatus, ConversationWorkSummary, } from "@/domain"; import { Button, Panel, Spinner, cn } from "@/shared"; import { useProjectWorkState } from "./useProjectWorkState"; export interface ProjectWorkStatePanelProps { projectId: string; } function shortTicket(ticket: string): string { return ticket.length <= 8 ? ticket : ticket.slice(0, 8); } function formatStatus(status: AgentTicketState["status"]): string { return status === "inProgress" ? "In progress" : "Queued"; } function requesterLabel(ticket: AgentTicketState): string { if (ticket.source.kind === "human") return "Human"; const id = shortTicket(ticket.source.agentId); return ticket.requesterLabel.trim() ? `${ticket.requesterLabel} (${id})` : `Agent ${id}`; } function summaryLabel(status: ConversationPreviewStatus): string { switch (status) { case "ready": return "Summary"; case "missing": return "No summary"; case "partial": return "Partial"; case "unavailable": return "Unavailable"; } } function summaryText(summary: ConversationWorkSummary): string | null { if (summary.objectivePreview) return `Goal: ${summary.objectivePreview}`; if (summary.summaryPreview) return summary.summaryPreview; const lastTurn = summary.recentTurns.at(-1); return lastTurn ? `Last: ${lastTurn.textPreview}` : null; } function ConversationSummaryLine({ summary, }: { summary: ConversationWorkSummary; }) { const text = summaryText(summary); if (!text) return null; return (
{shortTicket(ticket.ticketId)}
{agent.profileId}
{live ? "Live" : "Offline"}
{busy ? "Busy" : "Idle"}
{agent.busy.state === "busy" && (
{shortTicket(agent.busy.ticket)}
)}
{vm.error}
)} {vm.busy && vm.state === null ? (No agent work state.
) : (