diff --git a/apps/desktop/src/app/settings/providers-settings.test.tsx b/apps/desktop/src/app/settings/providers-settings.test.tsx index 634158ff26..8e0342c3ff 100644 --- a/apps/desktop/src/app/settings/providers-settings.test.tsx +++ b/apps/desktop/src/app/settings/providers-settings.test.tsx @@ -9,13 +9,15 @@ import type { EnvVarInfo, OAuthProvider } from '@/types/hermes' const listOAuthProviders = vi.fn() const disconnectOAuthProvider = vi.fn() const getEnvVars = vi.fn() +const setEnvVar = vi.fn() const startManualProviderOAuth = vi.fn() const startManualLocalEndpoint = vi.fn() const onboarding = atom({ manual: false }) vi.mock('@/hermes', () => ({ disconnectOAuthProvider: (providerId: string) => disconnectOAuthProvider(providerId), - getEnvVars: () => getEnvVars(), + getEnvVars: (profile?: string) => getEnvVars(profile), + setEnvVar: (key: string, value: string, profile?: string) => setEnvVar(key, value, profile), listOAuthProviders: () => listOAuthProviders() })) @@ -93,6 +95,33 @@ async function renderProvidersSettings() { } describe('ProvidersSettings', () => { + it('reads and saves API keys for the shared Settings target and reloads when it changes', async () => { + const { $settingsScopeOverride } = await import('@/store/settings-scope') + const { $activeGatewayProfile, $profiles } = await import('@/store/profile') + $activeGatewayProfile.set('profile-a') + $settingsScopeOverride.set('profile-b') + $profiles.set([{ name: 'profile-a' }, { name: 'profile-b' }] as never) + getEnvVars.mockResolvedValue({ WIDGET_API_KEY: keyVar({ provider: 'widget', provider_label: 'Widget' }) }) + const { ProvidersSettings } = await import('./providers-settings') + try { + render() + await screen.findByText('Widget') + expect(getEnvVars).toHaveBeenLastCalledWith('profile-b') + expect(screen.getByText('Applies to')).toBeTruthy() + const input = document.querySelector('input[type="password"]')! + fireEvent.focus(input) + fireEvent.change(input, { target: { value: 'fixture-key' } }) + fireEvent.click(screen.getByRole('button', { name: 'Save' })) + await waitFor(() => expect(setEnvVar).toHaveBeenCalledWith('WIDGET_API_KEY', 'fixture-key', 'profile-b')) + fireEvent.click(screen.getByRole('button', { name: 'profile-a' })) + await waitFor(() => expect(getEnvVars).toHaveBeenLastCalledWith(undefined)) + } finally { + cleanup() + $settingsScopeOverride.set(null) + $activeGatewayProfile.set('default') + $profiles.set([]) + } + }) it('disconnects a connected provider account and refreshes the accounts list', async () => { await renderProvidersSettings() diff --git a/apps/desktop/src/app/settings/providers-settings.tsx b/apps/desktop/src/app/settings/providers-settings.tsx index b492ffce19..0417431898 100644 --- a/apps/desktop/src/app/settings/providers-settings.tsx +++ b/apps/desktop/src/app/settings/providers-settings.tsx @@ -349,9 +349,7 @@ export function ProvidersSettings({ view }: ProvidersSettingsProps) { const { t } = useI18n() - // Shared settings "Applies to" scope: read + edit the selected profile's - // env store instead of the active one. Undefined → active profile. - // See https://github.com/NousResearch/hermes-agent/issues/103993 + // Match the shared Settings target for credential reads and writes. const scopeProfile = useStore($settingsRequestProfile) const { rowProps, vars } = useEnvCredentials(scopeProfile) const [oauthProviders, setOauthProviders] = useState([]) @@ -530,7 +528,6 @@ export function ProvidersSettings({ return ( - void handleDisconnect(provider)}