mirror of
https://github.com/block/buzz.git
synced 2026-10-30 05:51:46 +00:00
## Summary - remove synthetic preview runtime and configuration data so profiles show only real agent content - simplify model settings to the effective values and restore bare section icons - make owned-agent profiles resolve to the same current persona instance from every entry point ## Why Agent profiles opened from DMs or channels could fall back to a partial declared-owner view instead of the full managed-agent profile shown on the Agents page. Test preview content and configuration provenance also remained visible after the redesign. ## User impact Owned agent profiles now expose the same actions, runtime, channels, memories, and configuration regardless of where they are opened. Profiles no longer synthesize preview data, and model settings use the same simple title/value hierarchy as the rest of the panel. ## Validation - `pnpm --dir desktop check` - `pnpm --dir desktop build:e2e` - focused unit tests: 10 passed - profile entry-point integration tests: 2 passed - configuration screenshot suite: 7 passed, with six visually distinct captures Snapshots are attached in a PR comment. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@buzz.block.builderlab.xyz> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Co-authored-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@buzz.block.builderlab.xyz>
374 lines
12 KiB
TypeScript
374 lines
12 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { waitForAnimations } from "../helpers/animations";
|
|
import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
|
|
|
|
const SHOTS = "test-results/config-bridge";
|
|
|
|
// Use well-known test pubkeys that map to distinct config surface fixtures
|
|
const GOOSE_PUBKEY = TEST_IDENTITIES.tyler.pubkey;
|
|
const PRESPAWN_PUBKEY = TEST_IDENTITIES.bob.pubkey;
|
|
const RUNTIME_OVERRIDE_PUBKEY = TEST_IDENTITIES.outsider.pubkey;
|
|
// Synthetic agent whose config surface mixes four distinct provenance origins
|
|
// (matches PUBKEY_MULTI_ORIGIN in e2eBridge buildMockConfigSurface).
|
|
const MULTI_ORIGIN_PUBKEY =
|
|
"abc1230000000000000000000000000000000000000000000000000000000def";
|
|
const BUZZ_AGENT_PUBKEY =
|
|
"b0220000000000000000000000000000000000000000000000000000000000a9";
|
|
|
|
const MANAGED_AGENTS = [
|
|
{
|
|
pubkey: GOOSE_PUBKEY,
|
|
name: "Goose Agent",
|
|
status: "running" as const,
|
|
channelNames: ["agents"],
|
|
},
|
|
{
|
|
pubkey: PRESPAWN_PUBKEY,
|
|
name: "Pre-Spawn Agent",
|
|
status: "stopped" as const,
|
|
channelNames: ["agents"],
|
|
},
|
|
{
|
|
pubkey: RUNTIME_OVERRIDE_PUBKEY,
|
|
name: "Runtime Override Agent",
|
|
status: "running" as const,
|
|
channelNames: ["agents"],
|
|
},
|
|
{
|
|
pubkey: MULTI_ORIGIN_PUBKEY,
|
|
name: "Multi-Origin Agent",
|
|
status: "running" as const,
|
|
channelNames: ["agents"],
|
|
},
|
|
];
|
|
|
|
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 invokeMockCommand(
|
|
page: import("@playwright/test").Page,
|
|
command: string,
|
|
payload?: Record<string, unknown>,
|
|
): Promise<unknown> {
|
|
await waitForInvokeBridge(page);
|
|
return page.evaluate(
|
|
async ({ command: cmd, payload: pl }) => {
|
|
const tauriWindow = window as Window & {
|
|
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: (
|
|
command: string,
|
|
payload?: Record<string, unknown>,
|
|
) => Promise<unknown>;
|
|
__TAURI_INTERNALS__?: {
|
|
invoke?: (
|
|
command: string,
|
|
payload?: Record<string, unknown>,
|
|
) => Promise<unknown>;
|
|
};
|
|
};
|
|
const invoke =
|
|
tauriWindow.__BUZZ_E2E_INVOKE_MOCK_COMMAND__ ??
|
|
tauriWindow.__TAURI_INTERNALS__?.invoke;
|
|
if (!invoke) throw new Error("Mock invoke bridge is unavailable.");
|
|
return invoke(cmd, pl);
|
|
},
|
|
{ command, payload },
|
|
);
|
|
}
|
|
|
|
async function activatePersonas(page: import("@playwright/test").Page) {
|
|
for (const id of ["builtin:fizz"]) {
|
|
await invokeMockCommand(page, "set_persona_active", { id, active: true });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Open the #agents channel and click an agent's avatar in the message list
|
|
* to open the profile side panel, then navigate to the requested tab.
|
|
*/
|
|
async function openAgentProfileFromChannel(
|
|
page: import("@playwright/test").Page,
|
|
agentName: string,
|
|
{
|
|
anchorText = "Model",
|
|
scrollToBottom = true,
|
|
tab = "Info",
|
|
}: {
|
|
anchorText?: string;
|
|
scrollToBottom?: boolean;
|
|
tab?: "Info" | "Runtime";
|
|
} = {},
|
|
) {
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await waitForInvokeBridge(page);
|
|
await activatePersonas(page);
|
|
|
|
// Navigate to the #agents channel
|
|
await page.getByTestId("channel-agents").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("agents");
|
|
|
|
// Find the message row from this agent and click the avatar to open profile
|
|
const messageRow = page.getByTestId("message-row").filter({
|
|
has: page.getByText(agentName, { exact: false }),
|
|
});
|
|
await expect(messageRow.first()).toBeVisible({ timeout: 5_000 });
|
|
await messageRow.first().getByRole("button").first().click();
|
|
|
|
const panel = page.getByTestId("user-profile-panel");
|
|
await expect(panel).toBeVisible({ timeout: 10_000 });
|
|
|
|
await panel.getByRole("tab", { name: tab }).click();
|
|
|
|
const configAnchor = panel.getByText(anchorText, { exact: true }).first();
|
|
await expect(configAnchor).toBeVisible({ timeout: 10_000 });
|
|
// Scroll the panel's internal scroll container to the bottom so the
|
|
// config section content is fully visible.
|
|
await configAnchor.scrollIntoViewIfNeeded();
|
|
if (scrollToBottom) {
|
|
await panel.evaluate((el) => {
|
|
// The scrollable container is the profileBody div with overflow-y-auto.
|
|
// Find it by checking which child actually scrolls.
|
|
const scrollable =
|
|
el.querySelector("[data-radix-scroll-area-viewport]") ??
|
|
Array.from(el.querySelectorAll("*")).find(
|
|
(child) => child.scrollHeight > child.clientHeight + 10,
|
|
) ??
|
|
el;
|
|
scrollable.scrollTop = scrollable.scrollHeight;
|
|
});
|
|
}
|
|
await panel.page().waitForTimeout(200);
|
|
|
|
return panel;
|
|
}
|
|
|
|
// Settle any in-flight animations before capture.
|
|
async function settleAnimations(panel: import("@playwright/test").Locator) {
|
|
await waitForAnimations(panel.page());
|
|
}
|
|
|
|
test.describe("config bridge screenshots", () => {
|
|
test.use({ viewport: { width: 1280, height: 900 } });
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
page.on("pageerror", (err) => {
|
|
console.error(
|
|
"PAGE ERROR:",
|
|
err.message,
|
|
err.stack?.split("\n").slice(0, 5).join("\n"),
|
|
);
|
|
});
|
|
page.on("console", (msg) => {
|
|
if (msg.type() === "error") {
|
|
console.error("CONSOLE ERROR:", msg.text().slice(0, 500));
|
|
}
|
|
});
|
|
});
|
|
|
|
test("01 — folded config panel", async ({ page }) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
|
|
const panel = await openAgentProfileFromChannel(page, "Goose Agent", {
|
|
scrollToBottom: false,
|
|
tab: "Runtime",
|
|
});
|
|
|
|
// The shared config panel shows only the effective value.
|
|
await expect(panel.getByText("gpt-4o", { exact: true })).toBeVisible();
|
|
await expect(panel.getByText("gpt-4o-mini", { exact: true })).toHaveCount(
|
|
0,
|
|
);
|
|
await expect(panel.getByText("Set in Buzz")).toHaveCount(0);
|
|
await settleAnimations(panel);
|
|
|
|
await panel.screenshot({ path: `${SHOTS}/01-folded-config-panel.png` });
|
|
});
|
|
|
|
test("02 — live runtime override", async ({ page }) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
|
|
const panel = await openAgentProfileFromChannel(
|
|
page,
|
|
"Runtime Override Agent",
|
|
{ tab: "Runtime" },
|
|
);
|
|
|
|
// A runtime override shows only the effective live model.
|
|
await expect(
|
|
panel.getByText("claude-opus-4-20250514", { exact: true }),
|
|
).toBeVisible();
|
|
await expect(panel.getByText("gpt-4o", { exact: true })).toHaveCount(0);
|
|
await expect(
|
|
panel.getByText("Live override (this session only)"),
|
|
).toHaveCount(0);
|
|
await settleAnimations(panel);
|
|
|
|
await panel.screenshot({
|
|
path: `${SHOTS}/02-live-runtime-override.png`,
|
|
});
|
|
});
|
|
|
|
test("03 — effective values without provenance", async ({ page }) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
|
|
const panel = await openAgentProfileFromChannel(
|
|
page,
|
|
"Multi-Origin Agent",
|
|
{
|
|
tab: "Runtime",
|
|
},
|
|
);
|
|
|
|
// Values from different sources use the same simple two-line hierarchy.
|
|
await expect(panel.getByText("gpt-4o", { exact: true })).toBeVisible();
|
|
await expect(panel.getByText("openai", { exact: true })).toBeVisible();
|
|
await expect(panel.getByText("auto", { exact: true })).toBeVisible();
|
|
await expect(panel.getByText(/From config file/)).toHaveCount(0);
|
|
await expect(panel.getByText(/Inherited from/)).toHaveCount(0);
|
|
await expect(panel.getByText(/From environment variable/)).toHaveCount(0);
|
|
await settleAnimations(panel);
|
|
|
|
await panel.screenshot({
|
|
path: `${SHOTS}/03-effective-values.png`,
|
|
});
|
|
});
|
|
|
|
test("04 — pre-spawn state", async ({ page }) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
|
|
const panel = await openAgentProfileFromChannel(page, "Pre-Spawn Agent", {
|
|
tab: "Runtime",
|
|
});
|
|
|
|
// Unknown pre-start values stay empty rather than adding an explanatory row.
|
|
await expect(panel.getByText("Available after agent starts")).toHaveCount(
|
|
0,
|
|
);
|
|
await expect(panel.getByText("—", { exact: true })).toHaveCount(2);
|
|
await settleAnimations(panel);
|
|
|
|
await panel.screenshot({ path: `${SHOTS}/04-pre-spawn-state.png` });
|
|
});
|
|
|
|
test("05 — advanced flat list", async ({ page }) => {
|
|
await installMockBridge(page, { managedAgents: MANAGED_AGENTS });
|
|
|
|
const panel = await openAgentProfileFromChannel(page, "Goose Agent", {
|
|
anchorText: "MCP servers",
|
|
tab: "Runtime",
|
|
});
|
|
|
|
// Advanced runtime fields render directly in the profile panel's flat list,
|
|
// grouped under their own "Advanced" header.
|
|
await expect(panel.getByText("active_provider")).toBeVisible();
|
|
await expect(panel.getByText("Advanced", { exact: true })).toHaveCount(1);
|
|
// MCP servers render once each under their group label.
|
|
await expect(panel.getByText("developer", { exact: true })).toHaveCount(1);
|
|
await expect(panel.getByText("MCP servers", { exact: true })).toHaveCount(
|
|
1,
|
|
);
|
|
await settleAnimations(panel);
|
|
|
|
await panel.screenshot({ path: `${SHOTS}/05-advanced-expanded.png` });
|
|
});
|
|
|
|
test("06 — buzz-agent empty MCP servers", async ({ page }) => {
|
|
await installMockBridge(page, {
|
|
managedAgents: [
|
|
{
|
|
pubkey: BUZZ_AGENT_PUBKEY,
|
|
name: "Buzz Agent",
|
|
status: "running" as const,
|
|
channelNames: ["agents"],
|
|
},
|
|
],
|
|
});
|
|
|
|
const panel = await openAgentProfileFromChannel(page, "Buzz Agent", {
|
|
anchorText: "MCP servers",
|
|
tab: "Runtime",
|
|
});
|
|
|
|
await expect(
|
|
panel.getByText("No custom servers configured.", { exact: true }),
|
|
).toBeVisible();
|
|
await expect(panel.getByText("MCP servers", { exact: true })).toHaveCount(
|
|
1,
|
|
);
|
|
});
|
|
|
|
test("07 — profile side panel — Configuration section", async ({ page }) => {
|
|
// charlie (554cef…) is the well-known test pubkey that the mock bridge
|
|
// seeds as a bot owned by the test viewer, so isBot + isOwner + managedAgent
|
|
// are all true — the Configuration section renders in the profile panel.
|
|
await installMockBridge(page, {
|
|
managedAgents: [
|
|
{
|
|
pubkey:
|
|
"554cef57437abac34522ac2c9f0490d685b72c80478cf9f7ed6f9570ee8624ea",
|
|
name: "Charlie",
|
|
status: "running" as const,
|
|
channelNames: ["agents"],
|
|
},
|
|
],
|
|
});
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await waitForInvokeBridge(page);
|
|
await page.getByTestId("channel-agents").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("agents");
|
|
|
|
// Click an agent avatar in a full message row to open the profile side panel.
|
|
await page
|
|
.getByTestId("message-row")
|
|
.filter({ has: page.locator('[data-testid^="message-avatar-"]') })
|
|
.last()
|
|
.getByRole("button")
|
|
.first()
|
|
.click();
|
|
|
|
const panel = page.getByTestId("user-profile-panel");
|
|
await expect(panel).toBeVisible({ timeout: 10_000 });
|
|
|
|
// Editable configuration lives inside the Runtime tab.
|
|
await panel.getByRole("tab", { name: "Runtime" }).click();
|
|
|
|
// Wait for the config section to render and scroll it into view so
|
|
// it is fully visible before capture.
|
|
const configAnchor = panel.getByText("Model").first();
|
|
await expect(configAnchor).toBeVisible({ timeout: 10_000 });
|
|
await configAnchor.scrollIntoViewIfNeeded();
|
|
// Scroll the panel's internal scroll container to the bottom so config
|
|
// fields are fully visible, not just the heading at the edge.
|
|
await panel.evaluate((el) => {
|
|
const scrollable =
|
|
el.querySelector("[data-radix-scroll-area-viewport]") ??
|
|
Array.from(el.querySelectorAll("*")).find(
|
|
(child) => child.scrollHeight > child.clientHeight + 10,
|
|
) ??
|
|
el;
|
|
scrollable.scrollTop = scrollable.scrollHeight;
|
|
});
|
|
await panel.page().waitForTimeout(200);
|
|
|
|
await settleAnimations(panel);
|
|
|
|
await panel.screenshot({
|
|
path: `${SHOTS}/06-profile-side-panel-config.png`,
|
|
});
|
|
});
|
|
});
|