diff --git a/packages/app/e2e/browser/provider-catalog-icons.spec.ts b/packages/app/e2e/browser/provider-catalog-icons.spec.ts
new file mode 100644
index 0000000000..818d88ab84
--- /dev/null
+++ b/packages/app/e2e/browser/provider-catalog-icons.spec.ts
@@ -0,0 +1,51 @@
+import { ACP_PROVIDER_ICON_SVGS } from "../../src/assets/acp-provider-icons";
+import { expect, test, type Page } from "../support/fixtures";
+import type { TestInfo } from "@playwright/test";
+import { gotoAppShell, openSettings } from "../support/helpers/app";
+import { getServerId } from "../support/helpers/server-id";
+import {
+ findAcpCatalogProvider,
+ openAddProviderArea,
+ openSettingsHost,
+ openSettingsHostSection,
+} from "../support/helpers/settings";
+
+async function openProviderCatalog(page: Page) {
+ await gotoAppShell(page);
+ await openSettings(page);
+ await openSettingsHost(page, getServerId());
+ await openSettingsHostSection(page, getServerId(), "providers");
+ await openAddProviderArea(page);
+}
+
+async function expectProviderArtwork(
+ page: Page,
+ testInfo: TestInfo,
+ id: "devin" | "hermes" | "kiro",
+ name: string,
+) {
+ await findAcpCatalogProvider(page, name);
+ // Catalog tiles have no semantic role; scope artwork to the named provider's tile.
+ const icon = page.getByTestId(`provider-catalog-tile-${id}`).locator("svg").first();
+ await expect(icon).toBeVisible();
+ // Provider artwork is 24px; the generic PackagePlus fallback is 20px.
+ await expect.soft(icon).toHaveAttribute("width", "24");
+ const expectedPaths = await page.evaluate((xml) => {
+ const document = new DOMParser().parseFromString(xml, "image/svg+xml");
+ return Array.from(document.querySelectorAll("path"), (path) => path.getAttribute("d")!);
+ }, ACP_PROVIDER_ICON_SVGS[id]);
+ await expect(icon.locator("path")).toHaveCount(expectedPaths.length);
+ for (const [index, path] of expectedPaths.entries()) {
+ await expect(icon.locator("path").nth(index)).toHaveAttribute("d", path);
+ }
+ await page.screenshot({ path: testInfo.outputPath(`${id}-catalog.png`) });
+}
+
+test("#7087: the provider catalog shows Devin, Hermes, and Kiro artwork", async ({
+ page,
+}, testInfo) => {
+ await openProviderCatalog(page);
+ await expectProviderArtwork(page, testInfo, "devin", "Devin CLI");
+ await expectProviderArtwork(page, testInfo, "hermes", "Hermes");
+ await expectProviderArtwork(page, testInfo, "kiro", "Kiro CLI");
+});
diff --git a/packages/app/src/assets/acp-provider-icons.ts b/packages/app/src/assets/acp-provider-icons.ts
index cebb4ca76d..703473e6be 100644
--- a/packages/app/src/assets/acp-provider-icons.ts
+++ b/packages/app/src/assets/acp-provider-icons.ts
@@ -53,9 +53,10 @@ export const ACP_PROVIDER_ICON_SVGS = {
junie:
'\n',
kilo: '\n',
+ kiro: '',
kimi: '\n',
"minimax-code":
- '\n',
+ '\n',
"minion-code":
'\n',
"mistral-vibe":
@@ -74,7 +75,11 @@ export const ACP_PROVIDER_ICON_SVGS = {
stakpak:
'\n',
traecli:
- '\n',
+ '\n',
vtcode:
'\n',
+ devin:
+ '\n',
+ hermes:
+ '\n',
} as const;
diff --git a/packages/app/src/assets/acp-provider-icons/devin.svg b/packages/app/src/assets/acp-provider-icons/devin.svg
new file mode 100644
index 0000000000..e0f58c6513
--- /dev/null
+++ b/packages/app/src/assets/acp-provider-icons/devin.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/app/src/assets/acp-provider-icons/grok.svg b/packages/app/src/assets/acp-provider-icons/grok.svg
new file mode 100644
index 0000000000..35db840319
--- /dev/null
+++ b/packages/app/src/assets/acp-provider-icons/grok.svg
@@ -0,0 +1,4 @@
+
diff --git a/packages/app/src/assets/acp-provider-icons/hermes.svg b/packages/app/src/assets/acp-provider-icons/hermes.svg
new file mode 100644
index 0000000000..c6fdd22500
--- /dev/null
+++ b/packages/app/src/assets/acp-provider-icons/hermes.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/app/src/assets/acp-provider-icons/kiro.svg b/packages/app/src/assets/acp-provider-icons/kiro.svg
new file mode 100644
index 0000000000..31715c335a
--- /dev/null
+++ b/packages/app/src/assets/acp-provider-icons/kiro.svg
@@ -0,0 +1 @@
+
diff --git a/packages/app/src/assets/acp-provider-icons/minimax-code.svg b/packages/app/src/assets/acp-provider-icons/minimax-code.svg
new file mode 100644
index 0000000000..61fcb8afab
--- /dev/null
+++ b/packages/app/src/assets/acp-provider-icons/minimax-code.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/app/src/assets/acp-provider-icons/traecli.svg b/packages/app/src/assets/acp-provider-icons/traecli.svg
new file mode 100644
index 0000000000..6d5388fcaa
--- /dev/null
+++ b/packages/app/src/assets/acp-provider-icons/traecli.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/app/src/components/icons/antigravity-icon.tsx b/packages/app/src/components/icons/antigravity-icon.tsx
new file mode 100644
index 0000000000..baf2b4c4a6
--- /dev/null
+++ b/packages/app/src/components/icons/antigravity-icon.tsx
@@ -0,0 +1,17 @@
+import Svg, { Path } from "react-native-svg";
+
+interface AntigravityIconProps {
+ size?: number;
+ color?: string;
+}
+
+export function AntigravityIcon({ size = 16, color = "currentColor" }: AntigravityIconProps) {
+ return (
+
+ );
+}
diff --git a/packages/app/src/components/icons/kiro-icon.tsx b/packages/app/src/components/icons/kiro-icon.tsx
new file mode 100644
index 0000000000..830e9109aa
--- /dev/null
+++ b/packages/app/src/components/icons/kiro-icon.tsx
@@ -0,0 +1,18 @@
+import Svg, { Path } from "react-native-svg";
+
+interface KiroIconProps {
+ size?: number;
+ color?: string;
+}
+
+export function KiroIcon({ size = 16, color = "currentColor" }: KiroIconProps) {
+ return (
+
+ );
+}
diff --git a/packages/app/src/components/provider-icon-name.test.ts b/packages/app/src/components/provider-icon-name.test.ts
index 65c75166a5..0083926627 100644
--- a/packages/app/src/components/provider-icon-name.test.ts
+++ b/packages/app/src/components/provider-icon-name.test.ts
@@ -17,8 +17,10 @@ describe("resolveProviderIconName", () => {
it("returns the catalog identifier for ACP catalog provider ids that ship an icon", () => {
expect(resolveProviderIconName("amp-acp")).toEqual({ kind: "catalog", id: "amp-acp" });
+ expect(resolveProviderIconName("devin")).toEqual({ kind: "catalog", id: "devin" });
expect(resolveProviderIconName("gemini")).toEqual({ kind: "catalog", id: "gemini" });
expect(resolveProviderIconName("gjc")).toEqual({ kind: "catalog", id: "gjc" });
+ expect(resolveProviderIconName("hermes")).toEqual({ kind: "catalog", id: "hermes" });
expect(resolveProviderIconName("traecli")).toEqual({ kind: "catalog", id: "traecli" });
});
diff --git a/packages/app/src/components/provider-icons.test.ts b/packages/app/src/components/provider-icons.test.ts
index 025ff14bef..aceed473be 100644
--- a/packages/app/src/components/provider-icons.test.ts
+++ b/packages/app/src/components/provider-icons.test.ts
@@ -1,6 +1,6 @@
// @vitest-environment jsdom
import { act, cleanup, renderHook } from "@testing-library/react";
-import { Bot } from "lucide-react-native";
+import { Bot, PackagePlus } from "lucide-react-native";
import { SvgXml } from "react-native-svg";
import { afterEach, describe, expect, it } from "vitest";
import { replaceProviderSnapshotIcons } from "@/data/provider-icons";
@@ -17,6 +17,16 @@ function renderIcon(Component: ProviderIconComponent) {
}
describe("useProviderIcon", () => {
+ it.each(["devin", "hermes", "kiro", "antigravity"])(
+ "#7087: %s has a provider mark before a host snapshot arrives",
+ (provider) => {
+ const Icon = renderHook(() => useProviderIcon(provider)).result.current;
+ expect(Icon).not.toBe(Bot);
+ expect(Icon).not.toBe(PackagePlus);
+ expect(typeof Icon).toBe("function");
+ },
+ );
+
it("renders registered snapshot SVG metadata with the requested size and color", () => {
const svg = '';
replaceProviderSnapshotIcons("server-1", [{ provider: "rendered-provider", iconSvg: svg }]);
diff --git a/packages/app/src/components/provider-icons.ts b/packages/app/src/components/provider-icons.ts
index 883b4820f7..d8548cbd16 100644
--- a/packages/app/src/components/provider-icons.ts
+++ b/packages/app/src/components/provider-icons.ts
@@ -1,12 +1,14 @@
import { useStore } from "zustand";
import { providerSnapshotIcons } from "@/data/provider-icons";
-import { Bot, PackagePlus } from "lucide-react-native";
+import { Bot } from "lucide-react-native";
import { createElement, useCallback, type ComponentType } from "react";
import { SvgXml } from "react-native-svg";
import { TERMINAL_PROFILE_ICON_NAMES } from "@getpaseo/protocol/provider-icon-names";
+import { AntigravityIcon } from "@/components/icons/antigravity-icon";
import { ClaudeIcon } from "@/components/icons/claude-icon";
import { CodexIcon } from "@/components/icons/codex-icon";
import { CopilotIcon } from "@/components/icons/copilot-icon";
+import { KiroIcon } from "@/components/icons/kiro-icon";
import { MiniMaxIcon } from "@/components/icons/minimax-icon";
import { OpenCodeIcon } from "@/components/icons/opencode-icon";
import { OmpIcon } from "@/components/icons/omp-icon";
@@ -23,10 +25,11 @@ export interface ProviderIconProps {
export type ProviderIconComponent = ComponentType;
const BUILTIN_PROVIDER_ICONS: Record = {
+ antigravity: AntigravityIcon as unknown as ProviderIconComponent,
claude: ClaudeIcon as unknown as ProviderIconComponent,
codex: CodexIcon as unknown as ProviderIconComponent,
copilot: CopilotIcon as unknown as ProviderIconComponent,
- kiro: PackagePlus,
+ kiro: KiroIcon as unknown as ProviderIconComponent,
minimax: MiniMaxIcon as unknown as ProviderIconComponent,
omp: OmpIcon as unknown as ProviderIconComponent,
opencode: OpenCodeIcon as unknown as ProviderIconComponent,
diff --git a/packages/app/src/data/acp-provider-catalog.ts b/packages/app/src/data/acp-provider-catalog.ts
index 1bd32db53d..f91a9540f2 100644
--- a/packages/app/src/data/acp-provider-catalog.ts
+++ b/packages/app/src/data/acp-provider-catalog.ts
@@ -133,7 +133,7 @@ const CATALOG_DATA = [
title: "Devin CLI",
description: "Cognition's Devin for Terminal via Agent Client Protocol",
version: "manual",
- iconId: null,
+ iconId: "devin",
installLink: "https://cli.devin.ai/docs",
command: ["devin", "acp"],
},
@@ -235,7 +235,7 @@ const CATALOG_DATA = [
title: "Hermes",
description: "Nous Research self-improving AI agent",
version: "manual",
- iconId: null,
+ iconId: "hermes",
installLink: "https://hermes-agent.nousresearch.com/docs/user-guide/features/acp",
command: ["hermes", "acp"],
},
@@ -272,7 +272,7 @@ const CATALOG_DATA = [
title: "Kiro CLI",
description: "Amazon's AI coding agent with native ACP support",
version: "manual",
- iconId: null,
+ iconId: "kiro",
installLink: "https://kiro.dev/docs/cli/acp/",
command: ["kiro-cli", "acp"],
},
diff --git a/packages/app/src/hooks/use-acp-provider-catalog.test.ts b/packages/app/src/hooks/use-acp-provider-catalog.test.ts
index 201c17c022..c5fd59d53e 100644
--- a/packages/app/src/hooks/use-acp-provider-catalog.test.ts
+++ b/packages/app/src/hooks/use-acp-provider-catalog.test.ts
@@ -11,6 +11,13 @@ function findProvider(id: string) {
}
describe("ACP provider catalog", () => {
+ it.each(["devin", "hermes", "kiro"])(
+ "#7087: %s supplies its provider mark to the catalog picker",
+ (provider) => {
+ expect(findProvider(provider).iconSvg).toContain("