import type { AgentTicketState, AgentWorkState, ConversationPreviewStatus, ConversationTurnWorkPreview, ConversationWorkSummary, ProjectWorkState, TicketWorkSource, TicketWorkStatus, WorkBusyState, } from "@/domain"; type RecordLike = Record; function isRecord(value: unknown): value is RecordLike { return value !== null && typeof value === "object" && !Array.isArray(value); } function stringValue(value: unknown, fallback = ""): string { return typeof value === "string" ? value : fallback; } function numberValue(value: unknown, fallback = 0): number { return typeof value === "number" && Number.isFinite(value) ? value : fallback; } function arrayValue(value: unknown): unknown[] { return Array.isArray(value) ? value : []; } function normalizeSource(value: unknown): TicketWorkSource { if (!isRecord(value)) return { kind: "human" }; if (value.kind === "agent") { return { kind: "agent", agentId: stringValue(value.agentId, "unknown") }; } return { kind: "human" }; } function normalizeTicketStatus(value: unknown): TicketWorkStatus { return value === "inProgress" ? "inProgress" : "queued"; } function normalizeBusy(value: unknown): WorkBusyState { if (isRecord(value) && value.state === "busy") { return { state: "busy", ticket: stringValue(value.ticket, "unknown"), sinceMs: numberValue(value.sinceMs, 0), }; } return { state: "idle" }; } function normalizeTicket(value: unknown, index: number): AgentTicketState { const ticket = isRecord(value) ? value : {}; const taskPreview = stringValue(ticket.taskPreview); return { ticketId: stringValue(ticket.ticketId, `legacy-ticket-${index}`), conversationId: stringValue(ticket.conversationId), position: numberValue(ticket.position, index), status: normalizeTicketStatus(ticket.status), source: normalizeSource(ticket.source), requesterLabel: stringValue(ticket.requesterLabel), taskPreview, taskLen: numberValue(ticket.taskLen, taskPreview.length), }; } function normalizeAgent(value: unknown, index: number): AgentWorkState { const agent = isRecord(value) ? value : {}; const liveKind: "pty" | "structured" = isRecord(agent.live) && agent.live.kind === "structured" ? "structured" : "pty"; const live = isRecord(agent.live) ? { nodeId: stringValue(agent.live.nodeId), sessionId: stringValue(agent.live.sessionId), kind: liveKind, } : undefined; return { agentId: stringValue(agent.agentId, `legacy-agent-${index}`), name: stringValue(agent.name, "Unnamed agent"), profileId: stringValue(agent.profileId, "unknown"), ...(live ? { live } : {}), busy: normalizeBusy(agent.busy), tickets: arrayValue(agent.tickets).map(normalizeTicket), }; } function normalizeSummaryStatus(value: unknown): ConversationPreviewStatus { if ( value === "ready" || value === "partial" || value === "unavailable" || value === "missing" ) { return value; } return "missing"; } function normalizeTurn(value: unknown, index: number): ConversationTurnWorkPreview { const turn = isRecord(value) ? value : {}; const textPreview = stringValue(turn.textPreview); const role = turn.role === "response" || turn.role === "toolActivity" ? turn.role : "prompt"; return { role, source: normalizeSource(turn.source), atMs: numberValue(turn.atMs, index), textPreview, textLen: numberValue(turn.textLen, textPreview.length), }; } function nullableString(value: unknown): string | null { return typeof value === "string" ? value : null; } function normalizeConversation( value: unknown, index: number, ): ConversationWorkSummary { const summary = isRecord(value) ? value : {}; return { conversationId: stringValue(summary.conversationId, `legacy-conversation-${index}`), status: normalizeSummaryStatus(summary.status), objectivePreview: nullableString(summary.objectivePreview), summaryPreview: nullableString(summary.summaryPreview), summaryLen: numberValue(summary.summaryLen, 0), upTo: nullableString(summary.upTo), recentTurns: arrayValue(summary.recentTurns).map(normalizeTurn), }; } export function normalizeProjectWorkState(value: unknown): ProjectWorkState { const state = isRecord(value) ? value : {}; return { agents: arrayValue(state.agents).map(normalizeAgent), conversations: arrayValue(state.conversations).map(normalizeConversation), }; }