From 3246c40319b0f9329c375ae3fe73ee02261579bb Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 1 Apr 2026 14:57:56 -0400 Subject: [PATCH 01/12] feat(codex-dashboard): add manual long-context controls --- src/shared/compatible-cli-contracts.ts | 4 + .../services/codex-dashboard-service.ts | 14 ++ .../codex-dashboard-service.test.ts | 30 +++ .../codex-top-level-controls-card.tsx | 235 +++++++++++++++++- ui/src/lib/codex-config.ts | 4 + .../codex-overview-tab.test.tsx | 2 + .../codex-top-level-controls-card.test.tsx | 67 +++++ ui/tests/unit/hooks/use-codex.test.tsx | 2 + ui/tests/unit/ui/pages/codex-page.test.tsx | 2 + 9 files changed, 359 insertions(+), 1 deletion(-) diff --git a/src/shared/compatible-cli-contracts.ts b/src/shared/compatible-cli-contracts.ts index 0c0f1b42..7433d322 100644 --- a/src/shared/compatible-cli-contracts.ts +++ b/src/shared/compatible-cli-contracts.ts @@ -91,6 +91,8 @@ export interface CodexSupportMatrixEntry { export interface CodexUserConfigDiagnostics { model: string | null; modelReasoningEffort: string | null; + modelContextWindow: number | null; + modelAutoCompactTokenLimit: number | null; modelProvider: string | null; activeProfile: string | null; approvalPolicy: string | null; @@ -137,6 +139,8 @@ export interface CodexRawConfigResponse { export interface CodexTopLevelSettingsPatch { model?: string | null; modelReasoningEffort?: string | null; + modelContextWindow?: number | null; + modelAutoCompactTokenLimit?: number | null; modelProvider?: string | null; approvalPolicy?: string | null; sandboxMode?: string | null; diff --git a/src/web-server/services/codex-dashboard-service.ts b/src/web-server/services/codex-dashboard-service.ts index 725c0bc1..8bba991f 100644 --- a/src/web-server/services/codex-dashboard-service.ts +++ b/src/web-server/services/codex-dashboard-service.ts @@ -243,6 +243,18 @@ function applyTopLevelSettingsPatch( 'model_reasoning_effort' ); } + if (hasOwn(values, 'modelContextWindow')) { + setNumberField(target, 'model_context_window', values.modelContextWindow, { + integer: true, + min: 1, + }); + } + if (hasOwn(values, 'modelAutoCompactTokenLimit')) { + setNumberField(target, 'model_auto_compact_token_limit', values.modelAutoCompactTokenLimit, { + integer: true, + min: 1, + }); + } if (hasOwn(values, 'modelProvider')) { setStringField(target, 'model_provider', values.modelProvider); } @@ -778,6 +790,8 @@ export async function getCodexDashboardDiagnostics(): Promise { path.join(codexHome, 'config.toml'), `model = "gpt-5.4" profile = "work" +model_context_window = 800000 +model_auto_compact_token_limit = 700000 model_provider = "cliproxy" approval_policy = "never" sandbox_mode = "danger-full-access" @@ -217,6 +219,8 @@ model = "gpt-5.4" expect(diagnostics.binary.installed).toBe(true); expect(diagnostics.binary.supportsConfigOverrides).toBe(true); expect(diagnostics.config.model).toBe('gpt-5.4'); + expect(diagnostics.config.modelContextWindow).toBe(800000); + expect(diagnostics.config.modelAutoCompactTokenLimit).toBe(700000); expect(diagnostics.config.activeProfile).toBe('work'); expect(diagnostics.config.modelProvider).toBe('cliproxy'); expect(diagnostics.config.profileCount).toBe(1); @@ -376,6 +380,8 @@ bearer_token = "secret" values: { model: 'gpt-5.4', modelReasoningEffort: 'high', + modelContextWindow: 800000, + modelAutoCompactTokenLimit: 700000, approvalPolicy: 'never', sandboxMode: 'workspace-write', webSearch: 'cached', @@ -394,10 +400,14 @@ bearer_token = "secret" const diagnostics = await getCodexDashboardDiagnostics(); expect(diagnostics.config.model).toBe('gpt-5.4'); expect(diagnostics.config.modelReasoningEffort).toBe('high'); + expect(diagnostics.config.modelContextWindow).toBe(800000); + expect(diagnostics.config.modelAutoCompactTokenLimit).toBe(700000); expect(diagnostics.config.toolOutputTokenLimit).toBe(12000); expect(diagnostics.config.personality).toBe('friendly'); expect(diagnostics.config.projectTrust[0]?.path).toBe('/tmp/workspace-a'); expect(result.rawText).toContain('model = "gpt-5.4"'); + expect(result.rawText).toContain('model_context_window = 800000'); + expect(result.rawText).toContain('model_auto_compact_token_limit = 700000'); expect(result.config?.model).toBe('gpt-5.4'); }); @@ -665,4 +675,24 @@ bearer_token = "secret" }) ).rejects.toThrow(CodexRawConfigValidationError); }); + + it('rejects invalid long-context values in structured top-level patches', async () => { + await expect( + patchCodexConfig({ + kind: 'top-level', + values: { + modelContextWindow: 0, + }, + }) + ).rejects.toThrow(CodexRawConfigValidationError); + + await expect( + patchCodexConfig({ + kind: 'top-level', + values: { + modelAutoCompactTokenLimit: 1.5, + }, + }) + ).rejects.toThrow(CodexRawConfigValidationError); + }); }); diff --git a/ui/src/components/compatible-cli/codex-top-level-controls-card.tsx b/ui/src/components/compatible-cli/codex-top-level-controls-card.tsx index 15dc5927..2194b5b9 100644 --- a/ui/src/components/compatible-cli/codex-top-level-controls-card.tsx +++ b/ui/src/components/compatible-cli/codex-top-level-controls-card.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; -import { Loader2, SlidersHorizontal } from 'lucide-react'; +import { CircleAlert, Loader2, SlidersHorizontal } from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { @@ -14,6 +15,11 @@ import type { CodexTopLevelSettingsView } from '@/lib/codex-config'; import { CodexConfigCardShell } from './codex-config-card-shell'; const UNSET = '__unset__'; +const GPT_54_MAX_CONTEXT_WINDOW = 1_050_000; +const GPT_54_STANDARD_CONTEXT_WINDOW = 272_000; +const CCS_GPT_54_STARTER_CONTEXT_WINDOW = 800_000; +const CCS_GPT_54_STARTER_AUTO_COMPACT_TOKEN_LIMIT = 700_000; +const INTEGER_FORMATTER = new Intl.NumberFormat('en-US'); interface CodexTopLevelControlsCardProps { values: CodexTopLevelSettingsView; @@ -32,6 +38,14 @@ function withCurrentValue(options: string[], current: string | null | undefined) return current && !options.includes(current) ? [current, ...options] : options; } +function formatInteger(value: number) { + return INTEGER_FORMATTER.format(value); +} + +function isGpt54ModelId(value: string | null | undefined) { + return value?.trim().toLowerCase().startsWith('gpt-5.4') ?? false; +} + function buildTopLevelPatch( initialValues: CodexTopLevelSettingsView, draft: CodexTopLevelSettingsView @@ -42,6 +56,12 @@ function buildTopLevelPatch( if (draft.modelReasoningEffort !== initialValues.modelReasoningEffort) { patch.modelReasoningEffort = draft.modelReasoningEffort; } + if (draft.modelContextWindow !== initialValues.modelContextWindow) { + patch.modelContextWindow = draft.modelContextWindow; + } + if (draft.modelAutoCompactTokenLimit !== initialValues.modelAutoCompactTokenLimit) { + patch.modelAutoCompactTokenLimit = draft.modelAutoCompactTokenLimit; + } if (draft.modelProvider !== initialValues.modelProvider) { patch.modelProvider = draft.modelProvider; } @@ -91,6 +111,11 @@ function TopLevelControlsForm({ const personalityOptions = withCurrentValue(['none', 'friendly', 'pragmatic'], draft.personality); const patch = buildTopLevelPatch(initialValues, draft); const hasChanges = Object.keys(patch).length > 0; + const isGpt54Selected = isGpt54ModelId(draft.model); + const parseOptionalInteger = (value: string) => { + const trimmed = value.trim(); + return trimmed.length > 0 ? Number(trimmed) : null; + }; return ( <> @@ -266,6 +291,214 @@ function TopLevelControlsForm({ +
+
+
+
+ +

Long context override

+ + Manual opt-in only + + + {isGpt54Selected ? 'GPT-5.4 selected' : 'GPT-5.4 reference'} + +
+

Draft values only. Nothing applies until Save.

+
+ +
+ + + +
+
+ +
+
+

+ Official max +

+

1.05M / 1M

+

GPT-5.4 context cap

+
+
+

+ Standard window +

+

+ {formatInteger(GPT_54_STANDARD_CONTEXT_WINDOW)} +

+

Normal usage window

+
+
+

+ Above 272K +

+

Counts 2x

+

Usage-limit cost above 272K

+
+
+ +
+
+
+

+ One cautious pair +

+
+ Context {formatInteger(CCS_GPT_54_STARTER_CONTEXT_WINDOW)} +
+
+ Auto-compact {formatInteger(CCS_GPT_54_STARTER_AUTO_COMPACT_TOKEN_LIMIT)} +
+
+
+ + Not official + + + Draft only + +
+
+
+ Quick-fill only. Review before saving. + {!isGpt54Selected && draft.model ? ( + + {draft.model} should be checked separately. + + ) : null} +
+
+ +
+
+

Model context window

+ + setDraft((current) => ({ + ...current, + modelContextWindow: parseOptionalInteger(event.target.value), + })) + } + placeholder="Unset" + disabled={disabled} + /> +

+ Writes model_context_window. Leave unset to keep Codex defaults. +

+
+ +
+

Auto-compact token limit

+ + setDraft((current) => ({ + ...current, + modelAutoCompactTokenLimit: parseOptionalInteger(event.target.value), + })) + } + placeholder="Unset" + disabled={disabled} + /> +

+ Writes model_auto_compact_token_limit. Leave unset to keep model + defaults. +

+
+
+ +
+ Docs + + GPT-5.4 model page + + + Release notes + + + Config reference + +
+
+
-

Draft values only. Nothing applies until Save.

+

+ Draft values only. Nothing applies until Save. +

From 7fd2acadbe727a839e44f99081d42687322ee791 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 1 Apr 2026 15:44:06 -0400 Subject: [PATCH 04/12] docs(codex-dashboard): streamline Codex guidance copy --- .../compatible-cli/codex-docs-tab.tsx | 48 +++--- .../compatible-cli/codex-overview-tab.tsx | 148 ++++++++++-------- 2 files changed, 108 insertions(+), 88 deletions(-) diff --git a/ui/src/components/compatible-cli/codex-docs-tab.tsx b/ui/src/components/compatible-cli/codex-docs-tab.tsx index 1474155d..496390c5 100644 --- a/ui/src/components/compatible-cli/codex-docs-tab.tsx +++ b/ui/src/components/compatible-cli/codex-docs-tab.tsx @@ -107,25 +107,29 @@ export function CodexDocsTab({ diagnostics }: CodexDocsTabProps) { -

- Use ccsxp if you want the built-in CCS Codex provider shortcut on native - Codex. Use the saved recipe below if you want plain codex or a personal - alias like cxp to default to CLIProxy. -

+
+

+ Built-in: Use ccsxp for the CCS provider shortcut. +

+

+ Native: Configure the recipe below to use CLIProxy directly with{' '} + codex. +

+
               {CLIPROXY_NATIVE_CODEX_RECIPE}
             
-
-

- 1. Save the cliproxy provider in your user config. -

-

- 2. Set top-level model_provider to cliproxy. -

-

- 3. Export CLIPROXY_API_KEY in your shell before launching native Codex. -

-
+
    +
  1. + Save the cliproxy provider in your user config. +
  2. +
  3. + Set top-level model_provider to cliproxy. +
  4. +
  5. + Export CLIPROXY_API_KEY before launching native Codex. +
  6. +
@@ -137,11 +141,13 @@ export function CodexDocsTab({ diagnostics }: CodexDocsTabProps) { - {docsReference.notes.map((note, index) => ( -

- - {renderTextWithLinks(note)} -

- ))} + {docsReference.notes.length > 0 && ( +
    + {docsReference.notes.map((note, index) => ( +
  • {renderTextWithLinks(note)}
  • + ))} +
+ )}

Codex docs

diff --git a/ui/src/components/compatible-cli/codex-overview-tab.tsx b/ui/src/components/compatible-cli/codex-overview-tab.tsx index f74cd945..eb73f2eb 100644 --- a/ui/src/components/compatible-cli/codex-overview-tab.tsx +++ b/ui/src/components/compatible-cli/codex-overview-tab.tsx @@ -74,31 +74,23 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) { How Codex works in CCS - -

Codex is a first-class runtime target in CCS, but it stays runtime-only in v1.

-

- ccs-codex and ccsx launch native Codex against your saved - native config, while ccsxp is the opinionated shortcut for{' '} - ccs codex --target codex. -

-

- Plain codex or a personal alias like cxp needs{' '} - model_provider = "cliproxy" plus a matching{' '} - [model_providers.cliproxy] entry if you want CLIProxy as the saved native - default. -

-

- Built-in openai and oss providers are also valid native - defaults and do not need a custom [model_providers] stanza. -

-

- Saved default targets for API profiles and variants still remain on Claude or Droid. -

-

- CCS-backed Codex launches can apply transient -c overrides and inject - CCS_CODEX_API_KEY, so effective runtime values may not match this file - exactly. -

+ +
    +
  • Codex is a first-class, runtime-only target in CCS v1.
  • +
  • + Native config: ccs-codex and ccsx launch + native Codex using your saved defaults. +
  • +
  • + Transient overrides: ccsxp (or{' '} + ccs codex --target codex) uses the CCS provider shortcut. +
  • +
  • + CLIProxy default: To make plain codex use CLIProxy, + set model_provider = "cliproxy" and add the recipe below. +
  • +
  • API profiles continue to default to Claude or Droid.
  • +
@@ -145,32 +137,40 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) { {supportsManagedRouting ? ( <> -

- There are two supported paths. Use ccsxp if you want the built-in CCS - Codex provider shortcut. Use the saved recipe below if you want plain{' '} - codex or a personal alias like cxp to default to - CLIProxy. -

+
+

+ Two supported paths: +

+
    +
  • + Built-in: Use ccsxp for the CCS provider + shortcut. +
  • +
  • + Native: Configure the recipe below to use CLIProxy directly + with codex. +
  • +
+

Saved native Codex recipe

                     {CLIPROXY_NATIVE_CODEX_RECIPE}
                   
-
-

- 1. Save a provider named cliproxy with the base URL and env key - above. -

-

- 2. In Top-level settings, set Default provider{' '} - to cliproxy. -

-

- 3. Export CLIPROXY_API_KEY in your shell before launching native +

    +
  1. + Save a provider named cliproxy with the base URL and env key above. +
  2. +
  3. + In Top-level settings, set Default provider to{' '} + cliproxy. +
  4. +
  5. + Export CLIPROXY_API_KEY in your shell before launching native Codex. -

    -
+ + ) : (

@@ -320,30 +320,44 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) { -

-

Native Codex runtime

-

- Use ccs-codex, ccsx, or --target codex when - you want the local Codex CLI to honor your saved native user config. -

+
+

Native Codex runtime

+
    +
  • + ccs-codex +
  • +
  • + ccsx +
  • +
  • + --target codex +
  • +
+ + Honors saved native user config +
-
-

CCS Codex provider / bridge

-

- {supportsManagedRouting ? ( - <> - Use ccsxp or ccs codex --target codex when you want - the built-in CCS Codex provider on native Codex. That path uses transient - CCS-managed overrides and is separate from the saved cliproxy{' '} - recipe above. - - ) : ( - <> - The CCS Codex provider route is currently unavailable because the detected Codex - build does not expose --config overrides. - - )} -

+
+

CCS Codex provider / bridge

+ {supportsManagedRouting ? ( + <> +
    +
  • + ccsxp +
  • +
  • + ccs codex --target codex +
  • +
+ + Uses transient overrides + + + ) : ( +

+ Unavailable (Codex build lacks --config support). +

+ )}
From c1ab210095c723b8ae5c6cb88cf5f62867a6e84e Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 1 Apr 2026 16:14:16 -0400 Subject: [PATCH 05/12] feat(codex-dashboard): refine control center boundary card --- .../codex-control-center-tab.tsx | 77 +++++++++++++------ 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/ui/src/components/compatible-cli/codex-control-center-tab.tsx b/ui/src/components/compatible-cli/codex-control-center-tab.tsx index 9b701521..727a6e8d 100644 --- a/ui/src/components/compatible-cli/codex-control-center-tab.tsx +++ b/ui/src/components/compatible-cli/codex-control-center-tab.tsx @@ -1,11 +1,10 @@ -import { Route } from 'lucide-react'; +import { FileCode2, History, PenLine, Settings2 } from 'lucide-react'; import { CodexFeaturesCard } from '@/components/compatible-cli/codex-features-card'; import { CodexMcpServersCard } from '@/components/compatible-cli/codex-mcp-servers-card'; import { CodexModelProvidersCard } from '@/components/compatible-cli/codex-model-providers-card'; import { CodexProfilesCard } from '@/components/compatible-cli/codex-profiles-card'; import { CodexProjectTrustCard } from '@/components/compatible-cli/codex-project-trust-card'; import { CodexTopLevelControlsCard } from '@/components/compatible-cli/codex-top-level-controls-card'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { ScrollArea } from '@/components/ui/scroll-area'; import type { CodexConfigPatchInput, @@ -54,26 +53,60 @@ export function CodexControlCenterTab({ }: CodexControlCenterTabProps) { return ( -
- - - - - Structured controls boundary - - - -

- Guided controls write only the user-layer config.toml. They do not model - the full effective Codex runtime once trusted repo layers and CCS transient{' '} - -c overrides are involved. -

-

- Structured saves normalize TOML formatting and strip comments. Use the raw editor on - the right when exact layout matters. -

-
-
+
+
+
+ +
+
+
+
+ +
+
+

+ Structured controls boundary +

+
+
+ +
    +
  • + + + Writes exclusively to user-layer{' '} + + config.toml + + +
  • +
  • + + + Does not reflect repo trust layers or CLI overrides + +
  • +
+
+ +
+
+
+ +
+

+ Formatting Note +

+

+ Saves normalize TOML formatting and strip comments. Switch to the raw editor + if exact layout matters. +

+
+
+
+
+
+
Date: Wed, 1 Apr 2026 20:28:08 +0000 Subject: [PATCH 06/12] chore(release): 7.63.1-dev.1 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 515af049..2c39a603 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "7.63.1", + "version": "7.63.1-dev.1", "description": "Claude Code Switch - Instant profile switching between Claude, GLM, Kimi, and more", "keywords": [ "cli", From e8520133f98c58a254e0581de0efd83dd67fec23 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 1 Apr 2026 16:28:09 -0400 Subject: [PATCH 07/12] fix(account-flow): restore grouped quota hover details --- .../account/flow-viz/account-card.tsx | 66 ++++---- .../account/flow-viz/account-card.test.tsx | 141 ++++++++++++++++++ 2 files changed, 182 insertions(+), 25 deletions(-) create mode 100644 ui/tests/unit/components/account/flow-viz/account-card.test.tsx diff --git a/ui/src/components/account/flow-viz/account-card.tsx b/ui/src/components/account/flow-viz/account-card.tsx index b77f6dd7..b4f65156 100644 --- a/ui/src/components/account/flow-viz/account-card.tsx +++ b/ui/src/components/account/flow-viz/account-card.tsx @@ -3,9 +3,16 @@ */ import { AccountSurfaceCard } from '@/components/account/shared/account-surface-card'; +import { QuotaTooltipContent } from '@/components/shared/quota-tooltip-content'; import { Button } from '@/components/ui/button'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; -import { formatQuotaPercent, getProviderMinQuota, getQuotaFailureInfo, cn } from '@/lib/utils'; +import { + cn, + formatQuotaPercent, + getProviderMinQuota, + getProviderResetTime, + getQuotaFailureInfo, +} from '@/lib/utils'; import { GripVertical, Loader2, Pause, Play } from 'lucide-react'; import { useAccountQuota, @@ -207,36 +214,45 @@ export function AccountCard({ hasGroupedVariants && showQuota ? (account.variants ?? []).map((variant, index) => { const quotaQuery = variantQuotaQueries[index]; - const minQuota = getProviderMinQuota(account.provider, quotaQuery?.data); + const quota = quotaQuery?.data; + const minQuota = getProviderMinQuota(account.provider, quota); + const resetTime = getProviderResetTime(account.provider, quota); const quotaLabel = minQuota !== null ? formatQuotaPercent(minQuota) : null; const quotaValue = quotaLabel !== null ? Number(quotaLabel) : null; - const failureInfo = getQuotaFailureInfo(quotaQuery?.data); + const failureInfo = getQuotaFailureInfo(quota); const label = variant.audienceLabel ?? variant.detailLabel ?? cleanEmail(variant.email); return ( -
-
- {label} - - {quotaQuery?.isLoading - ? t('accountCard.quotaLoading') - : quotaValue !== null - ? `${quotaLabel}%` - : failureInfo?.label || t('accountCard.quotaUnavailable')} - -
- {quotaValue !== null && ( -
-
+ + +
+
+ {label} + + {quotaQuery?.isLoading + ? t('accountCard.quotaLoading') + : quotaValue !== null + ? `${quotaLabel}%` + : failureInfo?.label || t('accountCard.quotaUnavailable')} + +
+ {quotaValue !== null && ( +
+
+
+ )}
- )} -
+
+ + + +
); }) : null; diff --git a/ui/tests/unit/components/account/flow-viz/account-card.test.tsx b/ui/tests/unit/components/account/flow-viz/account-card.test.tsx new file mode 100644 index 00000000..8086b0a9 --- /dev/null +++ b/ui/tests/unit/components/account/flow-viz/account-card.test.tsx @@ -0,0 +1,141 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { render, screen, userEvent } from '@tests/setup/test-utils'; +import { AccountCard } from '@/components/account/flow-viz/account-card'; +import type { AccountData } from '@/components/account/flow-viz/types'; +import type { CodexQuotaResult } from '@/lib/api-client'; +import { useAccountQuota, useAccountQuotas } from '@/hooks/use-cliproxy-stats'; + +vi.mock('@/hooks/use-cliproxy-stats', async () => { + const actual = await vi.importActual( + '@/hooks/use-cliproxy-stats' + ); + + return { + ...actual, + useAccountQuota: vi.fn(), + useAccountQuotas: vi.fn(), + }; +}); + +const mockedUseAccountQuota = vi.mocked(useAccountQuota); +const mockedUseAccountQuotas = vi.mocked(useAccountQuotas); + +function makeCodexQuota(planType: 'plus' | 'team', fiveHour: number, weekly: number) { + return { + success: true, + planType, + lastUpdated: Date.now(), + windows: [ + { + label: 'Primary', + usedPercent: 100 - fiveHour, + remainingPercent: fiveHour, + resetAfterSeconds: 60 * 60, + resetAt: '2026-04-04T08:44:00Z', + }, + { + label: 'Secondary', + usedPercent: 100 - weekly, + remainingPercent: weekly, + resetAfterSeconds: 7 * 24 * 60 * 60, + resetAt: '2026-04-08T10:20:00Z', + }, + ], + coreUsage: { + fiveHour: { + label: 'Primary', + remainingPercent: fiveHour, + resetAfterSeconds: 60 * 60, + resetAt: '2026-04-04T08:44:00Z', + }, + weekly: { + label: 'Secondary', + remainingPercent: weekly, + resetAfterSeconds: 7 * 24 * 60 * 60, + resetAt: '2026-04-08T10:20:00Z', + }, + }, + } satisfies CodexQuotaResult; +} + +const groupedAccount: AccountData = { + id: 'codex:user@example.com', + email: 'user@example.com', + tokenFile: 'codex-user.json', + provider: 'codex', + successCount: 9, + failureCount: 1, + color: '#1e6091', + variants: [ + { + id: 'business@example.com', + email: 'user@example.com', + tokenFile: 'codex-business.json', + isDefault: false, + successCount: 5, + failureCount: 0, + audience: 'business', + audienceLabel: 'Business', + detailLabel: null, + }, + { + id: 'personal@example.com', + email: 'user@example.com', + tokenFile: 'codex-personal.json', + isDefault: true, + successCount: 4, + failureCount: 1, + audience: 'personal', + audienceLabel: 'Personal', + detailLabel: null, + }, + ], +}; + +describe('AccountCard grouped quota tooltip', () => { + beforeEach(() => { + mockedUseAccountQuota.mockReturnValue({ + data: undefined, + isLoading: false, + } as ReturnType); + + mockedUseAccountQuotas.mockReturnValue([ + { + data: makeCodexQuota('team', 95, 81), + isLoading: false, + }, + { + data: makeCodexQuota('plus', 64, 42), + isLoading: false, + }, + ] as ReturnType); + }); + + it('shows provider quota tooltip content for each grouped personal/business row on hover', async () => { + render( + undefined} + onMouseLeave={() => undefined} + onPointerDown={() => undefined} + onPointerMove={() => undefined} + onPointerUp={() => undefined} + /> + ); + + await userEvent.hover(screen.getByText('Business')); + expect((await screen.findAllByText('Plan: team')).length).toBeGreaterThan(0); + expect(screen.getAllByText('5h usage limit').length).toBeGreaterThan(0); + + await userEvent.hover(screen.getByText('Personal')); + expect((await screen.findAllByText('Plan: plus')).length).toBeGreaterThan(0); + expect(screen.getAllByText('Weekly usage limit').length).toBeGreaterThan(0); + }); +}); From 9336cfc2f6946f5d103f0861041720fbf4e097a1 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 1 Apr 2026 16:28:40 -0400 Subject: [PATCH 08/12] chore(packaging): restore prepack build hook --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 2c39a603..7bf43a96 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "prebuild": "node scripts/clean-dist.js", "prebuild:server": "node scripts/clean-dist.js", "prebuild:all": "rm -rf dist tsconfig.tsbuildinfo", + "prepack": "bun run build:all", "postbuild:all": "node scripts/verify-bundle.js", "typecheck": "tsc --noEmit", "lint": "eslint src/", From 2844704b5d79b91970e948de24b28ff6be9745dd Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 1 Apr 2026 16:32:15 -0400 Subject: [PATCH 09/12] fix(packaging): remove duplicate prepack script --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 7bf43a96..7c7e74b3 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,6 @@ "ui:build": "cd ui && bun run build", "ui:preview": "cd ui && bun run preview", "ui:validate": "cd ui && bun run validate", - "prepack": "bun run build:all", "prepare": "husky", "postinstall": "node scripts/postinstall.js" }, From 78f6c5d7156e1fb447e523b355c302a45d5b71bd Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 1 Apr 2026 16:41:28 -0400 Subject: [PATCH 10/12] hotfix: restore prepack packaging contract --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 41784d12..515af049 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "ui:build": "cd ui && bun run build", "ui:preview": "cd ui && bun run preview", "ui:validate": "cd ui && bun run validate", + "prepack": "bun run build:all", "prepare": "husky", "postinstall": "node scripts/postinstall.js" }, From c885a1ecd062ce1e767447cf214c84240dba271e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 1 Apr 2026 20:46:03 +0000 Subject: [PATCH 11/12] chore(release): 7.63.1-dev.2 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7c7e74b3..50f36aae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "7.63.1-dev.1", + "version": "7.63.1-dev.2", "description": "Claude Code Switch - Instant profile switching between Claude, GLM, Kimi, and more", "keywords": [ "cli", From 5e754dee55096977c56de5175413fefbda3992ba Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 1 Apr 2026 20:46:51 +0000 Subject: [PATCH 12/12] chore(release): 7.64.0 [skip ci] ## [7.64.0](https://github.com/kaitranntt/ccs/compare/v7.63.1...v7.64.0) (2026-04-01) ### Features * add first-class CCS WebSearch runtime ([e260df7](https://github.com/kaitranntt/ccs/commit/e260df7178dd030efe3d490b4cd9421b386c43c2)) * **cliproxy:** support duplicate-email codex accounts ([c73f338](https://github.com/kaitranntt/ccs/commit/c73f33872abc165aaaba8a240c06cc4ccbb6f5b4)) * **ui:** unify duplicate-email account surfaces ([80341f1](https://github.com/kaitranntt/ccs/commit/80341f18c3c93dd4cdca92f6965136cff90be6f7)) * **websearch:** add managed mcp runtime and provider cooldowns ([7f83e04](https://github.com/kaitranntt/ccs/commit/7f83e041b7fa8907a5d9484acd21ea88edac67e7)) * **websearch:** finish managed third-party rollout ([de7171d](https://github.com/kaitranntt/ccs/commit/de7171d81076ecfd4e5e6474ce062c44a4c8be9a)), closes [#862](https://github.com/kaitranntt/ccs/issues/862) ### Bug Fixes * bootstrap explicit CODEX_HOME for ccsxp ([b6717c5](https://github.com/kaitranntt/ccs/commit/b6717c55295dadeeff87ec356bc19d7b1b1d3733)) * **ci:** harden ai review output ([4950be7](https://github.com/kaitranntt/ccs/commit/4950be7fc025877dd6744b9c8c87b4714ec49de2)) * **codex:** harden duplicate-email account actions ([22f0916](https://github.com/kaitranntt/ccs/commit/22f091689fadf485d24fc46510f211a489680a2b)) * **codex:** harden native runtime detection ([ca54bad](https://github.com/kaitranntt/ccs/commit/ca54bad2aa774c9dc3de9429d3e1440d179796a7)) * **dashboard:** use shared base time for connection timeline events ([d76ef5b](https://github.com/kaitranntt/ccs/commit/d76ef5bcb30bd87ea487894586978473fcc8fead)), closes [#856](https://github.com/kaitranntt/ccs/issues/856) * **docker:** harden cliproxy local proxy with auth guard, dynamic port, and body handling ([6471cc5](https://github.com/kaitranntt/ccs/commit/6471cc55d72deeebcbca70d54983b90ae32c999a)) * **docker:** harden proxy with timeout, Bun compat, and test coverage ([27409b7](https://github.com/kaitranntt/ccs/commit/27409b789b29a1d008f621f55aab7eed9861d6d4)) * **docker:** proxy CLIProxy management panel through dashboard to avoid cross-origin errors ([881b061](https://github.com/kaitranntt/ccs/commit/881b061dfec497d7120b15d2045cf8e1a8f04bcc)) * harden ai review comment formatting ([fa37f39](https://github.com/kaitranntt/ccs/commit/fa37f391c17c51c74a5bfe109225caf01798dc96)) * merge existing disallowed tool flags for websearch ([b087c02](https://github.com/kaitranntt/ccs/commit/b087c02738c7e588491f84c8f2318c2ef5b03eca)) * normalize websearch hook output for local providers ([a5d71e3](https://github.com/kaitranntt/ccs/commit/a5d71e35606ebaf16f97d860405800e9a9454659)) * probe Codex config override support directly ([014a844](https://github.com/kaitranntt/ccs/commit/014a844844c76fe5dfce7763ed62e071ee063f58)) * **ui:** normalize grouped account audience order ([498448a](https://github.com/kaitranntt/ccs/commit/498448a43181280dc06035d80b694f7522a1b28a)) * use type-only import in instance manager ([f0cd44e](https://github.com/kaitranntt/ccs/commit/f0cd44e016c2c4a33ad06edcf6b13d8cbf1920bf)) * **websearch:** close review gaps in managed runtime ([cae5b71](https://github.com/kaitranntt/ccs/commit/cae5b710b86a6bd985424cce028a502265051d09)) ### Hotfixes * restore prepack packaging contract ([78f6c5d](https://github.com/kaitranntt/ccs/commit/78f6c5d7156e1fb447e523b355c302a45d5b71bd)) ### Documentation * **architecture:** document codex duplicate-email identity ([be0c597](https://github.com/kaitranntt/ccs/commit/be0c597d255a21a7e935f033dfcc4de4e19c8c8e)) ### Code Refactoring * remove duplicate copilot websearch sync ([2c15304](https://github.com/kaitranntt/ccs/commit/2c153045f33e02cc821a623f6594fb4a6bebb321)) ### Tests * **dashboard:** add regression tests for connection timeline event generation ([4ab5194](https://github.com/kaitranntt/ccs/commit/4ab51945a412321e3e56d0c6860d0121761dcbfa)) --- CHANGELOG.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c99c014b..2fb600a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,47 @@ +## [7.64.0](https://github.com/kaitranntt/ccs/compare/v7.63.1...v7.64.0) (2026-04-01) + +### Features + +* add first-class CCS WebSearch runtime ([e260df7](https://github.com/kaitranntt/ccs/commit/e260df7178dd030efe3d490b4cd9421b386c43c2)) +* **cliproxy:** support duplicate-email codex accounts ([c73f338](https://github.com/kaitranntt/ccs/commit/c73f33872abc165aaaba8a240c06cc4ccbb6f5b4)) +* **ui:** unify duplicate-email account surfaces ([80341f1](https://github.com/kaitranntt/ccs/commit/80341f18c3c93dd4cdca92f6965136cff90be6f7)) +* **websearch:** add managed mcp runtime and provider cooldowns ([7f83e04](https://github.com/kaitranntt/ccs/commit/7f83e041b7fa8907a5d9484acd21ea88edac67e7)) +* **websearch:** finish managed third-party rollout ([de7171d](https://github.com/kaitranntt/ccs/commit/de7171d81076ecfd4e5e6474ce062c44a4c8be9a)), closes [#862](https://github.com/kaitranntt/ccs/issues/862) + +### Bug Fixes + +* bootstrap explicit CODEX_HOME for ccsxp ([b6717c5](https://github.com/kaitranntt/ccs/commit/b6717c55295dadeeff87ec356bc19d7b1b1d3733)) +* **ci:** harden ai review output ([4950be7](https://github.com/kaitranntt/ccs/commit/4950be7fc025877dd6744b9c8c87b4714ec49de2)) +* **codex:** harden duplicate-email account actions ([22f0916](https://github.com/kaitranntt/ccs/commit/22f091689fadf485d24fc46510f211a489680a2b)) +* **codex:** harden native runtime detection ([ca54bad](https://github.com/kaitranntt/ccs/commit/ca54bad2aa774c9dc3de9429d3e1440d179796a7)) +* **dashboard:** use shared base time for connection timeline events ([d76ef5b](https://github.com/kaitranntt/ccs/commit/d76ef5bcb30bd87ea487894586978473fcc8fead)), closes [#856](https://github.com/kaitranntt/ccs/issues/856) +* **docker:** harden cliproxy local proxy with auth guard, dynamic port, and body handling ([6471cc5](https://github.com/kaitranntt/ccs/commit/6471cc55d72deeebcbca70d54983b90ae32c999a)) +* **docker:** harden proxy with timeout, Bun compat, and test coverage ([27409b7](https://github.com/kaitranntt/ccs/commit/27409b789b29a1d008f621f55aab7eed9861d6d4)) +* **docker:** proxy CLIProxy management panel through dashboard to avoid cross-origin errors ([881b061](https://github.com/kaitranntt/ccs/commit/881b061dfec497d7120b15d2045cf8e1a8f04bcc)) +* harden ai review comment formatting ([fa37f39](https://github.com/kaitranntt/ccs/commit/fa37f391c17c51c74a5bfe109225caf01798dc96)) +* merge existing disallowed tool flags for websearch ([b087c02](https://github.com/kaitranntt/ccs/commit/b087c02738c7e588491f84c8f2318c2ef5b03eca)) +* normalize websearch hook output for local providers ([a5d71e3](https://github.com/kaitranntt/ccs/commit/a5d71e35606ebaf16f97d860405800e9a9454659)) +* probe Codex config override support directly ([014a844](https://github.com/kaitranntt/ccs/commit/014a844844c76fe5dfce7763ed62e071ee063f58)) +* **ui:** normalize grouped account audience order ([498448a](https://github.com/kaitranntt/ccs/commit/498448a43181280dc06035d80b694f7522a1b28a)) +* use type-only import in instance manager ([f0cd44e](https://github.com/kaitranntt/ccs/commit/f0cd44e016c2c4a33ad06edcf6b13d8cbf1920bf)) +* **websearch:** close review gaps in managed runtime ([cae5b71](https://github.com/kaitranntt/ccs/commit/cae5b710b86a6bd985424cce028a502265051d09)) + +### Hotfixes + +* restore prepack packaging contract ([78f6c5d](https://github.com/kaitranntt/ccs/commit/78f6c5d7156e1fb447e523b355c302a45d5b71bd)) + +### Documentation + +* **architecture:** document codex duplicate-email identity ([be0c597](https://github.com/kaitranntt/ccs/commit/be0c597d255a21a7e935f033dfcc4de4e19c8c8e)) + +### Code Refactoring + +* remove duplicate copilot websearch sync ([2c15304](https://github.com/kaitranntt/ccs/commit/2c153045f33e02cc821a623f6594fb4a6bebb321)) + +### Tests + +* **dashboard:** add regression tests for connection timeline event generation ([4ab5194](https://github.com/kaitranntt/ccs/commit/4ab51945a412321e3e56d0c6860d0121761dcbfa)) + ## [7.63.1](https://github.com/kaitranntt/ccs/compare/v7.63.0...v7.63.1) (2026-04-01) ### Hotfixes diff --git a/package.json b/package.json index 515af049..e92eab14 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "7.63.1", + "version": "7.64.0", "description": "Claude Code Switch - Instant profile switching between Claude, GLM, Kimi, and more", "keywords": [ "cli",