fix(runtime): preserve codex-native lane state truth

This commit is contained in:
777genius 2026-04-19 19:46:00 +03:00
parent 30fce3c64d
commit 92a3124e3f
3 changed files with 215 additions and 0 deletions

View file

@ -76,6 +76,24 @@ function isCodexNativeLane(provider: CliProviderStatus): boolean {
);
}
function getSelectedRuntimeBackendOption(
provider: CliProviderStatus
): NonNullable<CliProviderStatus['availableBackends']>[number] | null {
const options = provider.availableBackends ?? [];
if (options.length === 0) {
return null;
}
const selectedBackendId = provider.selectedBackendId ?? null;
const resolvedBackendId = provider.resolvedBackendId ?? null;
return (
options.find((option) => option.id === selectedBackendId) ??
options.find((option) => option.id === resolvedBackendId) ??
null
);
}
export function isConnectionManagedRuntimeProvider(provider: CliProviderStatus): boolean {
return provider.providerId === 'codex' && (provider.availableBackends?.length ?? 0) === 0;
}
@ -106,6 +124,27 @@ export function getProviderCurrentRuntimeSummary(provider: CliProviderStatus): s
}
export function formatProviderStatusText(provider: CliProviderStatus): string {
const selectedBackendOption = getSelectedRuntimeBackendOption(provider);
if (
isCodexNativeLane(provider) &&
selectedBackendOption &&
selectedBackendOption.state &&
selectedBackendOption.state !== 'ready'
) {
return (
selectedBackendOption.statusMessage ?? provider.statusMessage ?? 'Codex native unavailable'
);
}
if (
isCodexNativeLane(provider) &&
selectedBackendOption?.audience === 'internal' &&
selectedBackendOption.statusMessage
) {
return selectedBackendOption.statusMessage;
}
if (!provider.supported) {
return provider.statusMessage ?? 'Unavailable in current runtime';
}

View file

@ -506,4 +506,65 @@ describe('ClaudeMultimodelBridgeService', () => {
statusMessage: 'Ready for internal use',
});
});
it('preserves codex-native runtime-missing rollout states from runtime status payloads', async () => {
execCliMock.mockResolvedValue({
stdout: JSON.stringify({
providers: {
codex: {
supported: true,
authenticated: false,
authMethod: null,
verificationState: 'unknown',
canLoginFromUi: false,
statusMessage: 'Codex native runtime not ready',
detailMessage: 'Codex native runtime requires the codex CLI binary to be installed.',
selectedBackendId: 'codex-native',
resolvedBackendId: null,
availableBackends: [
{
id: 'codex-native',
label: 'Codex native',
selectable: false,
recommended: false,
available: false,
state: 'runtime-missing',
audience: 'internal',
statusMessage: 'Codex CLI not found',
detailMessage: 'Install the codex CLI before enabling the lane.',
},
],
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: null,
},
},
}),
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: false,
available: false,
state: 'runtime-missing',
audience: 'internal',
statusMessage: 'Codex CLI not found',
});
});
});

View file

@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest';
import {
formatProviderStatusText,
getProviderConnectionModeSummary,
getProviderCredentialSummary,
getProviderCurrentRuntimeSummary,
@ -252,4 +253,118 @@ describe('providerConnectionUi', () => {
expect(getProviderCredentialSummary(provider)).toBe('Saved API key available in Manage');
});
it('keeps locked codex-native lanes visible instead of flattening them to connected-via-api-key', () => {
const provider = createCodexProvider({
authenticated: true,
authMethod: 'api_key',
statusMessage: 'Codex native runtime ready',
selectedBackendId: 'codex-native',
resolvedBackendId: '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',
detailMessage: 'Internal rollout only.',
},
],
backend: {
kind: 'codex-native',
label: 'Codex native',
},
});
expect(formatProviderStatusText(provider)).toBe('Ready but locked');
});
it('keeps internal codex-native ready state explicit instead of showing a generic auth label', () => {
const provider = createCodexProvider({
authenticated: true,
authMethod: 'api_key',
statusMessage: 'Codex native runtime ready',
selectedBackendId: 'codex-native',
resolvedBackendId: 'codex-native',
availableBackends: [
{
id: 'codex-native',
label: 'Codex native',
description: 'Use codex exec JSON mode.',
selectable: true,
recommended: false,
available: true,
state: 'ready',
audience: 'internal',
statusMessage: 'Ready for internal use',
detailMessage: 'Internal rollout only.',
},
],
backend: {
kind: 'codex-native',
label: 'Codex native',
},
});
expect(formatProviderStatusText(provider)).toBe('Ready for internal use');
});
it('surfaces native auth-required state from the selected backend option', () => {
const provider = createCodexProvider({
authenticated: false,
authMethod: null,
statusMessage: 'Codex native runtime not ready',
selectedBackendId: 'codex-native',
resolvedBackendId: null,
availableBackends: [
{
id: 'codex-native',
label: 'Codex native',
description: 'Use codex exec JSON mode.',
selectable: false,
recommended: false,
available: false,
state: 'authentication-required',
audience: 'internal',
statusMessage: 'Authentication required',
detailMessage: 'Set CODEX_API_KEY.',
},
],
backend: null,
});
expect(formatProviderStatusText(provider)).toBe('Authentication required');
});
it('surfaces native runtime-missing state from the selected backend option', () => {
const provider = createCodexProvider({
authenticated: false,
authMethod: null,
statusMessage: 'Codex native runtime not ready',
selectedBackendId: 'codex-native',
resolvedBackendId: null,
availableBackends: [
{
id: 'codex-native',
label: 'Codex native',
description: 'Use codex exec JSON mode.',
selectable: false,
recommended: false,
available: false,
state: 'runtime-missing',
audience: 'internal',
statusMessage: 'Codex CLI not found',
detailMessage: 'Install the codex CLI before enabling the lane.',
},
],
backend: null,
});
expect(formatProviderStatusText(provider)).toBe('Codex CLI not found');
});
});