diff --git a/desktop/src/features/home/ui/HomeView.tsx b/desktop/src/features/home/ui/HomeView.tsx index 6dabe35e3..8c5b60c25 100644 --- a/desktop/src/features/home/ui/HomeView.tsx +++ b/desktop/src/features/home/ui/HomeView.tsx @@ -49,6 +49,7 @@ import type { HomeFeedResponse } from "@/shared/api/types"; import { KIND_REACTION } from "@/shared/constants/kinds"; import { topChromeInset } from "@/shared/layout/chromeLayout"; import { cn } from "@/shared/lib/cn"; +import { normalizePubkey } from "@/shared/lib/pubkey"; import { resolveMentionNames } from "@/shared/lib/resolveMentionNames"; import { useElementWidth } from "@/shared/hooks/use-mobile"; import { @@ -212,6 +213,21 @@ export function HomeView({ enabled: feedProfilePubkeys.length > 0, }); const feedProfiles = feedProfilesQuery.data?.profiles; + const inboxAgentPubkeys = React.useMemo(() => { + const pubkeys = new Set(); + + for (const item of feed?.feed.agentActivity ?? []) { + pubkeys.add(normalizePubkey(item.pubkey)); + } + + for (const [pubkey, profile] of Object.entries(feedProfiles ?? {})) { + if (profile.isAgent) { + pubkeys.add(normalizePubkey(pubkey)); + } + } + + return pubkeys; + }, [feed?.feed.agentActivity, feedProfiles]); const inboxItems = React.useMemo( () => buildInboxItems({ @@ -460,6 +476,7 @@ export function HomeView({ {showListPane ? ( { }); type InboxDetailPaneProps = { + agentPubkeys?: ReadonlySet; canDelete: boolean; canOpenChannel: boolean; canReply: boolean; @@ -69,6 +70,7 @@ type InboxDetailPaneProps = { }; export function InboxDetailPane({ + agentPubkeys, canDelete, canOpenChannel, canReply, @@ -324,6 +326,7 @@ export function InboxDetailPane({
) : null} ; + agentPubkeys?: ReadonlySet; doneSet: ReadonlySet; filter: InboxFilter; items: InboxItem[]; @@ -116,6 +118,7 @@ type InboxListPaneProps = { export function InboxListPane({ activeReminderEventIds, + agentPubkeys, doneSet, filter, items, @@ -154,6 +157,9 @@ export function InboxListPane({ const hasActiveReminder = activeReminderEventIds?.has(item.id) ?? false; const hasChannelTarget = Boolean(item.item.channelId); const typeLabel = getInboxTypeLabel(item); + const isSenderAgent = + agentPubkeys?.has(normalizePubkey(item.item.pubkey)) === true; + const profileRole = isSenderAgent ? "bot" : undefined; const rowHighlightColor = isSelected ? "color-mix(in srgb, hsl(var(--background)) 70%, hsl(var(--muted)) 30%)" : "color-mix(in srgb, hsl(var(--background)) 75%, hsl(var(--muted)) 25%)"; @@ -188,11 +194,16 @@ export function InboxListPane({
- + diff --git a/desktop/src/features/home/ui/InboxMessageRow.tsx b/desktop/src/features/home/ui/InboxMessageRow.tsx index 01b03c401..7bf9a2de7 100644 --- a/desktop/src/features/home/ui/InboxMessageRow.tsx +++ b/desktop/src/features/home/ui/InboxMessageRow.tsx @@ -8,6 +8,7 @@ import { useReactionHandler } from "@/features/messages/ui/useReactionHandler"; import { useMessageEmoji } from "@/features/messages/lib/useMessageEmoji"; import { UserProfilePopover } from "@/features/profile/ui/UserProfilePopover"; import { cn } from "@/shared/lib/cn"; +import { normalizePubkey } from "@/shared/lib/pubkey"; import { Markdown } from "@/shared/ui/markdown"; import { UserAvatar } from "@/shared/ui/UserAvatar"; @@ -31,6 +32,7 @@ function toTimelineMessage(message: InboxDisplayMessage): TimelineMessage { } type InboxMessageRowProps = { + agentPubkeys?: ReadonlySet; canReply: boolean; /** Channel UUID for "Copy link" — passed straight through to MessageActionBar. */ channelId?: string | null; @@ -45,6 +47,7 @@ type InboxMessageRowProps = { }; export function InboxMessageRow({ + agentPubkeys, canReply, channelId = null, isFocusHighlightVisible, @@ -70,6 +73,9 @@ export function InboxMessageRow({ errorMessage: reactionErrorMessage, select: handleReactionSelect, } = useReactionHandler(timelineMessage, onToggleReaction); + const isAuthorAgent = + agentPubkeys?.has(normalizePubkey(message.authorPubkey)) === true; + const profileRole = isAuthorAgent ? "bot" : undefined; return (
@@ -117,7 +123,9 @@ export function InboxMessageRow({
@@ -134,7 +142,9 @@ export function InboxMessageRow({
diff --git a/desktop/tests/e2e/smoke.spec.ts b/desktop/tests/e2e/smoke.spec.ts index 74d66ce65..ac538c702 100644 --- a/desktop/tests/e2e/smoke.spec.ts +++ b/desktop/tests/e2e/smoke.spec.ts @@ -2,6 +2,9 @@ import { expect, test } from "@playwright/test"; import { installMockBridge } from "../helpers/bridge"; +const DEFAULT_AGENT_ACTIVITY_PUBKEY = + "db0b028cd36f4d3e36c8300cce87252c1f7fc9495ffecc53f393fcac341ffd36"; + async function getTimelineMetrics(page: import("@playwright/test").Page) { return page.getByTestId("message-timeline").evaluate((element) => { const timeline = element as HTMLDivElement; @@ -80,6 +83,42 @@ async function selectHomeInboxFilter( await page.getByRole("menuitemradio", { name: label }).click(); } +async function readCommandPayloadLog(page: import("@playwright/test").Page) { + return page.evaluate(() => { + return ( + ( + window as Window & { + __BUZZ_E2E_COMMAND_LOG__?: Array<{ + command: string; + payload: unknown; + }>; + } + ).__BUZZ_E2E_COMMAND_LOG__ ?? [] + ); + }); +} + +async function readStartHuddleMemberPubkeys( + page: import("@playwright/test").Page, +) { + const commandLog = await readCommandPayloadLog(page); + return commandLog.flatMap((entry) => { + if (entry.command !== "start_huddle") { + return []; + } + + const payload = + entry.payload && typeof entry.payload === "object" + ? (entry.payload as { + memberPubkeys?: unknown; + member_pubkeys?: unknown; + }) + : null; + const memberPubkeys = payload?.memberPubkeys ?? payload?.member_pubkeys; + return Array.isArray(memberPubkeys) ? memberPubkeys.map(String) : []; + }); +} + test.beforeEach(async ({ page }) => { await installMockBridge(page); }); @@ -191,6 +230,29 @@ test("inbox feed shows channel and agent activity sections", async ({ ); }); +test("inbox agent hover huddle passes the agent pubkey", async ({ page }) => { + await page.goto("/"); + + await selectHomeInboxFilter(page, "Agents"); + const agentRow = page.getByTestId("home-inbox-item-mock-feed-agent"); + await expect(agentRow).toContainText( + "Agent progress: channel index complete.", + ); + + await agentRow.getByTestId("home-inbox-item-avatar-mock-feed-agent").hover(); + const profilePopover = page.locator( + '[data-testid="user-profile-popover"][data-state="open"]', + ); + await expect(profilePopover).toBeVisible(); + await profilePopover + .getByTestId(`user-profile-popover-huddle-${DEFAULT_AGENT_ACTIVITY_PUBKEY}`) + .click(); + + await expect + .poll(() => readStartHuddleMemberPubkeys(page)) + .toEqual(expect.arrayContaining([DEFAULT_AGENT_ACTIVITY_PUBKEY])); +}); + test("opens a mocked forum activity item from the inbox feed", async ({ page, }) => {