fix: make acceptance timeline dedupe robust

This commit is contained in:
2026-05-26 12:04:12 +08:00
parent 2e4f8541ee
commit 29d175222d
2 changed files with 37 additions and 6 deletions

View File

@ -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',
},
},
];

View File

@ -168,6 +168,10 @@ function feedbackSummary(feedback: Record<string, unknown>): string | undefined
return firstString(feedback.comment, feedback.summary, feedback.acceptance_type);
}
function acceptanceTypeFromRecord(record: Record<string, unknown> | 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<string, unknown>,
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<string>();
const acceptanceEventKeys = new Set<string>();
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}`,