diff --git a/.changeset/fn-8423-duplicate-agent-badge.md b/.changeset/fn-8423-duplicate-agent-badge.md new file mode 100644 index 0000000000..372cd0cff7 --- /dev/null +++ b/.changeset/fn-8423-duplicate-agent-badge.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Show only one agent name badge when a task is assigned to its creator. +category: fix +dev: TaskCard suppresses redundant created-by pill when assigned and source agent identity match by ID-first rules (FN-8423). diff --git a/packages/dashboard/app/components/TaskCard.tsx b/packages/dashboard/app/components/TaskCard.tsx index b58700f7e3..62fa81c577 100644 --- a/packages/dashboard/app/components/TaskCard.tsx +++ b/packages/dashboard/app/components/TaskCard.tsx @@ -274,6 +274,35 @@ function isAgentCreatedTask(task: Task): boolean { return task.sourceType === "agent_heartbeat" || task.sourceType === "automation" || Boolean(getSourceAgentName(task)); } +function getNormalizedAgentIdentity(value: string | null | undefined): string | undefined { + return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined; +} + +/** + * FNXC:TaskCardAgentBadges 2026-07-20-00:00: + * FN-8423 requires TaskCard to retain assigned ownership but suppress redundant + * created-by provenance only for the same agent identity. IDs are authoritative: + * distinct present IDs stay distinct even when their display names collide; names + * are a case-insensitive fallback only when either identity has no usable ID. + */ +function isSameAgentIdentity( + assignedAgentId: string | undefined, + sourceAgentId: string | undefined, + assignedAgentName: string | null | undefined, + sourceAgentName: string | undefined, +): boolean { + const normalizedAssignedId = getNormalizedAgentIdentity(assignedAgentId); + const normalizedSourceId = getNormalizedAgentIdentity(sourceAgentId); + + if (normalizedAssignedId && normalizedSourceId) { + return normalizedAssignedId === normalizedSourceId; + } + + const normalizedAssignedName = getNormalizedAgentIdentity(assignedAgentName)?.toLocaleLowerCase(); + const normalizedSourceName = getNormalizedAgentIdentity(sourceAgentName)?.toLocaleLowerCase(); + return Boolean(normalizedAssignedName && normalizedSourceName && normalizedAssignedName === normalizedSourceName); +} + // ── Constants ─────────────────────────────────────────────────────────────── // Issue 1403: widened to ColumnId so `.has(task.column)` accepts custom column ids @@ -1405,6 +1434,15 @@ function TaskCardComponent({ const resolvedAssignedAgentName = assignedAgentNameFromMap ?? assignedAgentNameFromCache ?? agentName; const assignedAgentBadgeLabel = resolvedAssignedAgentName ?? task.assignedAgentId ?? ""; const isAgentNameLoading = Boolean(task.assignedAgentId && !resolvedAssignedAgentName); + const shouldShowCreatedAgentBadge = isAgentCreated && !( + task.assignedAgentId + && isSameAgentIdentity( + task.assignedAgentId, + task.sourceAgentId, + resolvedAssignedAgentName, + sourceAgentName, + ) + ); const taskProviders = useMemo(() => { const providers: string[] = []; if (task.modelProvider) providers.push(task.modelProvider); @@ -3742,7 +3780,7 @@ function TaskCardComponent({ )} )} - {isAgentCreated && ( + {shouldShowCreatedAgentBadge && (
{/** * FNXC:TaskCardLayout 2026-07-10-00:00: diff --git a/packages/dashboard/app/components/__tests__/TaskCard.test.tsx b/packages/dashboard/app/components/__tests__/TaskCard.test.tsx index c4630178cd..18c1dfe1bb 100644 --- a/packages/dashboard/app/components/__tests__/TaskCard.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskCard.test.tsx @@ -5232,6 +5232,78 @@ describe("TaskCard", () => { expect(container.querySelector(".card-agent-badge-row")).toBeNull(); }); + it("FN-8423 keeps only the assigned badge for the same agent ID", () => { + seedAgentsCache("fn-8423-same-id", [{ id: "agent-qa", name: "QA Engineer" }]); + + const { container } = render( + , + ); + + const assignedBadge = container.querySelector(".card-agent-badge"); + expect(assignedBadge).not.toBeNull(); + expect(assignedBadge).toHaveAttribute("title", "Assigned to QA Engineer"); + expect(container.querySelector(".card-agent-created-badge")).toBeNull(); + expect(container.querySelector(".card-agent-badge-row")).toBeNull(); + }); + + it("FN-8423 retains both badges for different IDs with the same display name", () => { + seedAgentsCache("fn-8423-distinct-ids", [ + { id: "agent-a", name: "QA Engineer" }, + { id: "agent-b", name: "QA Engineer" }, + ]); + + const { container } = render( + , + ); + + expect(container.querySelector(".card-agent-badge")).toHaveAttribute("title", "Assigned to QA Engineer"); + expect(container.querySelector(".card-agent-created-badge")).toHaveAttribute("title", "Created by agent: QA Engineer"); + expect(container.querySelector(".card-agent-badge-row")).not.toBeNull(); + }); + + it("FN-8423 falls back to matching names only when source agent ID is unavailable", () => { + seedAgentsCache("fn-8423-name-fallback", [{ id: "agent-qa", name: "QA Engineer" }]); + + const { container } = render( + , + ); + + expect(container.querySelector(".card-agent-badge")).toHaveAttribute("title", "Assigned to QA Engineer"); + expect(container.querySelector(".card-agent-created-badge")).toBeNull(); + expect(container.querySelector(".card-agent-badge-row")).toBeNull(); + }); + it("coexists with GitHub badge and timer metadata", () => { const { container } = render( { priority: "high", executionMode: "fast", sourceType: "automation", - sourceMetadata: { agentName: "Task Robot" }, + sourceMetadata: { agentName: "Created Robot" }, assignedAgentId: "agent-1", columnMovedAt: "2026-06-30T12:00:00.000Z", updatedAt: "2026-06-30T12:00:00.000Z",