From 8180ae1d7454e3900747fd2bf35ddcd092795f7b Mon Sep 17 00:00:00 2001 From: Galyarder Date: Sat, 27 Jun 2026 18:47:38 +0700 Subject: [PATCH] refactor(acp): migrate Gemini CLI to Antigravity CLI --- .../assets/agent-icons/acp/agents.json | 32 ++++++++--- .../assets/agent-icons/acp/manifest.json | 4 +- .../components/agents/AgentLauncher.test.tsx | 2 +- .../components/agents/AgentLauncher.tsx | 36 ++++++++---- .../components/chat/agent-templates.test.ts | 6 +- .../components/chat/agent-templates.tsx | 12 ++-- .../settings/AcpAgentsSettings.test.tsx | 2 +- src/renderer/hooks/use-acp-agents.test.ts | 6 +- .../lib/agents/acp-registry-catalog.test.ts | 2 +- src/renderer/lib/agents/acp-registry.test.ts | 56 ++++++++++++++++--- src/renderer/lib/agents/acp-registry.ts | 25 +++++++-- .../lib/agents/agent-registry.test.ts | 4 +- src/renderer/lib/agents/agent-registry.ts | 6 +- .../lib/agents/supported-acp-agents.ts | 2 +- 14 files changed, 139 insertions(+), 56 deletions(-) diff --git a/src/renderer/assets/agent-icons/acp/agents.json b/src/renderer/assets/agent-icons/acp/agents.json index 1986668f..314164cb 100644 --- a/src/renderer/assets/agent-icons/acp/agents.json +++ b/src/renderer/assets/agent-icons/acp/agents.json @@ -344,14 +344,32 @@ } }, { - "id": "gemini", - "name": "Gemini CLI", - "version": "0.45.1", - "description": "Google's official CLI for Gemini", + "id": "antigravity", + "name": "Antigravity CLI", + "version": "1.0.12", + "description": "Google's official CLI for Antigravity", "distribution": { - "npx": { - "package": "@google/gemini-cli@0.45.1", - "args": ["--acp"] + "binary": { + "darwin-aarch64": { + "cmd": "agy", + "args": ["--acp"] + }, + "darwin-x86_64": { + "cmd": "agy", + "args": ["--acp"] + }, + "linux-aarch64": { + "cmd": "agy", + "args": ["--acp"] + }, + "linux-x86_64": { + "cmd": "agy", + "args": ["--acp"] + }, + "windows-x86_64": { + "cmd": "agy.exe", + "args": ["--acp"] + } } } }, diff --git a/src/renderer/assets/agent-icons/acp/manifest.json b/src/renderer/assets/agent-icons/acp/manifest.json index 14ef6c85..9798eb4f 100644 --- a/src/renderer/assets/agent-icons/acp/manifest.json +++ b/src/renderer/assets/agent-icons/acp/manifest.json @@ -85,8 +85,8 @@ "file": "fast-agent.svg" }, { - "id": "gemini", - "name": "Gemini CLI", + "id": "antigravity", + "name": "Antigravity CLI", "file": "gemini.svg" }, { diff --git a/src/renderer/components/agents/AgentLauncher.test.tsx b/src/renderer/components/agents/AgentLauncher.test.tsx index 6c19f248..daf39df0 100644 --- a/src/renderer/components/agents/AgentLauncher.test.tsx +++ b/src/renderer/components/agents/AgentLauncher.test.tsx @@ -407,7 +407,7 @@ describe('AgentLauncher ACP new thread', () => { expect(agentPicker).not.toHaveTextContent('Codex CLI') fireEvent.click(agentPicker) expect(await screen.findByText('Claude Agent')).toBeInTheDocument() - expect(screen.getByText('Gemini CLI')).toBeInTheDocument() + expect(screen.getByText('Antigravity CLI')).toBeInTheDocument() expect(screen.getByText('Cursor')).toBeInTheDocument() expect(screen.getByText('OpenCode')).toBeInTheDocument() expect(screen.getByText('pi ACP')).toBeInTheDocument() diff --git a/src/renderer/components/agents/AgentLauncher.tsx b/src/renderer/components/agents/AgentLauncher.tsx index b7c53e4d..9ddb1c40 100644 --- a/src/renderer/components/agents/AgentLauncher.tsx +++ b/src/renderer/components/agents/AgentLauncher.tsx @@ -175,14 +175,33 @@ export function AgentLauncher({ paneId, className }: AgentLauncherProps): React. } }, [persistSelection, selectedConfigId, supportedAgents]) + const selectedConfigRef = useRef(selectedConfig) + selectedConfigRef.current = selectedConfig + + const acpConfigsRef = useRef(acpConfigs) + acpConfigsRef.current = acpConfigs + + const configSpawnKey = useMemo(() => { + if (!selectedConfig) return '' + return `${selectedConfig.command} ${selectedConfig.args.join(' ')} ${JSON.stringify(selectedConfig.env)}` + }, [selectedConfig]) + useEffect(() => { - if (!activeConfigId || !projectRoot || selectedEntry?.status !== 'ready' || !selectedConfig) + if ( + !activeConfigId || + !projectRoot || + selectedEntry?.status !== 'ready' || + !selectedConfigRef.current + ) return let cancelled = false void (async () => { try { - if (!acpConfigs.some((config) => config.id === selectedConfig.id)) { - await saveAgentConfig(selectedConfig) + const currentConfig = selectedConfigRef.current + if (!currentConfig) return + const currentConfigs = acpConfigsRef.current + if (!currentConfigs.some((config) => config.id === currentConfig.id)) { + await saveAgentConfig(currentConfig) if (cancelled) return } useAcpStore.getState().prepareChat(activeConfigId, projectRoot) @@ -190,19 +209,14 @@ export function AgentLauncher({ paneId, className }: AgentLauncherProps): React. console.warn('[acp] failed to prepare supported agent', activeConfigId, err) } })() + // Re-prepare when command/args/env change + void configSpawnKey const key = prepareChatKey(activeConfigId, projectRoot, undefined) return () => { cancelled = true useAcpStore.getState().cancelPreparedChat(key) } - }, [ - activeConfigId, - acpConfigs, - projectRoot, - saveAgentConfig, - selectedConfig, - selectedEntry?.status - ]) + }, [activeConfigId, projectRoot, saveAgentConfig, selectedEntry?.status, configSpawnKey]) const handleSelectAgent = useCallback( (entry: SupportedAcpAgentEntry) => { diff --git a/src/renderer/components/chat/agent-templates.test.ts b/src/renderer/components/chat/agent-templates.test.ts index 669fcc0d..2aa323e7 100644 --- a/src/renderer/components/chat/agent-templates.test.ts +++ b/src/renderer/components/chat/agent-templates.test.ts @@ -4,7 +4,7 @@ import { AGENT_TEMPLATES, templateById, templateIcon } from './agent-templates' describe('agent-templates', () => { it('includes the well-known ACP registry agents', () => { const ids = AGENT_TEMPLATES.map((t) => t.id) - for (const id of ['gemini', 'claude-acp', 'codex-acp', 'github-copilot-cli', 'custom']) { + for (const id of ['antigravity', 'claude-acp', 'codex-acp', 'github-copilot-cli', 'custom']) { expect(ids).toContain(id) } }) @@ -28,12 +28,12 @@ describe('agent-templates', () => { }) it('templateById resolves a known template and returns undefined otherwise', () => { - expect(templateById('gemini')?.label).toBe('Gemini CLI') + expect(templateById('antigravity')?.label).toBe('Antigravity CLI') expect(templateById('does-not-exist')).toBeUndefined() }) it('templateIcon returns a component for agents with icons and undefined otherwise', () => { - expect(templateIcon('gemini')).toBeTypeOf('function') + expect(templateIcon('antigravity')).toBeTypeOf('function') expect(templateIcon('codex-acp')).toBeTypeOf('function') // custom has no icon expect(templateIcon('custom')).toBeUndefined() diff --git a/src/renderer/components/chat/agent-templates.tsx b/src/renderer/components/chat/agent-templates.tsx index a91143ae..393b4456 100644 --- a/src/renderer/components/chat/agent-templates.tsx +++ b/src/renderer/components/chat/agent-templates.tsx @@ -34,14 +34,14 @@ export interface AgentTemplate { export const AGENT_TEMPLATES: AgentTemplate[] = [ { - id: 'gemini', - label: 'Gemini CLI', - notes: "Google's official CLI for Gemini. Runs via npx.", + id: 'antigravity', + label: 'Antigravity CLI', + notes: "Google's official CLI for Antigravity. Runs natively via agy.", icon: GeminiIcon, config: { - name: 'Gemini CLI', - command: 'npx', - args: ['-y', '@google/gemini-cli', '--acp'], + name: 'Antigravity CLI', + command: 'agy', + args: ['--acp'], env: {}, allowTerminal: false } diff --git a/src/renderer/components/settings/AcpAgentsSettings.test.tsx b/src/renderer/components/settings/AcpAgentsSettings.test.tsx index 5f9bb3a1..faca3d6b 100644 --- a/src/renderer/components/settings/AcpAgentsSettings.test.tsx +++ b/src/renderer/components/settings/AcpAgentsSettings.test.tsx @@ -36,7 +36,7 @@ describe('AcpAgentsSettings', () => { expect(screen.getByText('Codex CLI')).toBeInTheDocument() expect(screen.getByText('Claude Agent')).toBeInTheDocument() - expect(screen.getByText('Gemini CLI')).toBeInTheDocument() + expect(screen.getByText('Antigravity CLI')).toBeInTheDocument() expect(screen.getByText('Cursor')).toBeInTheDocument() expect(screen.getByText('OpenCode')).toBeInTheDocument() expect(screen.getByText('pi ACP')).toBeInTheDocument() diff --git a/src/renderer/hooks/use-acp-agents.test.ts b/src/renderer/hooks/use-acp-agents.test.ts index 3cf6dcff..855a460f 100644 --- a/src/renderer/hooks/use-acp-agents.test.ts +++ b/src/renderer/hooks/use-acp-agents.test.ts @@ -80,18 +80,18 @@ describe('useAcpAgents', () => { mockLoadAgentConfigs.mockImplementation(async () => { stateRef.current.agentConfigs = [ config('acp-registry:claude-acp'), - config('acp-registry:gemini') + config('acp-registry:antigravity') ] }) mockPersistRead.mockResolvedValue({ success: true, - data: { agentId: 'acp-registry:gemini', mode: 'acp' } + data: { agentId: 'acp-registry:antigravity', mode: 'acp' } }) renderHook(() => useAcpAgents()) await waitFor(() => { - expect(mockPrewarmAgent).toHaveBeenCalledWith('acp-registry:gemini', '/work/proj-1') + expect(mockPrewarmAgent).toHaveBeenCalledWith('acp-registry:antigravity', '/work/proj-1') }) expect(mockPrewarmAgent).toHaveBeenCalledTimes(1) }) diff --git a/src/renderer/lib/agents/acp-registry-catalog.test.ts b/src/renderer/lib/agents/acp-registry-catalog.test.ts index a35d2fb2..b792460a 100644 --- a/src/renderer/lib/agents/acp-registry-catalog.test.ts +++ b/src/renderer/lib/agents/acp-registry-catalog.test.ts @@ -32,7 +32,7 @@ const catalog: AcpRegistryCatalog = { fetchedAt: '2026-06-01T00:00:00Z', entries: [ { id: 'claude-acp', name: 'Claude Code', icon: 'https://cdn/claude.svg' }, - { id: 'gemini', name: 'Gemini', icon: 'https://cdn/gemini.svg' } + { id: 'antigravity', name: 'Antigravity', icon: 'https://cdn/antigravity.svg' } ] } diff --git a/src/renderer/lib/agents/acp-registry.test.ts b/src/renderer/lib/agents/acp-registry.test.ts index 0b548e4d..6c47c532 100644 --- a/src/renderer/lib/agents/acp-registry.test.ts +++ b/src/renderer/lib/agents/acp-registry.test.ts @@ -34,7 +34,7 @@ describe('currentPlatformArch', () => { describe('deriveAgentConfig', () => { it('derives an npx distribution with -y prefix', () => { const res = deriveAgentConfig( - agent({ npx: { package: '@google/gemini-cli@0.45.0', args: ['--acp'] } }), + agent({ npx: { package: 'some-acp-agent@1.0.0', args: ['--acp'] } }), 'windows-x86_64' ) expect(res).toEqual({ @@ -42,7 +42,7 @@ describe('deriveAgentConfig', () => { config: { name: 'Agent X', command: 'npx', - args: ['-y', '@google/gemini-cli@0.45.0', '--acp'], + args: ['-y', 'some-acp-agent@1.0.0', '--acp'], env: {}, allowTerminal: false } @@ -113,9 +113,17 @@ describe('deriveAgentConfig', () => { } }) - it('returns needs-install for a binary present on the current platform-arch', () => { + it('returns needs-install for a binary present on the current platform-arch with archive', () => { const res = deriveAgentConfig( - agent({ binary: { 'windows-x86_64': { cmd: './stakpak.exe', args: ['acp'] } } }), + agent({ + binary: { + 'windows-x86_64': { + cmd: './stakpak.exe', + archive: 'https://example.com/stakpak.zip', + args: ['acp'] + } + } + }), 'windows-x86_64' ) expect(res).toEqual({ @@ -123,7 +131,24 @@ describe('deriveAgentConfig', () => { cmd: './stakpak.exe', args: ['acp'], env: {}, - archiveUrl: undefined + archiveUrl: 'https://example.com/stakpak.zip' + }) + }) + + it('returns runnable for a binary present on the current platform-arch without archive', () => { + const res = deriveAgentConfig( + agent({ binary: { 'windows-x86_64': { cmd: './stakpak.exe', args: ['acp'] } } }), + 'windows-x86_64' + ) + expect(res).toEqual({ + kind: 'runnable', + config: { + name: 'Agent X', + command: './stakpak.exe', + args: ['acp'], + env: {}, + allowTerminal: false + } }) }) @@ -131,7 +156,12 @@ describe('deriveAgentConfig', () => { const res = deriveAgentConfig( agent({ binary: { - 'windows-x86_64': { cmd: 'vtcode.exe', args: ['acp'], env: { VT_ACP_ENABLED: '1' } } + 'windows-x86_64': { + cmd: 'vtcode.exe', + archive: 'https://example.com/vt.zip', + args: ['acp'], + env: { VT_ACP_ENABLED: '1' } + } } }), 'windows-x86_64' @@ -141,13 +171,21 @@ describe('deriveAgentConfig', () => { cmd: 'vtcode.exe', args: ['acp'], env: { VT_ACP_ENABLED: '1' }, - archiveUrl: undefined + archiveUrl: 'https://example.com/vt.zip' }) }) it('rejects a flag-like package and falls through to binary/unavailable', () => { const res = deriveAgentConfig( - agent({ npx: { package: '--evil-flag' }, binary: { 'windows-x86_64': { cmd: './x.exe' } } }), + agent({ + npx: { package: '--evil-flag' }, + binary: { + 'windows-x86_64': { + cmd: './x.exe', + archive: 'https://example.com/x.zip' + } + } + }), 'windows-x86_64' ) // npx is skipped (unsafe package) -> falls through to the binary path. @@ -156,7 +194,7 @@ describe('deriveAgentConfig', () => { cmd: './x.exe', args: [], env: {}, - archiveUrl: undefined + archiveUrl: 'https://example.com/x.zip' }) }) diff --git a/src/renderer/lib/agents/acp-registry.ts b/src/renderer/lib/agents/acp-registry.ts index 4fb03f72..843a21a4 100644 --- a/src/renderer/lib/agents/acp-registry.ts +++ b/src/renderer/lib/agents/acp-registry.ts @@ -158,12 +158,25 @@ export function deriveAgentConfig(agent: RegistryAgent, platformArch: string): D const target = dist.binary?.[platformArch] if (target) { const archiveUrl = supportedArchiveUrl(target.archive) - return { - kind: 'needs-install', - cmd: target.cmd, - args: [...(target.args ?? [])], - env: env(target.env), - archiveUrl + if (target.archive !== undefined) { + return { + kind: 'needs-install', + cmd: target.cmd, + args: [...(target.args ?? [])], + env: env(target.env), + archiveUrl + } + } else { + return { + kind: 'runnable', + config: { + name: agent.name, + command: target.cmd, + args: [...(target.args ?? [])], + env: env(target.env), + allowTerminal: false + } + } } } diff --git a/src/renderer/lib/agents/agent-registry.test.ts b/src/renderer/lib/agents/agent-registry.test.ts index 3c3930fb..bf5a4efc 100644 --- a/src/renderer/lib/agents/agent-registry.test.ts +++ b/src/renderer/lib/agents/agent-registry.test.ts @@ -113,7 +113,7 @@ describe('built-in agent definitions', () => { expect(byId.codex).toMatchObject({ command: 'codex', promptMode: 'positional' }) expect(byId.cursor).toMatchObject({ command: 'cursor-agent', promptMode: 'positional' }) expect(byId['gemini-cli']).toMatchObject({ - command: 'gemini', + command: 'agy', promptMode: 'flag', promptFlag: '-i' }) @@ -149,7 +149,7 @@ describe('built-in agent definitions', () => { args: ['P'] }) expect(buildAgentArgv(getBuiltInAgent('gemini-cli')!, 'P')).toEqual({ - program: 'gemini', + program: 'agy', args: ['-i', 'P'] }) expect(buildAgentArgv(getBuiltInAgent('opencode')!, 'P')).toEqual({ diff --git a/src/renderer/lib/agents/agent-registry.ts b/src/renderer/lib/agents/agent-registry.ts index 10ea7b6b..ed186643 100644 --- a/src/renderer/lib/agents/agent-registry.ts +++ b/src/renderer/lib/agents/agent-registry.ts @@ -131,12 +131,12 @@ export const BUILT_IN_AGENTS: readonly TerminalAgentDefinition[] = [ }, { id: 'gemini-cli', - name: 'Gemini CLI', - command: 'gemini', + name: 'Antigravity CLI', + command: 'agy', baseArgs: [], promptMode: 'flag', promptFlag: '-i', - registryId: 'gemini', + registryId: 'antigravity', icon: geminiIcon as string, isBuiltIn: true }, diff --git a/src/renderer/lib/agents/supported-acp-agents.ts b/src/renderer/lib/agents/supported-acp-agents.ts index 77722aea..49f5eed1 100644 --- a/src/renderer/lib/agents/supported-acp-agents.ts +++ b/src/renderer/lib/agents/supported-acp-agents.ts @@ -9,7 +9,7 @@ import { export const SUPPORTED_ACP_AGENT_IDS = [ 'codex-acp', 'claude-acp', - 'gemini', + 'antigravity', 'cursor', 'opencode', 'pi-acp'