diff --git a/app-instance/frontend/lib/task-timeline.test.ts b/app-instance/frontend/lib/task-timeline.test.ts index 0161dd1..4d40378 100644 --- a/app-instance/frontend/lib/task-timeline.test.ts +++ b/app-instance/frontend/lib/task-timeline.test.ts @@ -258,7 +258,10 @@ describe('buildTaskTimelineCards', () => { actor_id: 'task-system', actor_name: 'Task System', text: '可以', - created_at: '2026-05-26T10:05:00.000Z', + created_at: '2026-05-26T10:05:02.000Z', + metadata: { + acceptance_type: 'accept', + }, }, ]; diff --git a/app-instance/frontend/lib/task-timeline.ts b/app-instance/frontend/lib/task-timeline.ts index 2d75620..dd2e125 100644 --- a/app-instance/frontend/lib/task-timeline.ts +++ b/app-instance/frontend/lib/task-timeline.ts @@ -168,6 +168,10 @@ function feedbackSummary(feedback: Record): string | undefined return firstString(feedback.comment, feedback.summary, feedback.acceptance_type); } +function acceptanceTypeFromRecord(record: Record | undefined): string | null { + return firstString(record?.acceptance_type, record?.feedback_type)?.toLowerCase() ?? null; +} + function resultSummary(task: BackendTask): string | undefined { return firstString( task.metadata?.result_summary, @@ -209,8 +213,29 @@ function compareCardsByCreatedAt( return aTime - bTime || a.index - b.index; } -function acceptanceKey(runId: string | null | undefined, createdAt: string): string { - return `${runId ?? ''}:${createdAt}`; +type AcceptanceEventIdentity = { + runId: string | null; + acceptanceType: string | null; +}; + +function isCoveredByAcceptanceEvent( + feedback: Record, + acceptanceEvents: AcceptanceEventIdentity[], +): boolean { + const feedbackType = acceptanceTypeFromRecord(feedback); + if (!feedbackType) return false; + + const feedbackRunId = firstString(feedback.run_id) ?? null; + const matchingTypeEvents = acceptanceEvents.filter((event) => event.acceptanceType === feedbackType); + + if (feedbackRunId) { + return ( + matchingTypeEvents.some((event) => event.runId === feedbackRunId) || + (matchingTypeEvents.length === 1 && !matchingTypeEvents[0].runId) + ); + } + + return matchingTypeEvents.length === 1; } export function buildTaskTimelineCards(input: BuildTaskTimelineCardsInput): TaskTimelineCard[] { @@ -220,7 +245,7 @@ export function buildTaskTimelineCards(input: BuildTaskTimelineCardsInput): Task const processArtifacts = input.processArtifacts ?? task.process_artifacts ?? []; const runsById = buildRunMap(processRuns); const runsWithProgressEvents = new Set(); - const acceptanceEventKeys = new Set(); + const acceptanceEvents: AcceptanceEventIdentity[] = []; let hasResultEventCard = false; const cards: TaskTimelineCard[] = [ { @@ -246,7 +271,10 @@ export function buildTaskTimelineCards(input: BuildTaskTimelineCardsInput): Task hasResultEventCard = true; } if (type === 'acceptance') { - acceptanceEventKeys.add(acceptanceKey(event.run_id, event.created_at)); + acceptanceEvents.push({ + runId: firstString(event.run_id) ?? null, + acceptanceType: acceptanceTypeFromRecord(event.metadata), + }); } cards.push({ @@ -322,7 +350,7 @@ export function buildTaskTimelineCards(input: BuildTaskTimelineCardsInput): Task const feedback = task.feedback[index]; const runId = firstString(feedback.run_id) ?? null; const createdAt = feedbackCreatedAt(feedback, task); - if (acceptanceEventKeys.has(acceptanceKey(runId, createdAt))) continue; + if (isCoveredByAcceptanceEvent(feedback, acceptanceEvents)) continue; cards.push({ id: `${task.task_id}:acceptance:${index}`,