mirror of
https://github.com/block/buzz.git
synced 2026-11-14 05:12:27 +00:00
fix(desktop): trim duplicate MCP server rows
Render only server rows and retain the buzz-agent empty-state message. Keep the screenshot fixture aligned with parser output so it cannot recreate duplicate rows. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
parent
42959fa8d1
commit
0ea6188a6d
@@ -369,7 +369,6 @@ export function AgentConfigPanel({
|
||||
const { normalized, advanced, extensions, runtimeId, sources, isPreSpawn } =
|
||||
data;
|
||||
const configFilePath = sources.configFilePath;
|
||||
const mcpConfigFilePath = sources.mcpConfigFilePath;
|
||||
|
||||
const normalizedEntries = (
|
||||
Object.entries(normalized) as [
|
||||
@@ -420,7 +419,6 @@ export function AgentConfigPanel({
|
||||
</div>
|
||||
|
||||
<McpServersSection
|
||||
configFilePath={mcpConfigFilePath}
|
||||
extensions={extensions}
|
||||
runtimeId={runtimeId}
|
||||
variant={advancedMode === "flat" ? "profile" : "compact"}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import * as React from "react";
|
||||
import { CheckCircle2, CircleSlash, FolderOpen, Plug } from "lucide-react";
|
||||
import { revealItemInDir } from "@tauri-apps/plugin-opener";
|
||||
import type * as React from "react";
|
||||
import { CheckCircle2, CircleSlash } from "lucide-react";
|
||||
import type { ExtensionEntry } from "@/shared/api/types";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
|
||||
type McpServersSectionProps = {
|
||||
configFilePath: string | null;
|
||||
extensions: ExtensionEntry[];
|
||||
runtimeId: string | null;
|
||||
variant?: "compact" | "profile";
|
||||
@@ -14,23 +12,13 @@ type McpServersSectionProps = {
|
||||
|
||||
export function McpServersSection({
|
||||
buzzAgentSlot,
|
||||
configFilePath,
|
||||
extensions,
|
||||
runtimeId,
|
||||
variant = "compact",
|
||||
}: McpServersSectionProps) {
|
||||
const isBuzzAgent = runtimeId === "buzz-agent";
|
||||
const shouldRender =
|
||||
isBuzzAgent || extensions.length > 0 || configFilePath !== null;
|
||||
|
||||
const revealConfigFile = React.useCallback(() => {
|
||||
if (!configFilePath) {
|
||||
return;
|
||||
}
|
||||
void revealItemInDir(configFilePath);
|
||||
}, [configFilePath]);
|
||||
|
||||
if (!shouldRender) {
|
||||
if (!isBuzzAgent && extensions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -41,58 +29,10 @@ export function McpServersSection({
|
||||
variant === "compact" ? "mt-3 pt-2" : "divide-y divide-border/50",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex min-w-0 items-start gap-3",
|
||||
variant === "compact" ? "py-2" : "px-4 py-3",
|
||||
)}
|
||||
>
|
||||
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted/60">
|
||||
<Plug className="h-4 w-4 text-muted-foreground" />
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex min-w-0 items-center justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-xs font-medium text-foreground">
|
||||
MCP Servers
|
||||
</div>
|
||||
<div className="mt-0.5 truncate text-sm text-muted-foreground">
|
||||
{extensions.length > 0
|
||||
? `${extensions.length} configured`
|
||||
: isBuzzAgent
|
||||
? "No custom servers configured"
|
||||
: "No servers found"}
|
||||
</div>
|
||||
</div>
|
||||
{configFilePath ? (
|
||||
<button
|
||||
aria-label="Reveal MCP config file"
|
||||
className="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onClick={revealConfigFile}
|
||||
title="Reveal MCP config file"
|
||||
type="button"
|
||||
>
|
||||
<FolderOpen className="h-4 w-4" />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
{configFilePath ? (
|
||||
<div className="mt-1 truncate text-2xs text-muted-foreground/70">
|
||||
Edit in {configFilePath}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isBuzzAgent && buzzAgentSlot ? buzzAgentSlot : null}
|
||||
|
||||
{extensions.length > 0 ? (
|
||||
<div
|
||||
className={cn(
|
||||
"divide-y divide-border/50",
|
||||
variant === "compact" ? "mt-1" : "",
|
||||
)}
|
||||
>
|
||||
<div className="divide-y divide-border/50">
|
||||
{extensions.map((extension) => (
|
||||
<McpServerRow
|
||||
extension={extension}
|
||||
@@ -101,7 +41,16 @@ export function McpServersSection({
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
) : (
|
||||
<p
|
||||
className={cn(
|
||||
"text-sm text-muted-foreground",
|
||||
variant === "compact" ? "py-2" : "px-4 py-3",
|
||||
)}
|
||||
>
|
||||
No custom servers configured
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1318,37 +1318,12 @@ function buildMockConfigSurface(pubkey: string): {
|
||||
},
|
||||
advanced: [
|
||||
{
|
||||
key: "extensions.developer",
|
||||
label: "Extension: developer",
|
||||
value: "enabled",
|
||||
key: "active_provider",
|
||||
label: "active_provider",
|
||||
value: "openai",
|
||||
origin: "configFile",
|
||||
schemaType: { type: "enum", options: ["enabled", "disabled"] },
|
||||
writeVia: {
|
||||
type: "gooseNativeConfigWrite",
|
||||
configKey: "goose.extensions.developer",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "extensions.web_search",
|
||||
label: "Extension: web_search",
|
||||
value: "enabled",
|
||||
origin: "configFile",
|
||||
schemaType: { type: "enum", options: ["enabled", "disabled"] },
|
||||
writeVia: {
|
||||
type: "gooseNativeConfigWrite",
|
||||
configKey: "goose.extensions.web_search",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "extensions.memory",
|
||||
label: "Extension: memory",
|
||||
value: "disabled",
|
||||
origin: "configFile",
|
||||
schemaType: { type: "enum", options: ["enabled", "disabled"] },
|
||||
writeVia: {
|
||||
type: "gooseNativeConfigWrite",
|
||||
configKey: "goose.extensions.memory",
|
||||
},
|
||||
schemaType: { type: "string" },
|
||||
writeVia: { type: "readOnly" },
|
||||
},
|
||||
],
|
||||
extensions: [
|
||||
@@ -1685,10 +1660,25 @@ function buildMockConfigSurface(pubkey: string): {
|
||||
},
|
||||
};
|
||||
|
||||
// Map well-known test pubkeys to specific fixtures
|
||||
// Synthetic agent for the multi-origin provenance showcase (not a TEST_IDENTITY).
|
||||
const buzzAgentSurface = {
|
||||
...gooseSurface,
|
||||
runtimeId: "buzz-agent",
|
||||
runtimeLabel: "Buzz Agent",
|
||||
advanced: [],
|
||||
extensions: [],
|
||||
sources: {
|
||||
...gooseSurface.sources,
|
||||
configFilePath: null,
|
||||
mcpConfigFilePath: null,
|
||||
},
|
||||
};
|
||||
|
||||
// Map well-known test pubkeys to specific fixtures.
|
||||
// Synthetic agents are intentionally not TEST_IDENTITIES.
|
||||
const PUBKEY_MULTI_ORIGIN =
|
||||
"abc1230000000000000000000000000000000000000000000000000000000def";
|
||||
const PUBKEY_BUZZ_AGENT =
|
||||
"b0220000000000000000000000000000000000000000000000000000000000a9";
|
||||
|
||||
switch (pubkey) {
|
||||
case ALICE_PUBKEY:
|
||||
@@ -1701,6 +1691,8 @@ function buildMockConfigSurface(pubkey: string): {
|
||||
return runtimeOverrideSurface;
|
||||
case PUBKEY_MULTI_ORIGIN:
|
||||
return multiOriginSurface;
|
||||
case PUBKEY_BUZZ_AGENT:
|
||||
return buzzAgentSurface;
|
||||
default:
|
||||
return gooseSurface;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ const RUNTIME_OVERRIDE_PUBKEY = TEST_IDENTITIES.outsider.pubkey;
|
||||
// (matches PUBKEY_MULTI_ORIGIN in e2eBridge buildMockConfigSurface).
|
||||
const MULTI_ORIGIN_PUBKEY =
|
||||
"abc1230000000000000000000000000000000000000000000000000000000def";
|
||||
const BUZZ_AGENT_PUBKEY =
|
||||
"b0220000000000000000000000000000000000000000000000000000000000a9";
|
||||
|
||||
const MANAGED_AGENTS = [
|
||||
{
|
||||
@@ -244,13 +246,40 @@ test.describe("config bridge screenshots", () => {
|
||||
const panel = await openAgentProfileFromChannel(page, "Goose Agent");
|
||||
|
||||
// Advanced runtime fields render directly in the profile panel's flat list.
|
||||
await expect(panel.getByText("Extension: developer")).toBeVisible();
|
||||
await expect(panel.getByText("active_provider")).toBeVisible();
|
||||
// MCP servers render once each, without the removed summary row.
|
||||
await expect(panel.getByText("developer", { exact: true })).toHaveCount(1);
|
||||
await expect(panel.getByText("MCP Servers", { exact: true })).toHaveCount(
|
||||
0,
|
||||
);
|
||||
await settleAnimations(panel);
|
||||
|
||||
await panel.screenshot({ path: `${SHOTS}/05-advanced-expanded.png` });
|
||||
});
|
||||
|
||||
test("06 — profile side panel — Configuration section", async ({ page }) => {
|
||||
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");
|
||||
|
||||
await expect(
|
||||
panel.getByText("No custom servers configured", { exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(panel.getByText("MCP Servers", { exact: true })).toHaveCount(
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user