diff --git a/desktop/src/testing/e2eBridge.ts b/desktop/src/testing/e2eBridge.ts index e4028f01716..29c7ff0427a 100644 --- a/desktop/src/testing/e2eBridge.ts +++ b/desktop/src/testing/e2eBridge.ts @@ -2370,6 +2370,28 @@ function resetMockRelayAgents(config?: E2eConfig) { respond_to: seed.respondTo ?? "owner-only", respond_to_allowlist: seed.respondToAllowlist ?? [], }); + + // A relay-agent seed that names real channels represents a bot that is + // an actual member of those channels — e.g. another identity's agent + // added as a bot member of a shared channel — not just a + // relay-directory listing. Mirror resetMockManagedAgents's membership + // push (below) so specs can exercise the cross-owner "bot member of a + // shared channel" case without also declaring the agent as locally + // managed. + for (const channel of channels) { + if (channel.members.some((member) => member.pubkey === seed.pubkey)) { + continue; + } + channel.members.push({ + pubkey: seed.pubkey, + role: "bot", + is_agent: true, + joined_at: new Date().toISOString(), + display_name: seed.name, + }); + syncMockChannel(channel); + touchMockChannel(channel); + } } } @@ -3583,7 +3605,7 @@ function initializeMockHuddle( const openedExternalUrls: string[] = []; const defaultMockRelayAgents: RawRelayAgent[] = [ { - pubkey: ALICE_PUBKEY, + pubkey: "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a", name: "alice", agent_type: "goose", channels: ["general", "agents"], @@ -3597,7 +3619,7 @@ const defaultMockRelayAgents: RawRelayAgent[] = [ respond_to_allowlist: [], }, { - pubkey: CHARLIE_PUBKEY, + pubkey: "5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c", name: "charlie", agent_type: "codex", channels: ["general"], diff --git a/desktop/tests/e2e/mentions.spec.ts b/desktop/tests/e2e/mentions.spec.ts index 25d73b28ca7..0154d6d5d61 100644 --- a/desktop/tests/e2e/mentions.spec.ts +++ b/desktop/tests/e2e/mentions.spec.ts @@ -1933,6 +1933,69 @@ test("shared agents wait for initial directory authorization", async ({ }); }); +test("bot agents owned by another identity are mentionable when added to a shared channel", async ({ + page, +}) => { + const OTHER_OWNER_BOT_PUBKEY = + "7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b"; + await installMockBridge(page, { + relayAgents: [ + { + pubkey: OTHER_OWNER_BOT_PUBKEY, + name: "scout", + respondTo: "anyone", + channelNames: ["general"], + }, + ], + }); + await page.goto("/"); + await page.getByTestId("channel-general").click(); + await expect(page.getByTestId("chat-title")).toHaveText("general"); + + const input = page.getByTestId("message-input"); + await input.fill("@scout"); + + const dropdown = autocomplete(page); + await expect(dropdown.getByText("scout")).toBeVisible(); + await expect(dropdown.getByText("agent")).toBeVisible(); + + await input.press("Enter"); + await page.keyboard.type(" can you help?"); + await page.getByTestId("send-message").click(); + + const mentionChip = page + .getByTestId("message-row") + .last() + .locator("[data-mention].agent-mention-highlight", { hasText: "scout" }); + await expect(mentionChip).toBeVisible(); +}); + +test("bot agents owned by another identity stay hidden when not added to a shared channel", async ({ + page, +}) => { + const OTHER_OWNER_BOT_PUBKEY = + "8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c"; + await installMockBridge(page, { + relayAgents: [ + { + pubkey: OTHER_OWNER_BOT_PUBKEY, + name: "ghost", + respondTo: "anyone", + // Deliberately not added to "general" — respondTo=anyone alone must + // not be enough; the fix requires real channel membership too. + }, + ], + }); + await page.goto("/"); + await page.getByTestId("channel-general").click(); + await expect(page.getByTestId("chat-title")).toHaveText("general"); + + const input = page.getByTestId("message-input"); + await input.fill("@ghost"); + + await expect(autocomplete(page)).toHaveCount(0); +}); + test("mentioning an in-channel stopped managed agent starts it before sending", async ({ page, }) => {