import { expect, test } from "@playwright/test"; import type { RelayEvent } from "@/shared/api/types"; import { emojiAvatarDataUrl } from "@/features/profile/ui/ProfileAvatarEditor.utils"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge"; function createCatalogEvent(input: { ownerPubkey: string; sourcePersonaId: string; displayName: string; systemPrompt: string; createdAt?: number; shared?: boolean; avatarUrl?: string; }): RelayEvent { return { id: "1".repeat(64), pubkey: input.ownerPubkey, created_at: input.createdAt ?? 1_721_750_400, kind: 30175, tags: [ ["d", input.sourcePersonaId], ...(input.shared === false ? [] : [["shared", "true"]]), ], content: JSON.stringify({ display_name: input.displayName, system_prompt: input.systemPrompt, avatar_url: input.avatarUrl ?? null, runtime: null, model: null, provider: null, name_pool: [], }), sig: "2".repeat(128), }; } test.beforeEach(async ({ page }) => { await installMockBridge(page); }); async function gotoApp(page: import("@playwright/test").Page) { let lastError: unknown = null; for (const attempt of [0, 1]) { await page.goto("/", { waitUntil: "domcontentloaded" }); await waitForInvokeBridge(page); try { await expect(page.getByTestId("open-agents-view")).toBeVisible({ timeout: 10_000, }); return; } catch (error) { lastError = error; if (attempt === 1) { throw error; } } } throw lastError; } async function openPersonaCatalog(page: import("@playwright/test").Page) { await page.getByTestId("new-agent-card").click(); } async function getCatalogOrder(page: import("@playwright/test").Page) { return page .locator('[data-testid^="persona-catalog-list-item-"]') .evaluateAll((elements) => elements.map((element) => element.getAttribute("data-testid") ?? ""), ); } async function selectCatalogPersona( page: import("@playwright/test").Page, personaId: string, ) { await page.getByTestId(`persona-catalog-list-item-${personaId}`).click(); } async function sharePersonaToCatalog( page: import("@playwright/test").Page, displayName: string, ) { await page.getByLabel(`Open actions for ${displayName}`).click(); await page.getByRole("menuitem", { name: "Share" }).click(); const catalogToggle = page.getByTestId("persona-share-catalog-access"); await expect(catalogToggle).not.toBeChecked(); await catalogToggle.click(); await expect(catalogToggle).toBeChecked(); await page .getByTestId("persona-share-dialog") .getByRole("button", { name: "Close" }) .click(); } async function waitForInvokeBridge(page: import("@playwright/test").Page) { await page.waitForFunction( () => { const tauriWindow = window as Window & { __BUZZ_E2E_INVOKE_MOCK_COMMAND__?: unknown; __TAURI_INTERNALS__?: { invoke?: unknown; }; }; return ( typeof tauriWindow.__BUZZ_E2E_INVOKE_MOCK_COMMAND__ === "function" || typeof tauriWindow.__TAURI_INTERNALS__?.invoke === "function" ); }, null, { timeout: 5_000 }, ); } async function invokeTauri( page: import("@playwright/test").Page, command: string, payload?: Record, ): Promise { await waitForInvokeBridge(page); return page.evaluate( async ({ command: targetCommand, payload: targetPayload }) => { const tauriWindow = window as Window & { __BUZZ_E2E_INVOKE_MOCK_COMMAND__?: ( command: string, payload?: Record, ) => Promise; __TAURI_INTERNALS__?: { invoke?: ( command: string, payload?: Record, ) => Promise; }; }; const invoke = tauriWindow.__BUZZ_E2E_INVOKE_MOCK_COMMAND__ ?? tauriWindow.__TAURI_INTERNALS__?.invoke; if (!invoke) { throw new Error("Mock invoke bridge is unavailable."); } return (await invoke(targetCommand, targetPayload)) as T; }, { command, payload }, ); } async function invokeTauriExpectError( page: import("@playwright/test").Page, command: string, payload?: Record, ) { await waitForInvokeBridge(page); return page.evaluate( async ({ command: targetCommand, payload: targetPayload }) => { const tauriWindow = window as Window & { __BUZZ_E2E_INVOKE_MOCK_COMMAND__?: ( command: string, payload?: Record, ) => Promise; __TAURI_INTERNALS__?: { invoke?: ( command: string, payload?: Record, ) => Promise; }; }; const invoke = tauriWindow.__BUZZ_E2E_INVOKE_MOCK_COMMAND__ ?? tauriWindow.__TAURI_INTERNALS__?.invoke; if (!invoke) { throw new Error("Mock invoke bridge is unavailable."); } try { await invoke(targetCommand, targetPayload); return null; } catch (error) { return error instanceof Error ? error.message : String(error); } }, { command, payload }, ); } async function countCommandInvocations( page: import("@playwright/test").Page, command: string, ): Promise { return page.evaluate( (targetCommand) => ( window as Window & { __BUZZ_E2E_COMMANDS__?: string[]; } ).__BUZZ_E2E_COMMANDS__?.filter((invoked) => invoked === targetCommand) .length ?? 0, command, ); } test("catalog hides built-ins and shows the shared-agent empty state", async ({ page, }) => { await page.setViewportSize({ width: 1280, height: 420 }); await installMockBridge(page, { activePersonaIds: ["builtin:fizz", "builtin:honey", "builtin:bumble"], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await expect(page.getByTestId("agents-library-personas")).toBeVisible(); for (const personaName of ["Fizz", "Honey", "Bumble"]) { await expect(page.getByTestId("agents-library-personas")).toContainText( personaName, ); } await openPersonaCatalog(page); for (const personaName of ["Fizz", "Honey", "Bumble"]) { await expect(page.getByTestId("persona-catalog-dialog")).not.toContainText( personaName, ); } await expect(page.getByTestId("persona-catalog-dialog-header")).toBeVisible(); await expect(page.getByTestId("persona-catalog-dialog-body")).toBeVisible(); await expect( page.getByText("No shared agents", { exact: true }), ).toBeVisible(); await expect( page.locator('[data-testid^="persona-catalog-list-item-"]'), ).toHaveCount(0); await expect( page.getByTestId("persona-catalog-use-agent-target"), ).toHaveCount(0); await page .getByTestId("persona-catalog-dialog") .getByRole("button", { name: "Close" }) .click(); await page.getByLabel("Open actions for Fizz").click(); await page.getByRole("menuitem", { name: "Share" }).click(); await expect(page.getByTestId("persona-share-catalog")).toHaveCount(0); await expect(page.getByTestId("persona-share-catalog-access")).toHaveCount(0); }); test("catalog empty state remains available after reopening", async ({ page, }) => { await gotoApp(page); await page.getByTestId("open-agents-view").click(); await openPersonaCatalog(page); await expect( page.getByText("No shared agents", { exact: true }), ).toBeVisible(); await page .getByTestId("persona-catalog-dialog") .getByRole("button", { name: "Close" }) .click(); await expect(page.getByTestId("persona-catalog-dialog")).not.toBeVisible(); await openPersonaCatalog(page); await expect( page.getByText("No shared agents", { exact: true }), ).toBeVisible(); }); test("built-in persona edits persist", async ({ page }) => { await installMockBridge(page, { activePersonaIds: ["builtin:fizz"], globalAgentConfig: { env_vars: { ANTHROPIC_API_KEY: "sk-ant-test" }, provider: "anthropic", model: "claude-opus-4-5", }, }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await page.getByLabel("Open actions for Fizz").click(); await page.getByRole("menuitem", { name: "Edit" }).click(); const dialog = page.getByTestId("persona-dialog"); await dialog.getByLabel("Agent name").fill("My Fizz"); await dialog.getByLabel("Agent instruction").fill("User-edited instructions"); await dialog.getByRole("button", { name: "Save changes" }).click(); await expect(dialog).toHaveCount(0); await expect(page.getByTestId("agents-library-personas")).toContainText( "My Fizz", ); const personas = await invokeTauri< Array<{ id: string; display_name: string; system_prompt: string }> >(page, "list_personas"); expect( personas.find((persona) => persona.id === "builtin:fizz"), ).toMatchObject({ display_name: "My Fizz", system_prompt: "User-edited instructions", }); }); test("searches agent avatar emoji with focus on open", async ({ page }) => { await gotoApp(page); await page.getByTestId("open-agents-view").click(); await page.getByTestId("new-agent-card").click(); await expect(page.getByTestId("persona-dialog")).toBeVisible(); await page.getByLabel("Add avatar").click(); await page.getByRole("tab", { name: "Emoji" }).click(); const picker = page.locator("em-emoji-picker"); const searchInput = picker.locator("input[type='search']"); await expect(searchInput).toBeVisible(); await expect(searchInput).toBeFocused(); await searchInput.fill("rocket"); await picker.locator("button[aria-label='šŸš€']").first().click(); await expect(page.getByRole("img", { name: "Agent name avatar" })).toHaveText( "šŸš€", ); }); test("agent avatar emoji picker scrolls inside its popover", async ({ page, }) => { await gotoApp(page); await page.getByTestId("open-agents-view").click(); await page.getByTestId("new-agent-card").click(); await expect(page.getByTestId("persona-dialog")).toBeVisible(); await page.getByLabel("Add avatar").click(); await page.getByRole("tab", { name: "Emoji" }).click(); await expect(page.locator("em-emoji-picker")).toBeVisible(); await page.waitForFunction(() => { const picker = document.querySelector("em-emoji-picker"); const scroll = picker?.shadowRoot?.querySelector(".scroll"); return ( scroll instanceof HTMLElement && scroll.scrollHeight > scroll.clientHeight ); }); const before = await page.locator("em-emoji-picker").evaluate((picker) => { const scroll = picker.shadowRoot?.querySelector(".scroll"); return scroll instanceof HTMLElement ? scroll.scrollTop : -1; }); const box = await page.locator("em-emoji-picker").boundingBox(); if (!box) { throw new Error("Could not measure emoji picker bounds."); } await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2); await page.mouse.wheel(0, 500); await expect .poll(async () => page.locator("em-emoji-picker").evaluate((picker) => { const scroll = picker.shadowRoot?.querySelector(".scroll"); return scroll instanceof HTMLElement ? scroll.scrollTop : -1; }), ) .toBeGreaterThan(before); }); test("the new agent card opens unified create, catalog, and import flows", async ({ page, }) => { await installMockBridge(page, { activePersonaIds: ["builtin:fizz", "builtin:honey", "builtin:bumble"], personas: [ { id: "custom:code-reviewer", displayName: "Code Reviewer", systemPrompt: "Review code changes.", }, { id: "custom:layout-auditor", displayName: "Layout Auditor", systemPrompt: "Review responsive layouts.", }, ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); const newAgentCard = page.getByTestId("new-agent-card"); await expect(newAgentCard).toHaveText(""); await expect(newAgentCard.locator(".lucide-plus")).toBeVisible(); const agentCards = page.locator( '[data-testid^="persona-agent-row-"], [data-testid="new-agent-card"]', ); await expect(agentCards.first()).toBeVisible(); const headerBox = await page .getByRole("heading", { level: 1, name: "Agents" }) .locator("../..") .boundingBox(); const cardBoxes = await agentCards.evaluateAll((cards) => cards.map((card) => { const box = card.getBoundingClientRect(); return { left: box.left, right: box.right, top: box.top }; }), ); const firstRowTop = Math.min(...cardBoxes.map(({ top }) => top)); expect( cardBoxes.filter(({ top }) => Math.abs(top - firstRowTop) < 1), ).toHaveLength(5); const rightmostFirstRowCard = Math.max( ...cardBoxes .filter(({ top }) => Math.abs(top - firstRowTop) < 1) .map(({ right }) => right), ); const leftmostFirstRowCard = Math.min( ...cardBoxes .filter(({ top }) => Math.abs(top - firstRowTop) < 1) .map(({ left }) => left), ); expect(headerBox).not.toBeNull(); expect(Math.abs((headerBox?.x ?? 0) - leftmostFirstRowCard)).toBeLessThan(1); expect(rightmostFirstRowCard).toBeLessThanOrEqual( (headerBox?.x ?? 0) + (headerBox?.width ?? 0) + 1, ); await newAgentCard.click(); const catalogDialog = page.getByTestId("persona-catalog-dialog"); await expect(catalogDialog).toBeVisible(); await expect(page.getByTestId("agent-catalog-create")).toHaveAttribute( "aria-current", "true", ); const dialog = page.getByTestId("persona-dialog"); await expect(dialog).toBeVisible(); await expect( dialog.getByTestId("import-agent-snapshot-dialog-action"), ).toHaveCount(0); await expect(dialog).not.toContainText("Enter a name for this agent."); await page.getByTestId("agent-catalog-import").click(); await expect(page.getByTestId("agent-catalog-import-dropzone")).toBeVisible(); const fileChooserPromise = page.waitForEvent("filechooser"); await page.getByTestId("agent-catalog-import-dropzone").click(); const fileChooser = await fileChooserPromise; await fileChooser.setFiles({ buffer: Buffer.from("{}"), mimeType: "application/json", name: "imported.agent.json", }); await expect(page.getByTestId("agent-snapshot-import-dialog")).toBeVisible(); }); test("embedded create keeps its draft when discard is cancelled", async ({ page, }) => { await gotoApp(page); await page.getByTestId("open-agents-view").click(); await page.getByTestId("new-agent-card").click(); const dialog = page.getByTestId("persona-dialog"); const name = dialog.locator("#persona-display-name"); const instructions = dialog.locator("#persona-system-prompt"); await name.fill("Draft Keeper"); await instructions.fill("Preserve this draft through confirmation."); await dialog.getByRole("button", { name: "Cancel" }).click(); const discardDialog = page.getByTestId("discard-create-agent-dialog"); await expect(discardDialog).toBeVisible(); await discardDialog.getByRole("button", { name: "Keep editing" }).click(); await expect(dialog).toBeVisible(); await expect(name).toHaveValue("Draft Keeper"); await expect(instructions).toHaveValue( "Preserve this draft through confirmation.", ); }); test("the new team card offers create and import", async ({ page }) => { await gotoApp(page); await page.getByTestId("open-agents-view").click(); const newTeamCard = page.getByTestId("new-team-card"); await expect(newTeamCard).toHaveText(""); await expect(newTeamCard.locator(".lucide-plus")).toBeVisible(); await newTeamCard.click(); await expect( page.getByRole("menuitem", { exact: true, name: "Create team" }), ).toBeVisible(); await expect( page.getByRole("menuitem", { exact: true, name: "Import" }), ).toBeVisible(); }); test("team cards follow the agents grid alignment at compact widths", async ({ page, }) => { await installMockBridge(page, { personas: [ { id: "custom:team-layout", displayName: "Team layout agent", systemPrompt: "A test agent for team layout alignment.", }, ], teams: [ { id: "team-layout", name: "Team layout", personaIds: ["custom:team-layout"], }, ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); const agentsContent = page.getByTestId("agents-page-content"); const firstAgentCard = page.getByTestId( "persona-agent-row-custom:team-layout", ); const firstTeamCard = page.getByTestId("team-card-team-layout"); const agentGrid = firstAgentCard.locator("xpath=.."); const teamGrid = firstTeamCard.locator("xpath=.."); const firstAgentGridCard = agentGrid.locator(":scope > *").first(); const firstTeamGridCard = teamGrid.locator(":scope > *").first(); await agentsContent.evaluate((element) => { (element as HTMLElement).style.width = "650px"; }); const wideAgentBox = await firstAgentGridCard.boundingBox(); const wideTeamBox = await firstTeamGridCard.boundingBox(); expect(wideAgentBox).not.toBeNull(); expect(wideTeamBox).not.toBeNull(); expect(Math.abs((wideAgentBox?.x ?? 0) - (wideTeamBox?.x ?? 0))).toBeLessThan( 1, ); await agentsContent.evaluate((element) => { (element as HTMLElement).style.width = "600px"; }); const compactAgentBox = await firstAgentGridCard.boundingBox(); const compactTeamBox = await firstTeamGridCard.boundingBox(); expect(compactAgentBox?.width ?? 0).toBeLessThan(wideAgentBox?.width ?? 0); expect( Math.abs((compactAgentBox?.width ?? 0) - (compactTeamBox?.width ?? 0)), ).toBeLessThan(1); expect( Math.abs((compactAgentBox?.x ?? 0) - (compactTeamBox?.x ?? 0)), ).toBeLessThan(1); }); test("team cards use the thread-style overlapping avatar stack", async ({ page, }) => { const personaIds = ["custom:design", "custom:build", "custom:ship"]; await installMockBridge(page, { personas: [ { avatarUrl: "/onboarding/starter-team/fizz.png", id: personaIds[0], displayName: "Design", systemPrompt: "You design interfaces.", }, { id: personaIds[1], displayName: "Build", systemPrompt: "You build interfaces.", }, { id: personaIds[2], displayName: "Ship", systemPrompt: "You ship interfaces.", }, ], teams: [ { name: "Product crew", personaIds, }, ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); const stack = page.getByLabel("Product crew member avatars"); const avatars = stack.locator('[data-team-member-avatar="avatar"]'); await expect(avatars).toHaveCount(3); await expect(avatars.nth(1)).toHaveClass(/-ml-5/); await expect(avatars.nth(2)).toHaveClass(/-ml-5/); const boxes = await avatars.evaluateAll((elements) => elements.map((element) => { const box = element.getBoundingClientRect(); return { left: box.left, right: box.right }; }), ); expect(boxes[1]?.left).toBeLessThan(boxes[0]?.right ?? 0); expect(boxes[2]?.left).toBeLessThan(boxes[1]?.right ?? 0); await expect(avatars.first()).not.toHaveCSS("mask-image", "none"); await expect(avatars.last()).toHaveCSS("mask-image", "none"); const avatarSurfaceStyles = await avatars .locator(":scope > *") .evaluateAll((elements) => elements.map((element) => { const styles = getComputedStyle(element); const hasVisibleShadow = [ ...styles.boxShadow.matchAll(/rgba?\(([^)]+)\)/g), ].some((match) => { if (match[0].startsWith("rgb(")) return true; const channels = match[1]?.split(/[\s,/]+/).filter(Boolean) ?? []; return Number(channels.at(-1)) > 0; }); return { borderWidth: styles.borderTopWidth, hasVisibleShadow, }; }), ); expect(avatarSurfaceStyles).toEqual([ { borderWidth: "0px", hasVisibleShadow: false }, { borderWidth: "0px", hasVisibleShadow: false }, { borderWidth: "0px", hasVisibleShadow: false }, ]); }); test("agent defaults stays in the header without an actions menu", async ({ page, }) => { await installMockBridge(page, { acpRuntimesCatalog: [ { auth_status: { status: "logged_in" }, availability: "available", avatar_url: "", binary_path: "/usr/local/bin/codex", can_auto_install: false, command: "codex", default_args: [], id: "codex", install_hint: "", install_instructions_url: "https://example.com", label: "Codex", login_hint: null, mcp_command: null, node_required: false, underlying_cli_path: null, }, ], globalAgentConfig: { env_vars: {}, model: "gpt-5.5[high]", preferred_runtime: "codex", provider: null, }, }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await expect(page.getByTestId("agent-header-actions-button")).toHaveCount(0); await expect( page.getByRole("menuitem", { name: "Import agent" }), ).toHaveCount(0); const defaultsButton = page.getByTestId("agent-defaults-button"); await expect(defaultsButton).toHaveText("Agent defaults"); await defaultsButton.click(); const defaultsDialog = page.getByTestId("agent-ai-defaults-dialog"); await expect(defaultsDialog).toBeVisible(); await expect( defaultsDialog.getByTestId("global-agent-default-harness"), ).toHaveAttribute("data-value", "codex"); await expect( defaultsDialog.getByTestId("global-agent-default-harness"), ).toContainText("Codex"); await expect( defaultsDialog.getByTestId("global-agent-model"), ).toHaveAttribute("data-value", "gpt-5.5[high]"); await expect(defaultsDialog.getByTestId("global-agent-model")).toContainText( "gpt-5.5[high]", ); await page.keyboard.press("Escape"); await expect(defaultsDialog).toHaveCount(0); }); test("unconfigured agent defaults use the setup label", async ({ page }) => { await gotoApp(page); await page.getByTestId("open-agents-view").click(); await expect(page.getByTestId("agent-defaults-button")).toHaveText( "Set agent defaults", ); }); test("moves agent actions into an overflow menu in a narrow view", async ({ page, }) => { await installMockBridge(page, { personas: [ { id: "custom:compact-actions", displayName: "Compact actions agent", isActive: true, systemPrompt: "A test agent for compact header actions.", }, ], managedAgents: [ { name: "Compact actions instance", personaId: "custom:compact-actions", pubkey: "cd".repeat(32), status: "running", }, ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await page.getByTestId("agents-page-content").evaluate((element) => { (element as HTMLElement).style.width = "650px"; }); await expect(page.getByTestId("agent-defaults-button")).toBeVisible(); await expect( page.getByText("Set up and manage your agents.", { exact: true }), ).toHaveJSProperty("scrollHeight", 24); await page.getByTestId("agents-page-content").evaluate((element) => { (element as HTMLElement).style.width = "600px"; }); await expect(page.getByTestId("agent-defaults-button")).toBeHidden(); await page.getByTestId("agent-actions-menu-trigger").click(); await expect( page.getByRole("menuitem", { name: "Set agent defaults" }), ).toBeVisible(); await expect( page.getByRole("menuitem", { name: "Stop running agents" }), ).toBeVisible(); await page.getByRole("menuitem", { name: "Set agent defaults" }).click(); await expect(page.getByTestId("agent-ai-defaults-dialog")).toBeVisible(); await page.getByTestId("agents-page-content").evaluate((element) => { (element as HTMLElement).style.width = "650px"; }); await expect(page.getByTestId("agent-defaults-button")).toBeVisible(); await page.keyboard.press("Escape"); await expect(page.getByTestId("agent-ai-defaults-dialog")).toHaveCount(0); await expect(page.getByTestId("agent-defaults-button")).toBeFocused(); }); test("agent catalog chooser order stays stable when selection changes", async ({ page, }) => { await installMockBridge(page, { personas: [ { id: "custom:builder", displayName: "Builder", systemPrompt: "Build the requested change.", }, { id: "custom:reviewer", displayName: "Reviewer", systemPrompt: "Review the requested change.", }, ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await sharePersonaToCatalog(page, "Builder"); await sharePersonaToCatalog(page, "Reviewer"); await openPersonaCatalog(page); const before = await getCatalogOrder(page); await selectCatalogPersona(page, "custom:reviewer"); expect(await getCatalogOrder(page)).toEqual(before); }); test("catalog detail pane shows the full persona details", async ({ page }) => { const personaId = "custom:researcher"; await installMockBridge(page, { personas: [ { id: personaId, displayName: "Researcher", systemPrompt: "Research the question and cite the evidence.", }, ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await sharePersonaToCatalog(page, "Researcher"); await openPersonaCatalog(page); await selectCatalogPersona(page, personaId); const useAgentTarget = page.getByTestId( `persona-catalog-use-agent-target-${personaId}`, ); await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText( "Researcher", ); await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText( "Added by You", ); await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText( "Research the question and cite the evidence.", ); await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText( "Custom agent", ); await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText( "Preferred model", ); await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText( "Preferred runtime", ); await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText( "Agent instruction", ); await expect(useAgentTarget).toHaveAttribute( "aria-label", "Researcher is already in My Agents", ); await expect(useAgentTarget).toHaveText("Added to My Agents"); await expect(useAgentTarget).toBeDisabled(); }); type AgentShareCommand = { command: string; payload: unknown }; async function openSafetyShareDialog( page: import("@playwright/test").Page, options: Parameters[1] = {}, ) { await installMockBridge(page, { personas: [ { id: "custom:safety-auditor", displayName: "Safety Auditor", systemPrompt: "You audit safety boundaries.", }, ], searchProfiles: [ { pubkey: TEST_IDENTITIES.charlie.pubkey, displayName: "Charlie", }, ], uploadDescriptors: [ { url: `https://mock.relay/media/${"d".repeat(64)}.png`, sha256: "d".repeat(64), size: 1234, type: "image/png", uploaded: Math.floor(Date.now() / 1000), filename: "safety-auditor.agent.png", }, ], ...options, }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await page.getByLabel("Open actions for Safety Auditor").click(); await page.getByRole("menuitem", { name: "Share" }).click(); await expect(page.getByTestId("persona-share-dialog")).toBeVisible(); } async function selectCharlieRecipient(page: import("@playwright/test").Page) { const search = page.getByTestId("persona-share-recipient-search"); await expect(search).toBeEnabled({ timeout: 5_000 }); await search.fill("charlie"); await page .getByTestId( `persona-share-recipient-option-${TEST_IDENTITIES.charlie.pubkey}`, ) .click(); } async function readAgentShareCommands( page: import("@playwright/test").Page, ): Promise { return page.evaluate( () => ( window as Window & { __BUZZ_E2E_COMMAND_LOG__?: AgentShareCommand[]; } ).__BUZZ_E2E_COMMAND_LOG__ ?? [], ); } test("custom personas share with people and keep export separate", async ({ page, }) => { const sharedAgentUrl = `https://mock.relay/media/${"b".repeat(64)}.png`; await page.context().grantPermissions(["clipboard-read", "clipboard-write"]); await installMockBridge(page, { personas: [ { id: "custom:analyst", displayName: "Animation Auditor", systemPrompt: "You audit animations.", }, ], searchProfiles: [ { pubkey: TEST_IDENTITIES.charlie.pubkey, displayName: "Charlie", }, { pubkey: TEST_IDENTITIES.bob.pubkey, displayName: "Bob", }, ...Array.from({ length: 12 }, (_, index) => ({ pubkey: (index + 10).toString(16).padStart(64, "0"), displayName: `Person ${index + 1}`, })), ], uploadDescriptors: [ { url: sharedAgentUrl, sha256: "b".repeat(64), size: 1234, type: "image/png", uploaded: Math.floor(Date.now() / 1000), filename: "analyst.agent.png", }, ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await expect(page.getByTestId("agents-library-personas")).toContainText( "Animation Auditor", ); const actionsButton = page.getByLabel("Open actions for Animation Auditor"); await expect(actionsButton.locator("svg")).toHaveClass( /lucide-ellipsis-vertical/, ); await actionsButton.click(); await expect(page.getByRole("menuitem")).toHaveText([ "Edit", "Duplicate", "Share", "Delete", ]); await page.getByRole("menuitem", { name: "Share" }).click(); const shareDialog = page.getByTestId("persona-share-dialog"); await expect(shareDialog).toBeVisible(); const emptyRecipientSearch = shareDialog.getByTestId( "persona-share-recipient-search", ); const [searchIconColor, searchPlaceholderColor, searchInputColor] = await Promise.all([ shareDialog .locator("svg.lucide-search") .evaluate((element) => getComputedStyle(element).color), emptyRecipientSearch.evaluate( (element) => getComputedStyle(element, "::placeholder").color, ), emptyRecipientSearch.evaluate( (element) => getComputedStyle(element).color, ), ]); expect(searchIconColor).toBe(searchPlaceholderColor); expect(searchPlaceholderColor).not.toBe(searchInputColor); const shareCloseButton = shareDialog.getByRole("button", { name: "Close" }); expect( await shareCloseButton.evaluate( (button) => getComputedStyle(button).boxShadow, ), ).not.toContain("1px"); await expect( page.getByRole("heading", { name: "Share Animation Auditor" }), ).toBeVisible(); await expect(shareDialog.getByText("Added by You")).toHaveCount(0); const shareDescription = shareDialog.getByTestId( "persona-share-share-description", ); await expect(shareDescription).toHaveText( "Anyone you share this agent with will receive a copy they can add and use. Changes you make later won’t sync.", ); await expect(shareDescription).toHaveClass(/text-sm.*text-muted-foreground/); const shareDescriptionId = await shareDescription.getAttribute("id"); expect(shareDescriptionId).toBeTruthy(); await expect(shareDialog).toHaveAttribute( "aria-describedby", shareDescriptionId ?? "", ); const shareDescriptionMetrics = await shareDescription.evaluate( (element) => ({ height: element.getBoundingClientRect().height, lineHeight: Number.parseFloat(getComputedStyle(element).lineHeight), }), ); expect(shareDescriptionMetrics.height).toBeLessThanOrEqual( shareDescriptionMetrics.lineHeight * 2 + 1, ); await expect( shareDialog.getByRole("heading", { name: "Who has access" }), ).toHaveCount(0); await expect(shareDialog.getByText("Owner", { exact: true })).toHaveCount(0); await expect(shareDialog.getByText("(You)", { exact: true })).toHaveCount(0); const linkRow = page.getByTestId("persona-share-link-row"); await expect(page.getByTestId("persona-share-copy-link")).toBeVisible(); await expect( shareDialog.getByRole("heading", { name: "Share with a link" }), ).toHaveCount(0); await expect( shareDialog.getByText("Anyone with the link can add and use a copy."), ).toHaveCount(0); await expect(page.getByTestId("persona-share-send")).toHaveCount(0); const copyLinkButton = page.getByTestId("persona-share-copy-link"); const catalogSection = page.getByTestId("persona-share-catalog"); const shareMainCard = page.getByTestId("persona-share-main-card"); const exportAgentRow = page.getByTestId("persona-share-export"); await waitForAnimations(page); const [ linkRowBox, initialCopyLinkButtonBox, catalogSectionBox, shareMainCardBox, exportAgentRowBox, ] = await Promise.all([ linkRow.boundingBox(), copyLinkButton.boundingBox(), catalogSection.boundingBox(), shareMainCard.boundingBox(), exportAgentRow.boundingBox(), ]); const shareDescriptionBox = await shareDescription.boundingBox(); const recipientFieldBox = await page .getByTestId("persona-share-recipient-field") .boundingBox(); // Without memories, the recipient field flows directly into the link action. expect(recipientFieldBox?.y ?? 0).toBeGreaterThanOrEqual( (shareDescriptionBox?.y ?? 0) + (shareDescriptionBox?.height ?? 0), ); await expect(page.getByTestId("persona-share-link-settings")).toHaveCount(0); await expect(page.getByTestId("persona-share-share-level-row")).toHaveCount( 0, ); expect(linkRowBox?.y ?? 0).toBeGreaterThanOrEqual( (recipientFieldBox?.y ?? 0) + (recipientFieldBox?.height ?? 0), ); // Copy link stays inside the main card, after the shared settings divider, while catalog and // export are separate rows below it. expect(initialCopyLinkButtonBox?.y ?? 0).toBeGreaterThanOrEqual( linkRowBox?.y ?? 0, ); expect( (initialCopyLinkButtonBox?.y ?? 0) + (initialCopyLinkButtonBox?.height ?? 0), ).toBeLessThanOrEqual((linkRowBox?.y ?? 0) + (linkRowBox?.height ?? 0) + 1); await expect(page.getByTestId("persona-share-link-divider")).toHaveClass( /bg-input\/40/, ); expect(catalogSectionBox?.y ?? 0).toBeGreaterThanOrEqual( (shareMainCardBox?.y ?? 0) + (shareMainCardBox?.height ?? 0) + 12, ); expect(exportAgentRowBox?.y ?? 0).toBeGreaterThanOrEqual( (catalogSectionBox?.y ?? 0) + (catalogSectionBox?.height ?? 0) + 12, ); await expect(copyLinkButton).toHaveClass( /border.*bg-background.*border-border/, ); const copyLinkHasVisibleShadow = () => copyLinkButton.evaluate((element) => { const boxShadow = getComputedStyle(element).boxShadow; if (boxShadow === "none") return false; return [...boxShadow.matchAll(/rgba?\(([^)]+)\)/g)].some((match) => { if (match[0].startsWith("rgb(")) return true; const channels = match[1]?.split(/[\s,/]+/).filter(Boolean) ?? []; return Number(channels.at(-1)) > 0; }); }); await expect.poll(copyLinkHasVisibleShadow).toBe(false); await copyLinkButton.hover(); await expect.poll(copyLinkHasVisibleShadow).toBe(false); await expect(page.getByTestId("persona-share-share-level")).toHaveCount(0); await expect(page.getByTestId("persona-share-recipient-access")).toHaveCount( 0, ); await expect(page.getByTestId("persona-share-link-access")).toHaveCount(0); await expect( shareDialog.getByLabel("What to include in the link"), ).toHaveCount(0); await expect( shareDialog.getByLabel("What to include", { exact: true }), ).toHaveCount(0); await expect(shareDialog.getByText("Memories", { exact: true })).toHaveCount( 0, ); await expect( shareDialog.getByText("File format", { exact: true }), ).toHaveCount(0); await expect(exportAgentRow).toHaveText("Export agent"); await expect(shareMainCard.getByTestId("persona-share-export")).toHaveCount( 0, ); const [shareMainCardStyles, exportAgentRowStyles] = await Promise.all([ shareMainCard.evaluate((element) => ({ borderRadius: getComputedStyle(element).borderRadius, boxShadow: getComputedStyle(element).boxShadow, })), exportAgentRow.evaluate((element) => ({ borderRadius: getComputedStyle(element).borderRadius, boxShadow: getComputedStyle(element).boxShadow, })), ]); expect(exportAgentRowStyles.borderRadius).toBe( shareMainCardStyles.borderRadius, ); const shareMainCardShadow = shareMainCardStyles.boxShadow; const exportAgentRowShadow = exportAgentRowStyles.boxShadow; expect(exportAgentRowShadow).toBe(shareMainCardShadow); expect(exportAgentRowShadow).not.toBe("none"); await expect(exportAgentRow).toHaveCSS("position", "relative"); expect(exportAgentRowBox?.y ?? 0).toBeGreaterThanOrEqual( (shareMainCardBox?.y ?? 0) + (shareMainCardBox?.height ?? 0) + 12, ); await expect(page.getByTestId("agent-snapshot-export-dialog")).toHaveCount(0); await exportAgentRow.click(); await expect(shareDialog).toHaveCount(0); const exportDialog = page.getByTestId("agent-snapshot-export-dialog"); await expect(exportDialog).toBeVisible(); const exportDialogBox = await exportDialog.boundingBox(); expect(exportDialogBox?.width).toBeLessThanOrEqual(448); await expect( exportDialog.getByRole("heading", { name: "Export Animation Auditor", exact: true, }), ).toBeVisible(); await expect(exportDialog.getByText("portable snapshot")).toHaveCount(0); await expect( exportDialog.getByRole("button", { name: "Send in Buzz" }), ).toHaveCount(0); await expect(exportDialog.getByLabel("Memories")).toHaveCount(0); await expect( exportDialog.getByTestId("agent-snapshot-memory-value"), ).toHaveText("Agent only"); await expect(exportDialog.getByText("Start this agent")).toHaveCount(0); await expect( exportDialog.getByTestId("agent-snapshot-memory-value").locator("svg"), ).toHaveCount(0); const formatTrigger = exportDialog.getByLabel("File format"); await expect(formatTrigger).toHaveText("PNG"); expect((await formatTrigger.boundingBox())?.width).toBeLessThan(80); expect(await formatTrigger.evaluate((element) => element.tagName)).toBe( "BUTTON", ); await formatTrigger.click(); await expect(page.getByRole("menuitemradio", { name: "JSON" })).toBeVisible(); await page.getByRole("menuitemradio", { name: "PNG" }).click(); const exportFooter = exportDialog.getByTestId("agent-snapshot-export-footer"); const cancelButtonBox = await exportFooter .getByRole("button", { name: "Cancel" }) .boundingBox(); const exportButtonBox = await exportFooter .getByRole("button", { name: "Export" }) .boundingBox(); expect(cancelButtonBox?.x).toBeLessThan(exportButtonBox?.x ?? 0); expect( (exportButtonBox?.x ?? 0) - ((cancelButtonBox?.x ?? 0) + (cancelButtonBox?.width ?? 0)), ).toBeLessThanOrEqual(12); await exportDialog.getByRole("button", { name: "Cancel" }).click(); await actionsButton.click(); await page.getByRole("menuitem", { name: "Share" }).click(); await expect(shareDialog).toBeVisible(); await page.getByTestId("persona-share-copy-link").click(); await expect .poll(() => page.evaluate(() => navigator.clipboard.readText())) .toBe(sharedAgentUrl); const copiedAgent = await page.evaluate(() => { const commands = ( window as Window & { __BUZZ_E2E_COMMAND_LOG__?: Array<{ command: string; payload: { html?: string; text?: string }; }>; } ).__BUZZ_E2E_COMMAND_LOG__ ?? []; return commands.findLast( (entry) => entry.command === "copy_text_to_clipboard", )?.payload; }); expect(copiedAgent?.text).toBe(sharedAgentUrl); expect(copiedAgent?.html).toContain("data-buzz-agent-snapshot"); await page.keyboard.press("Escape"); await expect(shareDialog).toHaveCount(0); await page.getByTestId("channel-general").click(); await page.evaluate(async ({ html, text }) => { await navigator.clipboard.write([ new ClipboardItem({ "text/html": new Blob([html ?? ""], { type: "text/html" }), "text/plain": new Blob([text ?? ""], { type: "text/plain" }), }), ]); }, copiedAgent ?? {}); await page .getByTestId("message-composer") .locator("[contenteditable='true']") .click(); await page.keyboard.press("ControlOrMeta+V"); const composerAgentCard = page.getByTestId("composer-agent-snapshot-card"); await expect(composerAgentCard).toBeVisible(); await expect(composerAgentCard).toContainText("Animation Auditor"); const composerRemoveButton = page.getByTestId( "composer-agent-snapshot-remove", ); await expect(composerRemoveButton).toHaveCSS("border-top-width", "0px"); await composerRemoveButton.focus(); await page.keyboard.press("Tab"); await page.keyboard.press("Shift+Tab"); await expect(composerRemoveButton).toBeFocused(); expect( await composerRemoveButton.evaluate( (button) => getComputedStyle(button).boxShadow, ), ).not.toContain("1px"); await expect(composerRemoveButton).not.toHaveCSS( "background-color", "rgba(0, 0, 0, 0)", ); await expect(page.getByTestId("send-message")).toBeEnabled(); await page.getByTestId("send-message").click(); const pastedAgentCard = page.getByTestId("agent-snapshot-card").last(); await expect(pastedAgentCard).toBeVisible(); await expect(pastedAgentCard).toContainText("Animation Auditor"); await page.getByTestId("open-agents-view").click(); await actionsButton.click(); await page.getByRole("menuitem", { name: "Share" }).click(); await expect(shareDialog).toBeVisible(); const recipientSearch = page.getByTestId("persona-share-recipient-search"); const recipientField = page.getByTestId("persona-share-recipient-field"); await waitForAnimations(page); const emptyRecipientFieldWidth = (await recipientField.boundingBox())?.width ?? 0; await recipientSearch.click(); const recipientPopover = page.getByTestId("persona-share-recipient-popover"); await expect(recipientPopover).toBeVisible(); await page.evaluate(() => { const content = document.querySelector( '[data-testid="persona-share-recipient-popover"]', ); if (!content) throw new Error("Recipient popover is not mounted"); const stateChanges: string[] = []; const observer = new MutationObserver(() => { stateChanges.push(content.getAttribute("data-state") ?? "unmounted"); }); observer.observe(content, { attributeFilter: ["data-state"], attributes: true, }); ( window as Window & { __BUZZ_RECIPIENT_POPOVER_OBSERVER__?: MutationObserver; __BUZZ_RECIPIENT_POPOVER_STATE_CHANGES__?: string[]; } ).__BUZZ_RECIPIENT_POPOVER_OBSERVER__ = observer; ( window as Window & { __BUZZ_RECIPIENT_POPOVER_STATE_CHANGES__?: string[]; } ).__BUZZ_RECIPIENT_POPOVER_STATE_CHANGES__ = stateChanges; }); await recipientSearch.click(); await waitForAnimations(page); const recipientPopoverStateChanges = await page.evaluate(() => { const trackedWindow = window as Window & { __BUZZ_RECIPIENT_POPOVER_OBSERVER__?: MutationObserver; __BUZZ_RECIPIENT_POPOVER_STATE_CHANGES__?: string[]; }; trackedWindow.__BUZZ_RECIPIENT_POPOVER_OBSERVER__?.disconnect(); return trackedWindow.__BUZZ_RECIPIENT_POPOVER_STATE_CHANGES__ ?? []; }); expect(recipientPopoverStateChanges).not.toContain("closed"); const recipientList = page.getByTestId("persona-share-recipient-results"); await expect(recipientList).toBeVisible(); await expect .poll(() => recipientList.evaluate( (element) => element.scrollHeight - element.clientHeight, ), ) .toBeGreaterThan(0); const recipientListBox = await recipientList.boundingBox(); expect(recipientListBox).not.toBeNull(); if (!recipientListBox) return; await page.mouse.move( recipientListBox.x + recipientListBox.width / 2, recipientListBox.y + recipientListBox.height / 2, ); await page.mouse.wheel(0, 300); await expect .poll(() => recipientList.evaluate((element) => element.scrollTop)) .toBeGreaterThan(0); await recipientSearch.fill("charlie"); await page .getByTestId( `persona-share-recipient-option-${TEST_IDENTITIES.charlie.pubkey}`, ) .click(); const charlieChip = page.getByTestId( `persona-share-recipient-chip-${TEST_IDENTITIES.charlie.pubkey}`, ); await expect(charlieChip).toBeVisible(); await expect( page.getByRole("button", { name: "Verify charlie public key" }), ).toHaveCount(0); await expect(recipientSearch).toHaveValue(""); await expect(recipientSearch).toHaveAttribute("placeholder", ""); await expect( page .getByTestId("persona-share-recipient-field") .locator("svg.lucide-search"), ).toHaveCount(0); await expect( page .getByTestId("persona-share-recipient-field") .getByTestId("persona-share-recipient-access"), ).toHaveCount(0); await expect(page.getByTestId("persona-share-send")).toBeVisible(); await recipientSearch.fill("bob"); await page .getByTestId(`persona-share-recipient-option-${TEST_IDENTITIES.bob.pubkey}`) .click(); const bobChip = page.getByTestId( `persona-share-recipient-chip-${TEST_IDENTITIES.bob.pubkey}`, ); await expect(bobChip).toBeVisible(); await expect(bobChip).not.toHaveClass(/buzz-poof-trigger/); await waitForAnimations(page); const bobChipBox = await bobChip.boundingBox(); expect(bobChipBox).not.toBeNull(); if (!bobChipBox) return; const removePointer = { x: bobChipBox.x + bobChipBox.width - 3, y: bobChipBox.y + bobChipBox.height / 2, }; await page.mouse.move(removePointer.x, removePointer.y); await page.mouse.down(); await expect(page.locator(".buzz-poof-burst")).toHaveCount(0); await page.mouse.up(); await expect(bobChip).toHaveCount(0); await waitForAnimations(page); const selectedRecipientFieldWidth = (await recipientField.boundingBox())?.width ?? 0; const charlieChipBox = await charlieChip.boundingBox(); expect(charlieChipBox).not.toBeNull(); if (!charlieChipBox) return; await page.mouse.move( charlieChipBox.x + charlieChipBox.width / 2, charlieChipBox.y + charlieChipBox.height / 2, ); await page.mouse.down(); const lastChipRemovalSamples = await page.evaluate(async () => { const samples: Array<{ elapsed: number; fieldWidth: number }> = []; const start = performance.now(); await new Promise((resolve) => { const sample = (now: number) => { const field = document.querySelector( '[data-testid="persona-share-recipient-field"]', ); samples.push({ elapsed: now - start, fieldWidth: field?.getBoundingClientRect().width ?? 0, }); if (now - start >= 240) { resolve(); return; } requestAnimationFrame(sample); }; requestAnimationFrame(sample); }); return samples; }); await page.mouse.up(); await expect(charlieChip).toHaveCount(0); const firstExpandedSample = lastChipRemovalSamples.find( ({ fieldWidth }) => fieldWidth > selectedRecipientFieldWidth + 1, ); expect(firstExpandedSample?.elapsed).toBeLessThan(100); expect( Math.abs( (lastChipRemovalSamples.at(-1)?.fieldWidth ?? 0) - emptyRecipientFieldWidth, ), ).toBeLessThanOrEqual(1); await recipientSearch.fill("bob"); await page .getByTestId(`persona-share-recipient-option-${TEST_IDENTITIES.bob.pubkey}`) .click(); await page.getByTestId("persona-share-send").click(); await expect( page.getByText("Sent a copy of Animation Auditor"), ).toBeVisible(); const sentAgentMessages = await page.evaluate(() => ( ( window as Window & { __BUZZ_E2E_COMMAND_LOG__?: Array<{ command: string; payload: { content?: string }; }>; } ).__BUZZ_E2E_COMMAND_LOG__ ?? [] ).filter((entry) => entry.command === "send_channel_message"), ); expect(sentAgentMessages.at(-1)?.payload.content).toBe( `\n[Animation Auditor](${sharedAgentUrl})`, ); await expect(shareDialog).toHaveCount(0); }); test("custom personas can be shared to the relay catalog", async ({ page }) => { const personaId = "custom:catalog-analyst"; await installMockBridge(page, { globalAgentConfig: { env_vars: { ANTHROPIC_API_KEY: "sk-ant-test" }, provider: "anthropic", model: "claude-opus-4-5", }, personas: [ { id: personaId, displayName: "Catalog Analyst", respondTo: "allowlist", respondToAllowlist: [TEST_IDENTITIES.alice.pubkey], systemPrompt: `## Design System And Styling - For design-system changes, check the local guidance in \`DESIGN.md\`, \`docs/color-token-mapping.md\`, \`src/shared/ui/AGENTS.md\`, and \`src/features/design-system/AGENTS.md\` before judging the implementation. - Check every changed visual surface in both light and dark mode. Missing dark-mode support is a review issue, not visual polish. - Review the selected changes and explain whether \`git diff --cached --name-only --some-extremely-long-inline-option-that-must-wrap\` stays inside the catalog detail column. \`\`\`text This deliberately long fenced-code example must not establish the minimum width of the full custom-agent instruction document or force earlier prose outside the catalog detail pane. \`\`\` | Before | After | Why | | --- | --- | --- | | \`transition: all 300ms\` | \`transition: transform 200ms ease-out\` | Specify exact properties so a wide instruction table stays independently scrollable without expanding the full catalog detail pane. | | \`transform: scale(0)\` | \`transform: scale(0.95); opacity: 0\` | Preserve physicality while keeping the shared agent instructions inside their container. |`, }, ], }); await gotoApp(page); await page.evaluate(() => { document.documentElement.style.fontSize = "24px"; }); await page.getByTestId("open-agents-view").click(); await openPersonaCatalog(page); await expect( page.getByTestId(`persona-catalog-list-item-${personaId}`), ).toHaveCount(0); await page.keyboard.press("Escape"); await page.getByLabel("Open actions for Catalog Analyst").click(); await page.getByRole("menuitem", { name: "Share" }).click(); const catalogAccess = page.getByTestId("persona-share-catalog-access"); const shareDialog = page.getByTestId("persona-share-dialog"); const shareMainCard = shareDialog.getByTestId("persona-share-main-card"); const copyLinkButton = shareDialog.getByTestId("persona-share-copy-link"); const catalogSection = shareDialog.getByTestId("persona-share-catalog"); await expect(shareMainCard.getByTestId("persona-share-catalog")).toHaveCount( 0, ); await expect(catalogSection).toContainText("Share to catalog"); await expect(catalogSection).toContainText( "Anyone in this community can find and use a copy.", ); await expect(catalogSection).toContainText( "Your agent instruction is shared as plaintext. Memories and secrets aren’t included.", ); const [copyLinkButtonBox, catalogSectionBox, shareMainCardBox] = await Promise.all([ copyLinkButton.boundingBox(), catalogSection.boundingBox(), shareMainCard.boundingBox(), ]); // Catalog is its own card below the sharing controls. expect( (copyLinkButtonBox?.y ?? 0) + (copyLinkButtonBox?.height ?? 0), ).toBeLessThanOrEqual( (shareMainCardBox?.y ?? 0) + (shareMainCardBox?.height ?? 0), ); expect(catalogSectionBox?.y ?? 0).toBeGreaterThanOrEqual( (shareMainCardBox?.y ?? 0) + (shareMainCardBox?.height ?? 0), ); await expect(catalogAccess).not.toBeChecked(); await catalogAccess.click(); await expect(catalogAccess).toBeChecked(); const storedPersonas = await invokeTauri< Array<{ id: string; shared: boolean }> >(page, "list_personas"); expect( storedPersonas.find((persona) => persona.id === personaId)?.shared, ).toBe(true); await page .getByTestId("persona-share-dialog") .getByRole("button", { name: "Close" }) .click(); await openPersonaCatalog(page); await expect( page.getByTestId(`persona-catalog-list-item-${personaId}`), ).toContainText("Catalog Analyst"); await selectCatalogPersona(page, personaId); const catalogDialog = page.getByTestId("persona-catalog-dialog"); const catalogDetailPane = page.getByTestId("persona-catalog-detail-pane"); await expect(catalogDetailPane).toContainText("Design System And Styling"); await expect(catalogDialog).toBeVisible(); await expect(catalogDetailPane).toBeVisible(); await waitForAnimations(page); const [catalogDialogRight, catalogDetailPaneRight] = await Promise.all([ catalogDialog.evaluate((element) => element.getBoundingClientRect().right), catalogDetailPane.evaluate( (element) => element.getBoundingClientRect().right, ), ]); expect(catalogDetailPaneRight).toBeLessThanOrEqual(catalogDialogRight); expect( await catalogDetailPane.evaluate( (element) => element.scrollWidth - element.clientWidth, ), ).toBeLessThanOrEqual(1); const catalogInstruction = catalogDetailPane.locator(".message-markdown"); expect( await catalogInstruction.evaluate( (element) => element.scrollWidth - element.clientWidth, ), ).toBeLessThanOrEqual(1); await page.keyboard.press("Escape"); await page.getByLabel("Open actions for Catalog Analyst").click(); await page.getByRole("menuitem", { name: "Edit" }).click(); const editDialog = page.getByTestId("persona-dialog"); const catalogPublishNotice = editDialog.getByTestId( "persona-dialog-catalog-publish-notice", ); await expect(catalogPublishNotice).toHaveCount(0); await expect( editDialog.getByRole("button", { name: "Save and publish" }), ).toHaveCount(0); await expect( editDialog.getByRole("button", { name: "Save changes" }), ).toBeVisible(); await editDialog .getByLabel("Agent instructions") .fill("Review the latest catalog changes."); await expect(catalogPublishNotice).toHaveText( "This agent is in the community catalog. Your changes will be published when you save.", ); await expect( editDialog.getByRole("button", { name: "Save changes" }), ).toHaveCount(0); await editDialog.getByRole("button", { name: "Save and publish" }).click(); await expect(editDialog).toHaveCount(0); // The promise in the button label is only kept by the command that awaits the // relay; a plain `update_persona` merely enqueues a head best-effort. await expect .poll(() => countCommandInvocations(page, "update_persona_and_publish")) .toBe(1); expect(await countCommandInvocations(page, "update_persona")).toBe(0); await expect( page.getByText( "Updated Catalog Analyst and published it to the community catalog.", ), ).toBeVisible(); await openPersonaCatalog(page); await selectCatalogPersona(page, personaId); await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText( "Review the latest catalog changes.", ); await page.keyboard.press("Escape"); await page.getByLabel("Open actions for Catalog Analyst").click(); await page.getByRole("menuitem", { name: "Share" }).click(); await expect(catalogAccess).toBeChecked(); await catalogAccess.click(); await expect(catalogAccess).not.toBeChecked(); await page .getByTestId("persona-share-dialog") .getByRole("button", { name: "Close" }) .click(); await openPersonaCatalog(page); await expect( page.getByTestId(`persona-catalog-list-item-${personaId}`), ).toHaveCount(0); }); test("a queued catalog share is not presented as relay-published", async ({ page, }) => { const personaId = "custom:queued-catalog-agent"; await installMockBridge(page, { personas: [ { id: personaId, displayName: "Queued Catalog Agent", systemPrompt: "Wait for relay acceptance.", }, ], personaSharePublicationStatuses: ["queued"], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await page.getByLabel("Open actions for Queued Catalog Agent").click(); await page.getByRole("menuitem", { name: "Share" }).click(); await page.getByTestId("persona-share-catalog-access").click(); await expect( page.getByText( "Sharing Queued Catalog Agent is queued. It will appear after the relay accepts the update.", ), ).toBeVisible(); await page .getByTestId("persona-share-dialog") .getByRole("button", { name: "Close" }) .click(); await openPersonaCatalog(page); await expect( page.getByTestId(`persona-catalog-list-item-${personaId}`), ).toHaveCount(0); }); test("a foreign reader does not receive an unshared kind 30175 persona", async ({ page, }) => { const personaId = "private-reviewer"; const remoteCatalogId = `catalog:${TEST_IDENTITIES.alice.pubkey}:${personaId}`; await installMockBridge(page, { personaCatalogEvents: [ createCatalogEvent({ ownerPubkey: TEST_IDENTITIES.alice.pubkey, sourcePersonaId: personaId, displayName: "Alice’s Private Reviewer", systemPrompt: "This instruction must remain private.", shared: false, }), ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await openPersonaCatalog(page); await expect( page.getByTestId(`persona-catalog-list-item-${remoteCatalogId}`), ).toHaveCount(0); await expect( page.getByText("No shared agents", { exact: true }), ).toBeVisible(); }); test("a catalog entry keeps the owner's emoji avatar", async ({ page }) => { const personaId = "emoji-reviewer"; const remoteCatalogId = `catalog:${TEST_IDENTITIES.alice.pubkey}:${personaId}`; // Emoji avatars persist as inline percent-encoded SVG rather than a hosted // URL, so build the value with the same producer the editor uses — a // hand-rolled data URL would pass even if the real shape stopped matching. const avatarUrl = emojiAvatarDataUrl("šŸ", "#FFCC00"); await installMockBridge(page, { personaCatalogEvents: [ createCatalogEvent({ avatarUrl, ownerPubkey: TEST_IDENTITIES.alice.pubkey, sourcePersonaId: personaId, displayName: "Alice’s Reviewer", systemPrompt: "Review changes for the whole community.", }), ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await openPersonaCatalog(page); // An `` carrying the avatar — not the initials fallback — in both the // list row and the detail header is what proves the projection kept it. const remoteEntry = page.getByTestId( `persona-catalog-list-item-${remoteCatalogId}`, ); await expect(remoteEntry.locator("img")).toHaveAttribute("src", avatarUrl); await remoteEntry.click(); await expect( page.getByTestId("persona-catalog-detail-pane").locator("img").first(), ).toHaveAttribute("src", avatarUrl); }); test("a community member can discover and add another member's catalog agent", async ({ page, }) => { const personaId = "shared-reviewer"; const remoteCatalogId = `catalog:${TEST_IDENTITIES.alice.pubkey}:${personaId}`; await installMockBridge(page, { personaCatalogEvents: [ createCatalogEvent({ ownerPubkey: TEST_IDENTITIES.alice.pubkey, sourcePersonaId: personaId, displayName: "Alice’s Reviewer", systemPrompt: "Review changes for the whole community.", }), ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await openPersonaCatalog(page); const remoteEntry = page.getByTestId( `persona-catalog-list-item-${remoteCatalogId}`, ); await expect(remoteEntry).toContainText("Alice’s Reviewer"); await remoteEntry.click(); // The detail pane resolves the publisher's display name; 'Community member' // is only the fallback for an unresolvable pubkey. await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText( "Added by alice", ); await page .getByRole("button", { name: "Add Alice’s Reviewer from Agent Catalog", }) .click(); await expect .poll(() => countCommandInvocations(page, "create_persona")) .toBe(1); const imported = await invokeTauri< Array<{ display_name: string; system_prompt: string; shared: boolean; catalog_source: { owner_pubkey: string; persona_id: string } | null; }> >(page, "list_personas"); expect( imported.find((persona) => persona.display_name === "Alice’s Reviewer"), ).toMatchObject({ system_prompt: "Review changes for the whole community.", shared: false, // Provenance is what lets the catalog recognise the copy on the next open. catalog_source: { owner_pubkey: TEST_IDENTITIES.alice.pubkey, persona_id: personaId, }, }); // Reopening must offer the entry as already added rather than minting a // second copy — the copy has a fresh local id, so only the stored // coordinate can link it back to Alice's publication. await page.keyboard.press("Escape"); await openPersonaCatalog(page); // The entry now projects onto the local copy, so its list-item testid is the // local persona id rather than the catalog coordinate. await expect( page.getByTestId(`persona-catalog-list-item-${remoteCatalogId}`), ).toHaveCount(0); await page .locator('[data-testid^="persona-catalog-list-item-"]') .filter({ hasText: "Alice’s Reviewer" }) .click(); const addedTarget = page.getByRole("button", { name: "Alice’s Reviewer is already in My Agents", }); await expect(addedTarget).toBeDisabled(); await expect(addedTarget).toHaveText("Added to My Agents"); expect(await countCommandInvocations(page, "create_persona")).toBe(1); }); test("catalog detail shows Community member when the publisher profile cannot be resolved", async ({ page, }) => { // A pubkey that is not in the mock profile registry — profile resolution // will fail and the detail pane must fall back gracefully. const unknownPubkey = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const personaId = "unresolvable-reviewer"; await installMockBridge(page, { personaCatalogEvents: [ createCatalogEvent({ ownerPubkey: unknownPubkey, sourcePersonaId: personaId, displayName: "Mystery Agent", systemPrompt: "Published by someone whose profile cannot be fetched.", }), ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await openPersonaCatalog(page); await page .getByTestId( `persona-catalog-list-item-catalog:${unknownPubkey}:${personaId}`, ) .click(); await expect(page.getByTestId("persona-catalog-detail-pane")).toContainText( "Added by Community member", ); }); test("one share level selector drives both the link and send paths", async ({ page, }) => { await page.emulateMedia({ reducedMotion: "no-preference" }); const linkedAgentPubkey = TEST_IDENTITIES.alice.pubkey; const profileAvatarUrl = "https://mock.relay/media/profile-only-avatar.png"; const profileAvatarBytes = Uint8Array.from( atob( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=", ), (character) => character.charCodeAt(0), ); await page.route(profileAvatarUrl, async (route) => { await route.fulfill({ body: Buffer.from(profileAvatarBytes), contentType: "image/png", status: 200, }); }); await page.context().grantPermissions(["clipboard-read", "clipboard-write"]); await installMockBridge(page, { personas: [ { id: "custom:animation-auditor", displayName: "Animation Auditor", systemPrompt: "You audit animations.", }, ], managedAgents: [ { pubkey: linkedAgentPubkey, name: "Animation Auditor", personaId: "custom:animation-auditor", status: "running", }, ], searchProfiles: [ { pubkey: linkedAgentPubkey, displayName: "Animation Auditor", avatarUrl: profileAvatarUrl, isAgent: true, }, { pubkey: TEST_IDENTITIES.charlie.pubkey, displayName: "Charlie", }, ], uploadDescriptors: [ { url: `https://mock.relay/media/${"c".repeat(64)}.png`, sha256: "c".repeat(64), size: 1234, type: "image/png", uploaded: Math.floor(Date.now() / 1000), filename: "animation-auditor.agent.png", }, ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await page.getByLabel("Open actions for Animation Auditor").click(); await page.getByRole("menuitem", { name: "Share" }).click(); const shareDialog = page.getByTestId("persona-share-dialog"); const shareMainCard = shareDialog.getByTestId("persona-share-main-card"); const initialShareCardHeight = await shareMainCard.evaluate( (element) => element.getBoundingClientRect().height, ); const shareLevel = shareDialog.getByLabel("What to include", { exact: true, }); const catalogAccess = shareDialog.getByLabel("Share to catalog"); const recipientField = page.getByTestId("persona-share-recipient-field"); const emptyRecipientFieldBox = await recipientField.boundingBox(); await expect(shareDialog.getByTestId("persona-share-send")).toHaveCount(0); await expect(shareLevel).toHaveText("Agent only"); expect((await shareLevel.boundingBox())?.width).toBeLessThan(140); expect(await shareLevel.evaluate((element) => element.tagName)).toBe( "BUTTON", ); await expect(shareLevel).toHaveCSS("text-decoration-line", "none"); await expect(shareLevel).toHaveCSS("padding-left", "8px"); await expect(shareLevel).toHaveCSS("padding-right", "8px"); await expect(catalogAccess).not.toBeChecked(); await expect(catalogAccess).toHaveCSS("cursor", "default"); await expect( shareDialog.getByTestId("persona-share-link-settings"), ).toContainText("Share settings"); await expect( shareDialog.getByText("Share settings", { exact: true }), ).toHaveClass(/text-xs.*text-secondary-foreground\/75/); const copyLinkButton = shareDialog.getByTestId("persona-share-copy-link"); const recipientFieldBox = await recipientField.boundingBox(); const [shareLevelBox, copyLinkButtonBox, catalogAccessBox] = await Promise.all([ shareLevel.boundingBox(), copyLinkButton.boundingBox(), catalogAccess.boundingBox(), ]); // Reading order: who → what's included → copy link → catalog. expect(shareLevelBox?.y ?? 0).toBeGreaterThanOrEqual( (recipientFieldBox?.y ?? 0) + (recipientFieldBox?.height ?? 0), ); expect(copyLinkButtonBox?.y ?? 0).toBeGreaterThanOrEqual( (shareLevelBox?.y ?? 0) + (shareLevelBox?.height ?? 0), ); expect(catalogAccessBox?.y ?? 0).toBeGreaterThanOrEqual( (copyLinkButtonBox?.y ?? 0) + (copyLinkButtonBox?.height ?? 0), ); // The memory choice is stated once, governing both delivery actions — // neither the recipients row nor the link row carries its own copy. await expect( shareDialog.getByTestId("persona-share-recipient-access"), ).toHaveCount(0); await expect( shareDialog.getByTestId("persona-share-link-access"), ).toHaveCount(0); await expect(shareLevel).toHaveCount(1); await expect( shareDialog.getByTestId("persona-share-memory-warning"), ).toHaveCount(0); await shareLevel.click(); await expect(page.getByRole("menuitemradio")).toHaveText([ "Agent only", "Agent + core memory", "Agent + all memories", ]); await page .getByRole("menuitemradio", { name: "Agent + core memory" }) .click(); await expect(shareLevel).toHaveText("Agent + core memory"); await waitForAnimations(page); const expandedShareCardHeight = await shareMainCard.evaluate( (element) => element.getBoundingClientRect().height, ); const inlineMemoryWarning = shareDialog.getByTestId( "persona-share-memory-warning", ); // No recipient is selected yet: the warning tracks the chosen contents, not // whichever delivery button might be pressed. await expect(shareDialog.getByTestId("persona-share-send")).toHaveCount(0); await expect(inlineMemoryWarning).toBeVisible(); await expect(inlineMemoryWarning).toContainText( "Memory is stored as plaintext in the snapshot.", ); await expect(inlineMemoryWarning).toContainText( "Only share it with people you trust.", ); expect(expandedShareCardHeight).toBeGreaterThan(initialShareCardHeight); await page.getByTestId("persona-share-copy-link").click(); const memoryConfirmation = page.getByTestId( "persona-share-memory-confirmation", ); await expect(memoryConfirmation).toBeVisible(); await expect( memoryConfirmation.getByRole("heading", { name: "Share memories?" }), ).toBeVisible(); await expect(memoryConfirmation).toContainText("plaintext core memory"); await expect(memoryConfirmation).toContainText( "Anyone with the link can view it.", ); await expect(memoryConfirmation).toContainText( "Only share with people you trust.", ); const encodeLevelsBeforeLinkConfirmation = await page.evaluate(() => ( ( window as Window & { __BUZZ_E2E_COMMAND_LOG__?: Array<{ command: string; payload: { memoryLevel?: string }; }>; } ).__BUZZ_E2E_COMMAND_LOG__ ?? [] ) .filter((entry) => entry.command === "encode_agent_snapshot_for_send") .map((entry) => entry.payload.memoryLevel), ); expect(encodeLevelsBeforeLinkConfirmation).toEqual([]); await memoryConfirmation.getByTestId("persona-share-memory-confirm").click(); await expect(page.getByTestId("persona-share-copy-link")).toContainText( "Copied", ); await shareLevel.click(); await page .getByRole("menuitemradio", { name: "Agent only", exact: true }) .click(); await expect(shareLevel).toHaveText("Agent only"); await expect(inlineMemoryWarning).toHaveCount(0); const recipientSearch = page.getByTestId("persona-share-recipient-search"); await recipientSearch.fill("charlie"); await page .getByTestId( `persona-share-recipient-option-${TEST_IDENTITIES.charlie.pubkey}`, ) .click(); const recipientInputRegion = recipientField.getByTestId( "persona-share-recipient-input-region", ); await expect(recipientField).toHaveCSS("column-gap", "12px"); await expect(recipientInputRegion).toHaveCSS("flex-wrap", "wrap"); const sendButton = shareDialog.getByTestId("persona-share-send"); await expect(sendButton).toBeVisible(); await waitForAnimations(page); const resizedRecipientFieldBox = await recipientField.boundingBox(); expect(resizedRecipientFieldBox?.width).toBeLessThan( emptyRecipientFieldBox?.width ?? 0, ); await expect .poll(async () => { const [sendButtonBox, currentCopyLinkButtonBox] = await Promise.all([ sendButton.boundingBox(), copyLinkButton.boundingBox(), ]); return Math.abs( (sendButtonBox?.x ?? 0) + (sendButtonBox?.width ?? 0) - ((currentCopyLinkButtonBox?.x ?? 0) + (currentCopyLinkButtonBox?.width ?? 0)), ); }) .toBeLessThanOrEqual(1); // Same single selector now drives the send path; picking a level here is // what the send confirmation must report. await shareLevel.click(); await page .getByRole("menuitemradio", { name: "Agent + all memories" }) .click(); await expect(shareLevel).toHaveText("Agent + all memories"); await expect(inlineMemoryWarning).toBeVisible(); await waitForAnimations(page); expect( await shareLevel .locator("span") .evaluate((element) => element.scrollWidth <= element.clientWidth), ).toBe(true); await page.getByTestId("persona-share-send").click(); await expect(memoryConfirmation).toBeVisible(); await expect(memoryConfirmation).toContainText("plaintext all memories"); await expect(memoryConfirmation).toContainText( "Charlie—and anyone with the file link—can view it.", ); const encodeLevelsBeforeSendConfirmation = await page.evaluate(() => ( ( window as Window & { __BUZZ_E2E_COMMAND_LOG__?: Array<{ command: string; payload: { memoryLevel?: string }; }>; } ).__BUZZ_E2E_COMMAND_LOG__ ?? [] ) .filter((entry) => entry.command === "encode_agent_snapshot_for_send") .map((entry) => entry.payload.memoryLevel), ); expect(encodeLevelsBeforeSendConfirmation).not.toContain("none"); expect(encodeLevelsBeforeSendConfirmation).toContain("core"); expect(encodeLevelsBeforeSendConfirmation).not.toContain("everything"); await memoryConfirmation.getByTestId("persona-share-memory-confirm").click(); await expect( page.getByText("Sent a copy of Animation Auditor"), ).toBeVisible(); const encodePayloads = await page.evaluate(() => ( ( window as Window & { __BUZZ_E2E_COMMAND_LOG__?: Array<{ command: string; payload: unknown; }>; } ).__BUZZ_E2E_COMMAND_LOG__ ?? [] ) .filter((entry) => entry.command === "encode_agent_snapshot_for_send") .map((entry) => entry.payload), ); expect( encodePayloads.filter( (payload) => (payload as { memoryLevel?: string }).memoryLevel === "none", ), ).toHaveLength(0); expect(encodePayloads).toEqual( expect.arrayContaining([ expect.objectContaining({ memoryLevel: "core", memorySourcePubkey: linkedAgentPubkey, avatarPngDataUrl: `data:image/png;base64,${Buffer.from( profileAvatarBytes, ).toString("base64")}`, }), expect.objectContaining({ memoryLevel: "everything", memorySourcePubkey: linkedAgentPubkey, }), ]), ); }); test("people sharing excludes the moderation recipient", async ({ page }) => { await openSafetyShareDialog(page, { relaySelf: TEST_IDENTITIES.charlie.pubkey, relaySelfDelayMs: 800, }); const search = page.getByTestId("persona-share-recipient-search"); await expect(search).toBeEnabled({ timeout: 5_000 }); await search.fill("charlie"); await expect( page.getByTestId( `persona-share-recipient-option-${TEST_IDENTITIES.charlie.pubkey}`, ), ).toHaveCount(0); await expect(page.getByText("No people found.")).toBeVisible(); const commands = await readAgentShareCommands(page); expect( commands.filter((entry) => [ "open_dm", "encode_agent_snapshot_for_send", "upload_media_bytes", "send_channel_message", ].includes(entry.command), ), ).toEqual([]); }); test("people sharing blocks a timeout before encoding or upload", async ({ page, }) => { await openSafetyShareDialog(page); await selectCharlieRecipient(page); await page.evaluate(() => { ( window as Window & { __BUZZ_E2E_ACTIVATE_TIMEOUT__?: (expiresAtMs: number) => void; } ).__BUZZ_E2E_ACTIVATE_TIMEOUT__?.(Date.now() + 60_000); }); await page.getByTestId("persona-share-send").click(); await expect(page.getByText("Couldn’t send agent. Try again.")).toBeVisible(); const commands = await readAgentShareCommands(page); expect( commands.filter((entry) => [ "encode_agent_snapshot_for_send", "upload_media_bytes", "send_channel_message", ].includes(entry.command), ), ).toEqual([]); }); test("people sharing rechecks destination eligibility after encoding", async ({ page, }) => { await openSafetyShareDialog(page, { encodeDelayMs: 800 }); await selectCharlieRecipient(page); await page.getByTestId("persona-share-send").click(); await expect .poll(async () => { const commands = await readAgentShareCommands(page); return commands.filter( (entry) => entry.command === "encode_agent_snapshot_for_send", ).length; }) .toBe(1); await page.evaluate(() => { const testWindow = window as Window & { __BUZZ_E2E_MUTATE_CHANNEL__?: (options: { channelId: string; channelType: "forum"; }) => void; __BUZZ_E2E_INVALIDATE_CHANNELS__?: () => Promise; }; testWindow.__BUZZ_E2E_MUTATE_CHANNEL__?.({ channelId: "d1ec7000-d000-4000-8000-000000000001", channelType: "forum", }); return testWindow.__BUZZ_E2E_INVALIDATE_CHANNELS__?.(); }); await expect(page.getByText("Couldn’t send agent. Try again.")).toBeVisible({ timeout: 5_000, }); const commands = await readAgentShareCommands(page); expect( commands.filter( (entry) => entry.command === "encode_agent_snapshot_for_send", ), ).toHaveLength(1); expect( commands.filter((entry) => entry.command === "upload_media_bytes"), ).toHaveLength(0); expect( commands.filter((entry) => entry.command === "send_channel_message"), ).toHaveLength(0); }); test("people sharing guards the full action against duplicate sends", async ({ page, }) => { await openSafetyShareDialog(page, { encodeDelayMs: 500 }); await selectCharlieRecipient(page); await page.getByTestId("persona-share-send").evaluate((button) => { if (!(button instanceof HTMLButtonElement)) return; button.click(); button.click(); }); await expect(page.getByText("Sent a copy of Safety Auditor")).toBeVisible({ timeout: 5_000, }); await expect(page.getByText("Couldn’t send agent. Try again.")).toHaveCount( 0, ); const commands = await readAgentShareCommands(page); for (const command of [ "open_dm", "encode_agent_snapshot_for_send", "upload_media_bytes", "send_channel_message", ]) { expect(commands.filter((entry) => entry.command === command)).toHaveLength( 1, ); } }); test("people sharing stays mounted while a send is pending", async ({ page, }) => { await openSafetyShareDialog(page, { encodeDelayMs: 800 }); await selectCharlieRecipient(page); await page.getByTestId("persona-share-send").click(); await page.keyboard.press("Escape"); await expect(page.getByTestId("persona-share-dialog")).toBeVisible(); await expect(page.getByText("Sent a copy of Safety Auditor")).toBeVisible({ timeout: 5_000, }); }); test("export from share aligns selections and animates memory details", async ({ page, }) => { await page.emulateMedia({ reducedMotion: "no-preference" }); await installMockBridge(page, { personas: [ { id: "custom:animation-auditor", displayName: "Animation Auditor", systemPrompt: "You audit animations.", }, ], managedAgents: [ { pubkey: TEST_IDENTITIES.alice.pubkey, name: "Animation Auditor", personaId: "custom:animation-auditor", status: "running", }, ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await page.getByLabel("Open actions for Animation Auditor").click(); await page.getByRole("menuitem", { name: "Share" }).click(); await page.getByTestId("persona-share-export").click(); const exportDialog = page.getByTestId("agent-snapshot-export-dialog"); const memoryTrigger = exportDialog.getByLabel("Memories"); await expect(memoryTrigger).toHaveText("Agent only"); expect((await memoryTrigger.boundingBox())?.width).toBeLessThan(112); await expect(memoryTrigger).toHaveCSS("text-align", "right"); await expect(memoryTrigger.locator("svg.lucide-chevron-down")).toBeVisible(); const initialHeight = await exportDialog.evaluate( (element) => element.getBoundingClientRect().height, ); await memoryTrigger.click(); await page .getByRole("menuitemradio", { name: "Agent + core memory" }) .click(); const heightSamples = await exportDialog.evaluate(async (element) => { const samples: number[] = []; const start = performance.now(); await new Promise((resolve) => { const sample = (now: number) => { samples.push(element.getBoundingClientRect().height); if (now - start >= 280) { resolve(); return; } requestAnimationFrame(sample); }; requestAnimationFrame(sample); }); return samples; }); await expect( exportDialog.getByTestId("agent-snapshot-memory-warning"), ).toBeVisible(); expect(heightSamples.at(-1)).toBeGreaterThan(initialHeight); expect( new Set(heightSamples.map((height) => Math.round(height))).size, ).toBeGreaterThan(2); }); test("team-managed personas do not expose editable actions", async ({ page, }) => { await installMockBridge(page, { personas: [ { id: "team:analyst", displayName: "Team Analyst", sourceTeam: "team-research-002", systemPrompt: "You are Team Analyst.", }, ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await page.getByLabel("Open actions for Team Analyst").click(); await expect(page.getByRole("menuitem")).toHaveText([ "Duplicate", "Share", "Managed by team", ]); await expect(page.getByRole("menuitem", { name: "Edit" })).toHaveCount(0); await expect(page.getByRole("menuitem", { name: "Delete" })).toHaveCount(0); }); test("inactive built-ins cannot be used to create teams", async ({ page }) => { await gotoApp(page); const error = await invokeTauriExpectError(page, "create_team", { input: { name: "Honeys", personaIds: ["builtin:honey"], }, }); expect(error).toBe("Honey is not in My Agents."); }); test("built-in removal failures show up from My Agents", async ({ page }) => { await installMockBridge(page, { activePersonaIds: ["builtin:honey"], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await invokeTauri(page, "create_team", { input: { name: "Honeys", personaIds: ["builtin:honey"], }, }); await page.getByLabel("Open actions for Honey").click(); await page.getByRole("menuitem", { name: "Delete" }).click(); await expect( page .locator("[data-sonner-toast]") .filter({ hasText: "Honey is still referenced by a team." }), ).toBeVisible(); }); test("personas referenced by teams cannot be deleted", async ({ page }) => { await gotoApp(page); const persona = await invokeTauri<{ id: string }>(page, "create_persona", { input: { displayName: "Analyst", systemPrompt: "You are Analyst.", }, }); await invokeTauri(page, "create_team", { input: { name: "Analysts", personaIds: [persona.id], }, }); const error = await invokeTauriExpectError(page, "delete_persona", { id: persona.id, }); expect(error).toBe( "Analyst is still referenced by a team. Remove it from those teams first.", ); }); test("start pill morphs into the running dot without remounting the avatar", async ({ page, }) => { const personaId = "custom:motion-auditor"; const pubkey = "ab".repeat(32); const activeDotSize = 18; await page.emulateMedia({ reducedMotion: "no-preference" }); await installMockBridge(page, { personas: [ { avatarUrl: emojiAvatarDataUrl("✨", "#7657FF"), displayName: "Motion Auditor", id: personaId, systemPrompt: "You audit motion continuity.", }, ], managedAgents: [ { name: "Motion Auditor", personaId, pubkey, status: "stopped", }, ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); const card = page.getByTestId(`persona-agent-row-${personaId}`); const startButton = page.getByTestId(`agent-runtime-start-${pubkey}`); const badge = startButton.locator("xpath=../.."); const initialAvatar = await card .getByAltText("Motion Auditor avatar") .elementHandle(); expect(initialAvatar).not.toBeNull(); const samplesPromise = badge.evaluate(async (element) => { const samples: Array<{ backgroundColor: string; height: number; width: number; }> = []; const startedAt = performance.now(); while (performance.now() - startedAt < 440) { const bounds = element.getBoundingClientRect(); samples.push({ backgroundColor: getComputedStyle(element).backgroundColor, height: bounds.height, width: bounds.width, }); await new Promise((resolve) => requestAnimationFrame(() => resolve()), ); } return samples; }); await page.waitForTimeout(32); await startButton.click(); await expect( page.getByTestId(`agent-runtime-active-${pubkey}`), ).toBeVisible(); const samples = await samplesPromise; const finalAvatar = await card .getByAltText("Motion Auditor avatar") .elementHandle(); expect(samples[0]?.width).toBeCloseTo(56, 0); expect(samples[0]?.height).toBeCloseTo(36, 0); expect( samples.some( (sample) => sample.width > activeDotSize && sample.width < 56 && sample.height > activeDotSize && sample.height < 36, ), ).toBe(true); expect(samples.at(-1)?.width).toBeCloseTo(activeDotSize, 0); expect(samples.at(-1)?.height).toBeCloseTo(activeDotSize, 0); expect(samples.at(-1)?.backgroundColor).not.toBe(samples[0]?.backgroundColor); await expect( page.getByTestId(`agent-runtime-active-${pubkey}`).locator("xpath=../.."), ).toHaveClass(/bg-emerald-500/); expect( await initialAvatar?.evaluate( (before, after) => before === after, finalAvatar, ), ).toBe(true); }); test("duplicate instances move from the agents gallery into the agent profile", async ({ page, }) => { const personaId = "custom:duplicate-auditor"; const primaryPubkey = TEST_IDENTITIES.alice.pubkey; const additionalPubkey = TEST_IDENTITIES.charlie.pubkey; await installMockBridge(page, { personas: [ { id: personaId, displayName: "Duplicate Auditor", systemPrompt: "You audit duplicate instances.", }, ], managedAgents: [ { pubkey: primaryPubkey, name: "Duplicate Auditor", personaId, status: "running", }, { pubkey: additionalPubkey, name: "Duplicate Auditor", personaId, status: "stopped", }, ], }); await gotoApp(page); await page.getByTestId("open-agents-view").click(); await expect(page.getByText("Additional running agents")).toHaveCount(0); await expect( page.getByTestId(`managed-agent-${additionalPubkey}`), ).toHaveCount(0); await page.getByTestId(`persona-agent-row-${personaId}`).click(); await page.getByTestId("user-profile-instances").click(); await page.getByTestId(`user-profile-instance-${additionalPubkey}`).click(); await expect(page.getByTestId("user-profile-panel")).toBeVisible(); await page.getByTestId("user-profile-settings-menu-trigger").click(); await expect( page.getByTestId(`user-profile-agent-delete-${additionalPubkey}`), ).toBeVisible(); });