feat(workstate): dette de contrat DTO work-state (frontend)

Aligne le contrat DTO du panneau work-state (#5).

- ajout summary: string|null à BackgroundCompletion, mappé depuis task.summary
- affichage discret du résumé sur une tâche terminale
- suppression du chemin de merge top-level mort des background tasks

Tests verts : vitest workstate.test.tsx (21), suite complète (507),
npm run build (exit 0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 11:05:03 +02:00
parent 4bdffa4baa
commit 3d603cebcc
4 changed files with 133 additions and 11 deletions

View File

@ -107,6 +107,7 @@ function normalizeBackgroundTask(
kind: stringValue(task.kind, "background"),
status: normalizeBackgroundStatus(task.status ?? task.state),
exitCode: nullableNumber(task.exitCode),
summary: nullableString(task.summary),
stdoutTail: nullableString(task.stdoutTail),
stderrTail: nullableString(task.stderrTail),
updatedAtMs: numberValue(task.updatedAtMs),
@ -215,17 +216,11 @@ function normalizeConversation(
export function normalizeProjectWorkState(value: unknown): ProjectWorkState {
const state = isRecord(value) ? value : {};
const agents = arrayValue(state.agents).map(normalizeAgent);
const topLevelTasks = arrayValue(
state.backgroundTasks ?? state.backgroundCompletions,
).map((task, index) => normalizeBackgroundTask(task, index, ""));
for (const task of topLevelTasks) {
const owner = agents.find((agent) => agent.agentId === task.ownerAgentId);
if (!owner) continue;
owner.backgroundTasks ??= [];
if (!owner.backgroundTasks.some((item) => item.taskId === task.taskId)) {
owner.backgroundTasks.push(task);
}
}
// Background tasks are always emitted nested under their owning agent
// (`agent.backgroundTasks`) and the backend DTO carries no `ownerAgentId`, so a
// top-level merge keyed on `task.ownerAgentId` is unreachable for this contract
// and has been removed (ticket #5). The per-agent `ownerAgentId = agentId`
// fallback in `normalizeBackgroundTask` is what nested tasks rely on.
const topLevelInbox = arrayValue(state.inboxItems).map((item, index) =>
normalizeInboxItem(item, index, ""),
);