diff --git a/ui/src/components/app-sidebar-agent-menu.ts b/ui/src/components/app-sidebar-agent-menu.ts index ea58187e4e79..81263cd39cbf 100644 --- a/ui/src/components/app-sidebar-agent-menu.ts +++ b/ui/src/components/app-sidebar-agent-menu.ts @@ -135,9 +135,7 @@ function sidebarAgentMenuRows(params: { const agentId = normalizeAgentId(entry.id); return ( agentId.toLowerCase().includes(query) || - (params.identities.get(agentId)?.name?.trim() || normalizeAgentLabel(entry)) - .toLowerCase() - .includes(query) + normalizeAgentLabel(entry, params.identities.get(agentId)).toLowerCase().includes(query) ); }); return { rows, showFilter: true }; @@ -164,7 +162,7 @@ function sidebarAgentMenuRows(params: { function renderAgentRow(agent: AgentMenuAgent, params: SidebarAgentMenuParams) { const agentId = normalizeAgentId(agent.id); const identity = params.identities.get(agentId) ?? null; - const label = identity?.name?.trim() || normalizeAgentLabel(agent); + const label = normalizeAgentLabel(agent, identity); const active = agentId === params.activeId; const unread = active ? 0 : params.agentUnreadCount(agentId); const approvals = params.agentApprovalCount(agentId); diff --git a/ui/src/components/app-sidebar-render.ts b/ui/src/components/app-sidebar-render.ts index 2ee931177593..affa9ad0fd55 100644 --- a/ui/src/components/app-sidebar-render.ts +++ b/ui/src/components/app-sidebar-render.ts @@ -82,8 +82,7 @@ export function renderAppSidebarBrand(host: AppSidebarRenderHost) { const agentId = normalizeAgentId(entry.id); return agentId !== cardAgentId && host.agentUnreadCount(agentId) > 0; }); - const cardName = - cardIdentity?.name?.trim() || (cardAgent ? normalizeAgentLabel(cardAgent) : cardAgentId); + const cardName = cardAgent ? normalizeAgentLabel(cardAgent, cardIdentity) : cardAgentId; const approvalCount = host.sessionData.approvalBadgeSnapshot().agentCounts.get(cardAgentId) ?? 0; const gateway = host.sessionDataContext?.gateway; const avatarAuthToken = gateway diff --git a/ui/src/components/sidebar-menus-render.ts b/ui/src/components/sidebar-menus-render.ts index 45e29e83e677..82f618938d1f 100644 --- a/ui/src/components/sidebar-menus-render.ts +++ b/ui/src/components/sidebar-menus-render.ts @@ -84,7 +84,7 @@ export function renderSidebarAgentMenuForController(controller: SidebarMenusCont position, basePath: host.basePath, activeId, - activeName: identity?.name?.trim() || (agent ? normalizeAgentLabel(agent) : activeId), + activeName: agent ? normalizeAgentLabel(agent, identity) : activeId, agents, identities, filter: controller.agentMenuFilter, diff --git a/ui/src/lib/agents/display.ts b/ui/src/lib/agents/display.ts index d4658cc51b8e..2ba4e793cd4a 100644 --- a/ui/src/lib/agents/display.ts +++ b/ui/src/lib/agents/display.ts @@ -23,6 +23,8 @@ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "../str type AgentRosterEntry = { id: string; kind?: "agent" | "system"; + name?: string; + identity?: { name?: string }; }; /** Ordinary agent targets; system rows remain available to diagnostic surfaces. */ @@ -319,13 +321,16 @@ type ConfigSnapshot = { }; }; -export function normalizeAgentLabel(agent: { - id: string; - name?: string; - identity?: { name?: string }; -}) { +export function normalizeAgentLabel( + agent: AgentRosterEntry, + hydratedIdentity?: { name?: string } | null, +) { + // Roster labels own operator target identity; workspace identity only fills gaps. return ( - normalizeOptionalString(agent.name) ?? normalizeOptionalString(agent.identity?.name) ?? agent.id + normalizeOptionalString(agent.name) ?? + normalizeOptionalString(agent.identity?.name) ?? + normalizeOptionalString(hydratedIdentity?.name) ?? + agent.id ); } diff --git a/ui/src/test-helpers/app-sidebar-cases/agent-menu.ts b/ui/src/test-helpers/app-sidebar-cases/agent-menu.ts index 317acdda20f9..db86b11d30e5 100644 --- a/ui/src/test-helpers/app-sidebar-cases/agent-menu.ts +++ b/ui/src/test-helpers/app-sidebar-cases/agent-menu.ts @@ -52,6 +52,52 @@ describe("AppSidebar agent chip", () => { expect(request).toHaveBeenCalledWith("agent.identity.get", { agentId: "main" }); }); + it("keeps the configured roster label when identity hydration returns a fallback", async () => { + const request = vi.fn(async (_method: string, params: { agentId: string }) => + params.agentId === "main" + ? { agentId: "main", name: "Workspace Molty", avatar: "🦞" } + : { agentId: "rust-claw", name: "Assistant", avatar: "🦀" }, + ); + const gatewayHarness = createGatewayHarness({ request } as unknown as GatewayBrowserClient); + const agentIdentity = createAgentIdentityCapability(gatewayHarness.gateway); + const { sidebar } = await mountSidebar( + gatewayHarness.gateway, + createSessions("main", ["agent:main:main"]), + "panel", + { + defaultId: "main", + mainKey: "main", + scope: "per-sender", + agents: [{ id: "main" }, { id: "rust-claw", name: "rust-claw" }], + }, + [], + agentIdentity, + ); + + sidebar.connected = true; + await vi.waitFor(() => { + expect(sidebar.querySelector(".sidebar-agent-card__name")?.textContent?.trim()).toBe( + "Workspace Molty", + ); + }); + sidebar.querySelector(".sidebar-agent-card__main")?.click(); + await vi.waitFor(() => { + expect(request).toHaveBeenCalledWith("agent.identity.get", { agentId: "rust-claw" }); + const labels = [ + ...sidebar.querySelectorAll(".sidebar-agent-menu .agent-select__option-label"), + ].map((element) => element.textContent?.trim()); + expect(labels).toEqual(["Workspace Molty", "rust-claw"]); + }); + await vi.waitFor(() => { + const rustRow = [ + ...sidebar.querySelectorAll(".sidebar-agent-menu__agent-switch"), + ].find((row) => row.textContent?.includes("rust-claw")); + expect( + rustRow?.querySelector(".agent-select__avatar--text")?.getAttribute("data-avatar"), + ).toBe("🦀"); + }); + }); + it("hydrates agents added while the switcher remains open", async () => { const request = vi.fn(async (_method: string, params: { agentId: string }) => ({ agentId: params.agentId,