Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
getMentionableAgentPubkeys,
getSharedChannelIds,
relayAgentIsSharedWithUser,
shouldHideAgentFromMentions,
} from "./agentAutocompleteEligibility.ts";

const CURRENT_PUBKEY = "a".repeat(64);
Expand Down Expand Up @@ -134,6 +135,87 @@ test("getMentionableAgentPubkeys: keeps managed agents and shared relay agents",
assert.deepEqual(result, new Set([PUB_A, PUB_B, PUB_C]));
});

test("shouldHideAgentFromMentions: never hides non-agents", () => {
assert.equal(
shouldHideAgentFromMentions({
isAgent: false,
isMember: false,
pubkey: PUB_A,
mentionableAgentPubkeys: new Set(),
directoryAgentPubkeys: new Set([PUB_A]),
}),
false,
);
});

test("shouldHideAgentFromMentions: shows invocable agents even when non-member", () => {
assert.equal(
shouldHideAgentFromMentions({
isAgent: true,
isMember: false,
pubkey: PUB_A,
mentionableAgentPubkeys: new Set([PUB_A]),
directoryAgentPubkeys: new Set([PUB_A]),
}),
false,
);
});

test("shouldHideAgentFromMentions: hides non-member non-invocable agents", () => {
assert.equal(
shouldHideAgentFromMentions({
isAgent: true,
isMember: false,
pubkey: PUB_A,
mentionableAgentPubkeys: new Set(),
directoryAgentPubkeys: new Set(),
}),
true,
);
});

test("shouldHideAgentFromMentions: hides member agents with an explicit not-invocable directory entry (Fizz)", () => {
assert.equal(
shouldHideAgentFromMentions({
isAgent: true,
isMember: true,
pubkey: PUB_A,
mentionableAgentPubkeys: new Set(),
directoryAgentPubkeys: new Set([PUB_A]),
}),
true,
);
});

test("shouldHideAgentFromMentions: shows member agents with unknown invocability (not in directory)", () => {
assert.equal(
shouldHideAgentFromMentions({
isAgent: true,
isMember: true,
pubkey: PUB_A,
mentionableAgentPubkeys: new Set(),
directoryAgentPubkeys: new Set(),
}),
false,
);
});

test("shouldHideAgentFromMentions: normalizes the pubkey before lookup", () => {
const mixedCase = "Ab".repeat(32);
const normalized = mixedCase.toLowerCase();

assert.equal(
shouldHideAgentFromMentions({
isAgent: true,
isMember: true,
pubkey: mixedCase,
mentionableAgentPubkeys: new Set(),
directoryAgentPubkeys: new Set([normalized]),
}),
true,
);
});

test("coalesceAgentAutocompleteCandidates: merges agents with the same persona id", () => {
const first = makeAgent({ pubkey: PUB_A, personaId: "pinky" });
const second = makeAgent({
Expand Down
33 changes: 33 additions & 0 deletions desktop/src/features/agents/lib/agentAutocompleteEligibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,39 @@ export function getMentionableAgentPubkeys({
return pubkeys;
}

export function shouldHideAgentFromMentions({
isAgent,
isMember,
pubkey,
mentionableAgentPubkeys,
directoryAgentPubkeys,
}: {
isAgent: boolean;
isMember: boolean;
pubkey: string;
mentionableAgentPubkeys: ReadonlySet<string>;
directoryAgentPubkeys: ReadonlySet<string>;
}) {
if (!isAgent) return false;
const normalized = normalizePubkey(pubkey);
// Invocable => always show.
if (mentionableAgentPubkeys.has(normalized)) return false;
// Non-member, non-invocable => hide (preserves prior behavior).
if (!isMember) return true;
// Member (Option B): hide only when we have an explicit not-invocable
// signal — a relay directory (kind:10100) entry that excludes us.
// Unknown invocability (not in directory) => show.
//
// NOTE: this assumes `directoryAgentPubkeys` and `mentionableAgentPubkeys`
// share the same source query (`relayAgentsQuery.data`), so directory
// presence without membership in `mentionableAgentPubkeys` is a real
// explicit-exclusion signal. If a future change sources the directory set
// from a different query, an agent that's directory-present but whose
// mentionability is still loading could be hidden prematurely — keep the
// two sets derived from the same query.
return directoryAgentPubkeys.has(normalized);
Comment thread
atishpatel marked this conversation as resolved.
}

type AgentAutocompleteCandidate = {
pubkey?: string;
displayName?: string | null;
Expand Down
21 changes: 18 additions & 3 deletions desktop/src/features/messages/lib/useMentions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
coalesceAutocompleteCandidatesByKey,
getMentionableAgentPubkeys,
getSharedChannelIds,
shouldHideAgentFromMentions,
} from "@/features/agents/lib/agentAutocompleteEligibility";
import {
useInfiniteUserSearchQuery,
Expand Down Expand Up @@ -209,6 +210,15 @@ export function useMentions(
),
[relayAgentsQuery.data],
);
const directoryAgentPubkeys = React.useMemo(
() =>
new Set(
(relayAgentsQuery.data ?? []).map((agent) =>
normalizePubkey(agent.pubkey),
),
),
[relayAgentsQuery.data],
);
const sharedChannelIds = React.useMemo(
() => getSharedChannelIds(channelsQuery.data),
[channelsQuery.data],
Expand Down Expand Up @@ -269,9 +279,13 @@ export function useMentions(
return;
}
if (
candidate.isAgent &&
!candidate.isMember &&
!mentionableAgentPubkeys.has(pubkey)
shouldHideAgentFromMentions({
isAgent: candidate.isAgent === true,
isMember: candidate.isMember === true,
pubkey,
mentionableAgentPubkeys,
directoryAgentPubkeys,
})
) {
return;
}
Expand Down Expand Up @@ -425,6 +439,7 @@ export function useMentions(
userSearchResults,
canSearchGlobalUsers,
currentPubkey,
directoryAgentPubkeys,
isArchivedDiscovery,
managedAgentNamesByPubkey,
managedAgentPersonaIds,
Expand Down