diff --git a/mobile/src/components/MobileAgentIcon.tsx b/mobile/src/components/MobileAgentIcon.tsx
index ade35feda45..6c47fce7a5b 100644
--- a/mobile/src/components/MobileAgentIcon.tsx
+++ b/mobile/src/components/MobileAgentIcon.tsx
@@ -1,8 +1,11 @@
import { Image, StyleSheet, Text, View } from 'react-native'
+import type { ImageSourcePropType } from 'react-native'
import { Terminal } from 'lucide-react-native'
+import type { TuiAgent } from '../../../src/shared/types'
import Svg, { Defs, G, LinearGradient, Path, Stop } from 'react-native-svg'
import { colors } from '../theme/mobile-theme'
import { MOBILE_AGENT_CATALOG } from '../tasks/mobile-agent-catalog'
+import { MOBILE_AGENT_ICON_ASSETS } from './mobile-agent-icon-assets'
import { ClaudeIcon, OpenAIIcon } from './AgentIcons'
// Why: agent branding should match the desktop/new-worktree picker everywhere
@@ -49,6 +52,10 @@ function AiderIcon({ size = 16 }: { size?: number }) {
)
}
+function BundledIcon({ source, size = 16 }: { source: ImageSourcePropType; size?: number }) {
+ return
+}
+
function FaviconIcon({ domain, size = 16 }: { domain: string; size?: number }) {
return (
}
+ // Why: prefer the favicon bundled into the app over Google's favicon service,
+ // which is unreachable in some regions and offline (#8451).
+ const bundledIcon = MOBILE_AGENT_ICON_ASSETS[agentId as TuiAgent]
+ if (bundledIcon) {
+ return
+ }
const agent = MOBILE_AGENT_CATALOG.find((entry) => entry.id === agentId)
if (agent?.faviconDomain) {
return
diff --git a/mobile/src/components/mobile-agent-icon-assets.test.ts b/mobile/src/components/mobile-agent-icon-assets.test.ts
new file mode 100644
index 00000000000..edbe71760c7
--- /dev/null
+++ b/mobile/src/components/mobile-agent-icon-assets.test.ts
@@ -0,0 +1,45 @@
+import { existsSync, readFileSync } from 'node:fs'
+import path from 'node:path'
+import { describe, expect, it } from 'vitest'
+import type { TuiAgent } from '../../../src/shared/types'
+import { MOBILE_TUI_AGENT_FAVICON_DOMAINS } from '../tasks/mobile-tui-agents'
+
+// Agents that render a hand-authored glyph in MobileAgentIcon and therefore
+// never reach the favicon/bundled-asset path.
+const GLYPH_AGENT_IDS = new Set([
+ 'claude',
+ 'claude-agent-teams',
+ 'codex',
+ 'pi',
+ 'omp',
+ 'aider'
+])
+
+const dirname = import.meta.dirname
+const assetsDir = path.resolve(dirname, '../../../src/shared/agent-icons')
+const assetsModuleSource = readFileSync(path.join(dirname, 'mobile-agent-icon-assets.ts'), 'utf8')
+
+// Why: these agents previously loaded from Google's favicon service, which is
+// unreachable in some regions/offline (#8451). Every one that lacks a glyph
+// must ship a bundled PNG so the icon renders without a network request.
+const agentsNeedingBundledIcon = Object.keys(MOBILE_TUI_AGENT_FAVICON_DOMAINS).filter(
+ (id): id is TuiAgent => !GLYPH_AGENT_IDS.has(id as TuiAgent)
+)
+
+describe('mobile bundled agent icons', () => {
+ it('ships a bundled PNG on disk for every favicon-path agent', () => {
+ for (const id of agentsNeedingBundledIcon) {
+ expect(existsSync(path.join(assetsDir, `${id}.png`)), `missing bundled icon for ${id}`).toBe(
+ true
+ )
+ }
+ })
+
+ it('wires every favicon-path agent into the bundled asset map', () => {
+ for (const id of agentsNeedingBundledIcon) {
+ expect(assetsModuleSource, `${id} not mapped in mobile-agent-icon-assets.ts`).toContain(
+ `agent-icons/${id}.png`
+ )
+ }
+ })
+})
diff --git a/mobile/src/components/mobile-agent-icon-assets.ts b/mobile/src/components/mobile-agent-icon-assets.ts
new file mode 100644
index 00000000000..52615e89df8
--- /dev/null
+++ b/mobile/src/components/mobile-agent-icon-assets.ts
@@ -0,0 +1,41 @@
+import type { ImageSourcePropType } from 'react-native'
+import type { TuiAgent } from '../../../src/shared/types'
+
+// Why: mobile previously rendered these agent icons from Google's favicon
+// service, which is unreachable in some regions (e.g. mainland China) and
+// offline, leaving broken images in the agent picker (#8451). Bundle each
+// favicon so Metro packages it into the app and the icon renders without any
+// network request. The PNGs are shared with desktop in src/shared/agent-icons
+// (Metro watches src/shared via metro.config.js sharedRoot). Metro requires
+// static literal require() paths, so keep this as an explicit map rather than
+// building the path from the agent id.
+export const MOBILE_AGENT_ICON_ASSETS: Partial> = {
+ openclaude: require('../../../src/shared/agent-icons/openclaude.png'),
+ grok: require('../../../src/shared/agent-icons/grok.png'),
+ copilot: require('../../../src/shared/agent-icons/copilot.png'),
+ opencode: require('../../../src/shared/agent-icons/opencode.png'),
+ 'mimo-code': require('../../../src/shared/agent-icons/mimo-code.png'),
+ ante: require('../../../src/shared/agent-icons/ante.png'),
+ gemini: require('../../../src/shared/agent-icons/gemini.png'),
+ antigravity: require('../../../src/shared/agent-icons/antigravity.png'),
+ goose: require('../../../src/shared/agent-icons/goose.png'),
+ amp: require('../../../src/shared/agent-icons/amp.png'),
+ kilo: require('../../../src/shared/agent-icons/kilo.png'),
+ kiro: require('../../../src/shared/agent-icons/kiro.png'),
+ crush: require('../../../src/shared/agent-icons/crush.png'),
+ aug: require('../../../src/shared/agent-icons/aug.png'),
+ autohand: require('../../../src/shared/agent-icons/autohand.png'),
+ cline: require('../../../src/shared/agent-icons/cline.png'),
+ codebuff: require('../../../src/shared/agent-icons/codebuff.png'),
+ 'command-code': require('../../../src/shared/agent-icons/command-code.png'),
+ continue: require('../../../src/shared/agent-icons/continue.png'),
+ cursor: require('../../../src/shared/agent-icons/cursor.png'),
+ droid: require('../../../src/shared/agent-icons/droid.png'),
+ kimi: require('../../../src/shared/agent-icons/kimi.png'),
+ 'mistral-vibe': require('../../../src/shared/agent-icons/mistral-vibe.png'),
+ 'qwen-code': require('../../../src/shared/agent-icons/qwen-code.png'),
+ rovo: require('../../../src/shared/agent-icons/rovo.png'),
+ hermes: require('../../../src/shared/agent-icons/hermes.png'),
+ devin: require('../../../src/shared/agent-icons/devin.png'),
+ openclaw: require('../../../src/shared/agent-icons/openclaw.png')
+}
diff --git a/src/renderer/src/components/agent/AgentCombobox.test.tsx b/src/renderer/src/components/agent/AgentCombobox.test.tsx
index 6547c3704bd..d6fe7590f49 100644
--- a/src/renderer/src/components/agent/AgentCombobox.test.tsx
+++ b/src/renderer/src/components/agent/AgentCombobox.test.tsx
@@ -1,6 +1,8 @@
import { renderToStaticMarkup } from 'react-dom/server'
import { describe, expect, it, vi } from 'vitest'
+import type { TuiAgent } from '../../../../shared/types'
import { AGENT_CATALOG, AgentIcon } from '@/lib/agent-catalog'
+import { AGENT_FAVICON_ASSETS } from '@/lib/agent-favicon-assets'
import AgentCombobox from './AgentCombobox'
describe('AgentCombobox', () => {
@@ -37,4 +39,14 @@ describe('AgentCombobox', () => {
expect(markup).not.toContain('https://www.google.com/s2/favicons')
expect(markup).not.toContain('
{
+ // Why: previously loaded from Google's favicon service (#8451). Iterate the
+ // full asset map so missing files/key mismatches fail the test.
+ for (const agent of Object.keys(AGENT_FAVICON_ASSETS) as TuiAgent[]) {
+ const markup = renderToStaticMarkup()
+ expect(markup).toContain(`/shared/agent-icons/${agent}.png`)
+ expect(markup).not.toContain('https://www.google.com/s2/favicons')
+ }
+ })
})
diff --git a/src/renderer/src/lib/agent-catalog.tsx b/src/renderer/src/lib/agent-catalog.tsx
index d43401e502f..f9536dc1c34 100644
--- a/src/renderer/src/lib/agent-catalog.tsx
+++ b/src/renderer/src/lib/agent-catalog.tsx
@@ -14,6 +14,7 @@ import {
} from './agent-icon-glyphs'
import { translate } from '@/i18n/i18n'
import { createLocalizedCatalog } from '@/i18n/localized-catalog'
+import { AGENT_FAVICON_ASSETS } from './agent-favicon-assets'
export type AgentCatalogEntry = {
id: TuiAgent
@@ -113,7 +114,8 @@ export const getAgentCatalog = createLocalizedCatalog((): AgentCatalogEntry[] =>
id: 'omp',
label: translate('auto.lib.agent.catalog.09973b4d84', 'OMP'),
cmd: 'omp',
- faviconDomain: 'omp.sh',
+ // Why: no faviconDomain — omp renders the hand-authored OmpIcon glyph, so a
+ // favicon fallback would never be reached.
homepageUrl: 'https://omp.sh'
},
{
@@ -340,20 +342,29 @@ export function AgentIcon({
return
}
const catalogEntry = getAgentCatalog().find((a) => a.id === agent)
- if (catalogEntry?.iconUrl) {
+ // Why: prefer the favicon bundled at build time so the icon renders without a
+ // live network request — Google's favicon service is unreachable in some
+ // regions and offline, which left these icons broken (#8451).
+ const bundledFaviconUrl = AGENT_FAVICON_ASSETS[agent]
+ // Why: one resolved src for guard + attribute so empty `iconUrl` cannot pass
+ // a truthy `||` check while `??` still renders a broken `
`.
+ const iconSrc = catalogEntry?.iconUrl ?? bundledFaviconUrl
+ if (iconSrc) {
return (
)
}
if (catalogEntry?.faviconDomain) {
- // Why: agents without a published SVG icon use their site favicon via
- // Google's favicon service — same source the README uses for the agent badge list.
+ // Why: agents without a published SVG icon or bundled favicon fall back to
+ // their site favicon via Google's favicon service — same source the README
+ // uses for the agent badge list.
return (
> = {
+ grok: grokUrl,
+ 'mimo-code': mimoCodeUrl,
+ ante: anteUrl,
+ gemini: geminiUrl,
+ antigravity: antigravityUrl,
+ goose: gooseUrl,
+ amp: ampUrl,
+ kiro: kiroUrl,
+ crush: crushUrl,
+ aug: augUrl,
+ autohand: autohandUrl,
+ cline: clineUrl,
+ codebuff: codebuffUrl,
+ 'command-code': commandCodeUrl,
+ continue: continueUrl,
+ cursor: cursorUrl,
+ kimi: kimiUrl,
+ 'mistral-vibe': mistralVibeUrl,
+ 'qwen-code': qwenCodeUrl,
+ rovo: rovoUrl,
+ hermes: hermesUrl,
+ devin: devinUrl,
+ openclaw: openclawUrl
+}
diff --git a/src/shared/agent-icons/amp.png b/src/shared/agent-icons/amp.png
new file mode 100644
index 00000000000..9b57e20ff0f
Binary files /dev/null and b/src/shared/agent-icons/amp.png differ
diff --git a/src/shared/agent-icons/ante.png b/src/shared/agent-icons/ante.png
new file mode 100644
index 00000000000..c3dadcc2cec
Binary files /dev/null and b/src/shared/agent-icons/ante.png differ
diff --git a/src/shared/agent-icons/antigravity.png b/src/shared/agent-icons/antigravity.png
new file mode 100644
index 00000000000..be2992716b5
Binary files /dev/null and b/src/shared/agent-icons/antigravity.png differ
diff --git a/src/shared/agent-icons/aug.png b/src/shared/agent-icons/aug.png
new file mode 100644
index 00000000000..1664844ad7f
Binary files /dev/null and b/src/shared/agent-icons/aug.png differ
diff --git a/src/shared/agent-icons/autohand.png b/src/shared/agent-icons/autohand.png
new file mode 100644
index 00000000000..0c3326a5705
Binary files /dev/null and b/src/shared/agent-icons/autohand.png differ
diff --git a/src/shared/agent-icons/cline.png b/src/shared/agent-icons/cline.png
new file mode 100644
index 00000000000..f4bd762dfee
Binary files /dev/null and b/src/shared/agent-icons/cline.png differ
diff --git a/src/shared/agent-icons/codebuff.png b/src/shared/agent-icons/codebuff.png
new file mode 100644
index 00000000000..b81a6fa9c21
Binary files /dev/null and b/src/shared/agent-icons/codebuff.png differ
diff --git a/src/shared/agent-icons/command-code.png b/src/shared/agent-icons/command-code.png
new file mode 100644
index 00000000000..2066461b996
Binary files /dev/null and b/src/shared/agent-icons/command-code.png differ
diff --git a/src/shared/agent-icons/continue.png b/src/shared/agent-icons/continue.png
new file mode 100644
index 00000000000..cd2599d6e63
Binary files /dev/null and b/src/shared/agent-icons/continue.png differ
diff --git a/src/shared/agent-icons/copilot.png b/src/shared/agent-icons/copilot.png
new file mode 100644
index 00000000000..52126e047d0
Binary files /dev/null and b/src/shared/agent-icons/copilot.png differ
diff --git a/src/shared/agent-icons/crush.png b/src/shared/agent-icons/crush.png
new file mode 100644
index 00000000000..6d880c1305d
Binary files /dev/null and b/src/shared/agent-icons/crush.png differ
diff --git a/src/shared/agent-icons/cursor.png b/src/shared/agent-icons/cursor.png
new file mode 100644
index 00000000000..426cbfa4d0d
Binary files /dev/null and b/src/shared/agent-icons/cursor.png differ
diff --git a/src/shared/agent-icons/devin.png b/src/shared/agent-icons/devin.png
new file mode 100644
index 00000000000..3eee8d0d8d0
Binary files /dev/null and b/src/shared/agent-icons/devin.png differ
diff --git a/src/shared/agent-icons/droid.png b/src/shared/agent-icons/droid.png
new file mode 100644
index 00000000000..014b63332be
Binary files /dev/null and b/src/shared/agent-icons/droid.png differ
diff --git a/src/shared/agent-icons/gemini.png b/src/shared/agent-icons/gemini.png
new file mode 100644
index 00000000000..84976ebc7fd
Binary files /dev/null and b/src/shared/agent-icons/gemini.png differ
diff --git a/src/shared/agent-icons/goose.png b/src/shared/agent-icons/goose.png
new file mode 100644
index 00000000000..2da26f064ce
Binary files /dev/null and b/src/shared/agent-icons/goose.png differ
diff --git a/src/shared/agent-icons/grok.png b/src/shared/agent-icons/grok.png
new file mode 100644
index 00000000000..954e5b40da2
Binary files /dev/null and b/src/shared/agent-icons/grok.png differ
diff --git a/src/shared/agent-icons/hermes.png b/src/shared/agent-icons/hermes.png
new file mode 100644
index 00000000000..7d5ba85e54e
Binary files /dev/null and b/src/shared/agent-icons/hermes.png differ
diff --git a/src/shared/agent-icons/kilo.png b/src/shared/agent-icons/kilo.png
new file mode 100644
index 00000000000..053dba3fd76
Binary files /dev/null and b/src/shared/agent-icons/kilo.png differ
diff --git a/src/shared/agent-icons/kimi.png b/src/shared/agent-icons/kimi.png
new file mode 100644
index 00000000000..3640a259f8d
Binary files /dev/null and b/src/shared/agent-icons/kimi.png differ
diff --git a/src/shared/agent-icons/kiro.png b/src/shared/agent-icons/kiro.png
new file mode 100644
index 00000000000..4a6cc6bbf8c
Binary files /dev/null and b/src/shared/agent-icons/kiro.png differ
diff --git a/src/shared/agent-icons/mimo-code.png b/src/shared/agent-icons/mimo-code.png
new file mode 100644
index 00000000000..45cb84ff516
Binary files /dev/null and b/src/shared/agent-icons/mimo-code.png differ
diff --git a/src/shared/agent-icons/mistral-vibe.png b/src/shared/agent-icons/mistral-vibe.png
new file mode 100644
index 00000000000..f0f799cf546
Binary files /dev/null and b/src/shared/agent-icons/mistral-vibe.png differ
diff --git a/src/shared/agent-icons/openclaude.png b/src/shared/agent-icons/openclaude.png
new file mode 100644
index 00000000000..1e660b74090
Binary files /dev/null and b/src/shared/agent-icons/openclaude.png differ
diff --git a/src/shared/agent-icons/openclaw.png b/src/shared/agent-icons/openclaw.png
new file mode 100644
index 00000000000..03feb5b7638
Binary files /dev/null and b/src/shared/agent-icons/openclaw.png differ
diff --git a/src/shared/agent-icons/opencode.png b/src/shared/agent-icons/opencode.png
new file mode 100644
index 00000000000..1f45b1faee0
Binary files /dev/null and b/src/shared/agent-icons/opencode.png differ
diff --git a/src/shared/agent-icons/qwen-code.png b/src/shared/agent-icons/qwen-code.png
new file mode 100644
index 00000000000..9a6346691d4
Binary files /dev/null and b/src/shared/agent-icons/qwen-code.png differ
diff --git a/src/shared/agent-icons/rovo.png b/src/shared/agent-icons/rovo.png
new file mode 100644
index 00000000000..08a2af624f8
Binary files /dev/null and b/src/shared/agent-icons/rovo.png differ