Skip to content
Open
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 @@ -5,7 +5,7 @@ import {
coalesceAgentAutocompleteCandidates,
getMentionableAgentPubkeys,
getSharedChannelIds,
isAgentIdentityInManagedList,
isKnownLiveAgentIdentity,
relayAgentIsSharedWithUser,
shouldHideAgentFromMentions,
} from "./agentAutocompleteEligibility.ts";
Expand Down Expand Up @@ -136,27 +136,44 @@ test("getMentionableAgentPubkeys: keeps managed agents and shared relay agents",
assert.deepEqual(result, new Set([PUB_A, PUB_B, PUB_C]));
});

test("isAgentIdentityInManagedList: keeps people and only current managed agent identities", () => {
test("isKnownLiveAgentIdentity: keeps people, managed agents, and directory-listed foreign agents", () => {
const managedAgentPubkeys = new Set([PUB_A]);
const directoryAgentPubkeys = new Set([PUB_C]);

// People always pass.
assert.equal(
isAgentIdentityInManagedList(
isKnownLiveAgentIdentity(
{ isAgent: false, pubkey: PUB_B },
managedAgentPubkeys,
directoryAgentPubkeys,
),
true,
);
// Locally managed agent passes (pubkey normalization included).
assert.equal(
isAgentIdentityInManagedList(
isKnownLiveAgentIdentity(
{ isAgent: true, pubkey: PUB_A.toUpperCase() },
managedAgentPubkeys,
directoryAgentPubkeys,
),
true,
);
// Foreign agent with a live relay directory entry (kind:10100) passes —
// e.g. an agent managed by another desktop that is a channel member.
assert.equal(
isAgentIdentityInManagedList(
isKnownLiveAgentIdentity(
{ isAgent: true, pubkey: PUB_C },
managedAgentPubkeys,
directoryAgentPubkeys,
),
true,
);
// Stale agent identity — neither managed nor in the directory — is dropped.
assert.equal(
isKnownLiveAgentIdentity(
{ isAgent: true, pubkey: PUB_B },
managedAgentPubkeys,
directoryAgentPubkeys,
),
false,
);
Expand Down
19 changes: 16 additions & 3 deletions desktop/src/features/agents/lib/agentAutocompleteEligibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,26 @@ export function getMentionableAgentPubkeys({
return pubkeys;
}

export function isAgentIdentityInManagedList(
/**
* A concrete agent identity is kept in autocomplete only when there is live
* evidence for it: the current user's managed Agents list, or a relay agent
* directory entry (kind:10100) published by its owning desktop. Stale
* identities — old p-tag/channel-member keys with no directory entry — are
* dropped. People are always kept. Whether a surviving foreign agent is
* ultimately shown is decided by `shouldHideAgentFromMentions`, which hides
* explicitly non-invocable ones.
*/
export function isKnownLiveAgentIdentity(
candidate: { isAgent?: boolean; pubkey: string },
managedAgentPubkeys: ReadonlySet<string>,
directoryAgentPubkeys: ReadonlySet<string>,
) {
if (candidate.isAgent !== true) {
return true;
}
const normalized = normalizePubkey(candidate.pubkey);
return (
candidate.isAgent !== true ||
managedAgentPubkeys.has(normalizePubkey(candidate.pubkey))
managedAgentPubkeys.has(normalized) || directoryAgentPubkeys.has(normalized)
);
}

Expand Down
13 changes: 11 additions & 2 deletions desktop/src/features/channels/ui/MembersSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { attachManagedAgentToChannel } from "@/features/agents/channelAgents";
import {
coalesceAgentAutocompleteCandidates,
isAgentIdentityInManagedList,
isKnownLiveAgentIdentity,
} from "@/features/agents/lib/agentAutocompleteEligibility";
import { useIsArchivedPredicate } from "@/features/identity-archive/hooks";
import { useClassifiedMembers } from "@/features/channels/lib/useClassifiedMembers";
Expand Down Expand Up @@ -272,6 +272,11 @@ export function MembersSidebar({
.filter((label): label is string => Boolean(label)),
);
const managedAgentPubkeys = new Set(managedAgentsByPubkey.keys());
const directoryAgentPubkeys = new Set(
(relayAgentsQuery.data ?? []).map((agent) =>
normalizePubkey(agent.pubkey),
),
);

const addCandidate = (candidate: AddMemberSearchCandidate) => {
const pubkey = normalizePubkey(candidate.pubkey);
Expand All @@ -282,7 +287,11 @@ export function MembersSidebar({
)) ||
memberPubkeys.has(pubkey) ||
isArchivedDiscovery(pubkey) ||
!isAgentIdentityInManagedList(candidate, managedAgentPubkeys)
!isKnownLiveAgentIdentity(
candidate,
managedAgentPubkeys,
directoryAgentPubkeys,
)
) {
return;
}
Expand Down
10 changes: 8 additions & 2 deletions desktop/src/features/messages/lib/useMentions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
coalesceAutocompleteCandidatesByKey,
getMentionableAgentPubkeys,
getSharedChannelIds,
isAgentIdentityInManagedList,
isKnownLiveAgentIdentity,
shouldHideAgentFromMentions,
} from "@/features/agents/lib/agentAutocompleteEligibility";
import {
Expand Down Expand Up @@ -246,7 +246,13 @@ export function useMentions(
if (isArchivedDiscovery(pubkey)) {
return;
}
if (!isAgentIdentityInManagedList(candidate, managedAgentPubkeys)) {
if (
!isKnownLiveAgentIdentity(
candidate,
managedAgentPubkeys,
directoryAgentPubkeys,
)
) {
return;
}
if (
Expand Down