Skip to content
Closed
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,
isAgentIdentityInManagedList,
isMentionCandidateEligible,
relayAgentIsSharedWithUser,
shouldHideAgentFromMentions,
} from "./agentAutocompleteEligibility.ts";
Expand Down Expand Up @@ -188,6 +189,20 @@ test("shouldHideAgentFromMentions: shows invocable agents even when non-member",
);
});

test("isMentionCandidateEligible: admits another owner's anyone agent in a shared channel", () => {
assert.equal(
isMentionCandidateEligible({
isArchived: false,
isAgent: true,
isMember: true,
pubkey: PUB_A,
mentionableAgentPubkeys: new Set([PUB_A]),
directoryAgentPubkeys: new Set([PUB_A]),
}),
true,
);
});

test("shouldHideAgentFromMentions: hides non-member non-invocable agents", () => {
assert.equal(
shouldHideAgentFromMentions({
Expand Down
27 changes: 27 additions & 0 deletions desktop/src/features/agents/lib/agentAutocompleteEligibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,33 @@ export function shouldHideAgentFromMentions({
return directoryAgentPubkeys.has(normalized);
}

export function isMentionCandidateEligible({
isArchived,
isAgent,
isMember,
pubkey,
mentionableAgentPubkeys,
directoryAgentPubkeys,
}: {
isArchived: boolean;
isAgent: boolean;
isMember: boolean;
pubkey: string;
mentionableAgentPubkeys: ReadonlySet<string>;
directoryAgentPubkeys: ReadonlySet<string>;
}) {
return (
!isArchived &&
!shouldHideAgentFromMentions({
isAgent,
isMember,
pubkey,
mentionableAgentPubkeys,
directoryAgentPubkeys,
})
);
}

type AgentAutocompleteCandidate = {
pubkey?: string;
displayName?: string | null;
Expand Down
13 changes: 3 additions & 10 deletions desktop/src/features/messages/lib/useMentions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import {
coalesceAutocompleteCandidatesByKey,
getMentionableAgentPubkeys,
getSharedChannelIds,
isAgentIdentityInManagedList,
shouldHideAgentFromMentions,
isMentionCandidateEligible,
} from "@/features/agents/lib/agentAutocompleteEligibility";
import {
useInfiniteUserSearchQuery,
Expand Down Expand Up @@ -243,14 +242,9 @@ export function useMentions(

const addCandidate = (candidate: MentionCandidate & { pubkey: string }) => {
const pubkey = normalizePubkey(candidate.pubkey);
if (isArchivedDiscovery(pubkey)) {
return;
}
if (!isAgentIdentityInManagedList(candidate, managedAgentPubkeys)) {
return;
}
if (
shouldHideAgentFromMentions({
!isMentionCandidateEligible({
isArchived: isArchivedDiscovery(pubkey),
isAgent: candidate.isAgent === true,
isMember: candidate.isMember === true,
pubkey,
Expand Down Expand Up @@ -420,7 +414,6 @@ export function useMentions(
managedAgentNamesByPubkey,
managedAgentPersonaIds,
managedAgentPersonaIdsByPubkey,
managedAgentPubkeys,
managedAgentsQuery.data,
memberPubkeys,
members,
Expand Down
17 changes: 11 additions & 6 deletions desktop/tests/e2e/mentions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ async function expectAgentProfileActionsHidden(
).toHaveCount(0);
}

test("@ trigger prioritizes channel members before runnable personas and other managed agents", async ({
test("@ trigger shows shared external agents and prioritizes channel members", async ({
page,
}) => {
await installMockBridge(page, {
Expand All @@ -209,7 +209,7 @@ test("@ trigger prioritizes channel members before runnable personas and other m

const dropdown = autocomplete(page);
await expect(dropdown).toBeVisible();
await expect(dropdown.getByText("alice")).toHaveCount(0);
await expect(dropdown.getByText("alice")).toBeVisible();
await expect(dropdown.getByText("bob")).toBeVisible();
await expect(dropdown.getByText("Fizz")).toBeVisible();
await expect(dropdown.getByText("charlie")).toBeVisible();
Expand All @@ -225,6 +225,7 @@ test("@ trigger prioritizes channel members before runnable personas and other m

const suggestions = dropdown.locator("button");
const suggestionText = await suggestions.allInnerTexts();
const aliceIndex = suggestionText.findIndex((text) => text.includes("alice"));
const fizzIndex = suggestionText.findIndex((text) => text.includes("Fizz"));
const bobIndex = suggestionText.findIndex((text) => text.includes("bob"));
const charlieIndex = suggestionText.findIndex((text) =>
Expand All @@ -233,10 +234,12 @@ test("@ trigger prioritizes channel members before runnable personas and other m
const outsiderIndex = suggestionText.findIndex((text) =>
text.includes("outsider"),
);
expect(aliceIndex).toBeGreaterThanOrEqual(0);
expect(fizzIndex).toBeGreaterThanOrEqual(0);
expect(bobIndex).toBeGreaterThanOrEqual(0);
expect(charlieIndex).toBeGreaterThanOrEqual(0);
expect(outsiderIndex).toEqual(-1);
expect(aliceIndex).toBeLessThan(fizzIndex);
expect(bobIndex).toBeLessThan(fizzIndex);
expect(fizzIndex).toBeLessThan(charlieIndex);
});
Expand Down Expand Up @@ -826,16 +829,16 @@ test("managed relay agents are visible in channel mentions regardless of relay p
await expect(dropdown.getByText("agent")).toBeVisible();
});

test("relay-only agents stay hidden from channel mentions even when allowlisted", async ({
test("another owner's anyone agent in a shared channel is visible in mentions", async ({
page,
}) => {
await installMockBridge(page, {
relayAgents: [
{
pubkey: ALLOWLIST_RELAY_AGENT_PUBKEY,
name: "quinn",
respondTo: "allowlist",
respondToAllowlist: [MOCK_VIEWER_PUBKEY],
respondTo: "anyone",
channelNames: ["general"],
},
],
});
Expand All @@ -846,7 +849,9 @@ test("relay-only agents stay hidden from channel mentions even when allowlisted"
const input = page.getByTestId("message-input");
await input.fill("@quinn");

await expect(autocomplete(page)).toHaveCount(0);
const dropdown = autocomplete(page);
await expect(dropdown.getByText("quinn")).toBeVisible();
await expect(dropdown.getByText("agent")).toBeVisible();
});

test("mentioning an in-channel stopped managed agent starts it before sending", async ({
Expand Down