From 6eb4c0803b071c0daed1768951de95e8dcbf8fd7 Mon Sep 17 00:00:00 2001
From: Teknium <127238744+teknium1@users.noreply.github.com>
Date: Mon, 7 Sep 2026 02:18:06 -0700
Subject: [PATCH] fix(desktop): limit credential selector to API keys and cover
target changes
---
.../app/settings/providers-settings.test.tsx | 31 ++++++++++++++++++-
.../src/app/settings/providers-settings.tsx | 5 +--
2 files changed, 31 insertions(+), 5 deletions(-)
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)}