feat(runtime): surface codex-native internal rollout states
This commit is contained in:
parent
ba37c1caf5
commit
30fce3c64d
7 changed files with 346 additions and 17 deletions
|
|
@ -88,6 +88,14 @@ interface UnifiedRuntimeStatusResponse {
|
||||||
selectable?: boolean;
|
selectable?: boolean;
|
||||||
recommended?: boolean;
|
recommended?: boolean;
|
||||||
available?: boolean;
|
available?: boolean;
|
||||||
|
state?:
|
||||||
|
| 'ready'
|
||||||
|
| 'locked'
|
||||||
|
| 'disabled'
|
||||||
|
| 'authentication-required'
|
||||||
|
| 'runtime-missing'
|
||||||
|
| 'degraded';
|
||||||
|
audience?: 'general' | 'internal';
|
||||||
statusMessage?: string | null;
|
statusMessage?: string | null;
|
||||||
detailMessage?: string | null;
|
detailMessage?: string | null;
|
||||||
}[];
|
}[];
|
||||||
|
|
@ -270,6 +278,8 @@ export class ClaudeMultimodelBridgeService {
|
||||||
selectable: backend.selectable !== false,
|
selectable: backend.selectable !== false,
|
||||||
recommended: backend.recommended === true,
|
recommended: backend.recommended === true,
|
||||||
available: backend.available === true,
|
available: backend.available === true,
|
||||||
|
state: backend.state ?? undefined,
|
||||||
|
audience: backend.audience ?? undefined,
|
||||||
statusMessage: backend.statusMessage ?? null,
|
statusMessage: backend.statusMessage ?? null,
|
||||||
detailMessage: backend.detailMessage ?? null,
|
detailMessage: backend.detailMessage ?? null,
|
||||||
})) ?? [],
|
})) ?? [],
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,39 @@ interface Props {
|
||||||
onSelect: (providerId: CliProviderStatus['providerId'], backendId: string) => void;
|
onSelect: (providerId: CliProviderStatus['providerId'], backendId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getProviderRuntimeBackendStateLabel(
|
||||||
|
option: NonNullable<CliProviderStatus['availableBackends']>[number]
|
||||||
|
): string | null {
|
||||||
|
switch (option.state) {
|
||||||
|
case 'ready':
|
||||||
|
return null;
|
||||||
|
case 'locked':
|
||||||
|
return 'Locked';
|
||||||
|
case 'disabled':
|
||||||
|
return 'Disabled';
|
||||||
|
case 'authentication-required':
|
||||||
|
return 'Auth required';
|
||||||
|
case 'runtime-missing':
|
||||||
|
return 'Runtime missing';
|
||||||
|
case 'degraded':
|
||||||
|
return 'Degraded';
|
||||||
|
default:
|
||||||
|
if (!option.available) {
|
||||||
|
return 'Unavailable';
|
||||||
|
}
|
||||||
|
if (option.selectable === false) {
|
||||||
|
return 'Locked';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getProviderRuntimeBackendAudienceLabel(
|
||||||
|
option: NonNullable<CliProviderStatus['availableBackends']>[number]
|
||||||
|
): string | null {
|
||||||
|
return option.audience === 'internal' ? 'Internal' : null;
|
||||||
|
}
|
||||||
|
|
||||||
export function getOptionDisplayLabel(
|
export function getOptionDisplayLabel(
|
||||||
option: NonNullable<CliProviderStatus['availableBackends']>[number],
|
option: NonNullable<CliProviderStatus['availableBackends']>[number],
|
||||||
resolvedOption: NonNullable<CliProviderStatus['availableBackends']>[number] | null
|
resolvedOption: NonNullable<CliProviderStatus['availableBackends']>[number] | null
|
||||||
|
|
@ -44,8 +77,18 @@ export function getProviderRuntimeBackendSummary(provider: CliProviderStatus): s
|
||||||
const selectedBackendId = provider.selectedBackendId ?? options[0]?.id ?? '';
|
const selectedBackendId = provider.selectedBackendId ?? options[0]?.id ?? '';
|
||||||
const selectedOption = options.find((option) => option.id === selectedBackendId) ?? options[0];
|
const selectedOption = options.find((option) => option.id === selectedBackendId) ?? options[0];
|
||||||
const resolvedOption = options.find((option) => option.id === provider.resolvedBackendId) ?? null;
|
const resolvedOption = options.find((option) => option.id === provider.resolvedBackendId) ?? null;
|
||||||
|
const parts = [getOptionDisplayLabel(selectedOption, resolvedOption)];
|
||||||
|
const audienceLabel = getProviderRuntimeBackendAudienceLabel(selectedOption);
|
||||||
|
const stateLabel = getProviderRuntimeBackendStateLabel(selectedOption);
|
||||||
|
|
||||||
return getOptionDisplayLabel(selectedOption, resolvedOption);
|
if (audienceLabel) {
|
||||||
|
parts.push(audienceLabel.toLowerCase());
|
||||||
|
}
|
||||||
|
if (stateLabel) {
|
||||||
|
parts.push(stateLabel.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.join(' - ');
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProviderRuntimeBackendSelector = ({
|
export const ProviderRuntimeBackendSelector = ({
|
||||||
|
|
@ -62,6 +105,8 @@ export const ProviderRuntimeBackendSelector = ({
|
||||||
const selectedOption = options.find((option) => option.id === selectedBackendId) ?? options[0];
|
const selectedOption = options.find((option) => option.id === selectedBackendId) ?? options[0];
|
||||||
const resolvedOption = options.find((option) => option.id === provider.resolvedBackendId) ?? null;
|
const resolvedOption = options.find((option) => option.id === provider.resolvedBackendId) ?? null;
|
||||||
const selectedLabel = getOptionDisplayLabel(selectedOption, resolvedOption);
|
const selectedLabel = getOptionDisplayLabel(selectedOption, resolvedOption);
|
||||||
|
const selectedStateLabel = getProviderRuntimeBackendStateLabel(selectedOption);
|
||||||
|
const selectedAudienceLabel = getProviderRuntimeBackendAudienceLabel(selectedOption);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-2 space-y-2.5">
|
<div className="mt-2 space-y-2.5">
|
||||||
|
|
@ -120,25 +165,40 @@ export const ProviderRuntimeBackendSelector = ({
|
||||||
Recommended
|
Recommended
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
{!option.available ? (
|
{getProviderRuntimeBackendAudienceLabel(option) ? (
|
||||||
<span
|
<span
|
||||||
className="shrink-0 rounded-full px-1.5 py-0.5 text-[10px]"
|
className="shrink-0 rounded-full px-1.5 py-0.5 text-[10px]"
|
||||||
style={{
|
style={{
|
||||||
color: '#fca5a5',
|
color: '#93c5fd',
|
||||||
backgroundColor: 'rgba(248, 113, 113, 0.14)',
|
backgroundColor: 'rgba(59, 130, 246, 0.14)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Unavailable
|
{getProviderRuntimeBackendAudienceLabel(option)}
|
||||||
</span>
|
</span>
|
||||||
) : option.selectable === false ? (
|
) : null}
|
||||||
|
{getProviderRuntimeBackendStateLabel(option) ? (
|
||||||
<span
|
<span
|
||||||
className="shrink-0 rounded-full px-1.5 py-0.5 text-[10px]"
|
className="shrink-0 rounded-full px-1.5 py-0.5 text-[10px]"
|
||||||
style={{
|
style={{
|
||||||
color: 'var(--color-text-secondary)',
|
color:
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
option.state === 'disabled' ||
|
||||||
|
option.state === 'authentication-required' ||
|
||||||
|
option.state === 'runtime-missing' ||
|
||||||
|
option.state === 'degraded' ||
|
||||||
|
(!option.available && option.state !== 'locked')
|
||||||
|
? '#fca5a5'
|
||||||
|
: 'var(--color-text-secondary)',
|
||||||
|
backgroundColor:
|
||||||
|
option.state === 'disabled' ||
|
||||||
|
option.state === 'authentication-required' ||
|
||||||
|
option.state === 'runtime-missing' ||
|
||||||
|
option.state === 'degraded' ||
|
||||||
|
(!option.available && option.state !== 'locked')
|
||||||
|
? 'rgba(248, 113, 113, 0.14)'
|
||||||
|
: 'rgba(255, 255, 255, 0.08)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Locked
|
{getProviderRuntimeBackendStateLabel(option)}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -173,7 +233,18 @@ export const ProviderRuntimeBackendSelector = ({
|
||||||
Recommended
|
Recommended
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
{!selectedOption.available ? (
|
{selectedAudienceLabel ? (
|
||||||
|
<span
|
||||||
|
className="rounded-full px-1.5 py-0.5 text-[10px]"
|
||||||
|
style={{
|
||||||
|
color: '#93c5fd',
|
||||||
|
backgroundColor: 'rgba(59, 130, 246, 0.14)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{selectedAudienceLabel}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{!selectedStateLabel && !selectedOption.available ? (
|
||||||
<TooltipProvider delayDuration={150}>
|
<TooltipProvider delayDuration={150}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
|
|
@ -192,18 +263,24 @@ export const ProviderRuntimeBackendSelector = ({
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
) : selectedOption.selectable === false ? (
|
) : selectedStateLabel ? (
|
||||||
<TooltipProvider delayDuration={150}>
|
<TooltipProvider delayDuration={150}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<span
|
<span
|
||||||
className="cursor-help rounded-full px-1.5 py-0.5 text-[10px]"
|
className="cursor-help rounded-full px-1.5 py-0.5 text-[10px]"
|
||||||
style={{
|
style={{
|
||||||
color: 'var(--color-text-secondary)',
|
color:
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
selectedOption.state === 'locked'
|
||||||
|
? 'var(--color-text-secondary)'
|
||||||
|
: '#fca5a5',
|
||||||
|
backgroundColor:
|
||||||
|
selectedOption.state === 'locked'
|
||||||
|
? 'rgba(255, 255, 255, 0.08)'
|
||||||
|
: 'rgba(248, 113, 113, 0.14)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Locked
|
{selectedStateLabel}
|
||||||
</span>
|
</span>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
|
|
|
||||||
|
|
@ -46,12 +46,43 @@ export function getProvisioningProviderBackendSummary(
|
||||||
const options = provider.availableBackends ?? [];
|
const options = provider.availableBackends ?? [];
|
||||||
const optionById = new Map(options.map((option) => [option.id, option.label]));
|
const optionById = new Map(options.map((option) => [option.id, option.label]));
|
||||||
const effectiveBackendId = provider.resolvedBackendId ?? provider.selectedBackendId;
|
const effectiveBackendId = provider.resolvedBackendId ?? provider.selectedBackendId;
|
||||||
|
const effectiveOption = options.find((option) => option.id === effectiveBackendId) ?? null;
|
||||||
|
|
||||||
if (effectiveBackendId) {
|
const baseSummary = effectiveBackendId
|
||||||
return optionById.get(effectiveBackendId) ?? provider.backend?.label ?? effectiveBackendId;
|
? (optionById.get(effectiveBackendId) ?? provider.backend?.label ?? effectiveBackendId)
|
||||||
|
: (provider.backend?.label ?? null);
|
||||||
|
|
||||||
|
if (!baseSummary) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider.backend?.label ?? null;
|
const suffixes: string[] = [];
|
||||||
|
if (effectiveOption?.audience === 'internal') {
|
||||||
|
suffixes.push('internal');
|
||||||
|
}
|
||||||
|
if (effectiveOption?.state && effectiveOption.state !== 'ready') {
|
||||||
|
switch (effectiveOption.state) {
|
||||||
|
case 'locked':
|
||||||
|
suffixes.push('locked');
|
||||||
|
break;
|
||||||
|
case 'disabled':
|
||||||
|
suffixes.push('disabled');
|
||||||
|
break;
|
||||||
|
case 'authentication-required':
|
||||||
|
suffixes.push('auth required');
|
||||||
|
break;
|
||||||
|
case 'runtime-missing':
|
||||||
|
suffixes.push('runtime missing');
|
||||||
|
break;
|
||||||
|
case 'degraded':
|
||||||
|
suffixes.push('degraded');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return suffixes.length > 0 ? `${baseSummary} - ${suffixes.join(', ')}` : baseSummary;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateProviderCheck(
|
export function updateProviderCheck(
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,14 @@ export interface CliProviderBackendOption {
|
||||||
selectable: boolean;
|
selectable: boolean;
|
||||||
recommended: boolean;
|
recommended: boolean;
|
||||||
available: boolean;
|
available: boolean;
|
||||||
|
state?:
|
||||||
|
| 'ready'
|
||||||
|
| 'locked'
|
||||||
|
| 'disabled'
|
||||||
|
| 'authentication-required'
|
||||||
|
| 'runtime-missing'
|
||||||
|
| 'degraded';
|
||||||
|
audience?: 'general' | 'internal';
|
||||||
statusMessage?: string | null;
|
statusMessage?: string | null;
|
||||||
detailMessage?: string | null;
|
detailMessage?: string | null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -346,6 +346,8 @@ describe('ClaudeMultimodelBridgeService', () => {
|
||||||
selectable: false,
|
selectable: false,
|
||||||
recommended: false,
|
recommended: false,
|
||||||
available: true,
|
available: true,
|
||||||
|
state: 'locked',
|
||||||
|
audience: 'internal',
|
||||||
statusMessage: 'Experimental native lane',
|
statusMessage: 'Experimental native lane',
|
||||||
detailMessage: 'Phase 0 keeps the lane locked behind rollout policy.',
|
detailMessage: 'Phase 0 keeps the lane locked behind rollout policy.',
|
||||||
},
|
},
|
||||||
|
|
@ -422,6 +424,8 @@ describe('ClaudeMultimodelBridgeService', () => {
|
||||||
id: 'codex-native',
|
id: 'codex-native',
|
||||||
selectable: false,
|
selectable: false,
|
||||||
available: true,
|
available: true,
|
||||||
|
state: 'locked',
|
||||||
|
audience: 'internal',
|
||||||
statusMessage: 'Experimental native lane',
|
statusMessage: 'Experimental native lane',
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
@ -439,4 +443,67 @@ describe('ClaudeMultimodelBridgeService', () => {
|
||||||
expect(getProviderConnectionModeSummary(codex!)).toBeNull();
|
expect(getProviderConnectionModeSummary(codex!)).toBeNull();
|
||||||
expect(getProviderCurrentRuntimeSummary(codex!)).toBeNull();
|
expect(getProviderCurrentRuntimeSummary(codex!)).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('preserves codex-native internal unlock readiness from runtime status payloads', async () => {
|
||||||
|
execCliMock.mockResolvedValue({
|
||||||
|
stdout: JSON.stringify({
|
||||||
|
providers: {
|
||||||
|
codex: {
|
||||||
|
supported: true,
|
||||||
|
authenticated: true,
|
||||||
|
authMethod: 'api_key',
|
||||||
|
verificationState: 'verified',
|
||||||
|
canLoginFromUi: false,
|
||||||
|
selectedBackendId: 'codex-native',
|
||||||
|
resolvedBackendId: 'codex-native',
|
||||||
|
availableBackends: [
|
||||||
|
{
|
||||||
|
id: 'codex-native',
|
||||||
|
label: 'Codex native',
|
||||||
|
selectable: true,
|
||||||
|
recommended: false,
|
||||||
|
available: true,
|
||||||
|
state: 'ready',
|
||||||
|
audience: 'internal',
|
||||||
|
statusMessage: 'Ready for internal use',
|
||||||
|
detailMessage: 'Internal rollout only.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
capabilities: {
|
||||||
|
teamLaunch: true,
|
||||||
|
oneShot: true,
|
||||||
|
extensions: {
|
||||||
|
plugins: { status: 'unsupported', ownership: 'shared', reason: 'Phase 1' },
|
||||||
|
mcp: { status: 'unsupported', ownership: 'shared', reason: 'Phase 1' },
|
||||||
|
skills: { status: 'unsupported', ownership: 'shared', reason: 'Phase 1' },
|
||||||
|
apiKeys: { status: 'supported', ownership: 'shared', reason: null },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
backend: {
|
||||||
|
kind: 'codex-native',
|
||||||
|
label: 'Codex native',
|
||||||
|
authMethodDetail: 'api_key',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
stderr: '',
|
||||||
|
exitCode: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { ClaudeMultimodelBridgeService } =
|
||||||
|
await import('@main/services/runtime/ClaudeMultimodelBridgeService');
|
||||||
|
const service = new ClaudeMultimodelBridgeService();
|
||||||
|
|
||||||
|
const codex = await service.getProviderStatus('/mock/agent_teams_orchestrator', 'codex');
|
||||||
|
|
||||||
|
expect(codex.availableBackends?.find((backend) => backend.id === 'codex-native')).toMatchObject({
|
||||||
|
id: 'codex-native',
|
||||||
|
selectable: true,
|
||||||
|
available: true,
|
||||||
|
state: 'ready',
|
||||||
|
audience: 'internal',
|
||||||
|
statusMessage: 'Ready for internal use',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getProviderRuntimeBackendAudienceLabel,
|
||||||
|
getProviderRuntimeBackendStateLabel,
|
||||||
|
getProviderRuntimeBackendSummary,
|
||||||
|
} from '@renderer/components/runtime/ProviderRuntimeBackendSelector';
|
||||||
|
import { createDefaultCliExtensionCapabilities } from '@shared/utils/providerExtensionCapabilities';
|
||||||
|
|
||||||
|
import type { CliProviderStatus } from '@shared/types';
|
||||||
|
|
||||||
|
function createCodexProvider(
|
||||||
|
overrides?: Partial<CliProviderStatus>
|
||||||
|
): CliProviderStatus {
|
||||||
|
return {
|
||||||
|
providerId: 'codex',
|
||||||
|
displayName: 'Codex',
|
||||||
|
supported: true,
|
||||||
|
authenticated: true,
|
||||||
|
authMethod: 'oauth_token',
|
||||||
|
verificationState: 'verified',
|
||||||
|
statusMessage: 'Connected',
|
||||||
|
models: ['gpt-5-codex'],
|
||||||
|
canLoginFromUi: true,
|
||||||
|
capabilities: {
|
||||||
|
teamLaunch: true,
|
||||||
|
oneShot: true,
|
||||||
|
extensions: createDefaultCliExtensionCapabilities(),
|
||||||
|
},
|
||||||
|
selectedBackendId: 'codex-native',
|
||||||
|
resolvedBackendId: 'codex-native',
|
||||||
|
availableBackends: [
|
||||||
|
{
|
||||||
|
id: 'auto',
|
||||||
|
label: 'Auto',
|
||||||
|
description: 'Automatically choose the best backend.',
|
||||||
|
selectable: true,
|
||||||
|
recommended: true,
|
||||||
|
available: true,
|
||||||
|
state: 'ready',
|
||||||
|
audience: 'general',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'codex-native',
|
||||||
|
label: 'Codex native',
|
||||||
|
description: 'Use the local codex exec JSON seam.',
|
||||||
|
selectable: false,
|
||||||
|
recommended: false,
|
||||||
|
available: true,
|
||||||
|
state: 'locked',
|
||||||
|
audience: 'internal',
|
||||||
|
statusMessage: 'Ready but locked',
|
||||||
|
detailMessage: 'Internal rollout only.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
externalRuntimeDiagnostics: [],
|
||||||
|
backend: {
|
||||||
|
kind: 'codex-native',
|
||||||
|
label: 'Codex native',
|
||||||
|
},
|
||||||
|
connection: null,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ProviderRuntimeBackendSelector helpers', () => {
|
||||||
|
it('exposes explicit internal-audience and locked-state labels', () => {
|
||||||
|
const provider = createCodexProvider();
|
||||||
|
const option = provider.availableBackends?.find((backend) => backend.id === 'codex-native');
|
||||||
|
|
||||||
|
expect(option).toBeDefined();
|
||||||
|
expect(getProviderRuntimeBackendAudienceLabel(option!)).toBe('Internal');
|
||||||
|
expect(getProviderRuntimeBackendStateLabel(option!)).toBe('Locked');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds a runtime summary that keeps internal locked truth visible', () => {
|
||||||
|
const provider = createCodexProvider();
|
||||||
|
|
||||||
|
expect(getProviderRuntimeBackendSummary(provider)).toBe(
|
||||||
|
'Codex native - internal - locked'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows auth-required state for degraded internal native rollout', () => {
|
||||||
|
const provider = createCodexProvider({
|
||||||
|
availableBackends: [
|
||||||
|
{
|
||||||
|
id: 'codex-native',
|
||||||
|
label: 'Codex native',
|
||||||
|
description: 'Use the local codex exec JSON seam.',
|
||||||
|
selectable: false,
|
||||||
|
recommended: false,
|
||||||
|
available: false,
|
||||||
|
state: 'authentication-required',
|
||||||
|
audience: 'internal',
|
||||||
|
statusMessage: 'Authentication required',
|
||||||
|
detailMessage: 'Set CODEX_API_KEY.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const option = provider.availableBackends?.[0];
|
||||||
|
|
||||||
|
expect(getProviderRuntimeBackendAudienceLabel(option!)).toBe('Internal');
|
||||||
|
expect(getProviderRuntimeBackendStateLabel(option!)).toBe('Auth required');
|
||||||
|
expect(getProviderRuntimeBackendSummary(provider)).toBe(
|
||||||
|
'Codex native - internal - auth required'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -4,6 +4,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getPrimaryProvisioningFailureDetail,
|
getPrimaryProvisioningFailureDetail,
|
||||||
|
getProvisioningProviderBackendSummary,
|
||||||
ProvisioningProviderStatusList,
|
ProvisioningProviderStatusList,
|
||||||
createInitialProviderChecks,
|
createInitialProviderChecks,
|
||||||
} from '@renderer/components/team/dialogs/ProvisioningProviderStatusList';
|
} from '@renderer/components/team/dialogs/ProvisioningProviderStatusList';
|
||||||
|
|
@ -160,4 +161,30 @@ describe('ProvisioningProviderStatusList', () => {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps internal native rollout state visible in provisioning backend summaries', () => {
|
||||||
|
expect(
|
||||||
|
getProvisioningProviderBackendSummary({
|
||||||
|
selectedBackendId: 'codex-native',
|
||||||
|
resolvedBackendId: 'codex-native',
|
||||||
|
backend: {
|
||||||
|
kind: 'codex-native',
|
||||||
|
label: 'Codex native',
|
||||||
|
},
|
||||||
|
availableBackends: [
|
||||||
|
{
|
||||||
|
id: 'codex-native',
|
||||||
|
label: 'Codex native',
|
||||||
|
description: 'Use codex exec JSON mode.',
|
||||||
|
selectable: false,
|
||||||
|
recommended: false,
|
||||||
|
available: true,
|
||||||
|
state: 'locked',
|
||||||
|
audience: 'internal',
|
||||||
|
statusMessage: 'Ready but locked',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
).toBe('Codex native - internal, locked');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue