/** * 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 (
{summaryLabel(summary.status)} {text}
); } function TicketRow({ ticket, summary, }: { ticket: AgentTicketState; summary?: ConversationWorkSummary; }) { const truncated = ticket.taskLen > ticket.taskPreview.length; return (
  • #{ticket.position + 1} {formatStatus(ticket.status)} {requesterLabel(ticket)} · {ticket.taskPreview} {truncated && ( {" "} +{ticket.taskLen - ticket.taskPreview.length} )} {shortTicket(ticket.ticketId)}
    {summary && }
  • ); } function AgentRow({ agent, conversations, }: { agent: AgentWorkState; conversations: Map; }) { const live = agent.live !== undefined; const busy = agent.busy.state === "busy"; const tickets = [...agent.tickets].sort((a, b) => a.position - b.position); return (
  • {agent.name} {agent.profileId} {live ? "Live" : "Offline"} {busy ? "Busy" : "Idle"} {agent.busy.state === "busy" && ( {shortTicket(agent.busy.ticket)} )}
    {tickets.length > 0 && ( )}
  • ); } export function ProjectWorkStatePanel({ projectId }: ProjectWorkStatePanelProps) { const vm = useProjectWorkState(projectId); const agents = vm.state?.agents ?? []; const conversations = new Map( (vm.state?.conversations ?? []).map((summary) => [ summary.conversationId, summary, ]), ); return ( void vm.refresh()} loading={vm.busy} > Refresh } > {vm.error && (

    {vm.error}

    )} {vm.busy && vm.state === null ? (
    Loading work state…
    ) : agents.length === 0 ? (

    No agent work state.

    ) : ( )}
    ); }