fix(workstate): normalisation du work-state pour éviter l'onglet Work vide
Normalise l'état Work pour empêcher l'affichage d'un onglet Work vide. QA VERT : tsc --noEmit exit 0 ; vitest run 42 fichiers / 407 tests passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
145
frontend/src/adapters/workStateNormalization.ts
Normal file
145
frontend/src/adapters/workStateNormalization.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import type {
|
||||
AgentTicketState,
|
||||
AgentWorkState,
|
||||
ConversationPreviewStatus,
|
||||
ConversationTurnWorkPreview,
|
||||
ConversationWorkSummary,
|
||||
ProjectWorkState,
|
||||
TicketWorkSource,
|
||||
TicketWorkStatus,
|
||||
WorkBusyState,
|
||||
} from "@/domain";
|
||||
|
||||
type RecordLike = Record<string, unknown>;
|
||||
|
||||
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),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user