diff --git a/frontend/src/v2/__tests__/V2Inspector.test.tsx b/frontend/src/v2/__tests__/V2Inspector.test.tsx index 65c02cf04b..f64389bad7 100644 --- a/frontend/src/v2/__tests__/V2Inspector.test.tsx +++ b/frontend/src/v2/__tests__/V2Inspector.test.tsx @@ -141,6 +141,43 @@ describe('V2Inspector', () => { expect(screen.queryByRole('tab')).not.toBeInTheDocument(); }); + test('uses the resolved agent label while matching attention under an old profile ID', () => { + renderInspector({ detail: { ...detail, agents: [{ + agentName: 'scout', instanceId: 'default', displayName: 'Scout', + profile: { displayName: 'u1a9e5ecede' }, + }] } as any, attentionItems: [{ + id: 'legacy-request', kind: 'decision', title: 'Legacy request', + actorName: 'u1a9e5ecede', podId: 'pod-1', + }] }); + + expect(screen.getAllByText('Scout')).toHaveLength(2); + expect(screen.getByRole('button', { name: /Scout needs you · Legacy request/ })).toBeInTheDocument(); + expect(screen.queryByText('u1a9e5ecede')).not.toBeInTheDocument(); + }); + + test('does not assign anonymous attention to an agent without a profile name', () => { + renderInspector({ detail: { ...detail, agents: [{ + agentName: 'scout', instanceId: 'default', displayName: 'Scout', + }] } as any, attentionItems: [{ + id: 'human-question', kind: 'decision', title: 'Human question', podId: 'pod-1', + }] }); + + expect(screen.getByText('Human question')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Scout idle/ })).toBeInTheDocument(); + }); + + test('does not assign an unowned active task to an agent without a profile name', async () => { + mockGet.mockImplementation((url: string) => Promise.resolve(url === '/api/v1/tasks/pod-1' + ? { tasks: [{ taskId: 'TASK-1', title: 'Unowned work', status: 'claimed' }] } + : { items: [], total: 0 })); + renderInspector({ detail: { ...detail, agents: [{ + agentName: 'scout', instanceId: 'default', displayName: 'Scout', + }] } as any, attentionCount: 0, attentionItems: [] }); + + await screen.findByText('no files yet'); + expect(screen.getByRole('button', { name: /Scout idle/ })).toBeInTheDocument(); + }); + test('uses the short lowercase room name in the single-line agents label', () => { renderInspector({ detail: { ...detail, pod: { ...detail.pod, name: 'Sharpen · decision loop' } } as any }); diff --git a/frontend/src/v2/components/V2Inspector.tsx b/frontend/src/v2/components/V2Inspector.tsx index 6f1c4c3080..b8e0ae5180 100644 --- a/frontend/src/v2/components/V2Inspector.tsx +++ b/frontend/src/v2/components/V2Inspector.tsx @@ -48,7 +48,9 @@ const isWorkingTask = (task: TaskItem): boolean => ( const labelForAgent = (agent: V2Agent): string => ( - agent.profile?.displayName || agent.displayName || agent.agentName + // The roster's displayName is resolved from the agent User identity. A + // profile name may be an older generated ID, so it is only a fallback. + agent.displayName || agent.profile?.displayName || agent.agentName ); // Pods often carry a longer descriptive title, while the compact inspector @@ -58,12 +60,21 @@ const shortRoomName = (name: string): string => ( ); const matchesAgent = (agent: V2Agent, value?: string): boolean => { - const candidate = (value || '').toLowerCase(); + const candidate = (value || '').trim().toLowerCase(); + if (!candidate) return false; return candidate === (agent.agentName || '').toLowerCase() || candidate === labelForAgent(agent).toLowerCase() + || candidate === (agent.profile?.displayName || '').toLowerCase() || candidate === (agent.instanceId || '').toLowerCase(); }; +const labelForAttentionActor = (agents: V2Agent[], actorName: string): string => { + const legacyAgent = agents.find((agent) => ( + agent.profile?.displayName?.toLowerCase() === actorName.toLowerCase() + )); + return legacyAgent ? labelForAgent(legacyAgent) : actorName; +}; + const agentState = ( agent: V2Agent, attention: V2AttentionItem[], @@ -185,7 +196,7 @@ const V2Inspector: React.FC = ({ detail, currentUserId, onPodC {attention.map((item) => ( ))}