Add a show/hide toggle for the header project profile switcher under
the Homepage Display section in settings. Defaults to visible so
existing users keep the current behavior.
Expose the top-level `model` key of config.toml as an editable field in
the Codex provider form, so users can switch to newly released models
(e.g. gpt-5.6) without waiting for a preset update — preset updates only
affect newly added providers, existing ones keep their saved TOML.
The field syncs bidirectionally with the TOML editor (mirroring the
base-URL pattern), suggests models from the mapping catalog plus the
provider's /models endpoint, and offers a one-click "add to mapping"
action when the value is outside a non-empty catalog. Hidden for
official providers.
Details:
- Save-time catalog sync now only backfills the first mapping row into
the top-level model when the field was left empty, so the explicit
field wins over the implicit row-0 sync
- Escape model names (and base_url) with TOML basic-string escaping and
strip control characters from field input: /models ids are remote
data and unescaped interpolation could inject config.toml lines
(e.g. a forged [mcp_servers.*] command)
- The strict model-line matcher now recognizes escaped output, empty
strings and single-quoted literals, keeping extract/set round-trips
stable; deliberately no key-only loose matching (it would misfire on
assignment-looking text inside multiline strings)
- Invalidate the fetched model list whenever the request identity
changes (base URL, full-URL toggle, API key, custom UA) and discard
in-flight responses via a sequence guard, so the dropdown never shows
a previous provider's models
- i18n for zh/en/ja/zh-TW
Add Hunyuan Hy3 (released 2026-07-06, 256K context) to seed_model_pricing
under both the hunyuan-hy3 and hy3 ids, since the upstream billing id isn't
yet confirmed and one of the two should match logged usage.
Prices come from Tencent's launch-day list price (CNY 1 / 4 / 0.25 per 1M
input / output / cache-hit), converted at 1 USD ~= 7.14: 0.14 / 0.56 / 0.035.
cache_write is "0" (no published write rate). Hy3 actually uses input-length
tiered billing (<16K / 16-32K / >=32K); the single-price table holds the
lowest tier, so long-context requests are under-billed until this is revisited
against the official billing page. New model, so seed only.
Add the three GPT-5.6 tiers to seed_model_pricing, cross-checked against
OpenAI's official pricing page and OpenRouter:
gpt-5.6-sol 5 / 30 / 0.50 (cache read)
gpt-5.6-terra 2.50 / 15 / 0.25
gpt-5.6-luna 1 / 6 / 0.10
cache_write is "0": OpenAI publishes only a cached-input (read) rate and no
separate cache-write price, matching the gpt-5.x family convention. Base ids
only for now — effort-suffix variants are deferred until the upstream logging
suffix set for 5.6 is confirmed. New model, so seed only (no repair branch,
no SCHEMA_VERSION bump).
Switching the Claude provider from a third-party (DeepSeek/MiMo/...) to a
managed Codex provider wrote both ANTHROPIC_API_KEY and
ANTHROPIC_AUTH_TOKEN as PROXY_MANAGED into ~/.claude/settings.json,
making Claude Code warn "Both ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY
set - auth may not work as expected" on every run.
The double key is an accretion artifact: the ManagedAccount takeover
policy originally injected only ANTHROPIC_API_KEY (Copilot, #1049), and
the Codex fix that preserved ANTHROPIC_AUTH_TOKEN to avoid the login
prompt (#3784 / PR #3789) added the token on top without removing the
API key insert.
Inject exactly one placeholder instead: AUTH_TOKEN for Codex-managed
providers (keeps the #3784 fix), API_KEY for Copilot (keeps the #1049
behavior). Local-proxy auth is unaffected - the forwarder resolves the
PROXY_MANAGED placeholder from either header.
Updates the three Codex takeover tests to assert API_KEY is absent and
adds a regression test for the exact third-party -> Codex switch from
the report.
Fixes#4919
* Add test for Fable model env key exclusion
Add regression test to ensure Fable model env keys are excluded from common config.
* Add Fable model to CLAUDE_MODEL_OVERRIDE_ENV_KEYS
* Add files via upload
* Delete .github/workflows/build-windows-unsigned.yml
* feat(codex): support native Anthropic Messages protocol as upstream
Allow gateways that only expose the native Anthropic Messages protocol
(/v1/messages) to be used by Codex: the local proxy performs bidirectional
request/response/streaming conversion between Responses and Anthropic.
Backend:
- Add two conversion modules: transform_codex_anthropic / streaming_codex_anthropic
- codex.rs: add routing detection and auth: ANTHROPIC_AUTH_TOKEN→Bearer (default),
ANTHROPIC_API_KEY→x-api-key, mutually exclusive
- handlers.rs: add handle_codex_anthropic_to_responses_transform
- forwarder.rs: support optional Claude Code client fingerprint impersonation
(User-Agent / anthropic-beta / x-app / system prompt first-line injection)
and /responses→/v1/messages rewriting
- codex_config: the anthropic format reuses the NativeResponses profile to strip
custom tools
- ProviderMeta: add impersonateClaudeCode
Frontend:
- CodexApiFormat: add "anthropic"; the form adds auth field selection and an
impersonation toggle
- Add en/ja/zh/zh-TW copy
Robustness:
- Downgrade when tool history / forced tool_choice conflicts with extended
thinking, avoiding upstream 400s
- Emit cache_creation_input_tokens in usage and use saturating_add to guard
against overflow
- Append a unique suffix to non-streaming/streaming output-item ids to avoid
multi-segment text/thinking overwriting each other
* fix(codex): harden Anthropic bridge against empty text blocks and truncated streams
- Drop empty/whitespace-only text content blocks when rebuilding Anthropic
messages from Responses history; Anthropic 400s on empty text blocks (e.g. an
empty assistant text emitted alongside a tool_use), which broke follow-up and
tool-result requests. Also drop messages left without content.
- Do not report a truncated Anthropic stream as completed: when the SSE
connection ends before message_stop with no stop_reason, emit an incomplete
response if partial output exists, or a failed (stream_truncated) response
otherwise, mirroring the chat converter's EOF handling.
* fix(codex): resolve Anthropic-bridge review findings
Blocking:
1. Defer stripping the [1m] long-context marker until after catalog matching and model write-back, re-stripping it on the final Codex→Anthropic body and setting a flag to emit the context-1m-2025-08-07 beta header, so the marker is no longer lost or overridden by the default model.
2. Gate Anthropic thinking on the trailing turn only (via trailing_turn_allows_thinking) instead of scanning full history, so a Codex session that resends history each round no longer permanently loses thinking after the first tool call.
3. Inject 5m ephemeral cache_control on the Codex→Anthropic body by reusing cache_injector (handling the system string→array conversion), so system/tools/history are cached instead of re-sent at full price every round.
4. Add a shared base_url_is_full_endpoint helper (normalizing whitespace/query/fragment/trailing slash) used by both the Anthropic and Chat paths, so a base URL already ending in /v1/messages is treated as a full endpoint instead of double-appending to /v1/messages/v1/messages.
5. Align the catalog tool-profile predicate with the routing predicate so resolve_codex_catalog_tool_profile returns the Anthropic profile whenever the request converts to Anthropic, preventing freeform tools like apply_patch from being silently filtered by a ProxyChat catalog.
6. Explicitly disable native web_search for the Anthropic profile (including the no-catalog branch) via set_codex_native_web_search_field, so Codex no longer treats it as available while the transform silently drops it.
7. Only forward tool_choice when tools survive filtering, dropping it otherwise, to avoid a non-retryable 400 from upstream when tool_choice is sent with no tools.
8. Lower the fallback default to max_tokens=8192 (only when max_output_tokens is omitted) and clamp the thinking budget to max_tokens/2, disabling thinking below the 1024 floor, to avoid hard 400s on low-output-ceiling models/gateways.
Minor:
9. Centralize the Codex/OpenAI fingerprint-header denylist in is_codex_client_fingerprint_header so impersonating Claude Code uniformly drops originator/session_id/conversation_id/chatgpt-account-id/x-client-request-id/openai-* and the x-stainless-*/x-codex-* prefixes.
10. Retain content_block_start.input as start_input and fall back to it at block close when no input_json_delta arrived, so a gateway that carries the full tool input on the start event no longer yields empty tool arguments.
11. Extract a shared codex_responses_sse module as the single Responses SSE envelope builder that both the chat and anthropic streaming emitters delegate to, with byte-for-byte-unchanged wire output, so future event-format fixes touch one place instead of two.
* fix(codex): add per-provider max_output_tokens override for Codex→Anthropic path
Codex does not forward model_max_output_tokens in the request body,
causing the proxy to fall back to a conservative 8192 default. This
truncates long or thinking-heavy responses (stop_reason=max_tokens).
- Add maxOutputTokens field to ProviderMeta (Rust + TypeScript)
- Inject the value into the Anthropic request body before transform,
taking precedence over request-supplied and default values
- Add numeric input in Codex form fields (Anthropic format only)
- Add i18n entries for label, placeholder, and hint (en/zh/zh-TW/ja)
- Include roundtrip and omission unit tests for the new field
* fix(codex): harden Anthropic protocol bridge
* fix(codex): address Anthropic bridge review
* fix(codex): preserve flattened Anthropic inputs
* fix(codex): harden Anthropic recovery paths
* fix(ci): satisfy Rust clippy
---------
Co-authored-by: Jason <farion1231@gmail.com>
The fallback model (ANTHROPIC_MODEL) field was missing the 1M checkbox
that the role-specific model rows already have. Added the checkbox with
the same strip/set marker logic used by role rows.
Fixes#3679
Add the open-source AI infrastructure project new-api as the latest
sponsor entry, appended to the end of the sponsor table across all four
localized READMEs (en/zh/ja/de). Includes the banner asset.
updateTomlCommonConfigSnippet re-serialized the whole document through
smol-toml (parse -> deepMerge -> stringify): comments dropped, keys
reordered, and empty parent table headers synthesized -- the
long-standing "config.toml keeps getting reordered" symptom (audit C5,
introduced in 083e48bf).
Replace it with a backend command backed by the same
merge_toml_table_like / remove_toml_table_like used when writing live
configs, so the form preview and the live write share one merge
semantic and user formatting survives edit-time merges. The frontend
sync helper is deleted outright to keep the pattern from coming back.
Making the form operations async exposes them to interleaving, so a
result is discarded unless it is still current when it lands:
- a per-hook sequence number (last operation wins) covers rapid
toggle/save races where an earlier merge resolves after a later
removal and would flip the switch back;
- a config-baseline check covers the user hand-editing the TOML while
a merge is in flight -- the stale result must not clobber their edit.
The checkbox state self-heals via the existing inference effect.
Regression tests pin both by resolving a suspended merge after a newer
operation / an external edit, plus backend tests locking comment and
key-order preservation, scalar override, and value-matched removal.
import_mcp_from_apps swallowed every importer error with unwrap_or(0),
so a corrupt config.toml surfaced as "imported 0 servers" with no hint
that anything went wrong.
Move the aggregation into McpService::import_from_all_apps: each app
imports best-effort (one bad file doesn't block the rest), and failures
are collected into a single error naming the failing apps alongside the
count that did import. The frontend now refreshes the server list on
settle rather than success, since a partial failure still means new
servers were persisted.
sync_all_enabled iterated AppType::all() with `?`, so one app's corrupt
live file (e.g. a broken ~/.claude.json, which passes the existence
gate but fails to parse) blocked every app behind it in the iteration
order -- and bubbled the error into whatever operation triggered the
sync:
- switch/save had just rewritten only the target app's live file, yet a
broken unrelated file failed the whole operation after DB and live
were already updated, reporting a false "switch failed" to the user.
Both now project only the target app (sync_enabled_for_app) and
degrade projection failure to a warning: the primary operation has
already taken effect, and the projection self-heals on the next
switch or MCP toggle.
- sync_current_provider_for_app_to_live syncs a single app; it now
projects that app only, keeping failures (which can only concern the
target app) as errors.
- sync_current_to_live (config import / cloud-sync restore) keeps the
all-apps sweep, but sync_all_enabled is now best-effort: it projects
every app, collects failures, and reports them aggregated. Its error
is held until after skill sync so an MCP failure no longer skips it.
Claude already re-extracts the live config into the shared common-config
snippet right before switching away, so shared tweaks (plugins,
preferences) made in live propagate to all opted-in providers. Codex was
gated out because its TOML pipeline leaked provider-specific and
injected content into the snippet.
With the extractor now stripping all injection artifacts and routing
fields, and backfill stripping the MCP projection, open the gate to
Claude + Codex. The autosync-before-strip ordering also self-heals
stale snippet values previously baked into provider snapshots: the
re-extracted snippet matches the live values, so the value-match strip
removes them on the same switch.
End-to-end tests cover: new shared keys captured, deletions synced,
secrets/injected artifacts never entering the snippet, and provider A's
key not leaking into provider B's live.
Toggling unify_codex_session_history rewrites the current official
provider's live config.toml in full (intended design), which drops the
[mcp_servers] projection -- and nothing put it back, so enabled MCP
servers silently vanished until the next provider switch (#C2).
Re-project after the rewrite, with two deliberate choices:
- Project Codex only (new McpService::sync_enabled_for_app) instead of
sync_all_enabled: the all-apps sync short-circuits in AppType::all()
order, so a corrupt ~/.claude.json would error before Codex is ever
reached and the freshly wiped [mcp_servers] would stay missing. Only
Codex's live file was rewritten here, so only Codex needs
re-projection.
- Degrade projection failure to a warning: by this point the live file
already carries the new bucket state, so the toggle has taken effect.
Propagating the error would make save_settings roll back the setting,
creating the exact "setting=old, live=new bucket" session split the
rollback exists to prevent. The projection self-heals on the next
switch or any MCP toggle.
extract_codex_common_config kept several fields in the shared snippet
that must never cross providers:
- [mcp_servers] and the legacy [mcp.servers] form: owned by the DB
mcp_servers table; once in the snippet they get merged into every
opted-in provider and no sync path can ever clean them up.
- top-level experimental_bearer_token: normally lives inside
[model_providers.<id>] (stripped with the whole table), but three
fallbacks write it at top level -- leaking the API key into the
shared snippet.
- model_catalog_json: per-provider catalog projection pointer.
- web_search, only when it equals the injected "disabled" sentinel;
a user-set value remains a shareable preference.
- top-level wire_api: same provider-routing semantics as top-level
base_url (the fallback target when no model_provider is set); leaking
it would rewrite the next provider's protocol selection.
This makes the extractor safe as the source for switch-time
common-config autosync.
MCP servers are owned by the DB mcp_servers table; the [mcp_servers]
section in live config.toml is only a projection re-synced after every
live write. When switching away baked that projection into the stored
provider snapshot, servers deleted in the app were resurrected the next
time that provider was activated -- per-entry reconcile only knows rows
that still exist in the DB, so it can never clean up such orphans.
Strip [mcp_servers] (and the legacy [mcp.servers] form) from the live
settings during switch-away backfill. Previously polluted snapshots
self-heal the next time the user switches away from them.
sync_single_server_to_codex silently fell back to an empty DocumentMut
when the existing config.toml failed to parse, then wrote the whole file
back -- wiping every other section (model, model_providers, comments)
and leaving only the single synced [mcp_servers.<id>] entry. Introduced
by 3a548152, which fixed the removal path but left the destructive
fallback on the sync path.
Now the sync path returns an McpValidation error and leaves the file
untouched, matching the fail-closed behavior of the read/validate path.
The websiteUrl for 火山Agentplan, DouBaoSeed and BytePlus is intentionally the ccswitch-attributed apiKeyUrl link, per the Volcengine partner's request. Restore the invite/attribution links across all six app preset files.
This reverts the websiteUrl changes from 56248087; apiKeyUrl was already left intact there.
Remove the 'OpenAI Compatible' custom-template preset from the OpenCode
and OpenClaw preset lists. It was a duplicate entry point: the built-in
'custom' provider flow already exposes an interface-format selector whose
default (@ai-sdk/openai-compatible / openai-completions) produces a
byte-identical starting config. Having both left two list items pointing
to the same place.
Existing providers are unaffected — presets only seed form defaults on
creation; saved providers store concrete npm/baseUrl/apiKey/models values.
The '@ai-sdk/openai-compatible' dropdown label is intentionally kept; it
is the format option users pick in the custom flow, not the deleted preset.
* fix: sync openclaw live provider updates
* fix: sync hermes live provider updates
* fix test: hermes live import stores models as array after denormalize
The test import_hermes_providers_from_live_updates_existing_provider_from_live
seeded models as a dict, but import_hermes_providers_from_live reads via
get_providers() which calls denormalize_provider_models_for_read(), converting
models from YAML dict to UI-friendly array. The test assertion accessed models
as dict -> Null -> assertion failure.
Fix: access models as array by index, matching the actual storage format after
live import. Also verify the id field is preserved in the denormalized output.
* fix: sync existing opencode providers from live config
* fix: preserve OpenCode provider display names
* docs: update comments and log messages to reflect new update behavior
- fix stale comment in lib.rs that said existing providers are skipped
- change log message from 'Imported' to 'Synced' since count now
includes both new imports and updates
pnpm 10.13+ appends "set this to true or false" allowBuilds
placeholders to pnpm-workspace.yaml on every install for unreviewed
dependencies with lifecycle scripts. Approve esbuild (postinstall
ensures the platform binary) and ignore msw (postinstall only prints
an integration notice) so the file stops mutating itself.
Usage/quota queries frequently showed spurious "query failed" states that
manual refresh could not clear (#3820). Root cause: all transport-level
failures were folded into Ok(success:false), so react-query's retry never
fired and the failure body poisoned the cache as regular data.
Backend:
- balance/coding_plan/subscription services now return Err for send
failures and body-read failures (read body via bytes() before
serde_json::from_slice; reqwest's json() wraps read errors as Decode,
making error-kind checks on it dead code). Auth/4xx/parse errors stay
Ok(success:false) and surface immediately.
- Script path maps transient AppError keys (request_failed /
read_response_failed) to Err; Volcengine adds a Transient call variant.
- Command layer skips snapshot persistence, usage-cache-updated emit and
tray refresh on Err so the cache bridge cannot overwrite retained data.
- Expired-credential retry propagates transient errors instead of
rewriting them as "OAuth token has expired".
Frontend:
- resolveDisplayUsage generalized to subscription quotas; 5th param is now
an options object with a `rejected` flag: stale success data retained by
react-query across rejections is re-anchored to dataUpdatedAt and expires
through the same 10-minute keep-last-good window instead of being shown
indefinitely (a total outage used to mask longer than a single 5xx).
- useSubscriptionQuota / useCodexOauthQuota gain keep-last-good with
per-scope snapshot reset; rejected queries with no displayable value
synthesize a failure placeholder carrying the real error message so
footers keep rendering a retry entry point.
- HTTP 429 is classified transient (retry-later) alongside 5xx.
- UsageScriptModal surfaces string rejections via extractErrorMessage.
Tests: 6 backend behavior tests drive real HTTP against a local listener
to pin the Err/Ok channel semantics; frontend suite extends
keepLastGoodUsage coverage (405 passing).
- Add CODEOWNERS so PRs to main require owner review (closes the gap
where two collaborators could approve each other's changes).
- Gate the release job behind a 'release' environment, so signing
secrets are only unlocked after manual approval.
- Stop ignoring the whole .github directory; this had been silently
swallowing labeler.yml and workflows/labeler.yml, which are now tracked.
Paired with a tag ruleset (created out-of-band) that restricts creation
of v* tags to admins, this prevents a write collaborator from pushing a
tag to trigger a signed release build.
Fixes#5025. The rectifier's media fallback missed Volcano Coding Plan's
GLM 5.2 on both paths:
- Preventive: known_text_only_model had glm-5.1 but not glm-5.2, so image
blocks were forwarded verbatim. Added glm-5.2 as an exact tail match
(not a prefix, to avoid stripping images from a future glm-5.2v
multimodal variant following Zhipu's 4v/5v naming).
- Reactive: the upstream error "Model only support text input" never
mentions image/media, so the mentions_image gate rejected it before
hints ran; and the existing "only supports text" hint misses the
gateway's missing third-person "s". Added a self-evident phrase list
("only support text" / "only supports text") that asserts a modality
rejection on its own and bypasses the image-mention gate.
Includes regression tests using the verbatim #5025 error body and the
glm-5.2[1M] mapped-model form, plus a glm-5.2v negative assertion.
The desktop switch to d2 is cfg-gated to macOS/Windows, but the assertion
expecting d2 was not, so on Linux CI the desktop provider stays at the seeded
d1 and the assertion panics — poisoning the shared test mutex and cascading
into two more failures. Expect d1 on non-desktop platforms.
Replace the ?source=ccswitch referral with the new agent register invite
link (https://code0.ai/agent/register/B2XHxGjGmRvqgznY) on code0's
apiKeyUrl across all seven presets and the sponsor rows in all four
READMEs. API endpoints stay bare per the referral red-line.
Since switching projects automatically saves the current configuration
back to the project being left, the per-project "update from current"
button is redundant and even harmful: it allowed overwriting another
project's snapshot with the current state, breaking the invariant that
a project holds the state you last left it in.
- Remove the resnapshot button and confirm dialog from the manage
dialog; drop the now-unused scope prop and PROFILE_SCOPE_LABELS
- Add a footer Close button to the manage dialog (overlay click is
disabled app-wide, so it previously could only be closed via Esc)
- Update manageDescription in all four locales to describe the
autosave behavior; remove the two dead i18n keys
- Fix a shadowed `warnings` vec in ProfileService::apply that silently
dropped autosave-failure warnings before they reached the frontend
- Mention auto-capture on next switch in the "no snapshot for this
scope yet" warning and doc comments
The backend update command keeps its resnapshot parameter; it is now
only exercised by the pre-switch autosave path.
When switching to a different project, the current configuration of the
previously active project is automatically captured into that project's
snapshot before the target project's snapshot is applied. Scope is limited
to the active group (Claude/Codex); autosave failures are collected as
warnings and do not block the switch.
- Move autosave logic into ProfileService::apply for consistent behavior
across UI, tray, and tests
- Add integration test covering bidirectional autosave roundtrip
Refs: profile switch B方案
Projects are now global shared entities; Claude and Codex groups switch
independently via scoped current pointers and scoped payload slots.
- Remove scope column from profiles; keep current_profile_id_<scope>
- Use Option<Vec<String>> for mcp/skills to distinguish 'never captured'
from 'captured empty', preventing cross-side accidental disable
- Update/apply operations scoped to the active group via merge_scope_from
- Tray menu nests same shared list under Claude Code/Codex groups
- Add i18n for per-scope tooltips and 'not saved for this side' hint
Refs: profile P1 shared-entity redesign
Claude Desktop's only dimension managed by cc-switch is its provider
(MCP/Skills are hardcoded unsupported and prompts have no live file),
so snapshots capture just the current desktop provider while the empty
MCP/Skills sets and None prompt make apply a natural no-op for the
other dimensions - no per-dimension special casing needed.
- Add claude-desktop slot to PROFILE_APPS and PerApp (serde key uses
the hyphenated app id); old payloads without the key deserialize to
None and leave Claude Desktop untouched on apply
- Gate the tray Projects submenu by iterating PROFILE_APPS instead of
hardcoding Claude/Codex visibility
- Show the profile switcher on the Claude Desktop tab, mirror the
PerApp type, invalidate the claude-desktop providers cache on apply,
and extend the switcher tooltip in all four locales
- Extend the roundtrip integration test with the desktop provider
dimension; the desktop switch is cfg-gated to macOS/Windows because
desktop live writes error on Linux where CI runs cargo test
- Render the profile switcher only when the active tab is Claude or
Codex (frontend mirror of backend PROFILE_APPS), so viewing an
unsupported app no longer suggests its config was switched
- Move the switcher from beside the logo to the right of the route
toggles, where the flexible spacer absorbs its appearance and other
header controls no longer shift when switching tabs
Add a profile feature that captures the current provider, MCP,
skills and prompt state for Claude Code and Codex as a named
snapshot, and re-applies it in one click from the header switcher
or the tray Projects submenu.
- New profiles table (schema v12) with current marker in settings
- ProfileService orchestrates the four existing switch primitives
(provider first, then MCP diff, skills diff, prompt enable)
- Best-effort apply: dangling references become warnings, no rollback
- Header combobox switcher + snapshot-style manage dialog
- Tray Projects submenu shared with the UI apply/event pipeline
- i18n for zh/en/ja/zh-TW under the new profiles domain
- Integration tests covering roundtrip, dangling refs and clear
* feat: add Claude subagent takeover config
* feat: add Claude subagent model field
* i18n: add Claude subagent model labels
* fix(proxy): preserve configured subagent model mapping
* fix(providers): exclude subagent model from Claude common config
* style: format rust code
* fix(codex): display renamed session titles
* fix(codex): resolve custom state db for titles
* refactor(codex): dedupe state-db resolution and harden title lookup
- Extract a shared `codex_state_db` module for `state_5.sqlite` path
resolution (config `sqlite_home` / `CODEX_SQLITE_HOME`), previously
copy-pasted verbatim between codex_history_migration and the codex
session provider. `state_5.sqlite` is now a single source of truth.
- Add `busy_timeout` to the read-only title query; without it a read
during a concurrent Codex write fails immediately (SQLITE_BUSY) and
renamed titles silently fall back to the first user message.
- Push the `title == first_user_message` comparison into the SQL WHERE
clause (NULL-safe, aligning with Codex's `distinct_thread_metadata_title`
semantics) and stop SELECTing `first_user_message`, which can hold
large values (openai/codex#29007) — the comparison belongs in SQL
anyway, so the column no longer needs to cross into Rust.
* fix(subscription): display Codex free-plan 30-day quota window (#3651)
Codex free accounts are metered on a rolling 30-day secondary window
(limit_window_seconds = 2,592,000) instead of the weekly window used by
paid plans. The backend already maps this correctly to the tier name
"30_day", but the frontend TIER_I18N_KEYS whitelist had no entry for it,
so SubscriptionQuotaView filtered the tier out. When that was the only
surviving tier (as happens for free accounts), the whole quota footer
rendered nothing — free accounts showed no remaining quota or reset time
while paid accounts worked.
- Add "30_day" -> subscription.thirtyDay to TIER_I18N_KEYS
- Add the thirtyDay label to all four locales (zh/en/ja/zh-TW)
- Add a unit test locking in window_seconds_to_tier_name mappings,
including the 30-day case
Fixes#3651
* fix(tray): render Codex free-plan 30-day window in tray menu
The tray keeps its own tier-name whitelist (TIER_LABEL_GROUPS) independent
of the frontend TIER_I18N_KEYS. Its month group only listed "monthly", so a
free Codex account whose only tier is "30_day" produced empty labeled parts
→ format_subscription_summary returned None → the tray showed no quota, even
though the footer now does. That breaks the invariant the existing
gemini_summary_lite_only_still_renders test guards: any tier visible in the
footer must not leave the tray blank.
- Add TIER_THIRTY_DAY ("30_day") constant so the string is single-sourced
across backend mapping, tray grouping, and the frontend whitelist
- Map 2_592_000s explicitly to it in window_seconds_to_tier_name
- Add TIER_THIRTY_DAY to the tray month ("m") group
- Add codex_summary_thirty_day_only_still_renders regression test
Follow-up to the review on #3651 / PR for the 30-day footer fix.
* feat(universal-provider): Auto-sync after adding and drop unused addSuccess i18n key
Signed-off-by: Hu Butui <hot123tea123@gmail.com>
* fix(i18n): sync zh-TW universal provider strings
---------
Signed-off-by: Hu Butui <hot123tea123@gmail.com>
Co-authored-by: Jason <farion1231@gmail.com>
* Update Longcat presets to LongCat-2.0
* fix(proxy): classify LongCat-2.0 as text-only for media sanitizer
The Longcat presets now use LongCat-2.0, but the known_text_only_model
allowlist still only matched the retired longcat-flash-chat tail. Without
this, images pasted into a text-only LongCat-2.0 session are forwarded
upstream instead of being replaced with the unsupported-image marker,
causing a hard rejection. Add longcat-2.0 (keeping the retired name for
saved configs) and a regression test.
---------
Co-authored-by: chengzifeng <chengzifeng@meituan.com>
Co-authored-by: Jason <farion1231@gmail.com>