From 9cb93a839093c3f35335c85b54f8b6e9b2fe8ec6 Mon Sep 17 00:00:00 2001 From: Taksh Date: Wed, 29 Jul 2026 21:40:15 +0300 Subject: [PATCH] fix(desktop): confirm before re-adding mentioned non-member agents Mentioning a removed managed agent no longer silently re-attaches them. Non-member agent mentions go through the invite dialog; ensure-ready only starts agents that are already channel members. Signed-off-by: Taksh --- .../messages/ui/NonMemberMentionDialog.tsx | 5 +- .../messages/ui/useMentionSendFlow.ts | 59 +++++++++++-------- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/desktop/src/features/messages/ui/NonMemberMentionDialog.tsx b/desktop/src/features/messages/ui/NonMemberMentionDialog.tsx index 20f50924fb2..107bb6b5f30 100644 --- a/desktop/src/features/messages/ui/NonMemberMentionDialog.tsx +++ b/desktop/src/features/messages/ui/NonMemberMentionDialog.tsx @@ -39,11 +39,12 @@ export function NonMemberMentionDialog({ - Mention people outside this channel? + Mention someone outside this channel? {names.join(", ")} {names.length === 1 ? "is" : "are"} not in this - channel. Invite them to the channel, or send without inviting them. + channel. Invite them (this re-adds previously removed members), or + send without inviting them. {error ? ( diff --git a/desktop/src/features/messages/ui/useMentionSendFlow.ts b/desktop/src/features/messages/ui/useMentionSendFlow.ts index 6d4e007cd4f..be8981485aa 100644 --- a/desktop/src/features/messages/ui/useMentionSendFlow.ts +++ b/desktop/src/features/messages/ui/useMentionSendFlow.ts @@ -271,11 +271,14 @@ export function useMentionSendFlow({ await startAgentMutation.mutateAsync(agent.pubkey); } } else { - await attachAgentMutation.mutateAsync({ - channelId: capturedChannelId, - agent, - role: "bot", - }); + // Never silently re-attach a non-member agent on mention. + // Channel membership changes go through the invite confirmation + // (handleInviteNonMembers) so a prior Remove cannot be undone by + // typing @Name (#3577). + errors.push( + `${agent.name}: not in this channel — invite them before mentioning.`, + ); + continue; } pubkeys.push(pubkey); } catch (error) { @@ -294,7 +297,6 @@ export function useMentionSendFlow({ }; }, [ - attachAgentMutation, getManagedAgentsByPubkey, mentions.memberPubkeys, startAgentMutation, @@ -713,23 +715,10 @@ export function useMentionSendFlow({ buildCustomEmojiTags(finalContent, customEmoji), ); const nonMemberPubkeys = getNonMemberMentionPubkeys(pubkeys); - let promptNonMemberPubkeys = nonMemberPubkeys.filter( - (pubkey) => - !mentions.isManagedAgentPubkey(pubkey) && - !createdPersonaAgentPubkeySet.has(normalizePubkey(pubkey)), - ); - - if (promptNonMemberPubkeys.length > 0) { - try { - const managedAgentsByPubkey = await getManagedAgentsByPubkey(); - promptNonMemberPubkeys = promptNonMemberPubkeys.filter( - (pubkey) => !managedAgentsByPubkey.has(normalizePubkey(pubkey)), - ); - } catch { - // Keep the hook-based managed-agent filtering even if the query - // fallback misses; ordinary non-members still get prompted. - } - } + // Include managed agents that aren't channel members so a mention + // cannot silently re-attach a previously removed agent (#3577). + // First-time adds go through the same invite confirmation. + const promptNonMemberPubkeys = nonMemberPubkeys; const pendingDraft: PendingNonMemberMentionSend = { capturedChannelId: effectiveChannelId, @@ -769,12 +758,10 @@ export function useMentionSendFlow({ channelType, createMentionedPersonaAgents, customEmoji, - getManagedAgentsByPubkey, getNonMemberMentionPubkeys, getDmThreadAgentMentionError, mentions.extractMentionPubkeys, mentions.isAgentPubkey, - mentions.isManagedAgentPubkey, onPrepareSendChannel, ], ); @@ -827,11 +814,14 @@ export function useMentionSendFlow({ const managedAgentsByPubkey = await getManagedAgentsByPubkey(); const peoplePubkeys: string[] = []; const relayAgentPubkeys: string[] = []; + const managedAgentsToAttach: ManagedAgent[] = []; for (const pubkey of uniqueNormalizedPubkeys( pendingNonMemberSend.nonMemberPubkeys, )) { - if (managedAgentsByPubkey.has(pubkey)) { + const managed = managedAgentsByPubkey.get(pubkey); + if (managed) { + managedAgentsToAttach.push(managed); continue; } @@ -861,6 +851,22 @@ export function useMentionSendFlow({ errors.push(...result.errors.map((error) => error.error)); } + for (const agent of managedAgentsToAttach) { + try { + await attachAgentMutation.mutateAsync({ + channelId: pendingNonMemberSend.capturedChannelId ?? "", + agent, + role: "bot", + }); + } catch (error) { + errors.push( + `${agent.name}: ${ + error instanceof Error ? error.message : "Could not invite agent." + }`, + ); + } + } + if (errors.length > 0) { setNonMemberPromptError(errors.join("; ")); return; @@ -882,6 +888,7 @@ export function useMentionSendFlow({ }); }, [ addMembersMutation, + attachAgentMutation, completeSend, getManagedAgentsByPubkey, mentions.isAgentPubkey,