From 3446ef01004a52d1bff6eb9705860f81afee7fb6 Mon Sep 17 00:00:00 2001
From: 777genius
Date: Fri, 17 Apr 2026 20:20:27 +0300
Subject: [PATCH] fix(extensions): harden hidden provider runtime handling
---
.../components/dashboard/CliStatusBanner.tsx | 9 +-
.../extensions/ExtensionStoreView.tsx | 113 ++++++++++--------
.../utils/multimodelProviderVisibility.ts | 32 +++++
.../cli/CliStatusVisibility.test.ts | 66 +++++++---
.../multimodelProviderVisibility.test.ts | 65 ++++++++++
5 files changed, 218 insertions(+), 67 deletions(-)
create mode 100644 src/renderer/utils/multimodelProviderVisibility.ts
create mode 100644 test/renderer/utils/multimodelProviderVisibility.test.ts
diff --git a/src/renderer/components/dashboard/CliStatusBanner.tsx b/src/renderer/components/dashboard/CliStatusBanner.tsx
index be2ffe20..ff41320d 100644
--- a/src/renderer/components/dashboard/CliStatusBanner.tsx
+++ b/src/renderer/components/dashboard/CliStatusBanner.tsx
@@ -33,6 +33,7 @@ import { useStore } from '@renderer/store';
import { createLoadingMultimodelCliStatus } from '@renderer/store/slices/cliInstallerSlice';
import { formatBytes } from '@renderer/utils/formatters';
import { filterMainScreenCliProviders } from '@renderer/utils/geminiUiFreeze';
+import { isMultimodelRuntimeStatus } from '@renderer/utils/multimodelProviderVisibility';
import {
AlertTriangle,
CheckCircle,
@@ -321,7 +322,11 @@ function formatRuntimeAuthSummary(
cliStatus: NonNullable['cliStatus']>,
visibleProviders: readonly CliProviderStatus[]
): string | null {
- if (cliStatus.flavor === 'agent_teams_orchestrator' && visibleProviders.length > 0) {
+ if (isMultimodelRuntimeStatus(cliStatus)) {
+ if (visibleProviders.length === 0) {
+ return null;
+ }
+
if (
visibleProviders.every(
(provider) => provider.statusMessage === 'Checking...' && !provider.authenticated
@@ -351,7 +356,7 @@ function isCheckingMultimodelStatus(
visibleProviders: readonly CliProviderStatus[]
): boolean {
return (
- cliStatus.flavor === 'agent_teams_orchestrator' &&
+ isMultimodelRuntimeStatus(cliStatus) &&
visibleProviders.length > 0 &&
visibleProviders.every(
(provider) => provider.statusMessage === 'Checking...' && !provider.authenticated
diff --git a/src/renderer/components/extensions/ExtensionStoreView.tsx b/src/renderer/components/extensions/ExtensionStoreView.tsx
index 4d76908b..ffc9ff4c 100644
--- a/src/renderer/components/extensions/ExtensionStoreView.tsx
+++ b/src/renderer/components/extensions/ExtensionStoreView.tsx
@@ -20,6 +20,11 @@ import {
import { useTabIdOptional } from '@renderer/contexts/useTabUIContext';
import { useExtensionsTabState } from '@renderer/hooks/useExtensionsTabState';
import { useStore } from '@renderer/store';
+import {
+ formatCliExtensionCapabilityStatus,
+ getVisibleMultimodelProviders,
+ isMultimodelRuntimeStatus,
+} from '@renderer/utils/multimodelProviderVisibility';
import { resolveProjectPathById } from '@renderer/utils/projectLookup';
import { getExtensionActionDisableReason } from '@shared/utils/extensionNormalizers';
import { AlertTriangle, BookOpen, Info, Key, Plus, Puzzle, RefreshCw, Server } from 'lucide-react';
@@ -178,9 +183,8 @@ export const ExtensionStoreView = (): React.JSX.Element => {
);
const cliStatusBanner = useMemo(() => {
const providers = cliStatus?.providers ?? [];
- const visibleProviders = providers.filter((provider) => provider.providerId !== 'gemini');
- const isMultimodel =
- cliStatus?.flavor === 'agent_teams_orchestrator' && visibleProviders.length > 0;
+ const visibleProviders = getVisibleMultimodelProviders(providers);
+ const isMultimodel = isMultimodelRuntimeStatus(cliStatus);
if (cliStatusLoading || cliStatus === null) {
return (
@@ -260,57 +264,64 @@ export const ExtensionStoreView = (): React.JSX.Element => {
-
- {visibleProviders.map((provider) => {
- const statusTone = provider.authenticated
- ? 'border-emerald-500/30 bg-emerald-500/5 text-emerald-300'
- : provider.supported
- ? 'border-amber-500/30 bg-amber-500/5 text-amber-300'
- : 'border-border bg-surface-raised text-text-muted';
- const statusLabel = provider.authenticated
- ? 'Connected'
- : provider.supported
- ? 'Needs setup'
- : 'Unsupported';
- const pluginStatus = provider.capabilities.extensions.plugins.status;
+ {visibleProviders.length > 0 && (
+
+ {visibleProviders.map((provider) => {
+ const statusTone = provider.authenticated
+ ? 'border-emerald-500/30 bg-emerald-500/5 text-emerald-300'
+ : provider.supported
+ ? 'border-amber-500/30 bg-amber-500/5 text-amber-300'
+ : 'border-border bg-surface-raised text-text-muted';
+ const statusLabel = provider.authenticated
+ ? 'Connected'
+ : provider.supported
+ ? 'Needs setup'
+ : 'Unsupported';
+ const pluginStatus = provider.capabilities.extensions.plugins.status;
- return (
-
-
-
-
-
- {provider.displayName}
-
-
- {provider.statusMessage ?? provider.backend?.label ?? 'Ready to configure'}
-
+ return (
+
+
+
+
+
+ {provider.displayName}
+
+
+ {provider.statusMessage ??
+ provider.backend?.label ??
+ 'Ready to configure'}
+
+
+
+ {statusLabel}
+
+
+
+
+ Plugins: {formatCliExtensionCapabilityStatus(pluginStatus)}
+
+
+ MCP:{' '}
+ {formatCliExtensionCapabilityStatus(
+ provider.capabilities.extensions.mcp.status
+ )}
+
+
+ Skills: {provider.capabilities.extensions.skills.ownership}
+
-
- {statusLabel}
-
-
-
- Plugins: {pluginStatus === 'supported' ? 'supported' : 'limited'}
-
-
- MCP: {provider.capabilities.extensions.mcp.status}
-
-
- Skills: {provider.capabilities.extensions.skills.ownership}
-
-
-
- );
- })}
-
+ );
+ })}
+
+ )}
);
}
diff --git a/src/renderer/utils/multimodelProviderVisibility.ts b/src/renderer/utils/multimodelProviderVisibility.ts
new file mode 100644
index 00000000..8fa4d753
--- /dev/null
+++ b/src/renderer/utils/multimodelProviderVisibility.ts
@@ -0,0 +1,32 @@
+import { filterMainScreenCliProviders } from './geminiUiFreeze';
+
+import type {
+ CliExtensionCapability,
+ CliInstallationStatus,
+ CliProviderStatus,
+} from '@shared/types';
+
+export function getVisibleMultimodelProviders(
+ providers: readonly CliProviderStatus[]
+): CliProviderStatus[] {
+ return filterMainScreenCliProviders(providers);
+}
+
+export function isMultimodelRuntimeStatus(
+ cliStatus: Pick
| null | undefined
+): boolean {
+ return cliStatus?.flavor === 'agent_teams_orchestrator' && (cliStatus.providers?.length ?? 0) > 0;
+}
+
+export function formatCliExtensionCapabilityStatus(
+ status: CliExtensionCapability['status']
+): string {
+ switch (status) {
+ case 'supported':
+ return 'supported';
+ case 'read-only':
+ return 'read-only';
+ default:
+ return 'unsupported';
+ }
+}
diff --git a/test/renderer/components/cli/CliStatusVisibility.test.ts b/test/renderer/components/cli/CliStatusVisibility.test.ts
index 96527c46..d383257d 100644
--- a/test/renderer/components/cli/CliStatusVisibility.test.ts
+++ b/test/renderer/components/cli/CliStatusVisibility.test.ts
@@ -170,7 +170,8 @@ function createApiKeyMisconfiguredProvider(
connection: {
supportsOAuth: true,
supportsApiKey: true,
- configurableAuthModes: providerId === 'anthropic' ? ['auto', 'oauth', 'api_key'] : ['oauth', 'api_key'],
+ configurableAuthModes:
+ providerId === 'anthropic' ? ['auto', 'oauth', 'api_key'] : ['oauth', 'api_key'],
configuredAuthMode: 'api_key',
apiKeyBetaAvailable: providerId === 'codex' ? true : undefined,
apiKeyBetaEnabled: providerId === 'codex' ? true : undefined,
@@ -181,9 +182,7 @@ function createApiKeyMisconfiguredProvider(
};
}
-function createApiKeyModeProviderIssue(
- providerId: 'anthropic' | 'codex'
-): Record {
+function createApiKeyModeProviderIssue(providerId: 'anthropic' | 'codex'): Record {
return {
...createApiKeyMisconfiguredProvider(providerId),
statusMessage:
@@ -191,8 +190,8 @@ function createApiKeyModeProviderIssue(
? 'Anthropic API key was rejected by the runtime.'
: 'OpenAI API key was rejected by the runtime.',
connection: {
- ...((createApiKeyMisconfiguredProvider(providerId) as { connection: Record })
- .connection),
+ ...(createApiKeyMisconfiguredProvider(providerId) as { connection: Record })
+ .connection,
apiKeyConfigured: true,
apiKeySource: 'stored',
apiKeySourceLabel:
@@ -287,9 +286,7 @@ describe('CLI status visibility during completed install state', () => {
it('preserves dashboard runtime backend refresh errors for the manage dialog', async () => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
storeState.cliInstallerState = 'idle';
- storeState.fetchCliProviderStatus = vi.fn(() =>
- Promise.reject(new Error('refresh failed'))
- );
+ storeState.fetchCliProviderStatus = vi.fn(() => Promise.reject(new Error('refresh failed')));
const host = document.createElement('div');
document.body.appendChild(host);
@@ -345,6 +342,49 @@ describe('CLI status visibility during completed install state', () => {
});
});
+ it('does not fall back to direct-Claude auth copy when only hidden multimodel providers are available', async () => {
+ vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
+ storeState.cliInstallerState = 'idle';
+ storeState.cliStatus = createInstalledCliStatus({
+ flavor: 'agent_teams_orchestrator',
+ authLoggedIn: true,
+ providers: [
+ {
+ providerId: 'gemini',
+ displayName: 'Gemini',
+ supported: true,
+ authenticated: true,
+ authMethod: 'cli_oauth_personal',
+ verificationState: 'verified',
+ statusMessage: 'Resolved to CLI SDK',
+ models: [],
+ canLoginFromUi: true,
+ capabilities: {
+ teamLaunch: true,
+ oneShot: true,
+ },
+ },
+ ],
+ });
+
+ const host = document.createElement('div');
+ document.body.appendChild(host);
+ const root = createRoot(host);
+
+ await act(async () => {
+ root.render(React.createElement(CliStatusBanner));
+ await Promise.resolve();
+ });
+
+ expect(host.textContent).not.toContain('Authenticated');
+ expect(host.textContent).not.toContain('Providers:');
+
+ await act(async () => {
+ root.unmount();
+ await Promise.resolve();
+ });
+ });
+
it('shows a degraded runtime warning when a binary is found but the health check fails', async () => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
storeState.cliInstallerState = 'idle';
@@ -413,9 +453,7 @@ describe('CLI status visibility during completed install state', () => {
it('preserves settings runtime backend refresh errors for the manage dialog', async () => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
storeState.cliInstallerState = 'idle';
- storeState.fetchCliProviderStatus = vi.fn(() =>
- Promise.reject(new Error('refresh failed'))
- );
+ storeState.fetchCliProviderStatus = vi.fn(() => Promise.reject(new Error('refresh failed')));
const host = document.createElement('div');
document.body.appendChild(host);
@@ -490,8 +528,8 @@ describe('CLI status visibility during completed install state', () => {
expect(host.textContent).not.toContain('Already logged in?');
expect(host.textContent).not.toContain('Login');
- const manageButton = Array.from(host.querySelectorAll('button')).find(
- (button) => button.textContent?.includes('Manage Providers')
+ const manageButton = Array.from(host.querySelectorAll('button')).find((button) =>
+ button.textContent?.includes('Manage Providers')
);
expect(manageButton).not.toBeUndefined();
diff --git a/test/renderer/utils/multimodelProviderVisibility.test.ts b/test/renderer/utils/multimodelProviderVisibility.test.ts
new file mode 100644
index 00000000..47cac0f4
--- /dev/null
+++ b/test/renderer/utils/multimodelProviderVisibility.test.ts
@@ -0,0 +1,65 @@
+import { describe, expect, it } from 'vitest';
+
+import {
+ formatCliExtensionCapabilityStatus,
+ getVisibleMultimodelProviders,
+ isMultimodelRuntimeStatus,
+} from '@renderer/utils/multimodelProviderVisibility';
+
+import type { CliInstallationStatus, CliProviderStatus } from '@shared/types';
+
+function createProvider(providerId: CliProviderStatus['providerId']): CliProviderStatus {
+ return {
+ providerId,
+ displayName:
+ providerId === 'anthropic' ? 'Anthropic' : providerId === 'codex' ? 'Codex' : 'Gemini',
+ supported: true,
+ authenticated: true,
+ authMethod: 'oauth_token',
+ verificationState: 'verified',
+ canLoginFromUi: true,
+ capabilities: {
+ teamLaunch: true,
+ oneShot: true,
+ },
+ statusMessage: null,
+ detailMessage: null,
+ selectedBackendId: null,
+ resolvedBackendId: null,
+ availableBackends: [],
+ externalRuntimeDiagnostics: [],
+ models: [],
+ backend: null,
+ connection: null,
+ };
+}
+
+describe('multimodelProviderVisibility', () => {
+ it('keeps multimodel runtime detection true even when all visible provider cards are hidden', () => {
+ const cliStatus = {
+ flavor: 'agent_teams_orchestrator',
+ providers: [createProvider('gemini')],
+ } satisfies Pick;
+
+ expect(isMultimodelRuntimeStatus(cliStatus)).toBe(true);
+ expect(getVisibleMultimodelProviders(cliStatus.providers)).toHaveLength(0);
+ });
+
+ it('filters Gemini from the visible provider cards while keeping supported providers', () => {
+ const providers = [
+ createProvider('anthropic'),
+ createProvider('codex'),
+ createProvider('gemini'),
+ ];
+
+ expect(getVisibleMultimodelProviders(providers).map((provider) => provider.providerId)).toEqual(
+ ['anthropic', 'codex']
+ );
+ });
+
+ it('formats capability statuses without collapsing read-only into a vague limited label', () => {
+ expect(formatCliExtensionCapabilityStatus('supported')).toBe('supported');
+ expect(formatCliExtensionCapabilityStatus('read-only')).toBe('read-only');
+ expect(formatCliExtensionCapabilityStatus('unsupported')).toBe('unsupported');
+ });
+});