From 21ed60207d73ab2a048a2e17381b2d98e96514b2 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sun, 20 Sep 2026 18:04:24 -0700 Subject: [PATCH] feat(antigravity): show detected native account (#21798) --- .../native-account-service.test.ts | 11 ++++ .../antigravity/native-account-service.ts | 22 ++++++- src/preload/api/agent-account-api.ts | 6 ++ .../AntigravityAccountsSection.test.tsx | 58 +++++++++++++++++++ .../settings/AntigravityAccountsSection.tsx | 26 ++++++++- src/renderer/src/i18n/locales/en.json | 1 + .../web/preload-api/web-agent-accounts-api.ts | 2 +- 7 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 src/renderer/src/components/settings/AntigravityAccountsSection.test.tsx diff --git a/src/main/antigravity/native-account-service.test.ts b/src/main/antigravity/native-account-service.test.ts index 6ea2ce9cc99..3bab722c125 100644 --- a/src/main/antigravity/native-account-service.test.ts +++ b/src/main/antigravity/native-account-service.test.ts @@ -35,6 +35,17 @@ describe('AntigravityAccountService', () => { expect(state.activeAccountId).toBe(secondAccount.id.split('-').slice(0, 2).join('-')) }) + it('reports a signed-in account before it is explicitly saved to the vault', async () => { + const active = backend(first) + const service = new AntigravityAccountService(createMemoryAntigravityAccountStore(), active) + + const state = await service.listAccounts() + + expect(state.accounts).toEqual([]) + expect(state.activeAccountId).toBeNull() + expect(state.detectedAccount).toMatchObject({ authMethod: 'consumer' }) + }) + it('switches the live native credential and verifies readback', async () => { const active = backend(first) const account = createSyntheticAntigravityAccount(second) diff --git a/src/main/antigravity/native-account-service.ts b/src/main/antigravity/native-account-service.ts index 2f1e8133db4..a365188907e 100644 --- a/src/main/antigravity/native-account-service.ts +++ b/src/main/antigravity/native-account-service.ts @@ -31,6 +31,7 @@ export type AntigravityCredentialBackend = { export type AntigravityAccountState = { accounts: AntigravityAccountSummary[] activeAccountId: string | null + detectedAccount: Pick | null } function accountId(contents: string): string { @@ -58,9 +59,24 @@ export class AntigravityAccountService { async listAccounts(): Promise { const accounts = this.store.read() - const active = await this.knownActiveAccount(accounts) + const current = await this.credentialBackend.read() + const active = current + ? accounts.find((account) => account.id === accountId(current.contents)) + : undefined this.activeAccountId = active?.id ?? null - return { accounts: accounts.map(summary), activeAccountId: this.activeAccountId } + return { + accounts: accounts.map(summary), + activeAccountId: this.activeAccountId, + detectedAccount: + current && !active + ? { + id: accountId(current.contents), + email: current.identity?.email ?? null, + subject: current.identity?.subject ?? null, + authMethod: current.authMethod + } + : null + } } async addCurrentAccount(): Promise { @@ -99,7 +115,7 @@ export class AntigravityAccountService { throw new Error('Antigravity account switching could not be verified.') } this.activeAccountId = id - return { accounts: accounts.map(summary), activeAccountId: id } + return { accounts: accounts.map(summary), activeAccountId: id, detectedAccount: null } } async removeAccount(id: string): Promise { diff --git a/src/preload/api/agent-account-api.ts b/src/preload/api/agent-account-api.ts index b8c5a9e1421..37895f1b27b 100644 --- a/src/preload/api/agent-account-api.ts +++ b/src/preload/api/agent-account-api.ts @@ -68,6 +68,12 @@ export type AntigravityAccountState = { updatedAt: number }[] activeAccountId: string | null + detectedAccount: { + id: string + email: string | null + subject: string | null + authMethod: string + } | null } export type AntigravityAccountsApi = { diff --git a/src/renderer/src/components/settings/AntigravityAccountsSection.test.tsx b/src/renderer/src/components/settings/AntigravityAccountsSection.test.tsx new file mode 100644 index 00000000000..0435196e9f4 --- /dev/null +++ b/src/renderer/src/components/settings/AntigravityAccountsSection.test.tsx @@ -0,0 +1,58 @@ +// @vitest-environment happy-dom + +import '@testing-library/jest-dom/vitest' + +import React from 'react' +import { cleanup, render, screen } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +const list = vi.hoisted(() => vi.fn()) + +vi.mock('@/i18n/i18n', () => ({ + translate: (_key: string, fallback: string) => fallback +})) + +vi.mock('../ui/button', () => ({ + Button: ({ children, ...props }: React.ComponentProps<'button'>) => + React.createElement('button', props, children) +})) + +vi.mock('../status-bar/icons', () => ({ + GeminiIcon: () => React.createElement('span', { 'data-testid': 'gemini-icon' }) +})) + +import { AntigravityAccountsSection } from './AntigravityAccountsSection' + +describe('AntigravityAccountsSection', () => { + beforeEach(() => { + list.mockResolvedValue({ + accounts: [], + activeAccountId: null, + detectedAccount: { + id: 'antigravity-detected', + email: 'detected@example.com', + subject: 'subject', + authMethod: 'consumer' + } + }) + Object.defineProperty(window, 'api', { + configurable: true, + value: { antigravityAccounts: { list } } + }) + }) + + afterEach(() => { + cleanup() + vi.clearAllMocks() + }) + + it('shows a detected account before explicit vault save', async () => { + render() + + expect(await screen.findByText('detected@example.com')).toBeInTheDocument() + expect( + screen.getByText('Detected on this computer; add it to enable switching') + ).toBeInTheDocument() + expect(screen.getByText('Add current agy account')).toBeInTheDocument() + }) +}) diff --git a/src/renderer/src/components/settings/AntigravityAccountsSection.tsx b/src/renderer/src/components/settings/AntigravityAccountsSection.tsx index f71c83af347..149bdb5e905 100644 --- a/src/renderer/src/components/settings/AntigravityAccountsSection.tsx +++ b/src/renderer/src/components/settings/AntigravityAccountsSection.tsx @@ -13,7 +13,8 @@ export function AntigravityAccountsSection({ }): React.JSX.Element { const [state, setState] = useState({ accounts: [], - activeAccountId: null + activeAccountId: null, + detectedAccount: null }) const [busy, setBusy] = useState(false) @@ -74,6 +75,29 @@ export function AntigravityAccountsSection({ ))}

)} + {state.detectedAccount && ( +
+
+

+ {state.detectedAccount.email ?? + state.detectedAccount.subject ?? + translate( + 'auto.components.settings.AccountsPane.antigravitySignedIn', + 'Signed-in Antigravity account' + )} +

+

+ {translate( + 'auto.components.settings.AccountsPane.antigravityDetected', + 'Detected on this computer; add it to enable switching' + )} +

+
+
+ )} {state.accounts.map((account) => (
Promise.reject(new Error('Antigravity account storage is only available in the desktop app.')) return {