diff --git a/src/app/(dashboard)/dashboard/tools/agent-bridge/AgentBridgePageClient.tsx b/src/app/(dashboard)/dashboard/tools/agent-bridge/AgentBridgePageClient.tsx index 91184a9b8e..925f761952 100644 --- a/src/app/(dashboard)/dashboard/tools/agent-bridge/AgentBridgePageClient.tsx +++ b/src/app/(dashboard)/dashboard/tools/agent-bridge/AgentBridgePageClient.tsx @@ -8,7 +8,7 @@ import { AgentBridgeServerCard } from "./components/AgentBridgeServerCard"; import { AgentList } from "./components/AgentList"; import { EmptyStateNoProviders } from "./components/EmptyStateNoProviders"; import { useAgentBridgeState } from "./hooks/useAgentBridgeState"; -import type { MitmTarget } from "@/mitm/types"; +import type { MitmTargetView } from "@/mitm/types"; import type { MappingRow } from "./components/ModelMappingTable"; // ── Types ──────────────────────────────────────────────────────────────────── @@ -43,7 +43,7 @@ export interface AgentBridgePageData { interface AgentBridgePageClientProps { initialData: AgentBridgePageData; - targets: MitmTarget[]; + targets: MitmTargetView[]; hasProviders: boolean; } diff --git a/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentCard.tsx b/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentCard.tsx index b3be7f29bf..0d1bf052aa 100644 --- a/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentCard.tsx +++ b/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentCard.tsx @@ -7,7 +7,7 @@ import { DnsStatusBadge } from "./shared/DnsStatusBadge"; import { ModelMappingTable } from "./ModelMappingTable"; import { SetupWizard } from "./SetupWizard"; import { RiskNoticeModal } from "@/shared/components/RiskNoticeModal"; -import type { MitmTarget } from "@/mitm/types"; +import type { MitmTargetView } from "@/mitm/types"; import type { AgentStateEntry } from "../AgentBridgePageClient"; import type { MappingRow } from "./ModelMappingTable"; @@ -23,7 +23,7 @@ function hasAcceptedRisk(agentId: string): boolean { interface AgentCardProps { - target: MitmTarget; + target: MitmTargetView; agentState: AgentStateEntry | undefined; serverRunning: boolean; mappings: MappingRow[]; diff --git a/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentList.tsx b/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentList.tsx index d2f97e9bf0..d2fca8c359 100644 --- a/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentList.tsx +++ b/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentList.tsx @@ -3,12 +3,12 @@ import { useState } from "react"; import { useTranslations } from "next-intl"; import { AgentCard } from "./AgentCard"; -import type { MitmTarget } from "@/mitm/types"; +import type { MitmTargetView } from "@/mitm/types"; import type { AgentStateEntry, AgentMappingsMap } from "../AgentBridgePageClient"; import type { MappingRow } from "./ModelMappingTable"; interface AgentListProps { - targets: MitmTarget[]; + targets: MitmTargetView[]; agentStates: AgentStateEntry[]; serverRunning: boolean; mappingsMap: AgentMappingsMap; diff --git a/src/app/(dashboard)/dashboard/tools/agent-bridge/components/SetupWizard.tsx b/src/app/(dashboard)/dashboard/tools/agent-bridge/components/SetupWizard.tsx index dac0f8a7ad..59111ec325 100644 --- a/src/app/(dashboard)/dashboard/tools/agent-bridge/components/SetupWizard.tsx +++ b/src/app/(dashboard)/dashboard/tools/agent-bridge/components/SetupWizard.tsx @@ -3,10 +3,10 @@ import { useEffect, useState } from "react"; import { useTranslations } from "next-intl"; import type { AgentStateEntry } from "../AgentBridgePageClient"; -import type { MitmTarget } from "@/mitm/types"; +import type { MitmTargetView } from "@/mitm/types"; interface SetupWizardProps { - target: MitmTarget; + target: MitmTargetView; agentState: AgentStateEntry | undefined; serverRunning: boolean; onClose: () => void; diff --git a/src/app/(dashboard)/dashboard/tools/agent-bridge/page.tsx b/src/app/(dashboard)/dashboard/tools/agent-bridge/page.tsx index 1280339fdc..f72049d483 100644 --- a/src/app/(dashboard)/dashboard/tools/agent-bridge/page.tsx +++ b/src/app/(dashboard)/dashboard/tools/agent-bridge/page.tsx @@ -54,7 +54,7 @@ export default async function AgentBridgePage() { return ( rest)} hasProviders={hasProviders} /> ); diff --git a/src/mitm/types.ts b/src/mitm/types.ts index 2ed90b65dc..a153cd6d93 100644 --- a/src/mitm/types.ts +++ b/src/mitm/types.ts @@ -38,6 +38,13 @@ export interface MitmTarget { viability?: "investigating" | "supported" | "deprecated"; // Trae = "investigating" } +/** + * Serializable view of a MitmTarget for Server→Client Component props. + * Omits `handler` (a function): Next.js forbids passing functions across the + * Server/Client boundary, and the UI never invokes it. See agent-bridge/page.tsx. + */ +export type MitmTargetView = Omit; + export const MitmTargetSchema = z.object({ id: z.enum([ "antigravity", "kiro", "copilot", "codex", "cursor", "zed", diff --git a/tests/unit/agent-bridge-targets-serializable.test.ts b/tests/unit/agent-bridge-targets-serializable.test.ts new file mode 100644 index 0000000000..4afb647610 --- /dev/null +++ b/tests/unit/agent-bridge-targets-serializable.test.ts @@ -0,0 +1,33 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { ALL_TARGETS } from "../../src/mitm/targets/index.ts"; + +// Regression guard for the agent-bridge "erro ao carregar" bug. +// +// `agent-bridge/page.tsx` is a Server Component that passes `targets` to the +// `AgentBridgePageClient` Client Component. Each MitmTarget carries a +// `handler: () => Promise<...>` function. Next.js forbids passing functions +// across the Server/Client boundary, raising at runtime: +// "Functions cannot be passed directly to Client Components ..." +// which broke SSR for the whole page. The fix sanitizes the array via +// ALL_TARGETS.map(({ handler, ...rest }) => rest) +// (a MitmTargetView). These tests pin both halves of that contract. + +test("agent-bridge: sanitized targets (no handler) are fully serializable for Client Components", () => { + const views = ALL_TARGETS.map(({ handler, ...rest }) => rest); + assert.ok(views.length > 0, "expected at least one MITM target"); + for (const v of views) { + const id = (v as { id?: string }).id ?? ""; + assert.equal("handler" in v, false, `${id}: handler must be stripped before crossing to a Client Component`); + for (const [key, value] of Object.entries(v)) { + assert.notEqual(typeof value, "function", `${id}.${key} must not be a function (non-serializable)`); + } + assert.doesNotThrow(() => JSON.parse(JSON.stringify(v)), `${id} must be JSON-serializable`); + } +}); + +test("agent-bridge: raw ALL_TARGETS still carry a handler function (so the sanitization is required)", () => { + for (const t of ALL_TARGETS) { + assert.equal(typeof t.handler, "function", `${t.id} should expose a lazy handler function`); + } +});