Repository navigation
fix(usage): skip malformed UTF-8 parent records without hiding read errors - #8360
Merged
farion1231 merged 2 commits intoOct 16, 2026
Merged
Conversation
farion1231
reviewed
Oct 16, 2026
farion1231
left a comment
Owner
There was a problem hiding this comment.
LGTM, thanks for your contribution!
farion1231
approved these changes
Oct 16, 2026
JuntaoWu
added a commit
to JuntaoWu/cc-switch
that referenced
this pull request
Oct 23, 2026
* feat(presets): add icons for LM Studio, llama.cpp and vLLM
LM Studio and vLLM come from LobeHub (@lobehub/icons-static-svg 1.95.1).
LobeHub has no llama.cpp icon, so this inlines the transparent icon from
ggml-org/llama.cpp media/llama1-icon-transparent.svg, minified with the
viewBox tightened to a centered square around the artwork.
* fix(apps): stop the Apps page layout from shifting when buttons appear
The action column was sized by the widest visible button, so it grew
from zero the moment versions loaded or an update was found, pushing
the version column left. Reserve its width with invisible Install and
Update buttons in the header, which the existing width measurement
already picks up.
The header shifted too while checking for updates: the button label
switched to "Checking..." and changed width, and Update All vanished
and came back as each tool reloaded. Stack both labels so the button
keeps the wider width, and keep the pre-check Update All count while
the check runs.
* chore(release): v4.0.12
* docs(release): add v4.0.12 release notes
* fix(codex): match quoted provider table headers when writing base_url (#8276)
* fix(proxy): stop Chat stream converters at the DONE boundary (#8281)
* fix(usage): derive opencode session request latency from message times (#8309)
Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
* fix(cli): avoid quadratic search path deduplication (#8322)
* fix(usage): resolve paginated continuation pages for forked branch threads (#8290)
* fix(codex): preserve compaction preferences in proxy config (#8339)
* fix(windows): avoid native CSS drag regions (#8327)
* fix(presets): cap GPT context windows at the 272K subscription window
models.dev lists the OpenAI API window for GPT models (1.05M for the
GPT-5.6 and GPT-6 families), but many relays serve GPT through ChatGPT
subscriptions, whose Codex backend catalogs these models at 272K. The
base URL cannot tell the two apart. An overstated window keeps clients
from compacting until the upstream rejects the prompt and the turn
fails, while an understated one only compacts earlier.
- Cap every GPT window resolveModelMetadata returns at 272K, whether it
comes from a preset, the recognized models.dev provider or the vendor
fallback; smaller windows such as gpt-4o's are left alone
- Lower the GPT windows in the Hermes, OpenClaw, OpenCode, Pi and Codex
presets to the same cap, with a test guarding every preset source
- Pin the Claude Code ChatGPT subscription preset's context and
auto-compact windows to 272K; the 372K it used was the backend's
mid-2026 value and now sits above the backend's limit
* feat(codex): pick the aggregation sub-agent model and default classic tools on
Aggregation mode used to strip [agents] default_subagent_model, so Codex
sub-agents always followed the main model (#8286). Users can now choose a
sub-agent model and reasoning effort for aggregation, both in the
"Switch to Aggregation" dialog and in a new Aggregation settings sheet on
the Stack tab.
- The choice is a device setting (settings.json). On every rewrite it is
checked against the merged model catalog: an id that is no longer
published (member removed, or it is now the default provider) is not
written, so sub-agents fall back to the main model instead of every
spawn failing, and it comes back when the member is re-added. An effort
the model does not support is dropped.
- Codex reads [agents] when a thread starts: new conversations pick up a
change without a restart; open ones keep the old value.
- The classic sub-agent tools toggle moves from Settings into the sheet
and is now on by default: with the newer tools the main agent's
provider encrypts the task and a sub-agent on another provider gets
nothing (#8253). It is stored under a new key, codexStackClassicTools;
the old codexStackClassicSubagents key was written as false on every
save since v4.0.10, so it is no longer read.
- Docs and the four locales updated.
* feat(codex): let Auto Review use another model
Codex's automatic approval (Approve for me) sends every sandbox
escalation to a reviewer model on the same provider as the
conversation. Third-party cards had no way to pick it: without a model
list Codex asks the provider for its hidden codex-auto-review model
(404 on most relays), and with a list the main model reviews itself
(#8015, #5903, #3855). Codex reads the choice from the
auto_review_model_override field on the main model's catalog row, so
CC Switch now writes it there.
- Direct and Routing: a new "Auto Review model" field on the Codex card
(modelCatalog.autoReviewModel), limited to the card's own model
list. It is written on every row of the card's catalog; a value that
left the list is not written. Hidden for official cards; disabled
with an explanation when the card has no model list.
- Aggregation: a device setting (codexStackAutoReviewModel) in the
Aggregation settings sheet, picking any published ccs- model so
approvals can go to another provider. It is checked against the
merged catalog on every rewrite, like the sub-agent model, and the
card value is ignored in this mode. The default label follows the
default provider: official Auto Review, or the main model.
- Changing the catalog needs a Codex restart; the existing stale
catalog notice covers it.
- Inside a form, Radix Select's hidden native select reported an empty
value when the saved pick and the list arrived in the same render,
which cleared the field when editing a card; empty values are now
ignored.
- Docs and the four locales updated.
* feat(mode): confirm before going back to direct
Entering Routing or Aggregation asked for confirmation, but going back
to direct took effect at once and only offered Undo in a toast, which
was easy to miss and rarely useful. All three ways back to direct now
open a confirmation dialog: the "Back to direct" button, "Back to
direct and use this one" on a card, and the service-down notice.
- The dialog names the provider the client will connect to and the
client file whose address and key are rewritten; leaving Routing
notes that the target, failover queue and settings are kept.
- Errors are shown in the dialog instead of a toast; the Undo action
is removed.
- The note about aggregated models leaving /model is rewritten to say
which models disappear, that the aggregation list is kept, and what
to do if one of them is in use; its key becomes leaveStackNote since
both Aggregation-to-Routing and Aggregation-to-direct show it.
- Four locales updated; the Turkish note is updated and the unused
undo string removed.
* feat(claude-desktop): move Desktop into the device-level direct/route mode
Claude Desktop used to pick direct vs. model-mapping per provider card
(`meta.claudeDesktopMode`). It now has the same device-local mode as the
four routing apps (`live-state.json`), driven by `mode::controller`:
- `AppType::has_route_mode()` covers the routing apps plus Desktop;
`supports_local_proxy()` still gates `proxy_config` (no row, no failover
for Desktop). `PROXY_APPS` now includes Desktop.
- Route mode writes a gateway profile pointing at `/claude-desktop` with the
route card's model table; the contract hashes that profile, so switching
the route rewrites the file only when the model list changes.
- Direct mode writes the direct card; mapping / managed-OAuth cards are
refused ("needs routing") and exiting with such a pointer falls back to
the official profile. Files and pointer/mode now commit in one operation.
- Startup migrates a Desktop whose current card is a mapping card into
route mode with that card as the route. Desktop is not detached on exit
because it only reads its config at launch.
- The ad-hoc mapping service start/stop and the Desktop-only tray mode are
gone; the tray and the takeover status treat Desktop like the others.
* feat(claude-desktop): show Desktop with the shared direct/route mode panel
The Desktop page now uses the same mode tabs as the routing apps instead of
the per-card access bar:
- `MODE_APP_IDS` / `isModeAppId` (routing apps + Desktop) drive the mode
panel, the sidebar mode tags and the "apps using routing" list;
`PROXY_APP_IDS` stays for failover and routing settings, which Desktop
does not have (no failover queries, no route settings sheet).
- Desktop config checks move into the panel's notice slot.
- Entering route mode explains that the model list follows the routed
provider and that Desktop must be restarted; switching or leaving route
mode reminds to restart. The needs-routing dialog hides "switch anyway"
for Desktop because direct mode cannot write a mapping card.
- Drop the Desktop-only card sections, the "mapping" nav tag and the
mapping-service strings in all four locales.
* feat(claude-desktop): support aggregation (stack) mode on the backend
Desktop can now join the device-level stack mode alongside Claude Code
and Codex. Members publish their mapped models as
`claude-fable-ccs-<6 digits>` ids: Desktop's picker drops any id that
names a vendor and strips trailing 8-digit dates, so the id carries only
a stable FNV-1a code of (key, upstream model), and the proxy finds the
member by scanning the roster. The fable prefix gives the effort picker
without the embedded Claude Code treating the model as a real Fable.
- Route profile lists the default provider's mapping first, then the
members' models with "Provider / model" labels, capped at 200, and
writes modelDiscoveryEnabled=false (new floor key, cleared otherwise)
- Stacked requests skip the mapping table in the forwarder and only get
the upstream-specific MiMo normalization; misses use the Anthropic
error envelope and never fall back to the route
- /v1/models appends the stacked models; tray treats a default change
as a switch that needs a Desktop restart
- Members must be routable (validate_proxy_provider)
* feat(claude-desktop): offer aggregation mode in the Desktop panel
Claude Desktop joins STACK_APP_IDS, so its panel shows the Aggregation
tab next to direct and route, with the shared default/member cards.
Desktop only reads its config at launch and has no /model picker, so
the copy is its own: the confirm dialog says members appear in the
model list as "Provider / model" and that Desktop must be restarted
(no failover note, since Desktop routing has no failover), the enter
toast and member add/remove toasts ask for a restart, and the default
section help and stack-to-route note drop the ccs- / /model wording.
Texts added in zh, en, ja and zh-TW.
* fix(claude-desktop): resolve colliding aggregation ids to the listed provider
Desktop aggregation ids are a 6-digit hash, so two models can share one.
The published list skips the route provider and keeps the first listed
model, but resolving scanned every member in list order, route included.
When the route sat earlier in the list, the picker showed one provider's
model while requests went to the route provider.
Resolve now searches the published members first, in the same order the
list deduplicates, and only then the route provider for ids the client
kept from before it became the default.
* fix(claude-desktop): split a legacy mapping card into route and official direct
Before device-level modes, a Desktop mapping card meant the route was in
use: the old version pointed Desktop's gateway at the local route and
kept the card as the direct pointer. Startup only set the mode, so:
- Desktop was not marked attached. When the route failed to start (port
in use), falling back to direct wrote nothing and left Desktop on a
dead gateway, and later startups no longer treated it as legacy.
- After leaving the route, Desktop went back to official but the direct
pointer stayed on the mapping card, so the UI and project snapshots
showed the wrong provider.
The migration now records Desktop as attached, routes to the card, and
moves the device-local direct pointer to the official card. The mode is
committed before the pointer moves, so a crash in between still resumes
as route mode.
* fix(mode): preview the old default's models and fill in Desktop's app name
The mode dialog counted each member's models from the ids it publishes
now. The current default on Codex and Claude Desktop publishes none, so
picking a different default previewed it as contributing 0 models, while
it published its full list once moved. Stack member views now also carry
how many models a provider publishes as a plain member, and the preview
uses that.
The Desktop note about keeping CC Switch running interpolates {{app}}
but was translated without it, so the raw placeholder showed.
* fix(claude-desktop): give the back-to-direct confirmation Desktop's own notes
The confirmation added before going back to direct names the client file
and promises to keep the failover queue and settings. Desktop has no
single file in that table, so the note rendered with an empty path, and
it has no failover queue or route settings.
For Desktop the dialog now says the change applies after a restart, that
the routing target is kept, and, when leaving aggregation, that the
aggregated models leave Desktop's model list.
* test(uninstall): borrow single paths as slices for clippy
Rust 1.95's clippy flags `&[x.clone()]` as cloned_ref_to_slice_refs,
which fails `clippy --all-targets -D warnings`.
* test(apps): wait for re-probed rows before Update all after a remount
With an expired cache, remounting the Apps page probes the other tools'
versions again. The test clicked Update all right after the page header
appeared, so on slower CI runners the Codex row was still loading and
its Update button was missing. Wait for the Codex and Gemini buttons
first; reproduced locally by delaying the version probe.
* test(usage): skip Codex parse-cache counts where files have no identity
The deferred-rollout cache keys parent files by their file identity.
Windows cannot read one for files under \\wsl.localhost, so by design
nothing is cached there and every pass parses again. Three retryable
pending tests asserted parse counts unconditionally and failed on the
WSL2 nightly (4 parses where 1 or 2 were expected).
Guard the count assertions with the existing parent_cache_supported
check; the result assertions still run everywhere. Reproduced and
verified by forcing the file stamp to be unavailable.
* test(windows): run the lifecycle failure test outside the WSL temp dir
The test compares the whole stderr of a failing npm.cmd. On the WSL2
nightly the temp dir sits under \\wsl.localhost, and cmd.exe started
there first prints a warning that UNC paths are not supported, so the
output no longer matched. Use %SystemRoot% as the working directory;
it has no npm.cmd either.
* feat(update): download the portable zip directly from the update button
Portable builds already check for updates on launch and show the new
version, but "Update to vX" only opened the releases page, leaving users
to find the right zip among dozens of assets.
Now it opens the matching Portable.zip download for the running
architecture (x64 / ARM64), falling back to the latest release page when
the version string looks wrong. The About panel also explains that
extracting over cc-switch.exe is enough, since settings and data live in
~/.cc-switch and are untouched.
In-place self-replacement is deliberately not done: an app rewriting its
own exe is easily flagged by antivirus software.
Refs #8300
* chore(release): v4.0.13
* docs(release): add v4.0.13 release notes
* feat(presets): add TokenFactory sponsor presets across seven apps
TokenFactory is a paid sponsor serving Chinese models (GLM, Kimi,
DeepSeek, Qwen) behind one gateway. Endpoints and model IDs follow its
own tool guides and the public catalog at
api.tokenfactory.cn/v1/public/models (checked 2026-10-15).
- Claude Code: /anthropic base, GLM-5.3 main/Sonnet, Kimi K3 Opus,
DeepSeek-V4.1-Flash Haiku (its Anthropic tool calls are verified by
the platform; GLM-5.3-Flash's are not). Main/Sonnet/Opus carry [1M]
so the form shows 1M pre-checked; Haiku has no 1M option in the form.
- Claude Desktop: proxy + brandedRoutes with the same roles, 1M declared.
- Codex: Responses on /v1 with a six-model catalog; reasoning levels
come from the platform's per-model docs (Qwen3.8 Flash has no high
tier, so it defaults to xhigh).
- OpenCode / OpenClaw / Hermes / Pi: Chat Completions on /v1 with the
same six models.
- Gemini CLI and Grok Build are skipped: the gateway serves neither
family.
Also adds the inline TokenFactory tile icon, the README sponsor row in
all four languages (below GrooRoute Pro), and bumps the merged Claude
preset row count to 102.
* feat(claude): allow declaring 1M context on the Haiku tier
The Claude Code model mapping only offered the 1M checkbox on Sonnet,
Opus, Fable and Subagent, and stack mode stripped the [1M] marker from
the Haiku alias. Both assumed Haiku models never have a 1M window, which
no longer holds once the tier points at a third-party model.
Claude Code 2.1.296 honors [1M] on ANTHROPIC_DEFAULT_HAIKU_MODEL exactly
like the other tiers (checked against a local mock Anthropic endpoint):
without it a non-claude- model id is capped at a 200K window, with it the
window is 1M and the suffix is stripped before the request goes out.
- Form: every model row now has the 1M checkbox; the per-row
supportsOneM switch is gone, and quick-set keeps the marker on Haiku.
- Stack mode: Haiku gets the same Stack id as the other tiers, marker
included. Stack ids are not claude- prefixed, so without the marker the
Haiku tier fell back to 200K. Route mode is unchanged: its Haiku alias
claude-haiku-5-5 is already a 1M model to Claude Code.
* fix(presets): pre-check 1M for presets whose models have a 1M window
Claude Code treats any model id it does not recognize (anything not
claude- prefixed) as a 200K window unless the id ends in [1M] or
CLAUDE_CODE_MAX_CONTEXT_TOKENS is set. 23 Claude Code presets pointed at
1M models (GLM-5.3, Kimi K3, DeepSeek V4, Qwen3.8, MiniMax-M3, MiMo,
LongCat, ...) with neither, so in direct mode they auto-compacted around
200K. Windows come from the same vendors' presets in the other apps.
- Claude Code: every tier whose model is 1M now carries [1M], which is
what the form's 1M checkbox reads and writes. That covers those 23 plus
the 5 that pinned MAX_CONTEXT_TOKENS (Qwen x3, Meta, Hugging Face); MAX
stays there as the fallback for tiers the user later unchecks.
DeepInfra's Haiku (Ling-3.0-flash, 131072) stays unmarked.
- Claude Desktop: 12 presets on the same 1M models now declare 1M on
their branded routes.
- SambaNova's note about not adding [1M] is replaced: the context-1m beta
rides alongside the ten or so betas Claude Code already sends, so it
does not change upstream compatibility.
Left as is: Kimi For Coding deliberately pins a 256K compaction window.
* fix(presets): declare 1M on Claude Desktop presets that relay Claude 5
Claude Sonnet 5 / Opus 5 (and the 5.5 builds) have a 1M window, but 50
Claude Desktop presets that relay them left supports1m off, so the
Desktop model list never offered 1M for them.
- Route factories: supports1m now skips Claude Haiku 4.x, which only has
a 200K window. passthroughRoutes(true) and mappedRoutes(..., true) used
to mark the Haiku 4.5 route as 1M too (SubRouter, CCSub, OpenRouter,
TheRouter); those Haiku routes are now correctly left at 200K.
- 35 passthrough relays, 15 mapped relays and Soshow's two explicit
routes now declare 1M.
- Left as is: Kimi For Coding x2 and the Qwen Coding Plan (their Claude
route ids map to the vendor's own models), GitHub Copilot (Copilot caps
the Claude context) and AtlasCloud (nothing shows it relays Claude
rather than its own models).
* feat(presets): add Modelflare sponsor presets across nine apps
Modelflare (modelflare.dev) is a basic-tier sponsor: presets in the app
and a listing on the website, no README row. Endpoints and model IDs
follow its client guides (docs.modelflare.dev) and the public catalog at
modelflare.dev/api/pricing (checked 2026-10-15).
- Claude Code: site origin as the Anthropic base, AUTH_TOKEN. Main,
Sonnet and Opus follow Claude Code's defaults; the Haiku tier points at
claude-sonnet-5-5 because the claude-award and claude-tmp-cheap groups
do not serve claude-haiku-5-5.
- Claude Desktop: direct passthrough with 1M declared; the gateway serves
the claude-sonnet-5 / claude-opus-5 / claude-haiku-4-5 route IDs as-is.
- Codex (gpt-5.6-sol) and Grok Build (grok-4.5) on Responses at /v1, as in
the official guides.
- Gemini CLI on the native API (gemini-3.6-flash). No origin candidate:
only modelflare.dev serves /v1beta.
- OpenCode / OpenClaw / Hermes (Chat Completions) and Pi (Responses) on
/v1 with gpt-5.6-sol, gpt-6-sol and gpt-6-astra, capped at 272K.
- origin.modelflare.dev (China-facing host for the same app) is the
second endpoint candidate everywhere else.
- Invite link only in apiKeyUrl.
Also adds a 180px tile icon from the official logo and bumps the merged
Claude preset row count to 103.
* fix(presets): restore sponsor promo copy for four sponsors
The promo line under the API key input (ApiKeySection hint, shown with
the "Get API Key" link) was brought back on 2026-10-04, but recent
sponsors still shipped without a partnerPromotionKey, so nothing showed.
- Add partnerPromotionKey to every Modelflare, TokenFactory, OpenModel
and GrooRoute Pro preset, with four-locale copy: Modelflare and
TokenFactory from the sponsors' own perks (CCSWITCH code), OpenModel
and GrooRoute Pro condensed from their README sponsor rows.
SudoCode.us still has no promo copy; none has been provided.
* docs(release): update v4.0.13 release notes
* fix(claude-desktop): show one picker row per 1M model
Claude Desktop lists every model marked supports1m twice: the plain
entry and a "1M context window" variant. Desktop 1.21459.0 added
prefer1m, which keeps only the plain row and hands the embedded CLI
`<id>[1m]` when that row is picked, so the context window stays 1M.
Write prefer1m next to supports1m for every model entry. Route and
aggregation profiles pick it up on the next CC Switch launch (or the
next mode switch); direct profiles keep the old two rows until the
provider is switched or saved again. Requests still arrive with the
[1m] suffix, which routing already strips.
* docs(readme): remove TokenFactory sponsor entry
TokenFactory is no longer a sponsor. Drop its row from the four README
sponsor tables and delete the now-unused banner image.
* fix(presets): drop TokenFactory sponsor status and localize its name
TokenFactory is no longer a sponsor; only its presets stay.
- Remove isPartner and partnerPromotionKey from all seven TokenFactory
presets and delete the partnerPromotion.tokenfactory copy in the four
locales.
- Show it as 词元工厂 in Simplified Chinese (詞元工廠 in Traditional) and
TokenFactory in English and Japanese via
providerForm.presets.tokenfactory, and add the Chinese names as search
aliases so they still match in non-Chinese UIs.
* feat(providers): explain and remember the folder for Open Terminal
"Open Terminal" on a Claude provider card jumped straight into the
system folder picker with no explanation, so users could not tell what
the folder was for.
Open a dialog first: it says a terminal will open in the chosen folder
and start Claude Code with this provider's settings, then lets the user
pick the folder. The last folder that opened successfully is prefilled,
and up to five recent folders can be picked with one click or removed
from the list. They are kept in localStorage on this device only.
When the card carries its own base URL and key, the dialog also notes
that the terminal is isolated: switching providers in CC Switch later
won't affect it. Official login cards have an empty env, so their
terminal follows ~/.claude/settings.json and the global login, and the
note stays hidden for them.
* fix(misc): only treat shell command-missing output as not installed in WSL probe (#8167)
Matching any "not found" text marked tools as not installed when the tool
itself failed with errors like "module not found". Recognize only the
bash/zsh, dash and fish command-missing formats, and fix the distro name
doc comment to mention dots.
* feat(pricing): seed Gemini 4 Argon (#8356)
Gemini 4 Argon is Google's new flagship reasoning model (1M-token unified
context window). It is missing from the built-in pricing table, so its
requests are costed at $0 unless models.dev sync or a manual price is set.
Prefix matching only reaches longer seed ids, so it doesn't fall back to
any existing Gemini row.
Prices per the official pricing page (USD / MTok), introductory tier
(valid through 2026-11-30, list prices 4.00/20.00/0.20 take effect on
2026-12-01):
- gemini-4-argon: 2.00 / 10.00 / cache read 0.10 / cache write 0
(context caching is implicit-only, so no explicit cache write price)
Free tier is unavailable; Batch and Flex tiers are not seeded, matching
the existing convention of recording only the Standard tier. This row
follows the 3.6/3.7/3.8-Flash pattern of logging the introductory price
with an inline reminder, and is intentionally left out of audit-ignore.json
so the /jason-update-model audit will surface the 2026-12-01 list-price
transition as a follow-up.
Existing installs pick the row up via the startup INSERT OR IGNORE, and
zero-cost history is recomputed by the startup backfill.
* fix(usage): make exact model pricing lookup case-insensitive (#8414)
自定义定价常按界面显示名原样入库(如 GLM-5.3-Flash-EXL3),而查价候选
会被 clean_model_id_for_pricing 归一化为全小写;query_model_pricing_exact
的 model_id = ? 精确匹配是 BINARY collation(大小写敏感),含大写字母的
自定义定价行永远命中不了,成本恒为 0(写入计价与历史回填同路径)。
改为 model_id = ? COLLATE NOCASE(ASCII 不区分大小写),与 LIKE 前缀
匹配语义对齐,同时兼容已入库的旧数据,无需迁移。
Fixes #8413
Co-authored-by: Vincebye <Vincebye@users.noreply.github.com>
* fix(deeplink): refresh OpenCode live state after provider import (#8382)
* fix(usage): prefer opencode streamed time for session latency (#8410)
OpenCode writes time.completed only after tool execution finishes, so subtracting
created from completed includes tool time and makes the estimated speed far too
low for tool-heavy turns. Prefer time.streamed (end of the model stream) when it
is present and later than created, falling back to time.completed otherwise.
Refs #8402
* fix(usage): skip malformed UTF-8 parent records without hiding read errors (#8360)
* fix(version): stop false "update available" for dev-build local versions (#8398)
Hermes main/canary installs self-report as 0.22.1+238.g<sha> (PEP 440 local
version / git describe). VERSION_RE dropped the +local suffix during
extraction, so the probe compared a plain 0.22.1 against the latest GitHub
release and every dev-channel user saw a permanent "new version" nag that
`hermes update` could not clear.
- misc.rs: keep the +local suffix in VERSION_RE; parse_semver treats only
git-describe / canary-style local versions as incomparable dev builds
(None -> conservative handling), while ordinary semver build metadata
(e.g. 1.0.0+linux.1) is ignored per spec and compared as before
- version.ts: parseVersion captures the local segment; compareVersions
returns 0 only when a side is a dev-build local version, so
isUpdateAvailable stops nagging dev builds without hiding real updates
- unit tests on both sides (git describe / canary / plain build metadata);
no UI text changes
Fixes #8394
* fix(opencode): keep Unicode-named deep-link imports editable (#8381)
* refactor(usage): route usage amounts through one money formatter
Usage amounts were formatted in several places, each gluing "$" onto the
number itself: fmtUsd in format.ts, the request detail panel, the trend
chart's axis ticks and the session reader's own formatCost. Before a
display currency can be added, every one of them has to go through a
single path.
- Replace fmtUsd with formatMoney / formatMoneyCompact / formatMoneyTick.
They take the display currency as a required argument, so a call site
that is not converted fails the type check instead of silently showing
dollars.
- Add useMoneyFormat(): the usage dashboard, the sidebar "Today" label
and the session reader all format through it. It is fixed to USD for
now; the setting will be wired in here.
- The session reader's markdown export takes formatCost in its options,
so copied text uses the same currency as the screen.
- Pricing (entered in USD), quota and balance footers (the provider's own
currency) and the Codex credit conversion are deliberately untouched.
No visible change: every amount renders exactly as before.
* feat(usage): show usage amounts in a chosen currency
Add a Settings tab to the usage page that replaces the Pricing tab: an
amount display section on top (currency + hand-entered exchange rate) and
the existing pricing configuration below.
- Display currencies: USD, CNY, EUR, JPY, TWD, TRY; USD stays the default.
Rates are entered manually; untouched preset rates are not written to
settings.json so later preset updates still reach those users.
- Costs stay stored in USD; conversion only happens at display time.
Converted amounts carry a "≈" prefix and hovers add the USD value.
- Chart data is converted before plotting so axis ticks stay round.
- Yen drops two decimals, and usage amounts now group thousands.
- The sidebar "Today" amount drops decimals from 100 up so it no longer
squeezes the nav label.
- Settings are device-local (`usageDisplay` in ~/.cc-switch/settings.json).
* feat(usage): choose what the sidebar Today label shows
The usage settings tab gets a "Sidebar Today" control: cost (default),
tokens, both, or hidden. Tokens use the same real-consumption total as
the usage page card, and a zero part is left out.
The sidebar row no longer lets the trailing text squeeze the item name:
the name keeps its width and the trailing text wraps at the space. With
both values the row shows only the two numbers on two lines; the full
"Today ..." text moves to the hover tooltip and the accessible name.
* feat(usage): pick which request log columns to show
A columns menu sits next to the status filter on the request log tab.
Columns can be ticked on and off without the menu closing, and a
"Reset to default" item appears once anything differs from the default.
The time column always stays, since it opens the request detail.
Four new columns, hidden by default:
- Cache Write: tokens written to the cache. Codex, Gemini and Grok do
not report it, so they show "—" with a hover explanation.
- Hit Rate: per-request cache hit rate (#7298), same formula as the
overall hit rate. It is floored to one decimal so a near-full hit
never reads as 100%.
- Total Time: taken from latency_ms, which covers the whole streamed
response. The duration_ms column is never written and stays NULL.
Values estimated from session logs carry a "≈" prefix.
- Status Code: when shown, the error badge next to the time is dropped.
Only columns that differ from their default are saved
(`usageDisplay.requestLogColumns` in ~/.cc-switch/settings.json), so a
column added later still appears with its own default for existing
users. The table widens with the column count and scrolls sideways
instead of squeezing every column.
* feat(usage): remember the last view and move display choices into settings
The usage page now reopens on the tab, preset range and trend/heatmap
metrics picked last time. These are kept in localStorage as local UI
preferences, not in settings.json. A custom date range does not replace
the remembered preset, and the app/provider/model filters are not kept.
The request log column picker moves from the table header into the
Display section of the settings tab, and the sidebar Today label becomes
the same kind of checklist (cost and tokens; stored values unchanged).
Currency, sidebar Today and log columns now sit on one row.
* fix(usage): drop the ≈ mark and cap decimals on converted amounts
Converted amounts no longer start with ≈; it took room and the USD value
is still shown on hover. Since the rate is entered by hand, converted
amounts now show at most two decimals (yen keeps two fewer), instead of
the 4–6 used for USD in logs and hover text. An amount that would round
to zero is written with two significant digits rather than as 0.00.
* feat(usage): show requests or cache hit rate in the sidebar Today label
The sidebar Today label can now show requests and cache hit rate besides
cost and tokens. Up to two items can be picked, since two already take
both lines of the sidebar row; once two are ticked the rest are greyed
out. Items are kept in menu order, and the hit rate is left out before
the first request of the day.
The setting becomes a list, sidebarTodayItems, where an empty list means
hidden. The old single-value sidebarToday key was never released, so it
is not read.
* fix(presets): move SiliconFlow CN presets off the retired MiniMax M2.5
SiliconFlow's China site took Pro/MiniMaxAI/MiniMax-M2.5 offline on
2026-09-11. The Codex and OpenCode CN presets had already moved to
deepseek-ai/DeepSeek-V4-Flash, but Claude Code, Claude Desktop, Hermes
and OpenClaw still defaulted to M2.5, so a fresh SiliconFlow card in
those apps pointed at a model the platform no longer serves.
Align all four with the Codex/OpenCode default and declare its 1M
context window: [1M] on the Claude Code model ids, supports1m on the
Claude Desktop routes, context_length / contextWindow on the Hermes and
OpenClaw catalogs. The OpenClaw entry drops the M2.5 cost line rather
than guess a CNY price, matching the DeepSeek official preset.
* feat(icons): switch Pi to the three-color pi.dev mark
pi.dev now uses a three-color logo (coral, blue, yellow) on both light and
dark backgrounds. Same 800x800 geometry as before, so the icon keeps its
size and position; only the fills change from currentColor to fixed colors.
* fix(proxy): round-trip Gemini thought signatures on Codex chat tool calls
Codex routed through the local proxy to Gemini's OpenAI-compatible Chat
endpoint failed on the second turn of any tool call with HTTP 400
"Function call is missing a thought_signature in functionCall parts".
Root cause: Gemini returns the signature in
tool_calls[].extra_content.google.thought_signature and requires it back
on the assistant tool_calls of the next request. The Responses <-> Chat
bridge dropped that field in both directions, and the Codex client does
not echo unknown fields on function_call items anyway.
Fix: carry a non-empty extra_content from Chat tool calls onto the
Responses call items (non-streaming and streaming), let
CodexChatHistoryStore restore it by call_id like reasoning_content, and
copy it back onto the Chat tool_calls when converting the next request.
Upstreams that never send extra_content are unaffected.
Refs #8336
* fix(proxy): keep the underlying cause in upstream connection errors
Forwarding failures only reported reqwest's outer text such as
"error sending request" or "error decoding response body". The part that
tells DNS, TCP refusal, TLS and connection resets apart lives in the
error's source() chain, which the proxy dropped, so users seeing every
failover candidate fail in the same second could not tell whether the
failures shared a network path.
Add describe_error_chain / describe_reqwest_error to proxy::error and use
them for the reqwest send error, the hyper-util fallback request error,
and both body stream error paths. The reqwest helper strips the URL
first so query-string keys never reach the log. Error prefixes and the
timeout / connect classification are unchanged.
Refs #8416
* fix(providers): explain why the direct provider can't be deleted while routing
While routing or stacking, the card that the direct pointer points at is
not marked as in use on the route page, yet its delete item was disabled
with "the provider in use can't be deleted". The block itself is right:
the backend is_referenced refuses it because leaving routing switches
back to that provider. Only the explanation was wrong, so users saw an
idle card they could not delete.
Keep the same blocked set, but give the direct pointer its own reason
while not in direct mode: leaving routing falls back to it, and to
delete it first use "Back to direct and use" on another provider.
Refs #8277
* fix(failover): keep queue positions distinct when reordering
Moving a provider up or down in the failover queue reassigned the queue
members' existing sortIndex values to the new order. The backend orders
the queue by COALESCE(sort_index, 999999), id, so when two members
shared a sortIndex, or a provider without one (newly added providers get
none) fell back to its array index and landed on another member's value,
the swap wrote equal values: the order tied, the queue looked unchanged
and a success toast still appeared.
Extract the computation into queueMoveSortUpdates and make the
reassigned slots strictly increasing, with unit tests for ties and the
index fallback collision.
Refs #8297
* fix(sessions): merge Windows project groups that differ only in path case
On Windows, Claude Code can record the same working directory with a
different drive-letter case (D:\projects\demo vs d:\projects\demo),
depending on how the terminal or editor was launched. The session
manager grouped sessions by the raw projectDir string, so one project
showed up as two groups with its history split between them.
Group by a normalized key instead: trailing separators are dropped for
every path, and Windows paths (drive letter or UNC prefix) are also
case-folded with "/" unified to "\". POSIX paths stay case-sensitive.
The group still displays the original path of its first session; the
stored session data is untouched.
Refs #8233
* fix(openclaw): keep the User-Agent switch in sync with JSON header edits
The Send User-Agent switch was only derived from settingsConfig.headers
on mount and on preset reset. Editing headers directly in the
Configuration JSON editor left it stale in both directions: adding a UA
in JSON kept the switch off (and clicking it then replaced the custom
value with the default UA), removing it kept the switch on.
Add syncOpenclawUserAgentFromConfig to useOpenclawFormState and call it
from the OpenClaw JSON editor's onChange. It only reads valid JSON
objects, sets the switch to the case-insensitive presence of a
User-Agent header, never rewrites the JSON, and keeps the last state
while the draft is invalid.
Refs #8256
* fix(codex): keep retry and stream timeout keys in the proxy route table
In local-route (proxy) mode the [model_providers.custom] table written to
~/.codex/config.toml is rebuilt from proxy_route_table, carrying over only
name = "OpenAI" and capabilities.remote_compaction from the provider's own
route table. Codex client settings such as stream_max_retries were dropped,
so a provider configured with stream_max_retries = 50 fell back to Codex's
default of 5 retries. v3 takeover only rewrote base_url in place, so these
keys used to survive.
Copy request_max_retries, stream_max_retries and stream_idle_timeout_ms
from the row's route table into the proxy contract table. They are Codex's
own behavior and apply equally against the local proxy; switching to a
provider without them removes them along with the rest of the table.
Refs #8265
* fix(skills): spool repo archives to disk and lift the 128 MiB download cap
Skill discovery, install, check-updates and update all download the
whole GitHub repo ZIP. The compressed body was collected into a Vec in
memory and capped at 128 MiB, so repos whose docs/ images dwarf the
skills themselves (K-Dense-AI/scientific-agent-skills: 235 MiB ZIP,
277 MiB uncompressed, 20 MiB of skills) failed before extraction even
though they fit the 512 MiB extraction budget and 30k entry cap. The
download cap also reused ARCHIVE_TOO_LARGE, so the UI blamed the
extraction limit.
Stream the body chunk by chunk into an anonymous temp file (deleted on
drop, including error paths), still counting actually received bytes,
and open the ZipArchive from that file. With memory no longer the
constraint, align the download cap with the extraction budget, since a
larger compressed body could not be extracted anyway. Report the
download cap under its own ARCHIVE_DOWNLOAD_TOO_LARGE code with
four-locale text. Extraction budget, entry cap and path/symlink
protections are unchanged.
Refs #8421
* fix(proxy): fall back to Gemini's skip signature when a cached one is lost
The Gemini thought signature round trip relies on CodexChatHistoryStore,
which only lives in memory. If CC Switch restarts in the middle of a tool
call turn, the next request reaches Gemini without the signature and
fails with the same 400 as before.
Gemini validates only the first function call of each model step in the
current turn, and documents skip_thought_signature_validator as the
placeholder for history it cannot trace back. After the Chat request is
built, fill that placeholder on those calls when they still have no
signature. Real signatures are left alone, parallel sibling calls stay
unsigned, and only Gemini 3 or later model ids (relay prefixes allowed)
are touched, so other upstreams see an unchanged request.
Refs #8336
* chore(release): v4.0.14
* docs(release): add v4.0.14 release notes
* fix(proxy): preserve Claude JSON session identity (#8236)
Co-authored-by: Ty Cru <alpacalala05@outlook.com>
* fix(claude-desktop): allow native providers to bootstrap local routing (#8444)
Let native Claude Desktop providers without a model list enter local routing using the default route set, so model mappings can be saved on the same card. Fixes #8441.
* fix(i18n): translate new Turkish uninstall strings (#8291)
Translate the Turkish strings added around the manual/tool uninstall flows and plan quota, keeping commands and technical literals in English.
* fix(sidebar): show only the numbers in the usage Today badge
Drop the "Today" prefix from the badge next to Usage even when a single
item is shown; the full wording stays in the hover tip and the
accessible name, matching the two-item layout.
* fix(skills): stop branch fallback from masking non-404 download errors
download_repo tried the configured branch, then main, then master, and
moved on after any failure. A timeout, an oversized archive or an
extraction error on main was therefore replaced by master's 404, and the
user saw "repository or branch not found" instead of the real cause.
Only fall through to the next branch when the archive URL returns 404;
any other error is reported as-is. Errors returned from download_repo
now also carry the repo owner/name in their context when missing, so
messages such as skills.error.downloadTimeout no longer show raw
{{owner}}/{{name}} placeholders for errors raised below this layer.
The branch loop is split into try_repo_branches so the fallback rule is
covered by tests without network access.
* fix(tools): only reinstall Codex when --version actually exits with an error
The Codex self-heal path (npm uninstall, then install) ran whenever the
pre-update probe marked the install as not runnable. That probe has a 10s
deadline, so a healthy Codex whose --version timed out or failed to spawn
was treated as broken. If the reinstall then failed (slow or dropped
download), the old version had already been removed and the app showed
Codex as not installed. A plain `npm i -g` would have rolled back instead.
Track whether --version really ran and exited non-zero, and gate the
uninstall+install repair on that. Timeouts and spawn failures now take the
normal anchored npm upgrade.
* fix(settings): never overwrite settings.json with defaults after a failed load
On Windows some users lost their config dir overrides and visible-apps
choices after every update. If settings.json failed to read or parse, the
app silently used defaults in memory, and the next full save (for example
the What's New dialog shown after each update) wrote those defaults back.
- Save through a temp file + rename so a killed process can't leave a
truncated file.
- Retry transient read errors, strip a UTF-8 BOM, and when the full parse
fails keep every field that still deserializes, dropping only the
invalid ones.
- Back up a file that wasn't fully loaded to settings.json.broken-<time>.bak;
if that backup fails, retry it before the next save and refuse to save
until it succeeds.
* fix(icons): give the macOS app icon a white rounded-square background
macOS 26 puts icons whose shape is not the standard rounded square into a
grey system tile and shrinks the artwork, while macOS 15 shows the
transparent artwork as-is. Bake a white 824px rounded square (Apple's
1024px grid) into icon.icns so both versions show the same white tile.
Windows (.ico) and Linux (PNG) icons stay transparent.
* fix(usage): hide empty DeepSeek wallets when another currency has funds
DeepSeek's /user/balance lists one entry per currency wallet. An account
that pays from its USD wallet while the CNY wallet sits at zero got two
rows: the CNY row read "balance used up" in red, and two rows count as
multiple plans, so the provider card expanded the plan panel again on
every launch.
Parse the response in parse_deepseek_balance and, when any wallet still
has a positive balance, drop the wallets at or below zero. If every
wallet is empty they are all kept so the exhausted state stays visible;
rows whose amount cannot be parsed are left untouched.
Refs #8170
* fix(forms): keep the Codex thinking level picker inside short windows
The thinking level popover in the Codex model list only relied on Radix
flipping to avoid the window edges. When neither side of the trigger had
room for the whole panel (a short window with the row mid-form), the
panel overflowed and its bottom, including the default-level picker, was
cut off by the window.
Cap the popover at --radix-popover-content-available-height as a flex
column, let the Command shrink so the level list scrolls, and keep the
default-level section at its full height.
Refs #8094
* fix(claude-desktop): fetch models for managed OAuth providers
The Claude Desktop provider form hid the "Fetch models" button for
GitHub Copilot, Codex OAuth and xAI OAuth providers because the fetch
path only knew how to call the base URL with an API key, which managed
OAuth providers do not have. Claude Code and Codex forms already fetch
these lists through the account-scoped APIs.
Show the button for managed OAuth providers as well and route the fetch
to copilotGetModels / copilotGetModelsForAccount, fetchCodexOauthModels
or fetchXaiOauthModels based on the provider type, prompting to log in
first when the account is not authenticated. The fetched list is reset
and in-flight results are discarded when the provider type or selected
account changes, so models from one account never leak into another.
Refs #8426
* fix(sync): stop cutting off slow WebDAV/S3 transfers at a fixed 300s
WebDAV and S3 snapshot transfers set a per-request reqwest timeout of
300 seconds. reqwest's request timeout covers the entire response body,
so a healthy but slow download of a large skills.zip (e.g. ~90 MiB at
~100 KiB/s, ~15 minutes) was aborted mid-stream and surfaced as
"error decoding response body", even though data kept flowing.
The overall timeout now scales with the payload: 300s plus the time
needed at 16 KiB/s (body length for PUT, the size cap for GET). For GET,
waiting for response headers and each body chunk are additionally
guarded by a 120s idle timeout, so a stalled connection still fails
promptly instead of hanging until the larger overall budget runs out.
Refs #8428
* fix(settings): keep settings when an old save left a trailing tail
On some Windows machines the previous non-atomic save did not truncate
settings.json when the new content was shorter, so the last saved JSON
object was followed by leftover bytes of the old content. The strict
parse failed on the trailing data, and so did the lenient fallback
(serde_json::from_str::<Value> also rejects trailing characters), so
every setting - including hidden apps - fell back to defaults.
New saves already go through temp file + rename (8d4a4ffd7), so no new
tails are produced. For files that already carry one, the lenient path
now reads only the first JSON value and keeps its valid fields. The load
still counts as lossy, so the original file is backed up as before.
Refs #8429
* fix(terminal): give Windows provider terminals the restored user PATH
"Open terminal" on Windows writes a .bat that runs bare `claude` and starts
it with `cmd /C start wt|cmd|powershell ...`, so the terminal inherited CC
Switch's own process PATH. That PATH can miss the user-level registry
entries: after the MSI auto-update relaunch (#6061) or when CC Switch was
started before the CLI was installed. The new tab then reports "'claude' is
not recognized" while a terminal opened by hand finds it. Windows Terminal
launched from a command line passes the caller's environment to the new
tab, so it is affected too.
Detection and lifecycle commands already use `effective_path_string()`
(process PATH first, then user + machine registry PATH; #6284, #8173).
Build the `cmd /C start` command with that PATH as well. Only the launched
terminal sees it; CC Switch's own environment is unchanged. This covers the
provider terminal and `launch_terminal_running`.
Refs #8446
* fix(tools): add missing exited_with_error to the Windows WSL uninstall candidate
* fix(failover): refresh the routing marker after the proxy fails over
When the proxy fails over (a circuit breaker trips, or a reordered queue
sends requests to the new head), the backend only updates the device
route pointer via record_failover_route and emits provider-switched. The
App listener answered that by refetching the ["providers", app] list
query alone, but the panel's "routing" marker reads routeProviderId from
the separate, unpolled ["providers", app, "mode"] query, so it stayed on
the old provider until something else invalidated the prefix.
Invalidate the whole ["providers", appType] prefix on provider-switched
instead, for any app, and add an App integration test that fails without
the fix.
Refs #7998
* feat(hermes): declare image input per model
Hermes decides whether the main model can take images from the
per-model `supports_vision` key in config.yaml; without it unknown
models are treated as text-only and image parts get flattened into
text. The Hermes form had no way to set it, and saving the provider
dropped a hand-written value because the models block is rebuilt from
the form.
Add a "Supports image input" switch to the expanded model details.
Turning it on writes `supports_vision: true`; turning it off removes
the key so Hermes falls back to its own detection instead of being
forced to text-only. Picking a fetched model whose known metadata lists
image input pre-fills `true`, never `false`, and an explicit value is
left alone. The backend already round-trips unknown per-model keys.
Refs #8464
* chore(providers): follow up on panel transition cleanup (#8260)
Remove the unused fade preset from FullScreenPanel, drop redundant motionPreset="none" props on the add/edit provider dialogs, and fix misplaced comments in DailyMemoryPanel. No behavior change.
* fix(provider): prevent card stretching and overlap during drag sorting (#8315)
Use CSS.Translate so dragged provider cards keep their size, lift the drag layer onto the positioned wrapper, and lock horizontal movement. Fixes #8283.
* perf(usage): cache Claude pricing within each import transaction (#8226)
Reuse resolved (and missing) model pricing within each Claude session-log import transaction, for both inserts and streamed output-growth updates. The cache is dropped before the next transaction so price edits remain visible. Fixes #8224.
* fix(proxy): keep Codex Responses beta marker in stack mode (#8437)
Codex Stack mode stripped every x-codex-* header before forwarding to third-party providers, which also removed the non-identity x-codex-beta-features marker that some native Responses gateways require. Keep that one header for native Responses upstreams in Stack mode; identity headers are still stripped and the Anthropic/Chat conversion paths are unchanged. Fixes #8435.
* fix(usage): count Pi subagent usage from the child session files (#8318)
Import pi-subagents usage from each child run's own session file (including nested runs and forked contexts) so it carries the real model and provider instead of being recorded as unknown or missed entirely. Skip subagent-artifacts directories, drop the parent's aggregated summary when all linked child files are imported, and record a first-import cutoff so history is not backfilled or double-counted. Fixes #8298.
* fix(hermes): read live WSL databases inside their distribution (#8254)
Windows SQLite cannot reliably read a live Hermes WAL database through a WSL UNC path. For wsl.localhost / wsl$ Hermes paths only, read the database inside the owning distribution via wsl.exe and Python SQLite (mode=ro, query_only, one short read transaction), streaming the result back in bounded JSON frames into the existing session projection. Non-WSL paths and macOS/Linux are unchanged. Requires python3 (3.7+) and timeout inside the distribution. Fixes #8205.
* fix(proxy): report routed switches without unconditional restart prompts (#8378)
Route switches now return what was actually applied (remembered / route / stack) and whether the client configuration was rewritten, and the success toast is built from that instead of always asking Codex / Gemini CLI / Grok Build users to restart. A restart hint is shown only when client files changed. Direct-mode switches serialize unchanged. Fixes #8347.
* fix(codex): recover malformed tool-call history for Chat upstreams (#8241)
A single historical function_call whose arguments are not a JSON object (e.g. truncated output) made strict Chat Completions upstreams reject every later Codex turn. When converting Responses to Chat, turn such calls into assistant text and fold their tool results into the same message; valid history is left byte-identical. Fixes #5197.
* fix(proxy): normalize DeepSeek tool replay in Codex Chat requests (#8351)
When Codex replays tool-call history, empty assistant messages, progress text or reasoning can sit between a function_call and its results. Converted to Chat, these become standalone assistant messages and DeepSeek rejects the request with HTTP 400. For explicit DeepSeek model ids only, fold such messages into the open tool-call message after conversion (running after the malformed-call recovery from #8241); well-formed histories stay byte-identical. Refs #8331.
* feat(usage): add bar and area chart toggle (#8439)
Add a bar / area segmented toggle to the usage trend chart. Bar stays the default; the choice is remembered locally and unknown values fall back to bar. Both styles share the same metrics, axes (RFC3339 bucket keys), tooltip and currency conversion; the area chart draws a dot when there is a single data point. Fixes #8406.
* feat(usage): move the trend chart style into usage display settings
The bar / area toggle added in #8439 sat on the trend chart header. Move it
to the Display section of the usage Settings tab, next to currency, the
sidebar Today items and request log columns, and store it with them as
usageDisplay.trendChartType in settings.json (bar when unset or unknown).
The chart header keeps only the metric switch. The localStorage key is
dropped; the toggle was never released, so there is nothing to migrate.
* style(usage): lay out the Display settings as an aligned grid
The four Display controls sat in a wrapping flex row, so the fourth one
dropped onto its own line and labels and fields of different widths never
lined up. Put them in a two-pair grid: each label column takes its widest
label and every control shares one width, so both rows align. Currency and
trend chart style share the first row, the sidebar Today items and request
log columns the second, and the exchange rate (non-USD only) gets its own
row under the currency. Narrow viewports fall back to a single pair per row.
* feat(presets): add EveryAPI sponsor presets
Add EveryAPI (everyapi.ai) as a basic-tier sponsor across Claude Code,
Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, with
api-cn.everyapi.ai as the China-priority endpoint candidate.
- GPT models on the gateway only serve the Responses API, so OpenCode,
OpenClaw, Hermes and Pi use their Responses protocols.
- The gateway has no claude-haiku-4-5, so Claude Desktop maps the three
roles to the 5.5 models instead of passing route IDs through.
- No Gemini CLI or Grok Build preset: the catalog has no Gemini or Grok
models and no /v1beta endpoint.
- Add the partner promotion copy in all four locales and the icon.
* feat(presets): add AllTokens sponsor presets
Add AllTokens (alltokens.cc) as a basic-tier sponsor across all nine
switchable and additive apps except MiniMax Code, with partner
promotion copy in all four locales and its icon.
AllTokens runs Sub2API, where each API key is bound to a single group
and every group serves one platform, so each preset notes which group's
key it needs:
- Claude Code and Claude Desktop use the Claude group; Desktop passes
the route IDs through since the group serves claude-sonnet-5,
claude-opus-5 and claude-haiku-4-5.
- Codex, OpenCode, OpenClaw, Hermes and Pi use the GPT group over the
Responses API.
- Gemini CLI uses gemini-3.8-flash, the only model in the Gemini group;
Grok Build uses the default grok-4.5.
Models were checked against the public model plaza
(/api/v1/model-plaza).
* chore(release): v4.0.15
* docs(release): add v4.0.15 release notes
* fix(i18n): improve Turkish UI wording (#8481)
Correct 17 Turkish UI strings: appearance and startup settings, npm installation hints, provider import instructions, session grouping and usage labels. Keys and interpolation placeholders are unchanged.
* feat(tray): replace the tray menu with a glass panel (#8432)
On macOS and Windows, clicking the tray icon opens a panel with a Configuration tab (apps, in-use providers, modes, quota) and a Usage tab. Every row reuses the native menu's event id and goes through handle_tray_menu_event. Linux keeps the native menu and adds an "Open quick panel" item.
macOS uses native glass under each card. Windows keeps a fixed canvas per popup and clips it with a window region, with opaque cards.
Co-authored-by: funkpopo <funkpopoisme@gmail.com>
* fix(proxy): allow unforced Claude WebSearch through Codex OAuth (#7936)
* fix(claude): use catalog ids for 5.5 upstreams in routing takeover (#8450)
In routing takeover, Opus/Sonnet slots whose upstream is a 5.5 model now get Claude Code's catalog ids (claude-opus-5-5 / claude-sonnet-5-5), so Claude Code shows the right model and uses the 128K output cap instead of 64K. CLAUDE_CODE_SUBAGENT_MODEL is projected the same way, and the proxy maps the catalog form back to the configured subagent model when its tier is empty.
* fix(proxy): keep Chat tool outputs contiguous across hoisted developer notices (#8445)
Treat mid-history system/developer items (e.g. Codex's image_resize_notice) as hoisted rather than turn boundaries in the Responses -> Chat conversion: pending tool calls are still flushed, but tool media and orphan results wait until the whole tool-output batch ends, so strict Chat upstreams no longer see tool, user, tool. Commentary merging now looks past trailing hoisted system messages, and restored missing calls are inserted before the first output that belongs to them.
Fixes #7190
Fixes #7569
* fix(codex): honor configured GPT context windows (#8385)
Explicit provider-scoped or per-model context windows now apply to mirrored official GPT catalog entries (context_window and max_context_window), with the auto-compact limit kept in sync; official defaults remain when nothing is configured. Gemini input-overflow errors on the Codex Chat path are normalized to context_length_exceeded and returned as a response.failed stream so Codex can compact, and non-string SSE error codes are stringified so upstream messages survive.
Fixes #8366
* feat(minimax): add MiniMax-M3.1-Flash to the official MiniMax presets (#8518)
Make MiniMax-M3.1-Flash the default model on the official MiniMax / MiniMax en presets across Claude Code, Claude Desktop, Codex, Hermes, OpenCode, OpenClaw and Pi, keeping MiniMax-M3 selectable, and seed its pricing.
Claude Code keeps M3 in the Opus slot so it stays selectable through the local route. Hermes pins the background title model to M3 (M3.1 cannot turn thinking off) only when the user has not chosen their own title model or route, and releases the pin when no active card declares it.
* fix(hermes): prefill MiniMax provider keys that don't collide with Hermes built-ins (#8538)
Hermes routes model.provider `minimax` to its built-in provider and ignores a
same-named custom_provider, so no request is sent. The MiniMax presets now
prefill cc-switch-minimax / cc-switch-minimax-en, and MiniMax-M3.1-Flash
declares supports_vision so images go to the main request.
* fix(usage): show account balance when a Zhipu account has no coding plan
Since the token plan template became the default for Zhipu cards, pay-as-you-go
accounts showed "query failed": the quota endpoint answers HTTP 200 with
{"code":500,"msg":"当前用户不存在coding plan","success":false}, which looked like
the provider was broken (#8465).
Show the plan when there is one, otherwise the account balance:
- coding_plan: query_coding_plan returns CodingPlanOutcome, with ZhipuNoPlan
for the personal plan when msg says the account has no coding plan. Auth
failures, network errors and other business errors are unchanged.
get_coding_plan_quota keeps its old result (used by the modal test button).
The team plan does not fall back: the balance endpoint ignores the
organization/project headers.
- balance: query_zhipu_balance reads availableBalance from the undocumented
/api/biz/account/query-customer-account-report, which accepts the inference
API key (verified on open.bigmodel.cn, CNY). api.z.ai is assumed to share
the shape and is shown in USD; not verified. Not added to detect_provider so
it does not compete with the injected token plan template.
- The token_plan branch of query_provider_usage returns the balance result on
ZhipuNoPlan; the tray already renders it as a balance line.
- UsageFooter only renders token plan data as tiers when every row is a
percentage, so the fallback balance uses the balance layout.
* chore(presets): drop invite codes from Zhipu API key links
Remove the ?ic= referral parameter from the Zhipu GLM (bigmodel.cn) and
Zhipu GLM en (z.ai) apiKeyUrl in all seven presets; the landing pages stay
the same.
* style(providers): restore the light-blue "Get API Key" link
The v7 pass turned the link into underlined body text, which read like a
plain label. Bring back the pre-v7 blue (blue-400 light / blue-500 dark,
swapped on hover) and drop the underline; keep the caption size and the
↗ arrow.
* fix(providers): show the "Get API Key" link when editing a provider
Providers don't remember which preset they came from, so the edit form had
no preset and only linked the "Website" field; with that field empty the
link vanished. When no preset is selected, look the preset up by the
form's current base URL and use its apiKeyUrl and partner promotion, same
as the add form. If presets sharing that address disagree on the link or
the promotion, don't guess and fall back to the Website field.
* feat(tray): add a setting to switch between the tray panel and the native menu
Settings > General > Window and terminal gets a "Tray style" option:
Panel (default) or Menu. With Menu, clicking the tray icon on macOS and
Windows shows the native menu again, as before the glass panel: macOS
opens it on either button, Windows opens the main window on left click
and the menu on right click. The switch applies right away, no restart.
The choice is a device setting (nativeTrayMenu in settings.json).
Linux only has the native menu, so the option is hidden there.
* fix(tray): follow the usage display currency and chart style in the panel
The panel's Usage tab now draws the trend as bars or an area, whichever
is picked in the main window's usage settings. Changing the currency or
the chart style in the main window also shows up the next time the
panel opens; before, the panel kept the old values until you switched
tabs, because its window stays alive while hidden.
* feat(tray): default the tray style to the native menu
The tray panel becomes opt-in under Settings > General > Tray style.
A settings.json without nativeTrayMenu now reads as the native menu;
an explicitly saved false keeps the panel.
* feat(tray): color the mode name in the panel w…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to the maintainer's review of #8218.
A malformed UTF-8 record in a Codex parent rollout was being treated as a transient reader failure by
lines(). Its children could therefore remain deferred and be reparsed every pass, even when the rest of the parent history was readable.Read byte-delimited records with
read_until, then decode UTF-8 separately. Invalid text is skipped like malformed JSON, matching the child parser and the earlier parent behavior. Actual I/O and decompression errors still return as transient failures before any partial timeline is cached. This avoids depending on the error kind chosen by a particular zstd decoder.Validation against current main
67d63e3:git diff --checkpass on Rust 1.95/macOS arm64.No database migration, dependency, or accounting-policy change. Independent of #8230.
The latest head
1d8c0d5retains the new continuation-page/high-water logic from #8290 and passes all four backend CI targets (Linux, macOS, Windows and Windows+WSL2): run 38737997391.