;
+ };
+ expect(persisted.env.ANTHROPIC_MODEL).toBe('gpt-5.4-high');
+ expect(persisted.env.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('gpt-5.3-codex-xhigh');
+ expect(persisted.env.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe('gpt-5.3-codex-high');
+ expect(persisted.env.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe('gpt-5.4-mini-medium');
+ });
+
it('canonicalizes AGY preset values on POST /:profile/presets', async () => {
const settingsPath = path.join(tempHome, '.ccs', 'agy.settings.json');
writeSettings(settingsPath, {
diff --git a/ui/src/components/cliproxy/cliproxy-dialog.tsx b/ui/src/components/cliproxy/cliproxy-dialog.tsx
index b221b8a6..cb8ff68b 100644
--- a/ui/src/components/cliproxy/cliproxy-dialog.tsx
+++ b/ui/src/components/cliproxy/cliproxy-dialog.tsx
@@ -19,7 +19,13 @@ import { useTranslation } from 'react-i18next';
import { useCreateVariant, useCliproxyAuth } from '@/hooks/use-cliproxy';
import { usePrivacy } from '@/contexts/privacy-context';
import { formatAccountDisplayName } from '@/lib/account-identity';
-import { CLIPROXY_PROVIDERS, getProviderDisplayName } from '@/lib/provider-config';
+import {
+ CLIPROXY_PROVIDERS,
+ CLIPROXY_PROVIDER_SECTIONS,
+ getProviderDisplayName,
+ getProviderSection,
+ isPlusExtraProvider,
+} from '@/lib/provider-config';
import { isDeniedAgyModelId } from '@/lib/utils';
const singleProviderSchema = z.object({
@@ -104,6 +110,8 @@ export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) {
});
const selectedProvider = useWatch({ control: singleForm.control, name: 'provider' });
+ const compositeTiers = useWatch({ control: compositeForm.control, name: 'tiers' });
+ const selectedProviderSection = getProviderSection(selectedProvider);
const providerAuth = authData?.authStatus.find((s) => s.provider === selectedProvider);
const providerAccounts = providerAuth?.accounts || [];
@@ -197,10 +205,16 @@ export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) {
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
- {providerOptions.map((opt) => (
-
+ {CLIPROXY_PROVIDER_SECTIONS.map((section) => (
+
))}
{singleForm.formState.errors.provider && (
@@ -208,6 +222,14 @@ export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) {
{singleForm.formState.errors.provider.message}
)}
+ {selectedProviderSection && (
+
+ {t(selectedProviderSection.hintKey)}
+ {isPlusExtraProvider(selectedProvider)
+ ? ` ${t('providerConfig.plusTrackNote')}`
+ : ''}
+
+ )}
{selectedProvider && providerAccounts.length > 0 && (
@@ -297,12 +319,26 @@ export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) {
{...compositeForm.register(`tiers.${tier}.provider`)}
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
- {providerOptions.map((opt) => (
-
+ {CLIPROXY_PROVIDER_SECTIONS.map((section) => (
+
))}
+ {compositeTiers?.[tier]?.provider && (
+
+ {t(getProviderSection(compositeTiers[tier].provider)?.hintKey || '')}
+ {isPlusExtraProvider(compositeTiers[tier].provider)
+ ? ` ${t('providerConfig.plusTrackNote')}`
+ : ''}
+
+ )}
diff --git a/ui/src/components/cliproxy/cliproxy-edit-dialog.tsx b/ui/src/components/cliproxy/cliproxy-edit-dialog.tsx
index 9274e975..76540428 100644
--- a/ui/src/components/cliproxy/cliproxy-edit-dialog.tsx
+++ b/ui/src/components/cliproxy/cliproxy-edit-dialog.tsx
@@ -3,7 +3,7 @@
* Phase 05: Dashboard UI full CRUD for composite variants
*/
-import { useForm } from 'react-hook-form';
+import { useForm, useWatch } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
import { useEffect } from 'react';
@@ -15,7 +15,13 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
import { useUpdateVariant } from '@/hooks/use-cliproxy';
-import { CLIPROXY_PROVIDERS, getProviderDisplayName } from '@/lib/provider-config';
+import {
+ CLIPROXY_PROVIDERS,
+ CLIPROXY_PROVIDER_SECTIONS,
+ getProviderDisplayName,
+ getProviderSection,
+ isPlusExtraProvider,
+} from '@/lib/provider-config';
import type { UpdateVariant, Variant } from '@/lib/api-client';
import { isDeniedAgyModelId } from '@/lib/utils';
@@ -137,6 +143,8 @@ export function CliproxyEditDialog({ variant, open, onOpenChange }: CliproxyEdit
const compositeForm = useForm
({
resolver: zodResolver(compositeSchema),
});
+ const selectedProvider = useWatch({ control: singleForm.control, name: 'provider' });
+ const compositeTiers = useWatch({ control: compositeForm.control, name: 'tiers' });
// Pre-populate form when variant changes
useEffect(() => {
@@ -318,12 +326,26 @@ export function CliproxyEditDialog({ variant, open, onOpenChange }: CliproxyEdit
{...compositeForm.register(`tiers.${tier}.provider`)}
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
- {providerOptions.map((opt) => (
-
+ {CLIPROXY_PROVIDER_SECTIONS.map((section) => (
+
))}
+ {compositeTiers?.[tier]?.provider && (
+
+ {t(getProviderSection(compositeTiers[tier].provider)?.hintKey || '')}
+ {isPlusExtraProvider(compositeTiers[tier].provider)
+ ? ` ${t('providerConfig.plusTrackNote')}`
+ : ''}
+
+ )}
@@ -399,12 +421,26 @@ export function CliproxyEditDialog({ variant, open, onOpenChange }: CliproxyEdit
{...singleForm.register('provider')}
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
- {providerOptions.map((opt) => (
-
+ {CLIPROXY_PROVIDER_SECTIONS.map((section) => (
+
))}
+ {selectedProvider && (
+
+ {t(getProviderSection(selectedProvider)?.hintKey || '')}
+ {isPlusExtraProvider(selectedProvider)
+ ? ` ${t('providerConfig.plusTrackNote')}`
+ : ''}
+
+ )}
diff --git a/ui/src/components/cliproxy/provider-editor/index.tsx b/ui/src/components/cliproxy/provider-editor/index.tsx
index 72b3d7ef..65be5b3b 100644
--- a/ui/src/components/cliproxy/provider-editor/index.tsx
+++ b/ui/src/components/cliproxy/provider-editor/index.tsx
@@ -304,6 +304,7 @@ export function ProviderEditor({
Codex tip: suffixes -medium, -high, and -xhigh{' '}
- pin reasoning effort. Unsuffixed models use Thinking settings.
+ pin reasoning effort. Select a suffixed model to pin effort; unsuffixed models use
+ Thinking settings.
)}
diff --git a/ui/src/components/cliproxy/provider-editor/provider-info-tab.tsx b/ui/src/components/cliproxy/provider-editor/provider-info-tab.tsx
index 2ea26cdf..726ad564 100644
--- a/ui/src/components/cliproxy/provider-editor/provider-info-tab.tsx
+++ b/ui/src/components/cliproxy/provider-editor/provider-info-tab.tsx
@@ -10,11 +10,13 @@ import { Info, Shield } from 'lucide-react';
import { UsageCommand } from './usage-command';
import type { SettingsResponse } from './types';
import type { AuthStatus, CliTarget } from '@/lib/api-client';
+import { getProviderSection, isPlusExtraProvider } from '@/lib/provider-config';
import { useTranslation } from 'react-i18next';
interface ProviderInfoTabProps {
provider: string;
displayName: string;
+ baseProvider?: string;
defaultTarget?: CliTarget;
data?: SettingsResponse;
authStatus: AuthStatus;
@@ -24,6 +26,7 @@ interface ProviderInfoTabProps {
export function ProviderInfoTab({
provider,
displayName,
+ baseProvider,
defaultTarget,
data,
authStatus,
@@ -33,6 +36,8 @@ export function ProviderInfoTab({
const resolvedTarget = defaultTarget || 'claude';
const isDroidTarget = resolvedTarget === 'droid';
const isCodexProvider = provider === 'codex';
+ const sectionProvider = baseProvider || authStatus.provider || provider;
+ const providerSection = getProviderSection(sectionProvider);
const managementPrefix =
resolvedTarget === 'claude' ? `ccs ${provider}` : `ccs ${provider} --target claude`;
const changeModelCommand = `${managementPrefix} --config`;
@@ -101,6 +106,22 @@ export function ProviderInfoTab({
{resolvedTarget}
+ {providerSection && (
+
+
+ {t('providerConfig.trackLabel')}
+
+
+
{t(providerSection.labelKey)}
+
+ {t(providerSection.hintKey)}
+ {isPlusExtraProvider(sectionProvider)
+ ? ` ${t('providerConfig.plusTrackNote')}`
+ : ''}
+
+
+
+ )}
diff --git a/ui/src/components/cliproxy/provider-model-selector.tsx b/ui/src/components/cliproxy/provider-model-selector.tsx
index 75a2977e..471bd71a 100644
--- a/ui/src/components/cliproxy/provider-model-selector.tsx
+++ b/ui/src/components/cliproxy/provider-model-selector.tsx
@@ -12,7 +12,8 @@ import { Badge } from '@/components/ui/badge';
import { SearchableSelect } from '@/components/ui/searchable-select';
import { Skeleton } from '@/components/ui/skeleton';
import type { CliproxyProviderRoutingHints } from '@/lib/api-client';
-import { getCodexEffortDisplay } from '@/lib/codex-effort';
+import type { CodexEffort } from '@/lib/codex-effort';
+import { getCodexEffortDisplay, getCodexEffortVariants } from '@/lib/codex-effort';
import { getResolvedCatalogModels, getSupplementalCatalogModels } from '@/lib/model-catalogs';
import { cn } from '@/lib/utils';
@@ -35,6 +36,8 @@ export interface ModelEntry {
sonnet: string;
haiku: string;
};
+ /** Highest codex reasoning-effort suffix this model supports in the dashboard UI. */
+ codexMaxEffort?: CodexEffort;
}
/** Provider catalog */
@@ -313,6 +316,14 @@ function getPreferredOptionValue(
return routingHint?.recommendedModelId ?? modelId;
}
+function getModelOptionValues(
+ codexMaxEffort: CodexEffort | undefined,
+ optionValue: string,
+ isCodexProvider: boolean
+): string[] {
+ return isCodexProvider ? getCodexEffortVariants(optionValue, codexMaxEffort) : [optionValue];
+}
+
export function FlexibleModelSelector({
label,
description,
@@ -342,55 +353,64 @@ export function FlexibleModelSelector({
const recommendedOptionValues = useMemo(
() =>
new Set(
- resolvedCatalogModels.map((model) =>
- getPreferredOptionValue(model.id, routingHints.get(model.id.toLowerCase()))
+ resolvedCatalogModels.flatMap((model) =>
+ getModelOptionValues(
+ model.codexMaxEffort,
+ getPreferredOptionValue(model.id, routingHints.get(model.id.toLowerCase())),
+ isCodexProvider
+ )
)
),
- [resolvedCatalogModels, routingHints]
+ [isCodexProvider, resolvedCatalogModels, routingHints]
);
const selectedRoutingHint = useMemo(
() => routingHints.get(normalizeModelValue(value, routing).toLowerCase()),
[routing, routingHints, value]
);
- const recommendedOptions = resolvedCatalogModels.map((model) => ({
- value: getPreferredOptionValue(model.id, routingHints.get(model.id.toLowerCase())),
- groupKey: 'recommended',
- searchText: `${model.id} ${model.name} ${routingHints.get(model.id.toLowerCase())?.recommendedModelId ?? ''}`,
- keywords: [model.tier ?? '', catalog?.provider ?? ''],
- triggerContent: (
-
-
- {getPreferredOptionValue(model.id, routingHints.get(model.id.toLowerCase()))}
-
- {routingHints.get(model.id.toLowerCase())?.pinnedAvailable ? (
-
- {routingHints.get(model.id.toLowerCase())?.prefix}
-
- ) : null}
- {isCodexProvider && }
-
- ),
- itemContent: (
-
-
- {getPreferredOptionValue(model.id, routingHints.get(model.id.toLowerCase()))}
-
- {model.tier === 'paid' &&
}
- {routingHints.get(model.id.toLowerCase())?.unprefixedStatus === 'shadowed' ? (
-
- {t('providerModelSelector.shadowed')}
-
- ) : null}
- {routingHints.get(model.id.toLowerCase())?.unprefixedStatus === 'prefix-only' ? (
-
- {t('providerModelSelector.prefixOnly')}
-
- ) : null}
- {isCodexProvider &&
}
-
- ),
- }));
+ const recommendedOptions = resolvedCatalogModels.flatMap((model) => {
+ const routingHint = routingHints.get(model.id.toLowerCase());
+ const optionValues = getModelOptionValues(
+ model.codexMaxEffort,
+ getPreferredOptionValue(model.id, routingHint),
+ isCodexProvider
+ );
+
+ return optionValues.map((optionValue) => ({
+ value: optionValue,
+ groupKey: 'recommended',
+ searchText: `${optionValue} ${model.id} ${model.name} ${routingHint?.recommendedModelId ?? ''}`,
+ keywords: [model.tier ?? '', catalog?.provider ?? ''],
+ triggerContent: (
+
+ {optionValue}
+ {routingHint?.pinnedAvailable ? (
+
+ {routingHint.prefix}
+
+ ) : null}
+ {isCodexProvider && }
+
+ ),
+ itemContent: (
+
+
{optionValue}
+ {model.tier === 'paid' &&
}
+ {routingHint?.unprefixedStatus === 'shadowed' ? (
+
+ {t('providerModelSelector.shadowed')}
+
+ ) : null}
+ {routingHint?.unprefixedStatus === 'prefix-only' ? (
+
+ {t('providerModelSelector.prefixOnly')}
+
+ ) : null}
+ {isCodexProvider &&
}
+
+ ),
+ }));
+ });
const allModelOptions = supplementalModels
.filter((model) => !catalogModelIds.has(model.id))
@@ -400,43 +420,48 @@ export function FlexibleModelSelector({
getPreferredOptionValue(model.id, routingHints.get(model.id.toLowerCase()))
)
)
- .map((model) => ({
- value: getPreferredOptionValue(model.id, routingHints.get(model.id.toLowerCase())),
- groupKey: 'all',
- searchText: `${model.id} ${routingHints.get(model.id.toLowerCase())?.recommendedModelId ?? ''}`,
- keywords: [model.owned_by],
- triggerContent: (
-
-
- {getPreferredOptionValue(model.id, routingHints.get(model.id.toLowerCase()))}
-
- {routingHints.get(model.id.toLowerCase())?.pinnedAvailable ? (
-
- {routingHints.get(model.id.toLowerCase())?.prefix}
-
- ) : null}
- {isCodexProvider && }
-
- ),
- itemContent: (
-
-
- {getPreferredOptionValue(model.id, routingHints.get(model.id.toLowerCase()))}
-
- {routingHints.get(model.id.toLowerCase())?.unprefixedStatus === 'shadowed' ? (
-
- {t('providerModelSelector.shadowed')}
-
- ) : null}
- {routingHints.get(model.id.toLowerCase())?.unprefixedStatus === 'prefix-only' ? (
-
- {t('providerModelSelector.prefixOnly')}
-
- ) : null}
- {isCodexProvider && }
-
- ),
- }));
+ .flatMap((model) => {
+ const routingHint = routingHints.get(model.id.toLowerCase());
+ const optionValues = getModelOptionValues(
+ undefined,
+ getPreferredOptionValue(model.id, routingHint),
+ isCodexProvider
+ );
+
+ return optionValues.map((optionValue) => ({
+ value: optionValue,
+ groupKey: 'all',
+ searchText: `${optionValue} ${model.id} ${routingHint?.recommendedModelId ?? ''}`,
+ keywords: [model.owned_by],
+ triggerContent: (
+
+ {optionValue}
+ {routingHint?.pinnedAvailable ? (
+
+ {routingHint.prefix}
+
+ ) : null}
+ {isCodexProvider && }
+
+ ),
+ itemContent: (
+
+ {optionValue}
+ {routingHint?.unprefixedStatus === 'shadowed' ? (
+
+ {t('providerModelSelector.shadowed')}
+
+ ) : null}
+ {routingHint?.unprefixedStatus === 'prefix-only' ? (
+
+ {t('providerModelSelector.prefixOnly')}
+
+ ) : null}
+ {isCodexProvider && }
+
+ ),
+ }));
+ });
const selectedValueMissing =
Boolean(value) &&
!recommendedOptions.some((option) => option.value === value) &&
diff --git a/ui/src/lib/api-client.ts b/ui/src/lib/api-client.ts
index 0a8bc075..38e43778 100644
--- a/ui/src/lib/api-client.ts
+++ b/ui/src/lib/api-client.ts
@@ -234,14 +234,21 @@ export interface UpdateImageAnalysisSettingsPayload {
profileBackends?: Record;
}
+export type BrowserToolPolicy = 'auto' | 'manual';
+export type BrowserEvalMode = 'disabled' | 'readonly' | 'readwrite';
+
export interface BrowserSettingsConfig {
claude: {
enabled: boolean;
+ policy: BrowserToolPolicy;
userDataDir: string;
devtoolsPort: number;
+ evalMode: BrowserEvalMode;
};
codex: {
enabled: boolean;
+ policy: BrowserToolPolicy;
+ evalMode: BrowserEvalMode;
};
}
@@ -253,6 +260,7 @@ export interface BrowserLaunchCommands {
export interface ClaudeBrowserStatus {
enabled: boolean;
+ policy: BrowserToolPolicy;
source: 'config' | 'CCS_BROWSER_USER_DATA_DIR' | 'CCS_BROWSER_PROFILE_DIR';
overrideActive: boolean;
state: 'disabled' | 'path_missing' | 'browser_not_running' | 'endpoint_unreachable' | 'ready';
@@ -262,6 +270,7 @@ export interface ClaudeBrowserStatus {
effectiveUserDataDir: string;
recommendedUserDataDir: string;
devtoolsPort: number;
+ evalMode: BrowserEvalMode;
managedMcpServerName: string;
managedMcpServerPath: string;
launchCommands: BrowserLaunchCommands;
@@ -270,11 +279,13 @@ export interface ClaudeBrowserStatus {
export interface CodexBrowserStatus {
enabled: boolean;
+ policy: BrowserToolPolicy;
state: 'disabled' | 'enabled' | 'unsupported_build';
title: string;
detail: string;
nextStep: string;
serverName: string;
+ evalMode: BrowserEvalMode;
supportsConfigOverrides: boolean;
binaryPath: string | null;
version?: string;
diff --git a/ui/src/lib/codex-effort.ts b/ui/src/lib/codex-effort.ts
index 9f3b26f2..ff2d311a 100644
--- a/ui/src/lib/codex-effort.ts
+++ b/ui/src/lib/codex-effort.ts
@@ -1,14 +1,56 @@
export type CodexEffort = 'medium' | 'high' | 'xhigh';
const CODEX_EFFORT_SUFFIX_REGEX = /-(medium|high|xhigh)$/i;
+const CODEX_EFFORTS_IN_ORDER: readonly CodexEffort[] = ['medium', 'high', 'xhigh'];
+
+function trimModelId(modelId: string | undefined): string {
+ return modelId?.trim() ?? '';
+}
export function parseCodexEffort(modelId: string | undefined): CodexEffort | undefined {
if (!modelId) return undefined;
- const match = modelId.trim().match(CODEX_EFFORT_SUFFIX_REGEX);
+ const match = trimModelId(modelId).match(CODEX_EFFORT_SUFFIX_REGEX);
if (!match?.[1]) return undefined;
return match[1].toLowerCase() as CodexEffort;
}
+export function stripCodexEffortSuffix(modelId: string | undefined): string {
+ return trimModelId(modelId).replace(CODEX_EFFORT_SUFFIX_REGEX, '');
+}
+
+export function applyCodexEffortSuffix(
+ modelId: string | undefined,
+ effort: CodexEffort | undefined
+): string {
+ const normalizedModelId = stripCodexEffortSuffix(modelId);
+ if (!normalizedModelId || !effort) {
+ return normalizedModelId;
+ }
+ return `${normalizedModelId}-${effort}`;
+}
+
+export function getCodexEffortVariants(
+ modelId: string,
+ maxEffort: CodexEffort | undefined
+): string[] {
+ if (!maxEffort) {
+ const explicitEffort = parseCodexEffort(modelId);
+ return [applyCodexEffortSuffix(modelId, explicitEffort)];
+ }
+
+ const normalizedModelId = stripCodexEffortSuffix(modelId);
+ const variantIds = [normalizedModelId];
+
+ for (const effort of CODEX_EFFORTS_IN_ORDER) {
+ variantIds.push(applyCodexEffortSuffix(normalizedModelId, effort));
+ if (effort === maxEffort) {
+ break;
+ }
+ }
+
+ return variantIds;
+}
+
export function getCodexEffortDisplay(
modelId: string | undefined,
effortLabels?: { pinned: (effort: string) => string; auto: string }
diff --git a/ui/src/lib/i18n.ts b/ui/src/lib/i18n.ts
index 5329df3a..53f26f36 100644
--- a/ui/src/lib/i18n.ts
+++ b/ui/src/lib/i18n.ts
@@ -977,10 +977,13 @@ const resources = {
backendBinary: 'Backend Binary',
stopProxyToSwitch: 'Stop the running proxy in Instance Status to switch backend.',
default: 'Default',
- plusDesc: 'Full provider support including Kiro and GitHub Copilot',
- originalDesc: 'Original binary (Gemini, Codex, Antigravity only)',
+ plusDesc:
+ 'Optional track for extra providers. Still supported, but currently community-maintained instead of upstream-maintained.',
+ originalDesc: 'Default, always-available backend for the core provider track.',
+ plusFallbackNotice:
+ 'The Plus provider track is not deprecated, but local CLIProxy still falls back to the original backend while the maintained fork path is being brought back.',
variantsIncompatible:
- 'Existing Kiro/Copilot variants will not work with CLIProxyAPI. Switch to CLIProxyAPIPlus or remove those variants.',
+ 'Existing plus-extra variants ({{providers}}) will not run on the original backend. Keep them visible for reference, but switch to Plus before using them.',
safety: 'Safety',
agyModeTitle: 'Antigravity + Gemini Power User Mode',
agyModeDesc:
@@ -2234,6 +2237,13 @@ const resources = {
},
providerConfig: {
defaultDeviceCodeInstruction: 'Complete the authorization in your browser.',
+ trackLabel: 'Track',
+ sectionCoreLabel: 'Core / original backend',
+ sectionCoreHint: 'Default, always-available provider track',
+ sectionPlusLabel: 'Plus extras / community-maintained',
+ sectionPlusHint: 'Still supported, but separated from the default backend for now',
+ plusTrackNote:
+ 'Requires the optional Plus backend while that track remains community-maintained.',
},
// ========================================
@@ -2435,6 +2445,14 @@ const resources = {
nextStep: 'Next step',
technicalDetails: 'Technical Details',
diagnostics: 'Diagnostics',
+ evalMode: {
+ label: 'browser_eval access',
+ options: {
+ disabled: 'Disabled',
+ readonly: 'Read-only',
+ readwrite: 'Read/write',
+ },
+ },
actions: {
saveClaude: 'Save Claude settings',
saveCodex: 'Save Codex settings',
@@ -2461,6 +2479,8 @@ const resources = {
effectivePath: 'Effective attach path',
recommendedPath: 'Recommended path',
managedRuntime: 'Managed browser runtime',
+ evalModeHint:
+ 'Controls browser_eval access for Claude Browser Attach. Read-only allows inspection; read/write also allows page-side mutations.',
overrideMessage:
'An environment override is currently active from {{source}}. This dashboard remains the source of truth once that override is removed.',
launchGuidance: 'Launch guidance',
@@ -2480,6 +2500,8 @@ const resources = {
overrideUnsupported: 'Not supported',
binary: 'Detected Codex binary',
notDetected: 'Not detected',
+ evalModeHint:
+ 'Stored for Browser settings parity. In Phase 1, browser_eval enforcement primarily applies to Claude Browser Attach.',
},
},
},
@@ -3443,10 +3465,12 @@ const resources = {
backendBinary: '后端二进制',
stopProxyToSwitch: '请先在实例状态中停止正在运行的代理,再切换后端。',
default: '默认',
- plusDesc: '完整支持包括 Kiro 和 GitHub Copilot 在内的提供商',
- originalDesc: '原版二进制(仅 Gemini、Codex、Antigravity)',
+ plusDesc: '额外提供商的可选线路。仍受支持,但目前由社区维护而非上游维护。',
+ originalDesc: '核心提供商线路的默认、始终可用后端。',
+ plusFallbackNotice:
+ 'Plus 提供商线路并未弃用,但在受维护的 fork 恢复之前,本地 CLIProxy 仍会回退到原始后端。',
variantsIncompatible:
- '现有 Kiro/Copilot 变体与 CLIProxyAPI 不兼容。请切换到 CLIProxyAPIPlus 或移除这些变体。',
+ '现有 plus 扩展变体({{providers}})无法在原始后端上运行。可以保留作参考,但使用前请切换到 Plus。',
safety: '安全',
agyModeTitle: 'Antigravity + Gemini 高级模式',
agyModeDesc: '跳过 AGY 责任确认清单,以及 Gemini Dashboard 中输入风险短语的步骤。',
@@ -4656,6 +4680,12 @@ const resources = {
},
providerConfig: {
defaultDeviceCodeInstruction: '请在浏览器中完成授权。',
+ trackLabel: '分组',
+ sectionCoreLabel: '核心 / 原始后端',
+ sectionCoreHint: '默认且始终可用的提供商线路',
+ sectionPlusLabel: 'Plus 扩展 / 社区维护',
+ sectionPlusHint: '仍然受支持,但目前与默认后端分开显示',
+ plusTrackNote: '需要可选的 Plus 后端;当前这条线路由社区维护。',
},
profileEditorSections: {
imageAnalysis: '图片分析',
@@ -4850,6 +4880,14 @@ const resources = {
nextStep: '下一步',
technicalDetails: '技术细节',
diagnostics: '诊断信息',
+ evalMode: {
+ label: 'browser_eval 权限',
+ options: {
+ disabled: '禁用',
+ readonly: '只读',
+ readwrite: '可读写',
+ },
+ },
actions: {
saveClaude: '保存 Claude 设置',
saveCodex: '保存 Codex 设置',
@@ -4873,6 +4911,8 @@ const resources = {
effectivePath: '当前生效的 attach 路径',
recommendedPath: '推荐路径',
managedRuntime: '托管浏览器运行时',
+ evalModeHint:
+ '控制 Claude Browser Attach 中 browser_eval 的权限。只读仅允许读取,读写还允许页面侧修改。',
overrideMessage:
'当前存在来自 {{source}} 的环境变量覆盖。移除该覆盖后,Dashboard 配置将重新成为唯一来源。',
launchGuidance: '启动指引',
@@ -4889,6 +4929,8 @@ const resources = {
overrideUnsupported: '不支持',
binary: '检测到的 Codex 可执行文件',
notDetected: '未检测到',
+ evalModeHint:
+ '为保持 Browser 设置页一致性而保存该配置。Phase 1 中,browser_eval 的实际限制主要作用于 Claude Browser Attach。',
},
},
},
@@ -5934,10 +5976,13 @@ const resources = {
stopProxyToSwitch:
'Dừng proxy đang chạy trong Trạng thái phiên bản trước khi chuyển backend.',
default: 'Mặc định',
- plusDesc: 'Hỗ trợ đầy đủ nhà cung cấp, bao gồm Kiro và GitHub Copilot',
- originalDesc: 'Binary gốc (chỉ Gemini, Codex, Antigravity)',
+ plusDesc:
+ 'Nhánh tùy chọn cho các nhà cung cấp bổ sung. Vẫn được hỗ trợ nhưng hiện do cộng đồng duy trì thay vì upstream.',
+ originalDesc: 'Backend mặc định, luôn sẵn sàng cho nhóm nhà cung cấp cốt lõi.',
+ plusFallbackNotice:
+ 'Nhánh nhà cung cấp Plus chưa bị khai tử, nhưng CLIProxy cục bộ vẫn quay về backend gốc cho tới khi đường dẫn fork được duy trì được bật lại.',
variantsIncompatible:
- 'Các biến thể Kiro/Copilot hiện tại sẽ không hoạt động với CLIProxyAPI. Chuyển sang CLIProxyAPIPlus hoặc xóa các biến thể đó.',
+ 'Các biến thể plus-extra hiện có ({{providers}}) sẽ không chạy trên backend gốc. Có thể giữ lại để tham chiếu, nhưng hãy chuyển sang Plus trước khi dùng.',
safety: 'An toàn',
agyModeTitle: 'Chế độ power user Antigravity + Gemini',
agyModeDesc:
@@ -7170,6 +7215,12 @@ const resources = {
},
providerConfig: {
defaultDeviceCodeInstruction: 'Hoàn tất việc cấp quyền trong trình duyệt của bạn.',
+ trackLabel: 'Nhóm',
+ sectionCoreLabel: 'Core / backend gốc',
+ sectionCoreHint: 'Nhóm nhà cung cấp mặc định, luôn sẵn sàng',
+ sectionPlusLabel: 'Plus extras / cộng đồng duy trì',
+ sectionPlusHint: 'Vẫn được hỗ trợ nhưng hiện được tách khỏi backend mặc định',
+ plusTrackNote: 'Cần backend Plus tùy chọn; hiện tại nhánh này do cộng đồng duy trì.',
},
profileEditorSections: {
imageAnalysis: 'Phân tích hình ảnh',
@@ -7367,6 +7418,14 @@ const resources = {
nextStep: 'Bước tiếp theo',
technicalDetails: 'Chi tiết kỹ thuật',
diagnostics: 'Chẩn đoán',
+ evalMode: {
+ label: 'Quyền browser_eval',
+ options: {
+ disabled: 'Tắt',
+ readonly: 'Chỉ đọc',
+ readwrite: 'Đọc/ghi',
+ },
+ },
actions: {
saveClaude: 'Lưu cấu hình Claude',
saveCodex: 'Lưu cấu hình Codex',
@@ -7394,6 +7453,8 @@ const resources = {
effectivePath: 'Đường dẫn attach đang có hiệu lực',
recommendedPath: 'Đường dẫn khuyến nghị',
managedRuntime: 'Runtime trình duyệt được quản lý',
+ evalModeHint:
+ 'Điều khiển quyền browser_eval cho Claude Browser Attach. Chỉ đọc cho phép quan sát; đọc/ghi cũng cho phép thay đổi phía trang.',
overrideMessage:
'Hiện có override môi trường từ {{source}}. Khi bỏ override này, Dashboard sẽ lại là nguồn cấu hình chính.',
launchGuidance: 'Hướng dẫn khởi chạy',
@@ -7413,6 +7474,8 @@ const resources = {
overrideUnsupported: 'Không được hỗ trợ',
binary: 'Binary Codex được phát hiện',
notDetected: 'Không phát hiện',
+ evalModeHint:
+ 'Được lưu để giữ tương thích giao diện Browser settings. Ở Phase 1, việc áp dụng hạn chế browser_eval chủ yếu diễn ra trên Claude Browser Attach.',
},
},
},
@@ -8461,10 +8524,13 @@ const resources = {
stopProxyToSwitch:
'バックエンドを切り替える前に、インスタンス状態から実行中のプロキシを停止してください。',
default: 'デフォルト',
- plusDesc: 'Kiro と GitHub Copilot を含むすべてのプロバイダーをサポート',
- originalDesc: '元のバイナリ(Gemini、Codex、Antigravity のみ)',
+ plusDesc:
+ '追加プロバイダー向けのオプショントラックです。引き続きサポートされていますが、現在は upstream ではなくコミュニティ保守です。',
+ originalDesc: 'コアプロバイダートラック向けの既定かつ常時利用可能な backend。',
+ plusFallbackNotice:
+ 'Plus プロバイダートラックは廃止ではありませんが、保守中の fork が戻るまではローカル CLIProxy は引き続きオリジナル backend にフォールバックします。',
variantsIncompatible:
- '既存の Kiro/Copilot バリアントは CLIProxyAPI では動作しません。CLIProxyAPIPlus に切り替えるか、それらのバリアントを削除してください。',
+ '既存の plus-extra バリアント({{providers}})はオリジナル backend では動作しません。参照用に残すことはできますが、使用前に Plus へ切り替えてください。',
safety: '安全設定',
agyModeTitle: 'Antigravity + Gemini パワーユーザーモード',
agyModeDesc:
@@ -9791,6 +9857,14 @@ const resources = {
nextStep: '次の手順',
technicalDetails: '技術的な詳細',
diagnostics: '診断情報',
+ evalMode: {
+ label: 'browser_eval 権限',
+ options: {
+ disabled: '無効',
+ readonly: '読み取り専用',
+ readwrite: '読み書き可',
+ },
+ },
actions: {
saveClaude: 'Claude 設定を保存',
saveCodex: 'Codex 設定を保存',
@@ -9818,6 +9892,8 @@ const resources = {
effectivePath: '現在有効な attach パス',
recommendedPath: '推奨パス',
managedRuntime: '管理済みブラウザランタイム',
+ evalModeHint:
+ 'Claude Browser Attach における browser_eval の権限を制御します。読み取り専用は参照のみ、読み書き可はページ側の変更も許可します。',
overrideMessage:
'{{source}} から環境変数オーバーライドが有効です。これを外すと Dashboard の設定が再び主ソースになります。',
launchGuidance: '起動ガイダンス',
@@ -9837,6 +9913,8 @@ const resources = {
overrideUnsupported: '非対応',
binary: '検出された Codex バイナリ',
notDetected: '未検出',
+ evalModeHint:
+ 'Browser 設定画面の整合性のために保存されます。Phase 1 では browser_eval の制御は主に Claude Browser Attach に適用されます。',
},
},
},
@@ -9984,6 +10062,14 @@ const resources = {
},
providerConfig: {
defaultDeviceCodeInstruction: 'ブラウザーで認証を完了してください。',
+ trackLabel: 'トラック',
+ sectionCoreLabel: 'コア / オリジナル backend',
+ sectionCoreHint: '既定で常に利用できるプロバイダートラック',
+ sectionPlusLabel: 'Plus 拡張 / コミュニティ保守',
+ sectionPlusHint:
+ '引き続きサポートされていますが、当面は既定の backend から分離されています',
+ plusTrackNote:
+ 'このトラックはオプションの Plus backend が必要で、現在はコミュニティ保守です。',
},
updatesSpotlight: {
openUpdatesCenter: '更新センターを開く',
diff --git a/ui/src/lib/model-catalogs.ts b/ui/src/lib/model-catalogs.ts
index 025ddc01..5398645e 100644
--- a/ui/src/lib/model-catalogs.ts
+++ b/ui/src/lib/model-catalogs.ts
@@ -263,6 +263,7 @@ export const MODEL_CATALOGS: Record = {
id: 'gpt-5-codex',
name: 'GPT-5 Codex',
description: 'Cross-plan safe Codex default',
+ codexMaxEffort: 'xhigh',
presetMapping: {
default: 'gpt-5-codex',
opus: 'gpt-5-codex',
@@ -274,6 +275,7 @@ export const MODEL_CATALOGS: Record = {
id: 'gpt-5-codex-mini',
name: 'GPT-5 Codex Mini',
description: 'Faster and cheaper Codex option',
+ codexMaxEffort: 'high',
presetMapping: {
default: 'gpt-5-codex-mini',
opus: 'gpt-5-codex',
@@ -285,6 +287,7 @@ export const MODEL_CATALOGS: Record = {
id: 'gpt-5-mini',
name: 'GPT-5 Mini',
description: 'Legacy mini model ID kept for backwards compatibility',
+ codexMaxEffort: 'high',
presetMapping: {
default: 'gpt-5-mini',
opus: 'gpt-5-codex',
@@ -296,6 +299,7 @@ export const MODEL_CATALOGS: Record = {
id: 'gpt-5.1-codex-mini',
name: 'GPT-5.1 Codex Mini',
description: 'Legacy fast Codex mini model',
+ codexMaxEffort: 'high',
presetMapping: {
default: 'gpt-5.1-codex-mini',
opus: 'gpt-5.1-codex-max',
@@ -307,6 +311,7 @@ export const MODEL_CATALOGS: Record = {
id: 'gpt-5.1-codex-max',
name: 'GPT-5.1 Codex Max',
description: 'Higher-effort Codex model with xhigh support',
+ codexMaxEffort: 'xhigh',
presetMapping: {
default: 'gpt-5.1-codex-max',
opus: 'gpt-5.1-codex-max',
@@ -318,6 +323,7 @@ export const MODEL_CATALOGS: Record = {
id: 'gpt-5.2-codex',
name: 'GPT-5.2 Codex',
description: 'Cross-plan Codex model with xhigh support',
+ codexMaxEffort: 'xhigh',
presetMapping: {
default: 'gpt-5.2-codex',
opus: 'gpt-5.2-codex',
@@ -330,6 +336,7 @@ export const MODEL_CATALOGS: Record = {
name: 'GPT-5.3 Codex',
tier: 'paid',
description: 'Paid Codex plans only',
+ codexMaxEffort: 'xhigh',
presetMapping: {
default: 'gpt-5.3-codex',
opus: 'gpt-5.3-codex',
@@ -342,6 +349,7 @@ export const MODEL_CATALOGS: Record = {
name: 'GPT-5.3 Codex Spark',
tier: 'paid',
description: 'Paid Codex plans only, ultra-fast coding model',
+ codexMaxEffort: 'xhigh',
presetMapping: {
default: 'gpt-5.3-codex-spark',
opus: 'gpt-5.3-codex',
@@ -354,6 +362,7 @@ export const MODEL_CATALOGS: Record