fix: dedupe task timeline milestones
This commit is contained in:
@ -221,4 +221,52 @@ describe('buildTaskTimelineCards', () => {
|
||||
|
||||
expect(cards.map((card) => card.id)).toEqual(['task-1:created', 'evt-invalid-date']);
|
||||
});
|
||||
|
||||
it('dedupes synthetic result and acceptance milestones when lifecycle events exist', () => {
|
||||
const task = makeTask({
|
||||
is_open: false,
|
||||
status: 'closed',
|
||||
updated_at: '2026-05-26T10:04:00.000Z',
|
||||
closed_at: '2026-05-26T10:04:00.000Z',
|
||||
feedback: [
|
||||
{
|
||||
acceptance_type: 'accept',
|
||||
comment: '可以',
|
||||
created_at: '2026-05-26T10:05:00.000Z',
|
||||
run_id: 'run-main',
|
||||
},
|
||||
],
|
||||
});
|
||||
const processEvents: ProcessEvent[] = [
|
||||
{
|
||||
event_id: 'evt-result-ready',
|
||||
run_id: 'run-main',
|
||||
parent_run_id: null,
|
||||
kind: 'task_result_ready',
|
||||
actor_type: 'agent',
|
||||
actor_id: 'main-agent',
|
||||
actor_name: 'Main Agent',
|
||||
text: 'Result is ready.',
|
||||
created_at: '2026-05-26T10:04:00.000Z',
|
||||
},
|
||||
{
|
||||
event_id: 'evt-acceptance-recorded',
|
||||
run_id: 'run-main',
|
||||
parent_run_id: null,
|
||||
kind: 'task_acceptance_recorded',
|
||||
actor_type: 'system',
|
||||
actor_id: 'task-system',
|
||||
actor_name: 'Task System',
|
||||
text: '可以',
|
||||
created_at: '2026-05-26T10:05:00.000Z',
|
||||
},
|
||||
];
|
||||
|
||||
const cards = buildTaskTimelineCards({ task, processEvents });
|
||||
|
||||
expect(cards.filter((card) => card.type === 'result')).toHaveLength(1);
|
||||
expect(cards.filter((card) => card.type === 'acceptance')).toHaveLength(1);
|
||||
expect(cards.map((card) => card.id)).toContain('evt-result-ready');
|
||||
expect(cards.map((card) => card.id)).toContain('evt-acceptance-recorded');
|
||||
});
|
||||
});
|
||||
|
||||
@ -209,6 +209,10 @@ function compareCardsByCreatedAt(
|
||||
return aTime - bTime || a.index - b.index;
|
||||
}
|
||||
|
||||
function acceptanceKey(runId: string | null | undefined, createdAt: string): string {
|
||||
return `${runId ?? ''}:${createdAt}`;
|
||||
}
|
||||
|
||||
export function buildTaskTimelineCards(input: BuildTaskTimelineCardsInput): TaskTimelineCard[] {
|
||||
const { task } = input;
|
||||
const processRuns = input.processRuns ?? task.process_runs ?? [];
|
||||
@ -216,6 +220,8 @@ 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>();
|
||||
let hasResultEventCard = false;
|
||||
const cards: TaskTimelineCard[] = [
|
||||
{
|
||||
id: `${task.task_id}:created`,
|
||||
@ -236,6 +242,12 @@ export function buildTaskTimelineCards(input: BuildTaskTimelineCardsInput): Task
|
||||
if (type === 'agent_progress') {
|
||||
runsWithProgressEvents.add(event.run_id);
|
||||
}
|
||||
if (type === 'result') {
|
||||
hasResultEventCard = true;
|
||||
}
|
||||
if (type === 'acceptance') {
|
||||
acceptanceEventKeys.add(acceptanceKey(event.run_id, event.created_at));
|
||||
}
|
||||
|
||||
cards.push({
|
||||
id: event.event_id,
|
||||
@ -292,7 +304,7 @@ export function buildTaskTimelineCards(input: BuildTaskTimelineCardsInput): Task
|
||||
});
|
||||
}
|
||||
|
||||
if (RESULT_STATUSES.has(task.status)) {
|
||||
if (RESULT_STATUSES.has(task.status) && !hasResultEventCard) {
|
||||
cards.push({
|
||||
id: `${task.task_id}:result`,
|
||||
taskId: task.task_id,
|
||||
@ -308,15 +320,19 @@ export function buildTaskTimelineCards(input: BuildTaskTimelineCardsInput): Task
|
||||
|
||||
for (let index = 0; index < task.feedback.length; index += 1) {
|
||||
const feedback = task.feedback[index];
|
||||
const runId = firstString(feedback.run_id) ?? null;
|
||||
const createdAt = feedbackCreatedAt(feedback, task);
|
||||
if (acceptanceEventKeys.has(acceptanceKey(runId, createdAt))) continue;
|
||||
|
||||
cards.push({
|
||||
id: `${task.task_id}:acceptance:${index}`,
|
||||
taskId: task.task_id,
|
||||
runId: firstString(feedback.run_id) ?? null,
|
||||
runId,
|
||||
type: 'acceptance',
|
||||
title: titleForCard('acceptance'),
|
||||
summary: feedbackSummary(feedback),
|
||||
status: firstString(feedback.acceptance_type),
|
||||
createdAt: feedbackCreatedAt(feedback, task),
|
||||
createdAt,
|
||||
details: feedback,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user