fix(runtime): preserve codex-native lane state truth
This commit is contained in:
parent
30fce3c64d
commit
92a3124e3f
3 changed files with 215 additions and 0 deletions
|
|
@ -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 {
|
export function isConnectionManagedRuntimeProvider(provider: CliProviderStatus): boolean {
|
||||||
return provider.providerId === 'codex' && (provider.availableBackends?.length ?? 0) === 0;
|
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 {
|
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) {
|
if (!provider.supported) {
|
||||||
return provider.statusMessage ?? 'Unavailable in current runtime';
|
return provider.statusMessage ?? 'Unavailable in current runtime';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -506,4 +506,65 @@ describe('ClaudeMultimodelBridgeService', () => {
|
||||||
statusMessage: 'Ready for internal use',
|
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',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
formatProviderStatusText,
|
||||||
getProviderConnectionModeSummary,
|
getProviderConnectionModeSummary,
|
||||||
getProviderCredentialSummary,
|
getProviderCredentialSummary,
|
||||||
getProviderCurrentRuntimeSummary,
|
getProviderCurrentRuntimeSummary,
|
||||||
|
|
@ -252,4 +253,118 @@ describe('providerConnectionUi', () => {
|
||||||
|
|
||||||
expect(getProviderCredentialSummary(provider)).toBe('Saved API key available in Manage');
|
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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue