diff --git a/src/app/(dashboard)/dashboard/agents/page.tsx b/src/app/(dashboard)/dashboard/acp-agents/page.tsx
similarity index 61%
rename from src/app/(dashboard)/dashboard/agents/page.tsx
rename to src/app/(dashboard)/dashboard/acp-agents/page.tsx
index ac1c417b15..79d4d90f34 100644
--- a/src/app/(dashboard)/dashboard/agents/page.tsx
+++ b/src/app/(dashboard)/dashboard/acp-agents/page.tsx
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback } from "react";
import Link from "next/link";
import { Card, Button, Input } from "@/shared/components";
import ProviderIcon from "@/shared/components/ProviderIcon";
+import { CliConceptCard, CliComparisonCard } from "@/shared/components/cli";
import { useTranslations } from "next-intl";
interface AgentInfo {
@@ -65,7 +66,7 @@ export default function AgentsPage() {
versionCommand: "",
spawnArgs: "",
});
- const t = useTranslations("agents");
+ const t = useTranslations("acpAgents");
const fetchAgents = useCallback(async () => {
try {
@@ -160,174 +161,8 @@ export default function AgentsPage() {
-
-
-
-
-
{t("architectureTitle")}
-
{t("architectureDescription")}
-
-
-
open_in_new
- {t("cliToolsRedirectCta")}
-
-
-
-
- {t("flowOmniRoute")}
-
-
- arrow_forward
-
-
- {t("flowSpawn")}
-
-
- arrow_forward
-
-
- {t("flowLocalBinary")}
-
-
- arrow_forward
-
-
- {t("flowExecute")}
-
-
-
-
-
-
-
- devices
-
-
-
{t("flowDiagramClient")}
-
{t("flowDiagramClientDesc")}
-
-
-
- arrow_forward
-
-
-
-
- hub
-
-
{t("flowDiagramOmniRoute")}
-
- {t("flowDiagramOmniRouteDesc")}
-
-
-
-
- arrow_forward
-
-
-
-
-
- launch
-
-
-
- {t("flowDiagramSpawn")}
-
-
{t("flowDiagramSpawnDesc")}
-
-
-
- arrow_forward
-
-
-
-
-
- terminal
-
-
-
- {t("flowDiagramCli")}
-
-
{t("flowDiagramCliDesc")}
-
-
-
-
- {t("cliToolsRedirectTitle")}{" "}
- {t("cliToolsRedirectDesc")}{" "}
-
- {t("openCliTools")}
-
- .
-
-
-
-
-
-
-
-
-
- compare_arrows
-
-
-
{t("comparisonTitle")}
-
-
-
-
-
-
- arrow_forward
-
-
- {t("comparisonCliToolsLabel")}
-
-
-
- {t("comparisonCliToolsTitle")}
-
-
{t("comparisonCliToolsDesc")}
-
- IDE
- arrow_forward
- OmniRoute
- arrow_forward
- Provider API
-
-
-
-
-
-
- arrow_back
-
-
- {t("comparisonAgentsLabel")}
-
-
-
- {t("comparisonAgentsTitle")}
-
-
{t("comparisonAgentsDesc")}
-
- Client
- arrow_forward
- OmniRoute
- arrow_forward
- CLI Binary
-
-
-
-
-
{t("comparisonSummary")}
-
-
+
+
{/* Summary Cards */}
{summary && (
@@ -363,10 +198,10 @@ export default function AgentsPage() {
{t("setupGuideTitle")}
- {t("openCliTools")}
+ {t("cliCodeRedirectCta")}
diff --git a/src/shared/constants/sidebarVisibility.ts b/src/shared/constants/sidebarVisibility.ts
index 251bd0d1d6..f6cf0eb4dc 100644
--- a/src/shared/constants/sidebarVisibility.ts
+++ b/src/shared/constants/sidebarVisibility.ts
@@ -14,7 +14,7 @@ export const HIDEABLE_SIDEBAR_ITEM_IDS = [
"context-combos",
// OmniProxy > Tools
"cli-tools",
- "agents",
+ "acp-agents",
"cloud-agents",
// OmniProxy > Integrations
"api-endpoints",
@@ -236,10 +236,10 @@ const TOOLS_GROUP: SidebarItemGroup = {
icon: "terminal",
},
{
- id: "agents",
- href: "/dashboard/agents",
- i18nKey: "agents",
- subtitleKey: "agentsSubtitle",
+ id: "acp-agents",
+ href: "/dashboard/acp-agents",
+ i18nKey: "acpAgents",
+ subtitleKey: "acpAgentsSubtitle",
icon: "smart_toy",
},
{
@@ -802,7 +802,7 @@ const DEVELOPER_SHOWN: ReadonlySet
= new Set([
"context-rtk",
"context-combos",
"cli-tools",
- "agents",
+ "acp-agents",
"api-endpoints",
"analytics",
"analytics-combo-health",
diff --git a/tests/unit/ui/AcpAgentsPage.test.tsx b/tests/unit/ui/AcpAgentsPage.test.tsx
new file mode 100644
index 0000000000..c823701f0c
--- /dev/null
+++ b/tests/unit/ui/AcpAgentsPage.test.tsx
@@ -0,0 +1,195 @@
+// @vitest-environment jsdom
+import React from "react";
+import { act } from "react";
+import { createRoot } from "react-dom/client";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+// ── Mocks ─────────────────────────────────────────────────────────────────────
+
+let capturedTranslationsNamespace = "";
+
+vi.mock("next/link", () => ({
+ default: ({
+ href,
+ children,
+ ...props
+ }: React.AnchorHTMLAttributes & { href: string }) => (
+
+ {children}
+
+ ),
+}));
+
+vi.mock("next-intl", () => ({
+ useTranslations: (ns: string) => {
+ capturedTranslationsNamespace = ns;
+ return (key: string) => key;
+ },
+}));
+
+vi.mock("@/shared/components", () => ({
+ Card: ({ children, ...props }: React.HTMLAttributes) => (
+
+ {children}
+
+ ),
+ Button: ({
+ children,
+ onClick,
+ loading,
+ ...props
+ }: React.ButtonHTMLAttributes & { loading?: boolean }) => (
+
+ ),
+ Input: ({
+ label,
+ ...props
+ }: React.InputHTMLAttributes & { label?: string }) => (
+
+ ),
+}));
+
+vi.mock("@/shared/components/ProviderIcon", () => ({
+ default: ({ providerId }: { providerId: string; size?: number; type?: string }) => (
+
+ ),
+}));
+
+vi.mock("@/shared/components/cli", () => ({
+ CliConceptCard: ({ currentType }: { currentType: string }) => (
+
+ ),
+ CliComparisonCard: ({ currentType }: { currentType: string }) => (
+
+ ),
+}));
+
+// ── Fetch mock ────────────────────────────────────────────────────────────────
+
+const mockAgents = [
+ {
+ id: "claude-code",
+ name: "Claude Code",
+ binary: "claude",
+ version: "1.2.3",
+ installed: true,
+ protocol: "stdio",
+ isCustom: false,
+ },
+ {
+ id: "codex",
+ name: "Codex",
+ binary: "codex",
+ version: null,
+ installed: false,
+ protocol: "stdio",
+ isCustom: false,
+ },
+];
+
+const mockSummary = {
+ total: 2,
+ installed: 1,
+ notFound: 1,
+ builtIn: 2,
+ custom: 0,
+};
+
+const mockFetch = vi.fn().mockResolvedValue({
+ ok: true,
+ json: async () => ({ agents: mockAgents, summary: mockSummary }),
+});
+
+(globalThis as typeof globalThis & { fetch: typeof fetch }).fetch = mockFetch;
+
+// ── Import after mocks ────────────────────────────────────────────────────────
+
+const { default: AcpAgentsPage } = await import(
+ "@/app/(dashboard)/dashboard/acp-agents/page"
+);
+
+// ── Helpers ───────────────────────────────────────────────────────────────────
+
+const containers: HTMLElement[] = [];
+
+async function renderPage(): Promise {
+ const container = document.createElement("div");
+ document.body.appendChild(container);
+ containers.push(container);
+
+ const root = createRoot(container);
+ await act(async () => {
+ root.render();
+ });
+ // Allow data-fetching effects to resolve
+ await act(async () => {
+ await new Promise((r) => setTimeout(r, 0));
+ });
+ return container;
+}
+
+// ── Lifecycle ─────────────────────────────────────────────────────────────────
+
+beforeEach(() => {
+ (
+ globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
+ ).IS_REACT_ACT_ENVIRONMENT = true;
+ capturedTranslationsNamespace = "";
+ mockFetch.mockClear();
+});
+
+afterEach(() => {
+ while (containers.length > 0) {
+ containers.pop()?.remove();
+ }
+ document.body.innerHTML = "";
+});
+
+// ── Tests ─────────────────────────────────────────────────────────────────────
+
+describe("AcpAgentsPage", () => {
+ it("smoke: renders without crashing", async () => {
+ const container = await renderPage();
+ expect(container).toBeTruthy();
+ expect(container.innerHTML.length).toBeGreaterThan(0);
+ });
+
+ it("calls useTranslations with 'acpAgents' namespace", async () => {
+ await renderPage();
+ expect(capturedTranslationsNamespace).toBe("acpAgents");
+ });
+
+ it("renders ", async () => {
+ const container = await renderPage();
+ const card = container.querySelector("[data-testid='cli-concept-card']");
+ expect(card).not.toBeNull();
+ expect(card?.getAttribute("data-current-type")).toBe("acp");
+ });
+
+ it("renders ", async () => {
+ const container = await renderPage();
+ const card = container.querySelector("[data-testid='cli-comparison-card']");
+ expect(card).not.toBeNull();
+ expect(card?.getAttribute("data-current-type")).toBe("acp");
+ });
+
+ it("cross-link points to /dashboard/cli-code (not /dashboard/cli-tools)", async () => {
+ const container = await renderPage();
+ const links = container.querySelectorAll("a");
+ const hrefs = Array.from(links).map((a) => a.getAttribute("href"));
+ const cliCodeLinks = hrefs.filter((h) => h === "/dashboard/cli-code");
+ const cliToolsLinks = hrefs.filter((h) => h === "/dashboard/cli-tools");
+ expect(cliCodeLinks.length).toBeGreaterThan(0);
+ expect(cliToolsLinks).toHaveLength(0);
+ });
+
+ it("agent grid renders with mocked /api/acp/agents response", async () => {
+ const container = await renderPage();
+ expect(mockFetch).toHaveBeenCalledWith("/api/acp/agents");
+ // Agent names from mock should appear somewhere in the rendered output
+ expect(container.textContent).toContain("Claude Code");
+ expect(container.textContent).toContain("Codex");
+ });
+});