feat(antigravity): show detected native account (#21798)

This commit is contained in:
Neil
2026-09-20 18:04:24 -07:00
committed by GitHub
parent 27a3f16248
commit 21ed60207d
7 changed files with 121 additions and 5 deletions
@@ -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)
+19 -3
View File
@@ -31,6 +31,7 @@ export type AntigravityCredentialBackend = {
export type AntigravityAccountState = {
accounts: AntigravityAccountSummary[]
activeAccountId: string | null
detectedAccount: Pick<AntigravityAccountSummary, 'id' | 'email' | 'subject' | 'authMethod'> | null
}
function accountId(contents: string): string {
@@ -58,9 +59,24 @@ export class AntigravityAccountService {
async listAccounts(): Promise<AntigravityAccountState> {
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<AntigravityAccountState> {
@@ -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<AntigravityAccountState> {
+6
View File
@@ -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 = {
@@ -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(<AntigravityAccountsSection quota={null} />)
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()
})
})
@@ -13,7 +13,8 @@ export function AntigravityAccountsSection({
}): React.JSX.Element {
const [state, setState] = useState<AntigravityAccountState>({
accounts: [],
activeAccountId: null
activeAccountId: null,
detectedAccount: null
})
const [busy, setBusy] = useState(false)
@@ -74,6 +75,29 @@ export function AntigravityAccountsSection({
))}
</p>
)}
{state.detectedAccount && (
<div
className="flex items-center justify-between gap-3 rounded-md border border-dashed border-border px-3 py-2"
data-detected-account={state.detectedAccount.id}
>
<div className="min-w-0">
<p className="truncate text-sm">
{state.detectedAccount.email ??
state.detectedAccount.subject ??
translate(
'auto.components.settings.AccountsPane.antigravitySignedIn',
'Signed-in Antigravity account'
)}
</p>
<p className="text-xs text-muted-foreground">
{translate(
'auto.components.settings.AccountsPane.antigravityDetected',
'Detected on this computer; add it to enable switching'
)}
</p>
</div>
</div>
)}
{state.accounts.map((account) => (
<div
key={account.id}
+1
View File
@@ -6593,6 +6593,7 @@
"antigravityQuotaWindows": "{{value0}} model windows available",
"antigravityUnavailable": "Unavailable",
"antigravitySignedIn": "Signed-in Antigravity account",
"antigravityDetected": "Detected on this computer; add it to enable switching",
"antigravityActive": "Active",
"antigravityUse": "Use",
"antigravityRemove": "Remove",
@@ -52,7 +52,7 @@ export function createClaudeAccountsApi(): PreloadApi['claudeAccounts'] {
}
export function createAntigravityAccountsApi(): PreloadApi['antigravityAccounts'] {
const empty = { accounts: [], activeAccountId: null }
const empty = { accounts: [], activeAccountId: null, detectedAccount: null }
const unsupported = () =>
Promise.reject(new Error('Antigravity account storage is only available in the desktop app.'))
return {