From 07b8f35de422001ed35f1046b5f8fab35c8eda8a Mon Sep 17 00:00:00 2001 From: klopez4212 Date: Mon, 29 Jun 2026 20:01:15 +0100 Subject: [PATCH] Fix wave agent target classification --- .../src/features/channels/ui/ChannelPane.tsx | 8 ++---- .../features/channels/ui/ChannelScreen.tsx | 3 +-- .../messages/lib/formatTimelineMessages.ts | 6 ++++- .../src/features/messages/lib/waveMessage.ts | 15 +++++++++++ .../messages/ui/WaveMessageAttachment.tsx | 10 +++---- .../profile/ui/UserProfilePopover.tsx | 16 ++++++++--- desktop/tests/e2e/mentions.spec.ts | 27 +++++++++++-------- 7 files changed, 56 insertions(+), 29 deletions(-) diff --git a/desktop/src/features/channels/ui/ChannelPane.tsx b/desktop/src/features/channels/ui/ChannelPane.tsx index 143a9f79b..afbf7cb58 100644 --- a/desktop/src/features/channels/ui/ChannelPane.tsx +++ b/desktop/src/features/channels/ui/ChannelPane.tsx @@ -16,10 +16,7 @@ import { type AgentConversationMarker, } from "@/features/agents/agentConversations"; import { buildDirectMessageIntro } from "@/features/channels/lib/dmParticipantDisplay"; -import { - getDmHuddleMemberPubkeys, - hasOtherDmParticipant, -} from "@/features/channels/lib/dmHuddleMembers"; +import { getDmHuddleMemberPubkeys } from "@/features/channels/lib/dmHuddleMembers"; import { buildVideoReviewCommentsByRootId, buildVideoReviewContextForMessage, @@ -176,8 +173,7 @@ export const ChannelPane = React.memo(function ChannelPane({ () => getDmHuddleMemberPubkeys(activeChannel, agentPubkeys, currentPubkey), [activeChannel, agentPubkeys, currentPubkey], ); - const huddleMemberPubkeysPending = - agentPubkeysPending && hasOtherDmParticipant(activeChannel, currentPubkey); + const huddleMemberPubkeysPending = agentPubkeysPending; const isActiveWelcomeChannel = activeChannel !== null && isWelcomeChannel(activeChannel); React.useEffect(() => { diff --git a/desktop/src/features/channels/ui/ChannelScreen.tsx b/desktop/src/features/channels/ui/ChannelScreen.tsx index de2bf8aef..07bd395df 100644 --- a/desktop/src/features/channels/ui/ChannelScreen.tsx +++ b/desktop/src/features/channels/ui/ChannelScreen.tsx @@ -375,8 +375,7 @@ export function ChannelScreen({ (messageProfilesQuery.isPending || messageProfilesQuery.isFetching || messageProfilesQuery.isPlaceholderData)); - const agentPubkeysPending = - activeChannel?.channelType === "dm" && agentLookupPending; + const agentPubkeysPending = agentLookupPending; const { agentSessionCandidates, botTypingEntries, diff --git a/desktop/src/features/messages/lib/formatTimelineMessages.ts b/desktop/src/features/messages/lib/formatTimelineMessages.ts index 0baa9aeee..5fb6866b9 100644 --- a/desktop/src/features/messages/lib/formatTimelineMessages.ts +++ b/desktop/src/features/messages/lib/formatTimelineMessages.ts @@ -13,6 +13,7 @@ import { getThreadReference, isBroadcastReply, } from "@/features/messages/lib/threading"; +import { collectWaveTargetPubkeys } from "@/features/messages/lib/waveMessage"; import { resolveUserLabel, type UserProfileLookup, @@ -488,7 +489,7 @@ export function collectMessageAuthorPubkeys(events: RelayEvent[]) { } export function collectMessageMentionPubkeys( - events: Array<{ tags?: string[][] }>, + events: Array<{ tags?: string[][]; content?: string }>, ) { const pubkeys = new Set(); @@ -500,6 +501,9 @@ export function collectMessageMentionPubkeys( } } } + for (const pubkey of collectWaveTargetPubkeys(events)) { + pubkeys.add(pubkey); + } return [...pubkeys]; } diff --git a/desktop/src/features/messages/lib/waveMessage.ts b/desktop/src/features/messages/lib/waveMessage.ts index fe3d9a1cf..cd1e6ebfa 100644 --- a/desktop/src/features/messages/lib/waveMessage.ts +++ b/desktop/src/features/messages/lib/waveMessage.ts @@ -57,3 +57,18 @@ export function parseWaveMessageContent( targetIsAgent, }; } + +export function collectWaveTargetPubkeys( + events: Array<{ content?: string }>, +): string[] { + const pubkeys = new Set(); + + for (const event of events) { + const waveMessage = parseWaveMessageContent(event.content ?? ""); + if (waveMessage?.targetPubkey) { + pubkeys.add(waveMessage.targetPubkey); + } + } + + return [...pubkeys]; +} diff --git a/desktop/src/features/messages/ui/WaveMessageAttachment.tsx b/desktop/src/features/messages/ui/WaveMessageAttachment.tsx index 63675358c..a585cb7bb 100644 --- a/desktop/src/features/messages/ui/WaveMessageAttachment.tsx +++ b/desktop/src/features/messages/ui/WaveMessageAttachment.tsx @@ -56,11 +56,11 @@ export function WaveMessageAttachment({ const targetAgentLookupPending = targetIsAgent && normalizedTargetPubkey !== null && - (huddleMemberPubkeysPending || - (targetAgentPubkey === null && - !resolvedHuddleMemberPubkeys.some( - (pubkey) => normalizePubkey(pubkey) === normalizedTargetPubkey, - ))); + huddleMemberPubkeysPending && + targetAgentPubkey === null && + !resolvedHuddleMemberPubkeys.some( + (pubkey) => normalizePubkey(pubkey) === normalizedTargetPubkey, + ); const startHuddleDisabled = !channelId || isStarting || targetAgentLookupPending; diff --git a/desktop/src/features/profile/ui/UserProfilePopover.tsx b/desktop/src/features/profile/ui/UserProfilePopover.tsx index bad2b0f71..0a40a0354 100644 --- a/desktop/src/features/profile/ui/UserProfilePopover.tsx +++ b/desktop/src/features/profile/ui/UserProfilePopover.tsx @@ -212,8 +212,7 @@ export function UserProfilePopover({ const isAgentByProfile = Boolean( usersBatchQuery.data?.profiles[normalizePubkey(pubkey)]?.isAgent, ); - const isAgentTarget = - isBotProfile || isAgentByProfile || isAgentClassificationPending; + const isAgentTarget = isBotProfile || isAgentByProfile; const displayName = profile?.displayName ?? truncatePubkey(pubkey); // Owner signal mirrors UserProfilePanel: a declared NIP-OA owner whose agent // runs elsewhere holds no local seckey, so key custody (`isOwner`) alone @@ -368,7 +367,13 @@ export function UserProfilePopover({ ]); const handleWave = React.useCallback(async () => { - if (!showProfileActions || pendingAction !== null) return; + if ( + !showProfileActions || + pendingAction !== null || + isAgentClassificationPending + ) { + return; + } clearHoverTimer(); setPendingAction("wave"); @@ -453,6 +458,7 @@ export function UserProfilePopover({ currentPubkey, goChannel, identityQuery.data, + isAgentClassificationPending, isAgentTarget, openDmMutation, pendingAction, @@ -614,7 +620,9 @@ export function UserProfilePopover({ className="buzz-wave-hover-trigger shrink-0 px-3" data-testid={`user-profile-popover-wave-${pubkey}`} disabled={ - pendingAction !== null || openDmMutation.isPending + pendingAction !== null || + openDmMutation.isPending || + isAgentClassificationPending } onClick={() => { void handleWave(); diff --git a/desktop/tests/e2e/mentions.spec.ts b/desktop/tests/e2e/mentions.spec.ts index d0b6ff4e9..ea9de1680 100644 --- a/desktop/tests/e2e/mentions.spec.ts +++ b/desktop/tests/e2e/mentions.spec.ts @@ -1445,9 +1445,11 @@ test("wave attachment huddle passes the bot DM pubkey", async ({ page }) => { ); await expect(profilePopover).toBeVisible(); await expect(profilePopover.getByText("Codex")).toBeVisible(); - await profilePopover - .getByTestId(`user-profile-popover-wave-${TEST_IDENTITIES.charlie.pubkey}`) - .click(); + const charlieWaveButton = profilePopover.getByTestId( + `user-profile-popover-wave-${TEST_IDENTITIES.charlie.pubkey}`, + ); + await expect(charlieWaveButton).toBeEnabled(); + await charlieWaveButton.click(); await expect(page.getByTestId("message-wave-attachment")).toBeVisible(); await page @@ -1505,14 +1507,15 @@ test("wave attachment huddle waits for placeholder profile-only bot data", async '[data-testid="user-profile-popover"][data-state="open"]', ); await expect(profilePopover).toBeVisible(); - await profilePopover - .getByTestId(`user-profile-popover-wave-${PROFILE_ONLY_AGENT_PUBKEY}`) - .click(); + const miraWaveButton = profilePopover.getByTestId( + `user-profile-popover-wave-${PROFILE_ONLY_AGENT_PUBKEY}`, + ); + await expect(miraWaveButton).toBeEnabled({ timeout: 5_000 }); + await miraWaveButton.click(); const startHuddleButton = page .getByTestId("message-wave-attachment") .getByRole("button", { name: "Start huddle" }); - await expect(startHuddleButton).toBeDisabled(); await expect(startHuddleButton).toBeEnabled({ timeout: 5_000 }); await startHuddleButton.click(); @@ -1561,14 +1564,16 @@ test("wave attachment huddle waits for delayed bot DM pubkey", async ({ '[data-testid="user-profile-popover"][data-state="open"]', ); await expect(profilePopover).toBeVisible(); - await profilePopover - .getByTestId(`user-profile-popover-wave-${DELAYED_RELAY_AGENT_PUBKEY}`) - .click(); + const orbitWaveButton = profilePopover.getByTestId( + `user-profile-popover-wave-${DELAYED_RELAY_AGENT_PUBKEY}`, + ); + await expect(orbitWaveButton).toBeDisabled(); + await expect(orbitWaveButton).toBeEnabled({ timeout: 7_000 }); + await orbitWaveButton.click(); const startHuddleButton = page .getByTestId("message-wave-attachment") .getByRole("button", { name: "Start huddle" }); - await expect(startHuddleButton).toBeDisabled(); await expect(startHuddleButton).toBeEnabled({ timeout: 7_000 }); await startHuddleButton.click();