Skip to content

fix(codex): proxy image generations endpoint - #7036

Merged
farion1231 merged 1 commit into
farion1231:mainfrom
thisTom:fix/codex-images-generations-proxy
Sep 7, 2026
Merged

farion1231 merged 1 commit into
farion1231:mainfrom
thisTom:fix/codex-images-generations-proxy

Conversation

@thisTom

@thisTom thisTom commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary / 概述

修复 Codex 本地路由模式下内置 ImageGen 调用 legacy OpenAI Images API 时返回 404,或在 is_full_url = true provider 下被误转发到 Responses / Chat Completions URL 的问题。

Codex 的图片生成会请求 /v1/images/generations。此前本地 proxy 只注册了 Chat Completions、Responses、Responses Compact 和 Alpha Search 等 Codex 端点,Images generations 请求会在路由层直接 404,无法转发到当前选中的 Codex provider。

同时,如果当前 Codex provider 配置为 full URL(例如完整的 /v1/responses、/responses/compact 或 /v1/chat/completions 地址),普通 full-URL 分支会复用配置 URL,导致 ImageGen payload 被发送到聊天/Responses 端点,而不是 sibling /v1/images/generations。本 PR 将 Codex standalone endpoint 的 full URL 改写收敛到同一 helper:仅对可明确推导 sibling endpoint 的 URL 进行改写,已指向 /images/generations 的 full URL 会保留原路径并合并 query,无法安全推导的 opaque full URL 会 fail closed,避免把图片请求误发到无关端点。

请求仍走当前 Codex provider 的鉴权、模型映射、重试和响应透传;返回中的 usage.input_tokens / usage.output_tokens 也会通过 Codex usage parser 正确记录。

场景 修改前 修改后
Codex ImageGen 请求 /v1/images/generations 本地 proxy 未注册该端点,路由层返回 404 请求转发到上游 canonical /v1/images/generations
Codex 本地路径兼容 /images/generations、/v1/v1/images/generations、/codex/v1/images/generations 不可用 4 个本地 alias 都归一到同一个上游 Images generations endpoint
full URL Codex provider 完整 /v1/responses、/responses/compact 或 /v1/chat/completions provider 会把 ImageGen payload 继续发送到原聊天/Responses URL 可明确推导时改写到 sibling /v1/images/generations;已有 Images full URL 保持路径并合并 query
用量统计 Images API 返回的 input_tokens / output_tokens 无法通过 Chat Completions parser 正确记录 使用 Codex parser 自动解析 Images usage,并写入 proxy request logs
既有 Alpha Search 维持独立 handler 中重复的 passthrough 流程 与 Images generations 共享 Codex standalone passthrough/full URL rewrite helper,行为由原有 Alpha Search 测试继续覆盖

Related Issue / 关联 Issue

Fixes #5429
Fixes #6745

Screenshots / 截图

场景 Before / 修改前 After / 修改后
Codex ImageGen 本地路由 修改前:ImageGen 返回 404 修改后:ImageGen 成功生成图片
  • 修改前:Codex 通过本地路由调用 imagegen 技能时,/v1/images/generations 在 proxy 路由层返回 404 Not Found,无法生成图片。
  • 修改后:同样的 ImageGen 调用可正常转发并返回有效图片,Codex 会话中展示生成结果。

Testing / 测试

检查项 结果
cargo test --manifest-path src-tauri/Cargo.toml proxy::server::tests::codex_images_generation_aliases_forward_and_record_usage -- --exact 通过;覆盖 4 个 Images generations alias、上游转发、鉴权替换、query 保留、usage 记录,以及 full URL /v1/responses 改写到 /v1/images/generations
cargo test --manifest-path src-tauri/Cargo.toml proxy::forwarder::tests::images_generations_rewrites_known_full_codex_urls -- --exact 通过;覆盖 Responses、Responses Compact、Chat Completions full URL 改写到 Images generations
cargo test --manifest-path src-tauri/Cargo.toml proxy::forwarder::tests:: -- --nocapture 通过,75 个测试通过;覆盖 full URL rewrite、已有 Images full URL 保留、opaque full URL fail closed,以及既有 Alpha Search 行为
cargo fmt --check --manifest-path src-tauri/Cargo.toml 通过
cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings 通过
cargo test --manifest-path src-tauri/Cargo.toml -- --skip services::provider::tests::update_current_claude_desktop_provider_syncs_profile_when_proxy_takeover_is_active 通过,2765 passed,0 failed,5 ignored,1 filtered out
cargo test --manifest-path src-tauri/Cargo.toml 本机未完整通过:当前正在运行的 ai-manage 占用 127.0.0.1:15721,导致一个固定默认端口的既有测试 update_current_claude_desktop_provider_syncs_profile_when_proxy_takeover_is_active 绑定失败;该失败与本 PR 修改路径无关
手动验证(macOS ChatGPT/Codex 环境) 通过,在 macOS ChatGPT 环境下使用第三方模型供应商 sol + qwen 生图模型完成 ImageGen 调用,图片可正常生成并展示
git diff --check 通过
pnpm typecheck / pnpm format:check 未运行,本 PR 未修改前端或 TypeScript 文件

Checklist / 检查清单

  • pnpm typecheck passes / 通过 TypeScript 类型检查(N/A:未修改前端或 TypeScript 文件)
  • pnpm format:check passes / 通过代码格式检查(N/A:未修改前端或 TypeScript 文件)
  • cargo clippy passes (if Rust code changed) / 通过 Clippy 检查(如修改了 Rust 代码)
  • Updated i18n files if user-facing text changed / 如修改了用户可见文本,已更新国际化文件(N/A:无用户可见文案变更)

@thisTom
thisTom requested a review from farion1231 as a code owner September 1, 2026 10:49
@github-actions github-actions Bot added backend Backend (Rust/Tauri) proxy Related to proxy/API forwarding labels Sep 1, 2026
@qiyou011

qiyou011 commented Sep 1, 2026

Copy link
Copy Markdown

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-02T03:03:50.383387Z 5f495e7 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: db08ea5dbd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/proxy/handlers.rs
@thisTom
thisTom force-pushed the fix/codex-images-generations-proxy branch from db08ea5 to 4c006c1 Compare September 2, 2026 01:53
@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@qiyou011

qiyou011 commented Sep 2, 2026

Copy link
Copy Markdown

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4c006c1004

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/proxy/forwarder.rs
@thisTom
thisTom force-pushed the fix/codex-images-generations-proxy branch from 4c006c1 to 5f495e7 Compare September 2, 2026 02:23
@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@qiyou011

qiyou011 commented Sep 2, 2026

Copy link
Copy Markdown

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 5f495e7d1a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@thisTom

thisTom commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

@farion1231 大佬空了看下这个 PR,主要解决 #5429 #6745 这两个 bug issue

@thisTom

thisTom commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

@farion1231 大佬空了看下这个 PR,主要解决 #5429 #6745 这两个 bug issue

@farion1231 空了帮忙看看能合并吗

@LanternCX

Copy link
Copy Markdown
Contributor

同样遇到了这个问题,求修复 @farion1231

@xieluoli

xieluoli commented Sep 7, 2026

Copy link
Copy Markdown

@farion1231 相同问题 求修复 🥺

@farion1231 farion1231 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢 @thisTom 的修复,也感谢 #5429 / #6745 的报告者和一路 +1 的朋友们。这个 PR 我完整审过了,直接合并。

核实过的几点:

  • 4 个本地 alias 归到同一个透传 handler,与 Alpha Search 共用(handlers.rs#L974-L986)。Responses→Chat / Anthropic 桥的闸门是 /responses 白名单,/images/generations 不会误进转换。
  • 对照 openai/codex 上游(codex-api/src/endpoint/images.rs):ImageGen 是非流式 POST {base_url}/images/generations,整体 JSON 解码,与这里的透传形状一致;usage.input_tokens 能被 Codex 解析器正确记账。
  • full-URL 卡的 sibling 推导与 opaque fail-closed(forwarder.rs#L3294-L3365)逻辑清楚,既有 Alpha Search 三条测试只改了调用签名,行为不变。
  • 合并树对当前 main 干净,全量 Rust 测试与 clippy 通过。

合并后可以作为 follow-up 的几点(不需要在本 PR 处理,欢迎接着提):

[P2] /images/edits 未覆盖。 同一个 imagegen 工具在带 referenced_image_paths 或 num_last_images_to_include 时,Codex 会 POST images/edits(JSON body,images: [{image_url}],见上游 ImagesClient::edit 与 tool.rs 的 request_for_call_args),本地路由仍会回空 404,现象与本次修的一样。修法大致:CodexStandaloneEndpoint 加 ImagesEdits 变体(source_suffix 里补 /images/edits 与 /images/generations)、4 个路由 alias、一个 handler wrapper。

[P3] 定价表没有 gpt-image-* 条目,生图用量会记 token 但费用为 0。Codex 当前默认模型是 gpt-image-2(tool.rs#L58),补定价时注意图片 token 与文本 token 单价不同。

[P3] base_url 直接贴了完整 /chat/completions 或 /responses、但没开「完整 URL」开关的卡,standalone 请求会经 build_url 拼成 …/chat/completions/images/generations。Alpha Search 早有同样的缺口,不是本 PR 的回归;可以考虑把 standalone 改写也扩到 base_url_is_full_endpoint 命中的情况。

[P3] 流式生图的用量。 如果以后要把这个端点当通用 OpenAI Images 网关用:stream: true 时上游通过 image_generation.completed 事件顶层的 usage 回报用量,而 from_codex_stream_events_auto 找不到 response.completed 会回退到 Chat 解析并记 0。Codex 客户端本身不发流式生图请求,对本 PR 的场景没有影响;修法是在回退前扫一遍带 usage.input_tokens 的事件走 from_codex_response。

再次感谢,/images/edits 的 follow-up 提上来我会尽快复审。

@farion1231
farion1231 merged commit 17be909 into farion1231:main Sep 7, 2026
10 checks passed
thisTom added a commit to thisTom/cc-switch that referenced this pull request Sep 7, 2026
Follow-ups to farion1231#7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related farion1231#7036
farion1231 added a commit that referenced this pull request Sep 7, 2026
Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.
advance-lion pushed a commit to advance-lion/cc-switch that referenced this pull request Sep 10, 2026
Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After farion1231#7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.
farion1231 added a commit that referenced this pull request Sep 10, 2026
* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 5, 2026
feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and the tests assert each
product's line-up in order so future drift is caught.

Declare thinking control alongside the capabilities rather than leaving it to
defaults: the 31 DeepSeek entries carry a Tencent-specific compat so selecting
"off" actually disables thinking instead of silently billing reasoning tokens,
the eight Kimi K2.7 Code entries no longer offer an "off" level the model
cannot honour, and kimi-k2.7-code-highspeed is marked image-capable so
tool-returned screenshots are no longer dropped.

Drop minimax-m2.5 from every app: it is gone from Tencent's model tables and
the platform is retiring it.
fix(codex): proxy image generations endpoint (#7036)

Codex's built-in ImageGen calls the legacy OpenAI Images API, but the local
proxy only registered Responses / Chat Completions / Compact / Alpha Search
routes, so `/v1/images/generations` returned an empty 404 under Codex routing.

- Register `/images/generations` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them as a Codex standalone passthrough shared with Alpha Search.
- Generalize the Alpha Search full-URL rewrite into
  `rewrite_codex_standalone_full_url`: derive the sibling
  `/images/generations` from full Responses / Compact / Chat Completions URLs,
  keep an existing Images URL as-is, and fail closed on opaque full URLs.
- Record `usage.input_tokens` / `output_tokens` through the Codex usage parser.

Fixes #5429
Fixes #6745
feat(pricing): add Gemini 3.8 Flash pricing (#7164)

Seeds the built-in pricing row for Gemini 3.8 Flash at 0.75 / 3.75 input/output per million tokens, 0.075 cache read, no cache-write charge, plus a regression test locking all four fields.

Values verified against Google's own pricing page (introductory price through 2026-12-31; list price 1.50 / 7.50 / 0.15 from 2027-01-01) and models.dev.
feat(provider): extend PPIO presets for Pi (#6870)

Add a PPIO provider preset for Pi using the OpenAI-compatible endpoint,
and pin an explicit models URL on the Claude Code PPIO preset so model
discovery resolves against https://api.ppio.com/openai/v1/models instead
of the derived candidates, which all return 404.

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 Flash pricing (#7163)
fix(proxy): enable parallel tool calls for Codex OAuth (#7024)

Codex OAuth requests defaulted parallel_tool_calls to false when the incoming
Anthropic request carried no explicit value, forcing Claude Code into one tool
call per model turn. Default it to true (matching codex-rs), map Anthropic
tool_choice.disable_parallel_tool_use to the inverse Responses value so explicit
serial execution is preserved, and add regression coverage for both directions.

Carries #5722 forward on top of current main.

Fixes #5719

Co-authored-by: 太一 <taiyi@douyu.tv>
feat(pricing): add GPT-6 Astra pricing (#7162)
fix(codex): resolve input_modalities for unknown models in vendor catalog (#6750)

When a model slug is not in the bundled DeepSeek official catalog, the vendor path clones the flagship entry and previously inherited its text-only input_modalities. Resolve modalities for unmatched slugs through the same registry/fail-open logic the non-vendor path uses, so a vision variant such as deepseek-v4-flash-vision-exp is no longer declared text-only. Explicit user inputModalities still win; matched models keep the vendor's declaration verbatim.

Fixes #6725
fix(claude): expose Opus 5 and Sonnet 5 in takeover mode (#5882)

Bump the stable client-facing role aliases that takeover mode writes to
Claude Code: Opus claude-opus-4-8 -> claude-opus-5 and Sonnet
claude-sonnet-4-6 -> claude-sonnet-5, so the /model menu matches the
Claude Desktop default routes and preset defaults that already moved to
Opus 5 / Sonnet 5. Add "opus-5" to the adaptive-thinking classifier so
the Bedrock thinking optimizer, which runs on the client alias before
model mapping, keeps sending adaptive thinking instead of the removed
budget_tokens.

Routing is unaffected: the proxy maps client aliases by role keyword, and
the aliases are rewritten automatically the next time the proxy starts.

Fixes #5876

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 to built-in model pricing table (#6591)

Seed glm-5.3 at $1.4/$4.4 per MTok with $0.26 cache read, matching the
official Z.ai list price (identical to the GLM-5.2 tier):
https://docs.z.ai/guides/overview/pricing

New model id, so seed only; no repair entry and no SCHEMA_VERSION bump.
Existing installs pick the row up on next launch via INSERT OR IGNORE, and
the startup cost backfill re-prices historical glm-5.3 rows recorded at $0.
fix(codex): align OAuth client identity for GPT-6 (#7132)

Requests for gpt-6-astra through the Claude-to-Codex OAuth takeover route advertised Codex 0.144.1, below the model's 0.153.0 minimal_client_version, so ChatGPT's backend rejected them with HTTP 400 "requires a newer version of Codex". Bump the advertised client version to 0.153.4 and share the originator/version constants between generation and model discovery, which previously sent cc-switch's own package version and originator.

Two regression tests pin the generation headers and the model-list request to a version at or above the gpt-6-astra floor.

Refs #7129
fix(codex): disable supports_search_tool for DeepSeek catalog models (#6653)

DeepSeek's Responses API has no tool_search support, but the bundled
official catalog declared supports_search_tool=true for deepseek-v4-pro
and deepseek-v4-flash. Codex gates deferred/namespaced MCP tools on
this flag (search_tool_enabled = supports_search_tool &&
namespace_tools), so MCP tools were hidden behind a tool_search the
gateway cannot serve — no MCP tool could ever be called.

Set the flag to false for both models so Codex lists MCP tools inline.
Hosted web_search is unaffected (gated by provider capabilities, not
this flag) and DeepSeek's server-side web_search still works.

Fixes #6647
fix(codex): move $ref siblings into allOf for Moonshot/Kimi chat upstreams (#6863)

Moonshot's Chat Completions validator (api.moonshot.cn, api.moonshot.ai and the Kimi For Coding endpoint api.kimi.com) rejects a `$ref` that carries sibling keywords. Codex Desktop's built-in tool schemas carry exactly that shape, so every desktop turn routed through the local proxy to Kimi failed with HTTP 400.

When the resolved upstream host is Moonshot/Kimi, each `$ref` with siblings is moved into `allOf` after the Responses -> Chat conversion and the siblings stay in place. The traversal only enters schema-valued keywords and the rewrite is idempotent. Every other provider keeps byte-identical tool schemas, and the Codex -> Anthropic path is left alone because Moonshot's Anthropic-compatible endpoint accepts the original shape.

Fixes #6867

Co-authored-by: Jason <farion1231@gmail.com>
fix(codex): point Zhipu GLM presets at the official Responses endpoint (#6957)

Zhipu documents three separate base URLs per site (Anthropic /api/anthropic,
Chat /api/coding/paas/v4, Responses /api/v1) and warns that the wrong one
cannot use Coding Plan quota. Codex direct-connect speaks Responses, so the
presets must target /api/v1; the 400 in #6944 (`unknown variant custom`)
came from the Chat endpoint's strict legacy gateway.

- Switch the Zhipu GLM / Zhipu GLM en presets to apiFormat openai_responses
  (NativeResponses catalog: shell_command edits, no freeform apply_patch)
…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 5, 2026
移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and the tests assert each
product's line-up in order so future drift is caught.

Declare thinking control alongside the capabilities rather than leaving it to
defaults: the 31 DeepSeek entries carry a Tencent-specific compat so selecting
"off" actually disables thinking instead of silently billing reasoning tokens,
the eight Kimi K2.7 Code entries no longer offer an "off" level the model
cannot honour, and kimi-k2.7-code-highspeed is marked image-capable so
tool-returned screenshots are no longer dropped.

Drop minimax-m2.5 from every app: it is gone from Tencent's model tables and
the platform is retiring it.
fix(codex): proxy image generations endpoint (#7036)

Codex's built-in ImageGen calls the legacy OpenAI Images API, but the local
proxy only registered Responses / Chat Completions / Compact / Alpha Search
routes, so `/v1/images/generations` returned an empty 404 under Codex routing.

- Register `/images/generations` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them as a Codex standalone passthrough shared with Alpha Search.
- Generalize the Alpha Search full-URL rewrite into
  `rewrite_codex_standalone_full_url`: derive the sibling
  `/images/generations` from full Responses / Compact / Chat Completions URLs,
  keep an existing Images URL as-is, and fail closed on opaque full URLs.
- Record `usage.input_tokens` / `output_tokens` through the Codex usage parser.

Fixes #5429
Fixes #6745
feat(pricing): add Gemini 3.8 Flash pricing (#7164)

Seeds the built-in pricing row for Gemini 3.8 Flash at 0.75 / 3.75 input/output per million tokens, 0.075 cache read, no cache-write charge, plus a regression test locking all four fields.

Values verified against Google's own pricing page (introductory price through 2026-12-31; list price 1.50 / 7.50 / 0.15 from 2027-01-01) and models.dev.
feat(provider): extend PPIO presets for Pi (#6870)

Add a PPIO provider preset for Pi using the OpenAI-compatible endpoint,
and pin an explicit models URL on the Claude Code PPIO preset so model
discovery resolves against https://api.ppio.com/openai/v1/models instead
of the derived candidates, which all return 404.

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 Flash pricing (#7163)
fix(proxy): enable parallel tool calls for Codex OAuth (#7024)

Codex OAuth requests defaulted parallel_tool_calls to false when the incoming
Anthropic request carried no explicit value, forcing Claude Code into one tool
call per model turn. Default it to true (matching codex-rs), map Anthropic
tool_choice.disable_parallel_tool_use to the inverse Responses value so explicit
serial execution is preserved, and add regression coverage for both directions.

Carries #5722 forward on top of current main.

Fixes #5719

Co-authored-by: 太一 <taiyi@douyu.tv>
feat(pricing): add GPT-6 Astra pricing (#7162)
fix(codex): resolve input_modalities for unknown models in vendor catalog (#6750)

When a model slug is not in the bundled DeepSeek official catalog, the vendor path clones the flagship entry and previously inherited its text-only input_modalities. Resolve modalities for unmatched slugs through the same registry/fail-open logic the non-vendor path uses, so a vision variant such as deepseek-v4-flash-vision-exp is no longer declared text-only. Explicit user inputModalities still win; matched models keep the vendor's declaration verbatim.

Fixes #6725
fix(claude): expose Opus 5 and Sonnet 5 in takeover mode (#5882)

Bump the stable client-facing role aliases that takeover mode writes to
Claude Code: Opus claude-opus-4-8 -> claude-opus-5 and Sonnet
claude-sonnet-4-6 -> claude-sonnet-5, so the /model menu matches the
Claude Desktop default routes and preset defaults that already moved to
Opus 5 / Sonnet 5. Add "opus-5" to the adaptive-thinking classifier so
the Bedrock thinking optimizer, which runs on the client alias before
model mapping, keeps sending adaptive thinking instead of the removed
budget_tokens.

Routing is unaffected: the proxy maps client aliases by role keyword, and
the aliases are rewritten automatically the next time the proxy starts.

Fixes #5876

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 to built-in model pricing table (#6591)

Seed glm-5.3 at $1.4/$4.4 per MTok with $0.26 cache read, matching the
official Z.ai list price (identical to the GLM-5.2 tier):
https://docs.z.ai/guides/overview/pricing

New model id, so seed only; no repair entry and no SCHEMA_VERSION bump.
Existing installs pick the row up on next launch via INSERT OR IGNORE, and
the startup cost backfill re-prices historical glm-5.3 rows recorded at $0.
fix(codex): align OAuth client identity for GPT-6 (#7132)

Requests for gpt-6-astra through the Claude-to-Codex OAuth takeover route advertised Codex 0.144.1, below the model's 0.153.0 minimal_client_version, so ChatGPT's backend rejected them with HTTP 400 "requires a newer version of Codex". Bump the advertised client version to 0.153.4 and share the originator/version constants between generation and model discovery, which previously sent cc-switch's own package version and originator.

Two regression tests pin the generation headers and the model-list request to a version at or above the gpt-6-astra floor.

Refs #7129
fix(codex): disable supports_search_tool for DeepSeek catalog models (#6653)

DeepSeek's Responses API has no tool_search support, but the bundled
official catalog declared supports_search_tool=true for deepseek-v4-pro
and deepseek-v4-flash. Codex gates deferred/namespaced MCP tools on
this flag (search_tool_enabled = supports_search_tool &&
namespace_tools), so MCP tools were hidden behind a tool_search the
gateway cannot serve — no MCP tool could ever be called.

Set the flag to false for both models so Codex lists MCP tools inline.
Hosted web_search is unaffected (gated by provider capabilities, not
this flag) and DeepSeek's server-side web_search still works.

Fixes #6647
fix(codex): move $ref siblings into allOf for Moonshot/Kimi chat upstreams (#6863)

Moonshot's Chat Completions validator (api.moonshot.cn, api.moonshot.ai and the Kimi For Coding endpoint api.kimi.com) rejects a `$ref` that carries sibling keywords. Codex Desktop's built-in tool schemas carry exactly that shape, so every desktop turn rou…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 6, 2026
重放 fork 提交到 upstream/main(f3b18df1,v3.20.2)后统一版本号。
Cargo.lock 同步移除 updater 依赖树(fork 已禁用应用内更新器)。

chore(native): archive tray-left-click-toggle-window

chore(comet): 记录 tray-left-click-toggle-window 验收通过与归档状态

feat(tray): 托盘图标左键单击切换主窗口显示/隐藏

- tray.rs 新增 show_main_window/hide_main_window/toggle_main_window 助手:
  显示语义与"打开主界面"一致,隐藏与"关闭到托盘"平台处理一致
  (Windows set_skip_taskbar、macOS apply_tray_policy)
- lib.rs 托盘 show_menu_on_left_click 改为 false,左键 Click(Left, Up)
  触发切换(过滤 Down/Up 双事件),右键仍弹菜单;Enter/Click 用量刷新保持
- show_main 菜单事件与 macOS Reopen 复用 show_main_window

docs(readme): 更新日志补充连通性测试、双官网链接与托盘左键切换

feat(model-quick-switch): 获取模型列表成功后自动展开选择器

SearchableModelPicker 增加可选 open/onOpenChange 受控展开(不传保持内部自管,
表单等既有用法不受影响);快捷切换弹窗拉取成功且列表非空时直接展开,关闭弹窗复位。

chore(native): archive fix-quick-switch-1m

chore(comet): 记录 fix-quick-switch-1m 验收通过与归档状态

fix(model-quick-switch): 补齐 Haiku 显示名同步,支持不选模型仅翻转 1M

- 显示名同步映射补上 ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME(沿用空/旧base/旧base[1M] 覆盖、自定义保留语义)
- 新增 setClaudeOneMInSettings:仅原地翻转 1M 标记,不改各角色模型 base 与显示名
- 弹窗打开时 1M 开关按供应商当前状态初始化,避免已开 1M 被静默剥离
- claude 未选模型且 1M 状态有变化时允许保存;无可应用变化时保存禁用
- 补单测与四语言 toast 文案

feat(provider-website-links): 供应商支持第二个官网链接,主页横排双链接

- Rust Provider/UniversalProvider 新增 website_url_2(serde websiteUrl2,skip none)
- SQLite providers 表新增 website_url_2 列(schema v18→v19 幂等迁移 + create_tables 补列 + 旧 config.json 迁移)
- DAO SELECT/INSERT/UPDATE/replace 全链路带出新列
- 表单共享 BasicFormFields + 统一供应商表单第二输入框;zod 同第一链接规则校验与回填
- ProviderCard 主页双链接横排、两空格分隔、逐链接 openExternal 打开默认浏览器
- 搜索匹配、复制供应商、统一供应商生成 claude/codex/gemini 随行透传
- gemini_auth packycode 认证检测纳入 website_url_2;ClaudeDesktop/GrokBuild 预设切换清空第二链接;卡片同值链接去重
- 4 语言 i18n + 前端/前端单测覆盖

Comet change dual-website-links-homepage(19/19 验收通过,归档)

feat(connectivity-test): 拉取模型列表改为「搜索添加」选择器交互

- 新增共享组件 SearchableModelMultiPicker:搜索过滤 + 多选 + 已在列表标记 + 已选计数
- ConnectivityTestDialog 获取模型列表后不再全量追加合并去重,改为会话内缓存完整列表并自动弹出一次选择器;用户搜索挑选后添加进表格并自动勾选
- 重新拉取只刷新缓存,已添加进表格的行保留;拉取结果仍仅会话内有效,不写回供应商配置
- 四语言 i18n 新增 searchAdd / addToTest / alreadyInList / selectedCount
- 同步更新 ConnectivityTestDialog 测试并新增 SearchableModelMultiPicker 测试

feat(connectivity-test): 探测目标按供应商 API 格式对齐真实转发链路

- build_claude 按 get_claude_api_format 分派端点与请求体:openai_chat → /v1/chat/completions(复用 transform_claude_request_for_api_format,流式注入 stream_options.include_usage)、openai_responses → /v1/responses、gemini_native → /v1beta/models/{model}:{method}(resolve_gemini_native_url)、anthropic 保持 /v1/messages 并补 anthropic-version/beta 头
- build_codex 按 should_convert_codex_responses_to_chat/anthropic 分派:chat → /chat/completions(responses_to_chat_completions_with_reasoning)、anthropic → /v1/messages(responses_request_to_anthropic,含 max_output_tokens 注入与 [1m] 剥离 + context-1m beta 头)
- 鉴权改用 ClaudeAdapter/CodexAdapter 的 extract_auth + get_auth_headers(与真实出站请求同源:AUTH_TOKEN→Bearer、ANTHROPIC_API_KEY→x-api-key、gemini→x-goog-api-key、GeminiCli OAuth→Bearer、OpenRouter→Bearer、Codex 按 apiKeyField)
- 转换前剥离 [1m] 标记;meta.isFullUrl 或 base 已是完整端点时直接使用 base_url;自定义 body 浅合并移至转换后的最终协议体
- 新增 18 个目标构造断言单测;Comet 归档产物同步提交

chore: archive connectivity-test-optimizations

feat(connectivity-test): 单一模型表格 + 实时拉取模型 + 勾选即测试范围 + 新默认提示词

- 弹窗合并为单一模型表格:打开即全部模型行(初始待测试)、行勾选 + 表头全选(含半选态)、5 格统计卡,移除上方独立选择区
- 获取模型列表:凭据提取抽为共享模块 src/utils/providerCredentials.ts(ModelQuickSwitchDialog 同步改引用),复用 fetch_models_for_config 拉取 /v1/models,按 ID 追加合并去重、仅会话内有效
- 默认提示词改为「你好,你可以帮我做什么事情」:前后端常量同步,merge 差异判断基准跟随
- 勾选即测试范围:后端 connectivity_test_provider_models 新增 modelIds 参数(None/空白回退静态清单),前端逐模型独立 invoke 并行 + 行级流式更新
- 一轮测完自动勾选失败项,全部成功保持原勾选

fix(connectivity-test): UI 四项修复

- 弹窗抬至 alert 层(z-[60]):打开连通性测试时 header 切换栏正确变灰,
  详情子弹窗同步抬层(同 z + 更晚挂载 portal 在上)
- 批量检测入口移至 header 图标组(状态提升到 App,ProviderList 经
  props 读取探针结果),不再另起一行
- 批量检测可停止:runId 代次守卫(Tauri invoke 无法真正取消),再次
  点击即清除 waiting/running 徽标、丢弃在途迟到写入、不再拉起排队项
- 弹窗打开自动勾选模型:已保存默认测试模型 → 供应商当前模型
  (claude env.ANTHROPIC_MODEL / codex config.toml model,新增
  currentProviderModelId 与后端同口径)→ 清单首个

前端全量 141 文件 / 1106 测试全绿;typecheck clean

chore: archive per-model-connectivity-test

fix(connectivity-test): shouldShowTestEntry 增判 providerType + 收尾清理

chore(connectivity-test): 勾选 Task 9 / OpenSpec 5.1-5.2 + verify 报告

chore(connectivity-test): 勾选 Task 8 / OpenSpec 4.1-4.3

refactor(connectivity-test): 移除旧 stream_check 可达性探测全链路(保留表结构)

- 删除 6 个文件:commands/stream_check.rs、services/stream_check.rs、
  database/dao/stream_check.rs、hooks/useStreamCheck.ts、lib/api/connectivity-check.ts、
  components/usage/ConnectivityCheckConfigPanel.tsx
- 移除 mod.rs 声明、lib.rs generate_handler! 注册、cleanup_old_stream_check_logs 调用
- 从 SYNC_SKIP_TABLES / SYNC_PRESERVE_TABLES 移除 stream_check_logs(表结构保留在 schema.rs)
- 更新 backup.rs 测试:移除 stream_check_logs fixture 与断言
- 更新 SettingsPage:移除 ConnectivityCheckConfigPanel AccordionItem 与 FlaskConical 导入
- i18n:删除 streamCheck.* 命名空间(zh/en/ja/zh-TW 四语言)
- 更新注释:移除 stream_check 旧引用(forwarder.rs、provider.rs、userAgentPresets.ts、
  connectivity_test.rs)
- 修复 4 个 pre-existing clippy lint(connectivity_test: matches! 宏、unused_unit、
  derive Default)
- 移除 ProviderList.test.tsx 中已失效的 useStreamCheck mock

chore(connectivity-test): 勾选 Task 7 / OpenSpec 2.4 3.1-3.3

feat(connectivity-test): 供应商列表集成(弹窗入口 + 批量探针徽标)

chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(pro…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 6, 2026
修复 CI Frontend Checks 的 Check formatting 步骤失败
(App.tsx、ConnectivityTestDialog、ProviderCard、ProviderList、
provider schema 共 5 个文件)。

style: apply cargo fmt to connectivity-test era backend files

修复 CI 的 Check Rust formatting 步骤失败(connectivity_test、
backup、tests、provider 等文件格式不符合 rustfmt 规范)。

chore(native): archive sync-upstream

docs(fork): update rebase whitelist for connectivity-test era fork features

- 白名单补充连通性测试前后端、模型快捷切换、双官网链接测试、
  CI 裁剪版、README/CHANGELOG、.comet/superpowers 产物等 fork 专属文件
- 新增共享文件 fork 专属语义表(托盘左键切换、禁用更新器、hidden 字段等)
- 修正 §3.1 rebase 方向说明:rebase 中 fork 侧是 --theirs 而非 --ours
  (v3.20.2 同步实测确认,与 merge 语义相反)

chore: bump fork version to 3.20.2-1 after upstream v3.20.2 rebase

重放 fork 提交到 upstream/main(f3b18df1,v3.20.2)后统一版本号。
Cargo.lock 同步移除 updater 依赖树(fork 已禁用应用内更新器)。

chore(native): archive tray-left-click-toggle-window

chore(comet): 记录 tray-left-click-toggle-window 验收通过与归档状态

feat(tray): 托盘图标左键单击切换主窗口显示/隐藏

- tray.rs 新增 show_main_window/hide_main_window/toggle_main_window 助手:
  显示语义与"打开主界面"一致,隐藏与"关闭到托盘"平台处理一致
  (Windows set_skip_taskbar、macOS apply_tray_policy)
- lib.rs 托盘 show_menu_on_left_click 改为 false,左键 Click(Left, Up)
  触发切换(过滤 Down/Up 双事件),右键仍弹菜单;Enter/Click 用量刷新保持
- show_main 菜单事件与 macOS Reopen 复用 show_main_window

docs(readme): 更新日志补充连通性测试、双官网链接与托盘左键切换

feat(model-quick-switch): 获取模型列表成功后自动展开选择器

SearchableModelPicker 增加可选 open/onOpenChange 受控展开(不传保持内部自管,
表单等既有用法不受影响);快捷切换弹窗拉取成功且列表非空时直接展开,关闭弹窗复位。

chore(native): archive fix-quick-switch-1m

chore(comet): 记录 fix-quick-switch-1m 验收通过与归档状态

fix(model-quick-switch): 补齐 Haiku 显示名同步,支持不选模型仅翻转 1M

- 显示名同步映射补上 ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME(沿用空/旧base/旧base[1M] 覆盖、自定义保留语义)
- 新增 setClaudeOneMInSettings:仅原地翻转 1M 标记,不改各角色模型 base 与显示名
- 弹窗打开时 1M 开关按供应商当前状态初始化,避免已开 1M 被静默剥离
- claude 未选模型且 1M 状态有变化时允许保存;无可应用变化时保存禁用
- 补单测与四语言 toast 文案

feat(provider-website-links): 供应商支持第二个官网链接,主页横排双链接

- Rust Provider/UniversalProvider 新增 website_url_2(serde websiteUrl2,skip none)
- SQLite providers 表新增 website_url_2 列(schema v18→v19 幂等迁移 + create_tables 补列 + 旧 config.json 迁移)
- DAO SELECT/INSERT/UPDATE/replace 全链路带出新列
- 表单共享 BasicFormFields + 统一供应商表单第二输入框;zod 同第一链接规则校验与回填
- ProviderCard 主页双链接横排、两空格分隔、逐链接 openExternal 打开默认浏览器
- 搜索匹配、复制供应商、统一供应商生成 claude/codex/gemini 随行透传
- gemini_auth packycode 认证检测纳入 website_url_2;ClaudeDesktop/GrokBuild 预设切换清空第二链接;卡片同值链接去重
- 4 语言 i18n + 前端/前端单测覆盖

Comet change dual-website-links-homepage(19/19 验收通过,归档)

feat(connectivity-test): 拉取模型列表改为「搜索添加」选择器交互

- 新增共享组件 SearchableModelMultiPicker:搜索过滤 + 多选 + 已在列表标记 + 已选计数
- ConnectivityTestDialog 获取模型列表后不再全量追加合并去重,改为会话内缓存完整列表并自动弹出一次选择器;用户搜索挑选后添加进表格并自动勾选
- 重新拉取只刷新缓存,已添加进表格的行保留;拉取结果仍仅会话内有效,不写回供应商配置
- 四语言 i18n 新增 searchAdd / addToTest / alreadyInList / selectedCount
- 同步更新 ConnectivityTestDialog 测试并新增 SearchableModelMultiPicker 测试

feat(connectivity-test): 探测目标按供应商 API 格式对齐真实转发链路

- build_claude 按 get_claude_api_format 分派端点与请求体:openai_chat → /v1/chat/completions(复用 transform_claude_request_for_api_format,流式注入 stream_options.include_usage)、openai_responses → /v1/responses、gemini_native → /v1beta/models/{model}:{method}(resolve_gemini_native_url)、anthropic 保持 /v1/messages 并补 anthropic-version/beta 头
- build_codex 按 should_convert_codex_responses_to_chat/anthropic 分派:chat → /chat/completions(responses_to_chat_completions_with_reasoning)、anthropic → /v1/messages(responses_request_to_anthropic,含 max_output_tokens 注入与 [1m] 剥离 + context-1m beta 头)
- 鉴权改用 ClaudeAdapter/CodexAdapter 的 extract_auth + get_auth_headers(与真实出站请求同源:AUTH_TOKEN→Bearer、ANTHROPIC_API_KEY→x-api-key、gemini→x-goog-api-key、GeminiCli OAuth→Bearer、OpenRouter→Bearer、Codex 按 apiKeyField)
- 转换前剥离 [1m] 标记;meta.isFullUrl 或 base 已是完整端点时直接使用 base_url;自定义 body 浅合并移至转换后的最终协议体
- 新增 18 个目标构造断言单测;Comet 归档产物同步提交

chore: archive connectivity-test-optimizations

feat(connectivity-test): 单一模型表格 + 实时拉取模型 + 勾选即测试范围 + 新默认提示词

- 弹窗合并为单一模型表格:打开即全部模型行(初始待测试)、行勾选 + 表头全选(含半选态)、5 格统计卡,移除上方独立选择区
- 获取模型列表:凭据提取抽为共享模块 src/utils/providerCredentials.ts(ModelQuickSwitchDialog 同步改引用),复用 fetch_models_for_config 拉取 /v1/models,按 ID 追加合并去重、仅会话内有效
- 默认提示词改为「你好,你可以帮我做什么事情」:前后端常量同步,merge 差异判断基准跟随
- 勾选即测试范围:后端 connectivity_test_provider_models 新增 modelIds 参数(None/空白回退静态清单),前端逐模型独立 invoke 并行 + 行级流式更新
- 一轮测完自动勾选失败项,全部成功保持原勾选

fix(connectivity-test): UI 四项修复

- 弹窗抬至 alert 层(z-[60]):打开连通性测试时 header 切换栏正确变灰,
  详情子弹窗同步抬层(同 z + 更晚挂载 portal 在上)
- 批量检测入口移至 header 图标组(状态提升到 App,ProviderList 经
  props 读取探针结果),不再另起一行
- 批量检测可停止:runId 代次守卫(Tauri invoke 无法真正取消),再次
  点击即清除 waiting/running 徽标、丢弃在途迟到写入、不再拉起排队项
- 弹窗打开自动勾选模型:已保存默认测试模型 → 供应商当前模型
  (claude env.ANTHROPIC_MODEL / codex config.toml model,新增
  currentProviderModelId 与后端同口径)→ 清单首个

前端全量 141 文件 / 1106 测试全绿;typecheck clean

chore: archive per-model-connectivity-test

fix(connectivity-test): shouldShowTestEntry 增判 providerType + 收尾清理

chore(connectivity-test): 勾选 Task 9 / OpenSpec 5.1-5.2 + verify 报告

chore(connectivity-test): 勾选 Task 8 / OpenSpec 4.1-4.3

refactor(connectivity-test): 移除旧 stream_check 可达性探测全链路(保留表结构)

- 删除 6 个文件:commands/stream_check.rs、services/stream_check.rs、
  database/dao/stream_check.rs、hooks/useStreamCheck.ts、lib/api/connectivity-check.ts、
  components/usage/ConnectivityCheckConfigPanel.tsx
- 移除 mod.rs 声明、lib.rs generate_handler! 注册、cleanup_old_stream_check_logs 调用
- 从 SYNC_SKIP_TABLES / SYNC_PRESERVE_TABLES 移除 stream_check_logs(表结构保留在 schema.rs)
- 更新 backup.rs 测试:移除 stream_check_logs fixture 与断言
- 更新 SettingsPage:移除 ConnectivityCheckConfigPanel AccordionItem 与 FlaskConical 导入
- i18n:删除 streamCheck.* 命名空间(zh/en/ja/zh-TW 四语言)
- 更新注释:移除 stream_check 旧引用(forwarder.rs、provider.rs、userAgentPresets.ts、
  connectivity_test.rs)
- 修复 4 个 pre-existing clippy lint(connectivity_test: matches! 宏、unused_unit、
  derive Default)
- 移除 ProviderList.test.tsx 中已失效的 useStreamCheck mock

chore(connectivity-test): 勾选 Task 7 / OpenSpec 2.4 3.1-3.3

feat(connectivity-test): 供应商列表集成(弹窗入口 + 批量探针徽标)

chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 19…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 6, 2026
…itHub

Rebase fork main onto upstream/main (v3.20.3, 06082e18, 33 new commits)
while keeping all fork modifications: connectivity test, model quick
switch, sidebar panel visibility, preset filtering, tray left-click
toggle, dual website links, disabled in-app updater, fork CI.

- Bump fork version to 3.20.3-1 (package.json, tauri.conf.json,
  Cargo.toml, Cargo.lock).
- Merged upstream's v18->v19 schema migration with the fork's
  website_url_2 column migration in the same migration arm.
- Added mcode entry to forkOfficialAllowlist (no official presets,
  same treatment as hermes/openclaw/opencode/pi).
- Changed the top-left CC Switch brand link target from
  https://ccswitch.io to https://github.com/farion1231/cc-switch.
- Updated HOW_TO_REBASE_UPSTREAM.md whitelist with the brand link
  override.

style: apply prettier to files flagged by CI format check

修复 CI Frontend Checks 的 Check formatting 步骤失败
(App.tsx、ConnectivityTestDialog、ProviderCard、ProviderList、
provider schema 共 5 个文件)。

style: apply cargo fmt to connectivity-test era backend files

修复 CI 的 Check Rust formatting 步骤失败(connectivity_test、
backup、tests、provider 等文件格式不符合 rustfmt 规范)。

chore(native): archive sync-upstream

docs(fork): update rebase whitelist for connectivity-test era fork features

- 白名单补充连通性测试前后端、模型快捷切换、双官网链接测试、
  CI 裁剪版、README/CHANGELOG、.comet/superpowers 产物等 fork 专属文件
- 新增共享文件 fork 专属语义表(托盘左键切换、禁用更新器、hidden 字段等)
- 修正 §3.1 rebase 方向说明:rebase 中 fork 侧是 --theirs 而非 --ours
  (v3.20.2 同步实测确认,与 merge 语义相反)

chore: bump fork version to 3.20.2-1 after upstream v3.20.2 rebase

重放 fork 提交到 upstream/main(f3b18df1,v3.20.2)后统一版本号。
Cargo.lock 同步移除 updater 依赖树(fork 已禁用应用内更新器)。

chore(native): archive tray-left-click-toggle-window

chore(comet): 记录 tray-left-click-toggle-window 验收通过与归档状态

feat(tray): 托盘图标左键单击切换主窗口显示/隐藏

- tray.rs 新增 show_main_window/hide_main_window/toggle_main_window 助手:
  显示语义与"打开主界面"一致,隐藏与"关闭到托盘"平台处理一致
  (Windows set_skip_taskbar、macOS apply_tray_policy)
- lib.rs 托盘 show_menu_on_left_click 改为 false,左键 Click(Left, Up)
  触发切换(过滤 Down/Up 双事件),右键仍弹菜单;Enter/Click 用量刷新保持
- show_main 菜单事件与 macOS Reopen 复用 show_main_window

docs(readme): 更新日志补充连通性测试、双官网链接与托盘左键切换

feat(model-quick-switch): 获取模型列表成功后自动展开选择器

SearchableModelPicker 增加可选 open/onOpenChange 受控展开(不传保持内部自管,
表单等既有用法不受影响);快捷切换弹窗拉取成功且列表非空时直接展开,关闭弹窗复位。

chore(native): archive fix-quick-switch-1m

chore(comet): 记录 fix-quick-switch-1m 验收通过与归档状态

fix(model-quick-switch): 补齐 Haiku 显示名同步,支持不选模型仅翻转 1M

- 显示名同步映射补上 ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME(沿用空/旧base/旧base[1M] 覆盖、自定义保留语义)
- 新增 setClaudeOneMInSettings:仅原地翻转 1M 标记,不改各角色模型 base 与显示名
- 弹窗打开时 1M 开关按供应商当前状态初始化,避免已开 1M 被静默剥离
- claude 未选模型且 1M 状态有变化时允许保存;无可应用变化时保存禁用
- 补单测与四语言 toast 文案

feat(provider-website-links): 供应商支持第二个官网链接,主页横排双链接

- Rust Provider/UniversalProvider 新增 website_url_2(serde websiteUrl2,skip none)
- SQLite providers 表新增 website_url_2 列(schema v18→v19 幂等迁移 + create_tables 补列 + 旧 config.json 迁移)
- DAO SELECT/INSERT/UPDATE/replace 全链路带出新列
- 表单共享 BasicFormFields + 统一供应商表单第二输入框;zod 同第一链接规则校验与回填
- ProviderCard 主页双链接横排、两空格分隔、逐链接 openExternal 打开默认浏览器
- 搜索匹配、复制供应商、统一供应商生成 claude/codex/gemini 随行透传
- gemini_auth packycode 认证检测纳入 website_url_2;ClaudeDesktop/GrokBuild 预设切换清空第二链接;卡片同值链接去重
- 4 语言 i18n + 前端/前端单测覆盖

Comet change dual-website-links-homepage(19/19 验收通过,归档)

feat(connectivity-test): 拉取模型列表改为「搜索添加」选择器交互

- 新增共享组件 SearchableModelMultiPicker:搜索过滤 + 多选 + 已在列表标记 + 已选计数
- ConnectivityTestDialog 获取模型列表后不再全量追加合并去重,改为会话内缓存完整列表并自动弹出一次选择器;用户搜索挑选后添加进表格并自动勾选
- 重新拉取只刷新缓存,已添加进表格的行保留;拉取结果仍仅会话内有效,不写回供应商配置
- 四语言 i18n 新增 searchAdd / addToTest / alreadyInList / selectedCount
- 同步更新 ConnectivityTestDialog 测试并新增 SearchableModelMultiPicker 测试

feat(connectivity-test): 探测目标按供应商 API 格式对齐真实转发链路

- build_claude 按 get_claude_api_format 分派端点与请求体:openai_chat → /v1/chat/completions(复用 transform_claude_request_for_api_format,流式注入 stream_options.include_usage)、openai_responses → /v1/responses、gemini_native → /v1beta/models/{model}:{method}(resolve_gemini_native_url)、anthropic 保持 /v1/messages 并补 anthropic-version/beta 头
- build_codex 按 should_convert_codex_responses_to_chat/anthropic 分派:chat → /chat/completions(responses_to_chat_completions_with_reasoning)、anthropic → /v1/messages(responses_request_to_anthropic,含 max_output_tokens 注入与 [1m] 剥离 + context-1m beta 头)
- 鉴权改用 ClaudeAdapter/CodexAdapter 的 extract_auth + get_auth_headers(与真实出站请求同源:AUTH_TOKEN→Bearer、ANTHROPIC_API_KEY→x-api-key、gemini→x-goog-api-key、GeminiCli OAuth→Bearer、OpenRouter→Bearer、Codex 按 apiKeyField)
- 转换前剥离 [1m] 标记;meta.isFullUrl 或 base 已是完整端点时直接使用 base_url;自定义 body 浅合并移至转换后的最终协议体
- 新增 18 个目标构造断言单测;Comet 归档产物同步提交

chore: archive connectivity-test-optimizations

feat(connectivity-test): 单一模型表格 + 实时拉取模型 + 勾选即测试范围 + 新默认提示词

- 弹窗合并为单一模型表格:打开即全部模型行(初始待测试)、行勾选 + 表头全选(含半选态)、5 格统计卡,移除上方独立选择区
- 获取模型列表:凭据提取抽为共享模块 src/utils/providerCredentials.ts(ModelQuickSwitchDialog 同步改引用),复用 fetch_models_for_config 拉取 /v1/models,按 ID 追加合并去重、仅会话内有效
- 默认提示词改为「你好,你可以帮我做什么事情」:前后端常量同步,merge 差异判断基准跟随
- 勾选即测试范围:后端 connectivity_test_provider_models 新增 modelIds 参数(None/空白回退静态清单),前端逐模型独立 invoke 并行 + 行级流式更新
- 一轮测完自动勾选失败项,全部成功保持原勾选

fix(connectivity-test): UI 四项修复

- 弹窗抬至 alert 层(z-[60]):打开连通性测试时 header 切换栏正确变灰,
  详情子弹窗同步抬层(同 z + 更晚挂载 portal 在上)
- 批量检测入口移至 header 图标组(状态提升到 App,ProviderList 经
  props 读取探针结果),不再另起一行
- 批量检测可停止:runId 代次守卫(Tauri invoke 无法真正取消),再次
  点击即清除 waiting/running 徽标、丢弃在途迟到写入、不再拉起排队项
- 弹窗打开自动勾选模型:已保存默认测试模型 → 供应商当前模型
  (claude env.ANTHROPIC_MODEL / codex config.toml model,新增
  currentProviderModelId 与后端同口径)→ 清单首个

前端全量 141 文件 / 1106 测试全绿;typecheck clean

chore: archive per-model-connectivity-test

fix(connectivity-test): shouldShowTestEntry 增判 providerType + 收尾清理

chore(connectivity-test): 勾选 Task 9 / OpenSpec 5.1-5.2 + verify 报告

chore(connectivity-test): 勾选 Task 8 / OpenSpec 4.1-4.3

refactor(connectivity-test): 移除旧 stream_check 可达性探测全链路(保留表结构)

- 删除 6 个文件:commands/stream_check.rs、services/stream_check.rs、
  database/dao/stream_check.rs、hooks/useStreamCheck.ts、lib/api/connectivity-check.ts、
  components/usage/ConnectivityCheckConfigPanel.tsx
- 移除 mod.rs 声明、lib.rs generate_handler! 注册、cleanup_old_stream_check_logs 调用
- 从 SYNC_SKIP_TABLES / SYNC_PRESERVE_TABLES 移除 stream_check_logs(表结构保留在 schema.rs)
- 更新 backup.rs 测试:移除 stream_check_logs fixture 与断言
- 更新 SettingsPage:移除 ConnectivityCheckConfigPanel AccordionItem 与 FlaskConical 导入
- i18n:删除 streamCheck.* 命名空间(zh/en/ja/zh-TW 四语言)
- 更新注释:移除 stream_check 旧引用(forwarder.rs、provider.rs、userAgentPresets.ts、
  connectivity_test.rs)
- 修复 4 个 pre-existing clippy lint(connectivity_test: matches! 宏、unused_unit、
  derive Default)
- 移除 ProviderList.test.tsx 中已失效的 useStreamCheck mock

chore(connectivity-test): 勾选 Task 7 / OpenSpec 2.4 3.1-3.3

feat(connectivity-test): 供应商列表集成(弹窗入口 + 批量探针徽标)

chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four READ…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 6, 2026
新建/编辑供应商表单里,可从其他已启用应用的已有供应商一次性带入
名称、备注、官网链接、API Key、请求地址、默认(兜底)模型名与上游
API 格式,其余配置由用户自行填写。10 个应用的配置形状各不相同,
映射层按目标应用分别落键;编辑模式只覆盖这三个键位,配置对象其余
部分(providerKey、模型表、Codex modelCatalog、Gemini 顶层 config、
env/options/auth 其他键含 Codex 登录态键)原样保留。

- src/utils/providerImport.ts:读侧按应用取可迁移字段;写侧拆成
  buildImportedSettingsConfig(新建,按目标应用「自定义」模板重建)
  与 patchImportedSettingsConfig(编辑,就地覆盖)两条路径
- src/components/providers/forms/ProviderImportEntry.tsx:来源应用
  按钮行 + 供应商选择弹窗,预告「本次将写入」的字段,不渲染 API Key
- useProviderImportSources / useProviderImportApply:来源枚举与表单
  写入;ProviderPresetSelector 增加 extraActions 插槽
- src/utils/piProviderConfig.ts:buildPiSettingsConfig 自
  PiProviderForm 抽出,供映射层复用
- i18n 四语言新增 providerImport 文案

release 工作流改为直接以正式版发布(prerelease: false),不再先挂
预发布等人工提升;注意这同时移除了那道人工放行关卡,tag 一推发布
即对 /releases/latest、下载页和应用内更新器可见。sync-r2 不受影响,
其 released 触发器对直接以 stable 发布同样触发。

chore(native): archive cross-app-provider-import

chore(native): archive sync-upstream-3203

chore: sync fork with upstream v3.20.3 and rebrand top-left link to GitHub

Rebase fork main onto upstream/main (v3.20.3, 06082e18, 33 new commits)
while keeping all fork modifications: connectivity test, model quick
switch, sidebar panel visibility, preset filtering, tray left-click
toggle, dual website links, disabled in-app updater, fork CI.

- Bump fork version to 3.20.3-1 (package.json, tauri.conf.json,
  Cargo.toml, Cargo.lock).
- Merged upstream's v18->v19 schema migration with the fork's
  website_url_2 column migration in the same migration arm.
- Added mcode entry to forkOfficialAllowlist (no official presets,
  same treatment as hermes/openclaw/opencode/pi).
- Changed the top-left CC Switch brand link target from
  https://ccswitch.io to https://github.com/farion1231/cc-switch.
- Updated HOW_TO_REBASE_UPSTREAM.md whitelist with the brand link
  override.

style: apply prettier to files flagged by CI format check

修复 CI Frontend Checks 的 Check formatting 步骤失败
(App.tsx、ConnectivityTestDialog、ProviderCard、ProviderList、
provider schema 共 5 个文件)。

style: apply cargo fmt to connectivity-test era backend files

修复 CI 的 Check Rust formatting 步骤失败(connectivity_test、
backup、tests、provider 等文件格式不符合 rustfmt 规范)。

chore(native): archive sync-upstream

docs(fork): update rebase whitelist for connectivity-test era fork features

- 白名单补充连通性测试前后端、模型快捷切换、双官网链接测试、
  CI 裁剪版、README/CHANGELOG、.comet/superpowers 产物等 fork 专属文件
- 新增共享文件 fork 专属语义表(托盘左键切换、禁用更新器、hidden 字段等)
- 修正 §3.1 rebase 方向说明:rebase 中 fork 侧是 --theirs 而非 --ours
  (v3.20.2 同步实测确认,与 merge 语义相反)

chore: bump fork version to 3.20.2-1 after upstream v3.20.2 rebase

重放 fork 提交到 upstream/main(f3b18df1,v3.20.2)后统一版本号。
Cargo.lock 同步移除 updater 依赖树(fork 已禁用应用内更新器)。

chore(native): archive tray-left-click-toggle-window

chore(comet): 记录 tray-left-click-toggle-window 验收通过与归档状态

feat(tray): 托盘图标左键单击切换主窗口显示/隐藏

- tray.rs 新增 show_main_window/hide_main_window/toggle_main_window 助手:
  显示语义与"打开主界面"一致,隐藏与"关闭到托盘"平台处理一致
  (Windows set_skip_taskbar、macOS apply_tray_policy)
- lib.rs 托盘 show_menu_on_left_click 改为 false,左键 Click(Left, Up)
  触发切换(过滤 Down/Up 双事件),右键仍弹菜单;Enter/Click 用量刷新保持
- show_main 菜单事件与 macOS Reopen 复用 show_main_window

docs(readme): 更新日志补充连通性测试、双官网链接与托盘左键切换

feat(model-quick-switch): 获取模型列表成功后自动展开选择器

SearchableModelPicker 增加可选 open/onOpenChange 受控展开(不传保持内部自管,
表单等既有用法不受影响);快捷切换弹窗拉取成功且列表非空时直接展开,关闭弹窗复位。

chore(native): archive fix-quick-switch-1m

chore(comet): 记录 fix-quick-switch-1m 验收通过与归档状态

fix(model-quick-switch): 补齐 Haiku 显示名同步,支持不选模型仅翻转 1M

- 显示名同步映射补上 ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME(沿用空/旧base/旧base[1M] 覆盖、自定义保留语义)
- 新增 setClaudeOneMInSettings:仅原地翻转 1M 标记,不改各角色模型 base 与显示名
- 弹窗打开时 1M 开关按供应商当前状态初始化,避免已开 1M 被静默剥离
- claude 未选模型且 1M 状态有变化时允许保存;无可应用变化时保存禁用
- 补单测与四语言 toast 文案

feat(provider-website-links): 供应商支持第二个官网链接,主页横排双链接

- Rust Provider/UniversalProvider 新增 website_url_2(serde websiteUrl2,skip none)
- SQLite providers 表新增 website_url_2 列(schema v18→v19 幂等迁移 + create_tables 补列 + 旧 config.json 迁移)
- DAO SELECT/INSERT/UPDATE/replace 全链路带出新列
- 表单共享 BasicFormFields + 统一供应商表单第二输入框;zod 同第一链接规则校验与回填
- ProviderCard 主页双链接横排、两空格分隔、逐链接 openExternal 打开默认浏览器
- 搜索匹配、复制供应商、统一供应商生成 claude/codex/gemini 随行透传
- gemini_auth packycode 认证检测纳入 website_url_2;ClaudeDesktop/GrokBuild 预设切换清空第二链接;卡片同值链接去重
- 4 语言 i18n + 前端/前端单测覆盖

Comet change dual-website-links-homepage(19/19 验收通过,归档)

feat(connectivity-test): 拉取模型列表改为「搜索添加」选择器交互

- 新增共享组件 SearchableModelMultiPicker:搜索过滤 + 多选 + 已在列表标记 + 已选计数
- ConnectivityTestDialog 获取模型列表后不再全量追加合并去重,改为会话内缓存完整列表并自动弹出一次选择器;用户搜索挑选后添加进表格并自动勾选
- 重新拉取只刷新缓存,已添加进表格的行保留;拉取结果仍仅会话内有效,不写回供应商配置
- 四语言 i18n 新增 searchAdd / addToTest / alreadyInList / selectedCount
- 同步更新 ConnectivityTestDialog 测试并新增 SearchableModelMultiPicker 测试

feat(connectivity-test): 探测目标按供应商 API 格式对齐真实转发链路

- build_claude 按 get_claude_api_format 分派端点与请求体:openai_chat → /v1/chat/completions(复用 transform_claude_request_for_api_format,流式注入 stream_options.include_usage)、openai_responses → /v1/responses、gemini_native → /v1beta/models/{model}:{method}(resolve_gemini_native_url)、anthropic 保持 /v1/messages 并补 anthropic-version/beta 头
- build_codex 按 should_convert_codex_responses_to_chat/anthropic 分派:chat → /chat/completions(responses_to_chat_completions_with_reasoning)、anthropic → /v1/messages(responses_request_to_anthropic,含 max_output_tokens 注入与 [1m] 剥离 + context-1m beta 头)
- 鉴权改用 ClaudeAdapter/CodexAdapter 的 extract_auth + get_auth_headers(与真实出站请求同源:AUTH_TOKEN→Bearer、ANTHROPIC_API_KEY→x-api-key、gemini→x-goog-api-key、GeminiCli OAuth→Bearer、OpenRouter→Bearer、Codex 按 apiKeyField)
- 转换前剥离 [1m] 标记;meta.isFullUrl 或 base 已是完整端点时直接使用 base_url;自定义 body 浅合并移至转换后的最终协议体
- 新增 18 个目标构造断言单测;Comet 归档产物同步提交

chore: archive connectivity-test-optimizations

feat(connectivity-test): 单一模型表格 + 实时拉取模型 + 勾选即测试范围 + 新默认提示词

- 弹窗合并为单一模型表格:打开即全部模型行(初始待测试)、行勾选 + 表头全选(含半选态)、5 格统计卡,移除上方独立选择区
- 获取模型列表:凭据提取抽为共享模块 src/utils/providerCredentials.ts(ModelQuickSwitchDialog 同步改引用),复用 fetch_models_for_config 拉取 /v1/models,按 ID 追加合并去重、仅会话内有效
- 默认提示词改为「你好,你可以帮我做什么事情」:前后端常量同步,merge 差异判断基准跟随
- 勾选即测试范围:后端 connectivity_test_provider_models 新增 modelIds 参数(None/空白回退静态清单),前端逐模型独立 invoke 并行 + 行级流式更新
- 一轮测完自动勾选失败项,全部成功保持原勾选

fix(connectivity-test): UI 四项修复

- 弹窗抬至 alert 层(z-[60]):打开连通性测试时 header 切换栏正确变灰,
  详情子弹窗同步抬层(同 z + 更晚挂载 portal 在上)
- 批量检测入口移至 header 图标组(状态提升到 App,ProviderList 经
  props 读取探针结果),不再另起一行
- 批量检测可停止:runId 代次守卫(Tauri invoke 无法真正取消),再次
  点击即清除 waiting/running 徽标、丢弃在途迟到写入、不再拉起排队项
- 弹窗打开自动勾选模型:已保存默认测试模型 → 供应商当前模型
  (claude env.ANTHROPIC_MODEL / codex config.toml model,新增
  currentProviderModelId 与后端同口径)→ 清单首个

前端全量 141 文件 / 1106 测试全绿;typecheck clean

chore: archive per-model-connectivity-test

fix(connectivity-test): shouldShowTestEntry 增判 providerType + 收尾清理

chore(connectivity-test): 勾选 Task 9 / OpenSpec 5.1-5.2 + verify 报告

chore(connectivity-test): 勾选 Task 8 / OpenSpec 4.1-4.3

refactor(connectivity-test): 移除旧 stream_check 可达性探测全链路(保留表结构)

- 删除 6 个文件:commands/stream_check.rs、services/stream_check.rs、
  database/dao/stream_check.rs、hooks/useStreamCheck.ts、lib/api/connectivity-check.ts、
  components/usage/ConnectivityCheckConfigPanel.tsx
- 移除 mod.rs 声明、lib.rs generate_handler! 注册、cleanup_old_stream_check_logs 调用
- 从 SYNC_SKIP_TABLES / SYNC_PRESERVE_TABLES 移除 stream_check_logs(表结构保留在 schema.rs)
- 更新 backup.rs 测试:移除 stream_check_logs fixture 与断言
- 更新 SettingsPage:移除 ConnectivityCheckConfigPanel AccordionItem 与 FlaskConical 导入
- i18n:删除 streamCheck.* 命名空间(zh/en/ja/zh-TW 四语言)
- 更新注释:移除 stream_check 旧引用(forwarder.rs、provider.rs、userAgentPresets.ts、
  connectivity_test.rs)
- 修复 4 个 pre-existing clippy lint(connectivity_test: matches! 宏、unused_unit、
  derive Default)
- 移除 ProviderList.test.tsx 中已失效的 useStreamCheck mock

chore(connectivity-test): 勾选 Task 7 / OpenSpec 2.4 3.1-3.3

feat(connectivity-test): 供应商列表集成(弹窗入口 + 批量探针徽标)

chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
it…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 6, 2026
设置页可隐藏主页顶栏批量检测入口;关闭时停止进行中的探测。旧数据缺字段时默认显示。README 补上该开关,以及从其他应用导入供应商。

chore(native): archive sync-upstream-latest

chore(native): record sync-upstream-latest verification state

Commit the Runtime-managed comet-state.yaml and verification.md so the
working tree is clean after the v3.20.4 upstream sync.

chore: sync fork with upstream v3.20.4

Rebase fork main onto upstream/main (v3.20.4, 8e478b2b, 40 new commits)
while keeping all fork modifications: connectivity test, model quick
switch, sidebar panel visibility, preset filtering, tray left-click
toggle, dual website links, cross-app provider import, disabled in-app
updater, fork CI.

- Bump fork version to 3.20.4-1 (package.json, tauri.conf.json,
  Cargo.toml, Cargo.lock).
- Keep vitest testTimeout at 30s so full provider-form jsdom tests stop
  flaking past the 5s default.
- Resolved conflicts: version files kept on the fork side, dropped the
  duplicate GitHub button in AboutSection (upstream now ships its own
  star prompt), and restored .comet/config.yaml with native enabled.
- Updated HOW_TO_REBASE_UPSTREAM.md whitelist with the cross-app import
  files, searchable model pickers, and the dev config.

feat(provider-import): 从其他已启用应用导入供应商配置

新建/编辑供应商表单里,可从其他已启用应用的已有供应商一次性带入
名称、备注、官网链接、API Key、请求地址、默认(兜底)模型名与上游
API 格式,其余配置由用户自行填写。10 个应用的配置形状各不相同,
映射层按目标应用分别落键;编辑模式只覆盖这三个键位,配置对象其余
部分(providerKey、模型表、Codex modelCatalog、Gemini 顶层 config、
env/options/auth 其他键含 Codex 登录态键)原样保留。

- src/utils/providerImport.ts:读侧按应用取可迁移字段;写侧拆成
  buildImportedSettingsConfig(新建,按目标应用「自定义」模板重建)
  与 patchImportedSettingsConfig(编辑,就地覆盖)两条路径
- src/components/providers/forms/ProviderImportEntry.tsx:来源应用
  按钮行 + 供应商选择弹窗,预告「本次将写入」的字段,不渲染 API Key
- useProviderImportSources / useProviderImportApply:来源枚举与表单
  写入;ProviderPresetSelector 增加 extraActions 插槽
- src/utils/piProviderConfig.ts:buildPiSettingsConfig 自
  PiProviderForm 抽出,供映射层复用
- i18n 四语言新增 providerImport 文案

release 工作流改为直接以正式版发布(prerelease: false),不再先挂
预发布等人工提升;注意这同时移除了那道人工放行关卡,tag 一推发布
即对 /releases/latest、下载页和应用内更新器可见。sync-r2 不受影响,
其 released 触发器对直接以 stable 发布同样触发。

chore(native): archive cross-app-provider-import

chore(native): archive sync-upstream-3203

chore: sync fork with upstream v3.20.3 and rebrand top-left link to GitHub

Rebase fork main onto upstream/main (v3.20.3, 06082e18, 33 new commits)
while keeping all fork modifications: connectivity test, model quick
switch, sidebar panel visibility, preset filtering, tray left-click
toggle, dual website links, disabled in-app updater, fork CI.

- Bump fork version to 3.20.3-1 (package.json, tauri.conf.json,
  Cargo.toml, Cargo.lock).
- Merged upstream's v18->v19 schema migration with the fork's
  website_url_2 column migration in the same migration arm.
- Added mcode entry to forkOfficialAllowlist (no official presets,
  same treatment as hermes/openclaw/opencode/pi).
- Changed the top-left CC Switch brand link target from
  https://ccswitch.io to https://github.com/farion1231/cc-switch.
- Updated HOW_TO_REBASE_UPSTREAM.md whitelist with the brand link
  override.

style: apply prettier to files flagged by CI format check

修复 CI Frontend Checks 的 Check formatting 步骤失败
(App.tsx、ConnectivityTestDialog、ProviderCard、ProviderList、
provider schema 共 5 个文件)。

style: apply cargo fmt to connectivity-test era backend files

修复 CI 的 Check Rust formatting 步骤失败(connectivity_test、
backup、tests、provider 等文件格式不符合 rustfmt 规范)。

chore(native): archive sync-upstream

docs(fork): update rebase whitelist for connectivity-test era fork features

- 白名单补充连通性测试前后端、模型快捷切换、双官网链接测试、
  CI 裁剪版、README/CHANGELOG、.comet/superpowers 产物等 fork 专属文件
- 新增共享文件 fork 专属语义表(托盘左键切换、禁用更新器、hidden 字段等)
- 修正 §3.1 rebase 方向说明:rebase 中 fork 侧是 --theirs 而非 --ours
  (v3.20.2 同步实测确认,与 merge 语义相反)

chore: bump fork version to 3.20.2-1 after upstream v3.20.2 rebase

重放 fork 提交到 upstream/main(f3b18df1,v3.20.2)后统一版本号。
Cargo.lock 同步移除 updater 依赖树(fork 已禁用应用内更新器)。

chore(native): archive tray-left-click-toggle-window

chore(comet): 记录 tray-left-click-toggle-window 验收通过与归档状态

feat(tray): 托盘图标左键单击切换主窗口显示/隐藏

- tray.rs 新增 show_main_window/hide_main_window/toggle_main_window 助手:
  显示语义与"打开主界面"一致,隐藏与"关闭到托盘"平台处理一致
  (Windows set_skip_taskbar、macOS apply_tray_policy)
- lib.rs 托盘 show_menu_on_left_click 改为 false,左键 Click(Left, Up)
  触发切换(过滤 Down/Up 双事件),右键仍弹菜单;Enter/Click 用量刷新保持
- show_main 菜单事件与 macOS Reopen 复用 show_main_window

docs(readme): 更新日志补充连通性测试、双官网链接与托盘左键切换

feat(model-quick-switch): 获取模型列表成功后自动展开选择器

SearchableModelPicker 增加可选 open/onOpenChange 受控展开(不传保持内部自管,
表单等既有用法不受影响);快捷切换弹窗拉取成功且列表非空时直接展开,关闭弹窗复位。

chore(native): archive fix-quick-switch-1m

chore(comet): 记录 fix-quick-switch-1m 验收通过与归档状态

fix(model-quick-switch): 补齐 Haiku 显示名同步,支持不选模型仅翻转 1M

- 显示名同步映射补上 ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME(沿用空/旧base/旧base[1M] 覆盖、自定义保留语义)
- 新增 setClaudeOneMInSettings:仅原地翻转 1M 标记,不改各角色模型 base 与显示名
- 弹窗打开时 1M 开关按供应商当前状态初始化,避免已开 1M 被静默剥离
- claude 未选模型且 1M 状态有变化时允许保存;无可应用变化时保存禁用
- 补单测与四语言 toast 文案

feat(provider-website-links): 供应商支持第二个官网链接,主页横排双链接

- Rust Provider/UniversalProvider 新增 website_url_2(serde websiteUrl2,skip none)
- SQLite providers 表新增 website_url_2 列(schema v18→v19 幂等迁移 + create_tables 补列 + 旧 config.json 迁移)
- DAO SELECT/INSERT/UPDATE/replace 全链路带出新列
- 表单共享 BasicFormFields + 统一供应商表单第二输入框;zod 同第一链接规则校验与回填
- ProviderCard 主页双链接横排、两空格分隔、逐链接 openExternal 打开默认浏览器
- 搜索匹配、复制供应商、统一供应商生成 claude/codex/gemini 随行透传
- gemini_auth packycode 认证检测纳入 website_url_2;ClaudeDesktop/GrokBuild 预设切换清空第二链接;卡片同值链接去重
- 4 语言 i18n + 前端/前端单测覆盖

Comet change dual-website-links-homepage(19/19 验收通过,归档)

feat(connectivity-test): 拉取模型列表改为「搜索添加」选择器交互

- 新增共享组件 SearchableModelMultiPicker:搜索过滤 + 多选 + 已在列表标记 + 已选计数
- ConnectivityTestDialog 获取模型列表后不再全量追加合并去重,改为会话内缓存完整列表并自动弹出一次选择器;用户搜索挑选后添加进表格并自动勾选
- 重新拉取只刷新缓存,已添加进表格的行保留;拉取结果仍仅会话内有效,不写回供应商配置
- 四语言 i18n 新增 searchAdd / addToTest / alreadyInList / selectedCount
- 同步更新 ConnectivityTestDialog 测试并新增 SearchableModelMultiPicker 测试

feat(connectivity-test): 探测目标按供应商 API 格式对齐真实转发链路

- build_claude 按 get_claude_api_format 分派端点与请求体:openai_chat → /v1/chat/completions(复用 transform_claude_request_for_api_format,流式注入 stream_options.include_usage)、openai_responses → /v1/responses、gemini_native → /v1beta/models/{model}:{method}(resolve_gemini_native_url)、anthropic 保持 /v1/messages 并补 anthropic-version/beta 头
- build_codex 按 should_convert_codex_responses_to_chat/anthropic 分派:chat → /chat/completions(responses_to_chat_completions_with_reasoning)、anthropic → /v1/messages(responses_request_to_anthropic,含 max_output_tokens 注入与 [1m] 剥离 + context-1m beta 头)
- 鉴权改用 ClaudeAdapter/CodexAdapter 的 extract_auth + get_auth_headers(与真实出站请求同源:AUTH_TOKEN→Bearer、ANTHROPIC_API_KEY→x-api-key、gemini→x-goog-api-key、GeminiCli OAuth→Bearer、OpenRouter→Bearer、Codex 按 apiKeyField)
- 转换前剥离 [1m] 标记;meta.isFullUrl 或 base 已是完整端点时直接使用 base_url;自定义 body 浅合并移至转换后的最终协议体
- 新增 18 个目标构造断言单测;Comet 归档产物同步提交

chore: archive connectivity-test-optimizations

feat(connectivity-test): 单一模型表格 + 实时拉取模型 + 勾选即测试范围 + 新默认提示词

- 弹窗合并为单一模型表格:打开即全部模型行(初始待测试)、行勾选 + 表头全选(含半选态)、5 格统计卡,移除上方独立选择区
- 获取模型列表:凭据提取抽为共享模块 src/utils/providerCredentials.ts(ModelQuickSwitchDialog 同步改引用),复用 fetch_models_for_config 拉取 /v1/models,按 ID 追加合并去重、仅会话内有效
- 默认提示词改为「你好,你可以帮我做什么事情」:前后端常量同步,merge 差异判断基准跟随
- 勾选即测试范围:后端 connectivity_test_provider_models 新增 modelIds 参数(None/空白回退静态清单),前端逐模型独立 invoke 并行 + 行级流式更新
- 一轮测完自动勾选失败项,全部成功保持原勾选

fix(connectivity-test): UI 四项修复

- 弹窗抬至 alert 层(z-[60]):打开连通性测试时 header 切换栏正确变灰,
  详情子弹窗同步抬层(同 z + 更晚挂载 portal 在上)
- 批量检测入口移至 header 图标组(状态提升到 App,ProviderList 经
  props 读取探针结果),不再另起一行
- 批量检测可停止:runId 代次守卫(Tauri invoke 无法真正取消),再次
  点击即清除 waiting/running 徽标、丢弃在途迟到写入、不再拉起排队项
- 弹窗打开自动勾选模型:已保存默认测试模型 → 供应商当前模型
  (claude env.ANTHROPIC_MODEL / codex config.toml model,新增
  currentProviderModelId 与后端同口径)→ 清单首个

前端全量 141 文件 / 1106 测试全绿;typecheck clean

chore: archive per-model-connectivity-test

fix(connectivity-test): shouldShowTestEntry 增判 providerType + 收尾清理

chore(connectivity-test): 勾选 Task 9 / OpenSpec 5.1-5.2 + verify 报告

chore(connectivity-test): 勾选 Task 8 / OpenSpec 4.1-4.3

refactor(connectivity-test): 移除旧 stream_check 可达性探测全链路(保留表结构)

- 删除 6 个文件:commands/stream_check.rs、services/stream_check.rs、
  database/dao/stream_check.rs、hooks/useStreamCheck.ts、lib/api/connectivity-check.ts、
  components/usage/ConnectivityCheckConfigPanel.tsx
- 移除 mod.rs 声明、lib.rs generate_handler! 注册、cleanup_old_stream_check_logs 调用
- 从 SYNC_SKIP_TABLES / SYNC_PRESERVE_TABLES 移除 stream_check_logs(表结构保留在 schema.rs)
- 更新 backup.rs 测试:移除 stream_check_logs fixture 与断言
- 更新 SettingsPage:移除 ConnectivityCheckConfigPanel AccordionItem 与 FlaskConical 导入
- i18n:删除 streamCheck.* 命名空间(zh/en/ja/zh-TW 四语言)
- 更新注释:移除 stream_check 旧引用(forwarder.rs、provider.rs、userAgentPresets.ts、
  connectivity_test.rs)
- 修复 4 个 pre-existing clippy lint(connectivity_test: matches! 宏、unused_unit、
  derive Default)
- 移除 ProviderList.test.tsx 中已失效的 useStreamCheck mock

chore(connectivity-test): 勾选 Task 7 / OpenSpec 2.4 3.1-3.3

feat(connectivity-test): 供应商列表集成(弹窗入口 + 批量探针徽标)

chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini c…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and the tests assert each
product's line-up in order so future drift is caught.

Declare thinking control alongside the capabilities rather than leaving it to
defaults: the 31 DeepSeek entries carry a Tencent-specific compat so selecting
"off" actually disables thinking instead of silently billing reasoning tokens,
the eight Kimi K2.7 Code entries no longer offer an "off" level the model
cannot honour, and kimi-k2.7-code-highspeed is marked image-capable so
tool-returned screenshots are no longer dropped.

Drop minimax-m2.5 from every app: it is gone from Tencent's model tables and
the platform is retiring it.
fix(codex): proxy image generations endpoint (#7036)

Codex's built-in ImageGen calls the legacy OpenAI Images API, but the local
proxy only registered Responses / Chat Completions / Compact / Alpha Search
routes, so `/v1/images/generations` returned an empty 404 under Codex routing.

- Register `/images/generations` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them as a Codex standalone passthrough shared with Alpha Search.
- Generalize the Alpha Search full-URL rewrite into
  `rewrite_codex_standalone_full_url`: derive the sibling
  `/images/generations` from full Responses / Compact / Chat Completions URLs,
  keep an existing Images URL as-is, and fail closed on opaque full URLs.
- Record `usage.input_tokens` / `output_tokens` through the Codex usage parser.

Fixes #5429
Fixes #6745
feat(pricing): add Gemini 3.8 Flash pricing (#7164)

Seeds the built-in pricing row for Gemini 3.8 Flash at 0.75 / 3.75 input/output per million tokens, 0.075 cache read, no cache-write charge, plus a regression test locking all four fields.

Values verified against Google's own pricing page (introductory price through 2026-12-31; list price 1.50 / 7.50 / 0.15 from 2027-01-01) and models.dev.
feat(provider): extend PPIO presets for Pi (#6870)

Add a PPIO provider preset for Pi using the OpenAI-compatible endpoint,
and pin an explicit models URL on the Claude Code PPIO preset so model
discovery resolves against https://api.ppio.com/openai/v1/models instead
of the derived candidates, which all return 404.

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 Flash pricing (#7163)
fix(proxy): enable parallel tool calls for Codex OAuth (#7024)

Codex OAuth requests defaulted parallel_tool_calls to false when the incoming
Anthropic request carried no explicit value, forcing Claude Code into one tool
call per model turn. Default it to true (matching codex-rs), map Anthropic
tool_choice.disable_parallel_tool_use to the inverse Responses value so explicit
serial execution is preserved, and add regression coverage for both directions.

Carries #5722 forward on top of current main.

Fixes #5719

Co-authored-by: 太一 <taiyi@douyu.tv>
feat(pricing): add GPT-6 Astra pricing (#7162)
fix(codex): resolve input_modalities for unknown models in vendor catalog (#6750)

When a model slug is not in the bundled DeepSeek official catalog, the vendor path clones the flagship entry and previously inherited its text-only input_modalities. Resolve modalities for unmatched slugs through the same registry/fail-open logic the non-vendor path uses, so a vision variant such as deepseek-v4-flash-vision-exp is no longer declared text-only. Explicit user inputModalities still win; matched models keep the vendor's declaration verbatim.

Fixes #6725
fix(claude): expose Opus 5 and Sonnet 5 in takeover mode (#5882)

Bump the stable client-facing role aliases that takeover mode writes to
Claude Code: Opus claude-opus-4-8 -> claude-opus-5 and Sonnet
claude-sonnet-4-6 -> claude-sonnet-5, so the /model menu matches the
Claude Desktop default routes and preset defaults that already moved to
Opus 5 / Sonnet 5. Add "opus-5" to the adaptive-thinking classifier so
the Bedrock thinking optimizer, which runs on the client alias before
model mapping, keeps sending adaptive thinking instead of the removed
budget_tokens.

Routing is unaffected: the proxy maps client aliases by role keyword, and
the aliases are rewritten automatically the next time the proxy starts.

Fixes #5876

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 to built-in model pricing table (#6591)

Seed glm-5.3 at $1.4/$4.4 per MTok with $0.26 cache read, matching the
official Z.ai list price (identical to the GLM-5.2 tier):
https://docs.z.ai/guides/overview/pricing

New model id, so seed only; no repair entry and no SCHEMA_VERSION bump.
Existing installs pick the row up on next launch via INSERT OR IGNORE, and
the startup cost backfill re-prices historical glm-5.3 rows recorded at $0.
fix(codex): align OAuth client identity for GPT-6 (#7132)

Requests for gpt-6-astra through the Claude-to-Codex OAuth takeover route advertised Codex 0.144.1, below the model's 0.153.0 minimal_client_version, so ChatGPT's backend rejected them with HTTP 400 "requires a newer version of Codex". Bump the advertised client version to 0.153.4 and share the originator/version constants between generation and model discovery, which previously sent cc-switch's own package version and originator.

Two regression tests pin the generation headers and the model-list request to a version at or above the gpt-6-astra floor.

Refs #7129
fix(codex): disable supports_search_tool for DeepSeek catalog models (#6653)

DeepSeek's Responses API has no tool_search support, but the bundled
official catalog declared supports_search_tool=true for deepseek-v4-pro
and deepseek-v4-flash. Codex gates deferred/namespaced MCP tools on
this flag (search_tool_enabled = supports_search_tool &&
namespace_tools), so MCP tools were hidden behind a tool_search the
gateway cannot serve — no MCP tool could ever be called.

Set the flag to false for both models so Codex lists MCP tools inline.
Hosted web_search is unaffected (gated by provider capabilities, not
this flag) and DeepSeek's server-side web_search still works.

Fixes #6647
fix(codex): move $ref siblings into allOf for Moonshot/Kimi chat upstreams (#6863)

Moonshot's Chat Completions validator (api.moonshot.cn, api.moonshot.ai and the Kimi For Coding endpoint api.kimi.com) rejects a `$ref` that carries sibling keywords. Codex Desktop's built-in tool schemas carry exactly that shape, so every desktop turn routed through the local proxy to Kimi failed with HTTP 400.

When the resolved upstream host is Moonshot/Kimi, each `$ref` with siblings is moved into `allOf` after the Responses -> Chat conversion and the siblings stay in place. The traversal only enters schema-valued keywords and the rewrite is idempotent. Every other provider keeps byte-identical tool schemas, and the Codex -> Anthropic path is left alone because Moonshot's Anthropic-compatible endpoint accepts the original shape.

Fixes #6867

Co-authored-by: Jason <farion1231@gmail.com>
fix(codex): point Zhipu GLM presets at the official Responses endpoint (#6957)

Zhipu documents three separate base URLs per site (Anthropic /api/anthropic,
Chat /api/coding/paas/v4, Responses /api/v1) and warns that the wrong one
cannot use Coding Plan quota. Codex direct-connect speaks Responses, so the
presets must target /api/v1; the 400 in #6944 (`unknown variant custom`)
came from the Chat endpoint's strict legacy gateway.

- Switch the Zhipu GLM / Zhipu GLM en presets to apiFormat openai_responses
  (NativeResponses catalog: shell_command edits, no freeform apply_patch)
…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and the tests assert each
product's line-up in order so future drift is caught.

Declare thinking control alongside the capabilities rather than leaving it to
defaults: the 31 DeepSeek entries carry a Tencent-specific compat so selecting
"off" actually disables thinking instead of silently billing reasoning tokens,
the eight Kimi K2.7 Code entries no longer offer an "off" level the model
cannot honour, and kimi-k2.7-code-highspeed is marked image-capable so
tool-returned screenshots are no longer dropped.

Drop minimax-m2.5 from every app: it is gone from Tencent's model tables and
the platform is retiring it.
fix(codex): proxy image generations endpoint (#7036)

Codex's built-in ImageGen calls the legacy OpenAI Images API, but the local
proxy only registered Responses / Chat Completions / Compact / Alpha Search
routes, so `/v1/images/generations` returned an empty 404 under Codex routing.

- Register `/images/generations` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them as a Codex standalone passthrough shared with Alpha Search.
- Generalize the Alpha Search full-URL rewrite into
  `rewrite_codex_standalone_full_url`: derive the sibling
  `/images/generations` from full Responses / Compact / Chat Completions URLs,
  keep an existing Images URL as-is, and fail closed on opaque full URLs.
- Record `usage.input_tokens` / `output_tokens` through the Codex usage parser.

Fixes #5429
Fixes #6745
feat(pricing): add Gemini 3.8 Flash pricing (#7164)

Seeds the built-in pricing row for Gemini 3.8 Flash at 0.75 / 3.75 input/output per million tokens, 0.075 cache read, no cache-write charge, plus a regression test locking all four fields.

Values verified against Google's own pricing page (introductory price through 2026-12-31; list price 1.50 / 7.50 / 0.15 from 2027-01-01) and models.dev.
feat(provider): extend PPIO presets for Pi (#6870)

Add a PPIO provider preset for Pi using the OpenAI-compatible endpoint,
and pin an explicit models URL on the Claude Code PPIO preset so model
discovery resolves against https://api.ppio.com/openai/v1/models instead
of the derived candidates, which all return 404.

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 Flash pricing (#7163)
fix(proxy): enable parallel tool calls for Codex OAuth (#7024)

Codex OAuth requests defaulted parallel_tool_calls to false when the incoming
Anthropic request carried no explicit value, forcing Claude Code into one tool
call per model turn. Default it to true (matching codex-rs), map Anthropic
tool_choice.disable_parallel_tool_use to the inverse Responses value so explicit
serial execution is preserved, and add regression coverage for both directions.

Carries #5722 forward on top of current main.

Fixes #5719

Co-authored-by: 太一 <taiyi@douyu.tv>
feat(pricing): add GPT-6 Astra pricing (#7162)
fix(codex): resolve input_modalities for unknown models in vendor catalog (#6750)

When a model slug is not in the bundled DeepSeek official catalog, the vendor path clones the flagship entry and previously inherited its text-only input_modalities. Resolve modalities for unmatched slugs through the same registry/fail-open logic the non-vendor path uses, so a vision variant such as deepseek-v4-flash-vision-exp is no longer declared text-only. Explicit user inputModalities still win; matched models keep the vendor's declaration verbatim.

Fixes #6725
fix(claude): expose Opus 5 and Sonnet 5 in takeover mode (#5882)

Bump the stable client-facing role aliases that takeover mode writes to
Claude Code: Opus claude-opus-4-8 -> claude-opus-5 and Sonnet
claude-sonnet-4-6 -> claude-sonnet-5, so the /model menu matches the
Claude Desktop default routes and preset defaults that already moved to
Opus 5 / Sonnet 5. Add "opus-5" to the adaptive-thinking classifier so
the Bedrock thinking optimizer, which runs on the client alias before
model mapping, keeps sending adaptive thinking instead of the removed
budget_tokens.

Routing is unaffected: the proxy maps client aliases by role keyword, and
the aliases are rewritten automatically the next time the proxy starts.

Fixes #5876

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 to built-in model pricing table (#6591)

Seed glm-5.3 at $1.4/$4.4 per MTok with $0.26 cache read, matching the
official Z.ai list price (identical to the GLM-5.2 tier):
https://docs.z.ai/guides/overview/pricing

New model id, so seed only; no repair entry and no SCHEMA_VERSION bump.
Existing installs pick the row up on next launch via INSERT OR IGNORE, and
the startup cost backfill re-prices historical glm-5.3 rows recorded at $0.
fix(codex): align OAuth client identity for GPT-6 (#7132)

Requests for gpt-6-astra through the Claude-to-Codex OAuth takeover route advertised Codex 0.144.1, below the model's 0.153.0 minimal_client_version, so ChatGPT's backend rejected them with HTTP 400 "requires a newer version of Codex". Bump the advertised client version to 0.153.4 and share the originator/version constants between generation and model discovery, which previously sent cc-switch's own package version and originator.

Two regression tests pin the generation headers and the model-list request to a version at or above the gpt-6-astra floor.

Refs #7129
fix(codex): disable supports_search_tool for DeepSeek catalog models (#6653)

DeepSeek's Responses API has no tool_search support, but the bundled
official catalog declared supports_search_tool=true for deepseek-v4-pro
and deepseek-v4-flash. Codex gates deferred/namespaced MCP tools on
this flag (search_tool_enabled = supports_search_tool &&
namespace_tools), so MCP tools were hidden behind a tool_search the
gateway cannot serve — no MCP tool could ever be called.

Set the flag to false for both models so Codex lists MCP tools inline.
Hosted web_search is unaffected (gated by provider capabilities, not
this flag) and DeepSeek's server-side web_search still works.

Fixes #6647
fix(codex): move $ref siblings into allOf for Moonshot/Kimi chat upstreams (#6863)

Moonshot's Chat Completions validator (api.moonshot.cn, api.moonshot.ai and the Kimi For Coding endpoint api.kimi.com) rejects a `$ref` that carries sibling keywords. Codex Desktop's built-in tool schemas carry exactly that shape, so every desktop turn rou…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and the tests assert each
product's line-up in order so future drift is caught.

Declare thinking control alongside the capabilities rather than leaving it to
defaults: the 31 DeepSeek entries carry a Tencent-specific compat so selecting
"off" actually disables thinking instead of silently billing reasoning tokens,
the eight Kimi K2.7 Code entries no longer offer an "off" level the model
cannot honour, and kimi-k2.7-code-highspeed is marked image-capable so
tool-returned screenshots are no longer dropped.

Drop minimax-m2.5 from every app: it is gone from Tencent's model tables and
the platform is retiring it.
fix(codex): proxy image generations endpoint (#7036)

Codex's built-in ImageGen calls the legacy OpenAI Images API, but the local
proxy only registered Responses / Chat Completions / Compact / Alpha Search
routes, so `/v1/images/generations` returned an empty 404 under Codex routing.

- Register `/images/generations` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them as a Codex standalone passthrough shared with Alpha Search.
- Generalize the Alpha Search full-URL rewrite into
  `rewrite_codex_standalone_full_url`: derive the sibling
  `/images/generations` from full Responses / Compact / Chat Completions URLs,
  keep an existing Images URL as-is, and fail closed on opaque full URLs.
- Record `usage.input_tokens` / `output_tokens` through the Codex usage parser.

Fixes #5429
Fixes #6745
feat(pricing): add Gemini 3.8 Flash pricing (#7164)

Seeds the built-in pricing row for Gemini 3.8 Flash at 0.75 / 3.75 input/output per million tokens, 0.075 cache read, no cache-write charge, plus a regression test locking all four fields.

Values verified against Google's own pricing page (introductory price through 2026-12-31; list price 1.50 / 7.50 / 0.15 from 2027-01-01) and models.dev.
feat(provider): extend PPIO presets for Pi (#6870)

Add a PPIO provider preset for Pi using the OpenAI-compatible endpoint,
and pin an explicit models URL on the Claude Code PPIO preset so model
discovery resolves against https://api.ppio.com/openai/v1/models instead
of the derived candidates, which all return 404.

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 Flash pricing (#7163)
fix(proxy): enable parallel tool calls for Codex OAuth (#7024)

Codex OAuth requests defaulted parallel_tool_calls to false when the incoming
Anthropic request carried no explicit value, forcing Claude Code into one tool
call per model turn. Default it to true (matching codex-rs), map Anthropic
tool_choice.disable_parallel_tool_use to the inverse Responses value so explicit
serial execution is preserved, and add regression coverage for both directions.

Carries #5722 forward on top of current main.

Fixes #5719

Co-authored-by: 太一 <taiyi@douyu.tv>
feat(pricing): add GPT-6 Astra pricing (#7162)
fix(codex): resolve input_modalities for unknown models in vendor catalog (#6750)

When a model slug is not in the bundled DeepSeek official catalog, the vendor path clones the flagship entry and previously inherited its text-only input_modalities. Resolve modalities for unmatched slugs through the same registry/fail-open logic the non-vendor path uses, so a vision variant such as deepseek-v4-flash-vision-exp is no longer declared text-only. Explicit user inputModalities still win; matched models keep the vendor's declaration verbatim.

Fixes #6725
fix(claude): expose Opus 5 and Sonnet 5 in takeover mode (#5882)

Bump the stable client-facing role aliases that takeover mode writes to
Claude Code: Opus claude-opus-4-8 -> claude-opus-5 and Sonnet
claude-sonnet-4-6 -> claude-sonnet-5, so the /model menu matches the
Claude Desktop default routes and preset defaults that already moved to
Opus 5 / Sonnet 5. Add "opus-5" to the adaptive-thinking classifier so
the Bedrock thinking optimizer, which runs on the client alias before
model mapping, keeps sending adaptive thinking instead of the removed
budget_tokens.

Routing is unaffected: the proxy maps client aliases by role keyword, and
the aliases are rewritten automatically the next time the proxy starts.

Fixes #5876

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 to built-in model pricing table (#6591)

Seed glm-5.3 at $1.4/$4.4 per MTok with $0.26 cache read, matching the
official Z.ai list price (identical to the GLM-5.2 tier):
https://docs.z.ai/guides/overview/pricing

New model id, so seed only; no repair entry and no SCHEMA_VERSION bump.
Existing installs pick the row up on next launch via INSERT OR IGNORE, and
the startup cost backfill re-prices historical glm-5.3 rows recorded at $0.
fix(codex): align OAuth client identity for GPT-6 (#7132)

Requests for gpt-6-astra through the Claude-to-Codex OAuth takeover route advertised Codex 0.144.1, below the model's 0.153.0 minimal_client_version, so ChatGPT's backend rejected them with HTTP 400 "requires a newer version of Codex". Bump the advertised client version to 0.153.4 and share the originator/version constants between generation and model discovery, which previously sent cc-switch's own package version and originator.

Two regression tests pin the generation headers and the model-list request to a version at or above the gpt-6-astra floor.

Refs #7129
fix(codex): disable supports_search_tool for DeepSeek catalog models (#6653)

DeepSeek's Responses API has no tool_search support, but the bundled
official catalog declared supports_search_tool=true for deepseek-v4-pro
and deepseek-v4-flash. Codex gates deferred/namespaced MCP tools on
this flag (search_tool_enabled = supports_search_tool &&
namespace_tools), so MCP tools were hidden behind a tool_search the
gateway cannot serve — no MCP tool could ever be called.

Set the flag to false for both models so Codex lists MCP tools inline.
Hosted web_search is unaffected (gated by provider capabilit…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and the tests assert each
product's line-up in order so future drift is caught.

Declare thinking control alongside the capabilities rather than leaving it to
defaults: the 31 DeepSeek entries carry a Tencent-specific compat so selecting
"off" actually disables thinking instead of silently billing reasoning tokens,
the eight Kimi K2.7 Code entries no longer offer an "off" level the model
cannot honour, and kimi-k2.7-code-highspeed is marked image-capable so
tool-returned screenshots are no longer dropped.

Drop minimax-m2.5 from every app: it is gone from Tencent's model tables and
the platform is retiring it.
fix(codex): proxy image generations endpoint (#7036)

Codex's built-in ImageGen calls the legacy OpenAI Images API, but the local
proxy only registered Responses / Chat Completions / Compact / Alpha Search
routes, so `/v1/images/generations` returned an empty 404 under Codex routing.

- Register `/images/generations` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them as a Codex standalone passthrough shared with Alpha Search.
- Generalize the Alpha Search full-URL rewrite into
  `rewrite_codex_standalone_full_url`: derive the sibling
  `/images/generations` from full Responses / Compact / Chat Completions URLs,
  keep an existing Images URL as-is, and fail closed on opaque full URLs.
- Record `usage.input_tokens` / `output_tokens` through the Codex usage parser.

Fixes #5429
Fixes #6745
feat(pricing): add Gemini 3.8 Flash pricing (#7164)

Seeds the built-in pricing row for Gemini 3.8 Flash at 0.75 / 3.75 input/output per million tokens, 0.075 cache read, no cache-write charge, plus a regression test locking all four fields.

Values verified against Google's own pricing page (introductory price through 2026-12-31; list price 1.50 / 7.50 / 0.15 from 2027-01-01) and models.dev.
feat(provider): extend PPIO presets for Pi (#6870)

Add a PPIO provider preset for Pi using the OpenAI-compatible endpoint,
and pin an explicit models URL on the Claude Code PPIO preset so model
discovery resolves against https://api.ppio.com/openai/v1/models instead
of the derived candidates, which all return 404.

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 Flash pricing (#7163)
fix(proxy): enable parallel tool calls for Codex OAuth (#7024)

Codex OAuth requests defaulted parallel_tool_calls to false when the incoming
Anthropic request carried no explicit value, forcing Claude Code into one tool
call per model turn. Default it to true (matching codex-rs), map Anthropic
tool_choice.disable_parallel_tool_use to the inverse Responses value so explicit
serial execution is preserved, and add regression coverage for both directions.

Carries #5722 forward on top of current main.

Fixes #5719

Co-authored-by: 太一 <taiyi@douyu.tv>
feat(pricing): add GPT-6 Astra pricing (#7162)
fix(codex): resolve input_modalities for unknown models in vendor catalog (#6750)

When a model slug is not in the bundled DeepSeek official catalog, the vendor path clones the flagship entry and previously inherited its text-only input_modalities. Resolve modalities for unmatched slugs through the same registry/fail-open logic the non-vendor path uses, so a vision variant such as deepseek-v4-flash-vision-exp is no longer declared text-only. Explicit user inputModalities still win; matched models keep the vendor's declaration verbatim.

Fixes #6725
fix(claude): expose Opus 5 and Sonnet 5 in takeover mode (#5882)

Bump the stable client-facing role aliases that takeover mode writes to
Claude Code: Opus claude-opus-4-8 -> claude-opus-5 and Sonnet
claude-sonnet-4-6 -> claude-sonnet-5, so the /model menu matches the
Claude Desktop default routes and preset defaults that already moved to
Opus 5 / Sonnet 5. Add "opus-5" to the adaptive-thinking classifier so
the Bedrock thinking optimizer, which runs on the client alias before
model mapping, keeps sending adaptive thinking instead of the removed
budget_tokens.

Routing is unaffected: the proxy maps client aliases by role keyword, and
the aliases are rewritten automatically the next time the proxy starts.

Fixes #5876

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 to built-in model pricing table (#6591)

Seed glm-5.3 at $1.4/$4.4 per MTok with $0.26 cache read, matching the
official Z.ai list price (identical to the GLM-5.2 tier):
https://docs.z.ai/guides/overview/pricing

New model id, so seed only; no repair entry and no SCHEMA_VERSION bump.
Existing installs pick the row up on next launch via INSERT OR IGNORE, and
the startup cost backfill re-prices historical glm-5.3 rows recorded at $0.
fix(codex): align OAuth client identity for GPT-6 (#7132)

Requests for gpt-6-astra through the Claude-to-Codex OAuth takeover route advertised Codex 0.144.1, below the model's 0.153.0 minimal_client_version, so ChatGPT's backend rejected them with HTTP 400 "requires a newer version of Codex". Bump the advertised client version to 0.153.4 and share the originator/version constants between generation and model discovery, which previously sent cc-switch's own package version and originator.

Two regression tests pin the generation headers and the model-list request to a version at or above the gpt-6-astra floor.

Refs #7129
fix(codex): disable supports_search_tool for DeepSeek catalog models (#6653)

DeepSeek's Responses API has no tool_search support, but the bundled
official catalog declared supports_search_tool=true for deepseek-v4-pro
and deepseek-v4-flash. Codex gates…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
…iders at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and the tests assert each
product's line-up in order so future drift is caught.

Declare thinking control alongside the capabilities rather than leaving it to
defaults: the 31 DeepSeek entries carry a Tencent-specific compat so selecting
"off" actually disables thinking instead of silently billing reasoning tokens,
the eight Kimi K2.7 Code entries no longer offer an "off" level the model
cannot honour, and kimi-k2.7-code-highspeed is marked image-capable so
tool-returned screenshots are no longer dropped.

Drop minimax-m2.5 from every app: it is gone from Tencent's model tables and
the platform is retiring it.
fix(codex): proxy image generations endpoint (#7036)

Codex's built-in ImageGen calls the legacy OpenAI Images API, but the local
proxy only registered Responses / Chat Completions / Compact / Alpha Search
routes, so `/v1/images/generations` returned an empty 404 under Codex routing.

- Register `/images/generations` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them as a Codex standalone passthrough shared with Alpha Search.
- Generalize the Alpha Search full-URL rewrite into
  `rewrite_codex_standalone_full_url`: derive the sibling
  `/images/generations` from full Responses / Compact / Chat Completions URLs,
  keep an existing Images URL as-is, and fail closed on opaque full URLs.
- Record `usage.input_tokens` / `output_tokens` through the Codex usage parser.

Fixes #5429
Fixes #6745
feat(pricing): add Gemini 3.8 Flash pricing (#7164)

Seeds the built-in pricing row for Gemini 3.8 Flash at 0.75 / 3.75 input/output per million tokens, 0.075 cache read, no cache-write charge, plus a regression test locking all four fields.

Values verified against Google's own pricing page (introductory price through 2026-12-31; list price 1.50 / 7.50 / 0.15 from 2027-01-01) and models.dev.
feat(provider): extend PPIO presets for Pi (#6870)

Add a PPIO provider preset for Pi using the OpenAI-compatible endpoint,
and pin an explicit models URL on the Claude Code PPIO preset so model
discovery resolves against https://api.ppio.com/openai/v1/models instead
of the derived candidates, which all return 404.

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 Flash pricing (#7163)
fix(proxy): enable parallel tool calls for Codex OAuth (#7024)

Codex OAuth requests defaulted parallel_tool_calls to false when the incoming
Anthropic request carried no explicit value, forcing Claude Code into one tool
call per model turn. Default it to true (matching codex-rs), map Anthropic
tool_choice.disable_parallel_tool_use to the inverse Responses value so explicit
serial execution is preserved, and add regression coverage for both directions.

Carries #5722 forward on top of current main.

Fixes #5719

Co-authored-by: 太一 <taiyi@douyu.tv>
feat(pricing): add GPT-6 Astra pricing (#7162)
fix(codex): resolve input_modalities for unknown models in vendor catalog (#6750)

When a model slug is not in the bundled DeepSeek official catalog, the vendor path clones the flagship entry and previously inherited its text-only input_modalities. Resolve modalities for unmatched slugs through the same registry/fail-open logic the non-vendor path uses, so a vision variant such as deepseek-v4-flash-vision-exp is no longer declared text-only. Explicit user inputModalities still win; matched models keep the vendor's declaration verbatim.

Fixes #6725
fix(claude): expose Opus 5 and Sonnet 5 in takeover mode (#5882)

Bump the stable client-facing role aliases that takeover mode writes to
Claude Code: Opus claude-opus-4-8 -> claude-opus-5 and Sonnet
claude-sonnet-4-6 -> claude-sonnet-5, so the /model menu matches the
Claude Desktop default routes and preset defaults that already moved to
Opus 5 / Sonnet 5. Add "opus-5" to the adaptive-thinking classifier so
the Bedrock thinking optimizer, which runs on the client alias before
model mapping, keeps sending adaptive thinking instead of the removed
budget_tokens.

Routing is unaffected: the proxy maps client aliases by role keyword, and
the aliases are rewritten automatically the next time the proxy starts.

Fixes #5876

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 to built-in model pricing table (#6591)

Seed glm-5.3 at $1.4/$4.4 per MTok with $0.26 cache read, matching the
official Z.ai list price (identical to the GLM-5.2 tier):
https://docs.z.ai/guides/overview/pricing

New model id, so seed only; no repair entry and no SCHEMA_VERSION bump.
Existing installs pick the row up on next launch via INSERT OR IGNORE, and
the startup cost backfill re-prices historical glm-5.3 rows recorded at $0.
fix(codex): align OAuth client identity for…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and the tests assert each
product's line-up in order so future drift is caught.

Declare thinking control alongside the capabilities rather than leaving it to
defaults: the 31 DeepSeek entries carry a Tencent-specific compat so selecting
"off" actually disables thinking instead of silently billing reasoning tokens,
the eight Kimi K2.7 Code entries no longer offer an "off" level the model
cannot honour, and kimi-k2.7-code-highspeed is marked image-capable so
tool-returned screenshots are no longer dropped.

Drop minimax-m2.5 from every app: it is gone from Tencent's model tables and
the platform is retiring it.
fix(codex): proxy image generations endpoint (#7036)

Codex's built-in ImageGen calls the legacy OpenAI Images API, but the local
proxy only registered Responses / Chat Completions / Compact / Alpha Search
routes, so `/v1/images/generations` returned an empty 404 under Codex routing.

- Register `/images/generations` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them as a Codex standalone passthrough shared with Alpha Search.
- Generalize the Alpha Search full-URL rewrite into
  `rewrite_codex_standalone_full_url`: derive the sibling
  `/images/generations` from full Responses / Compact / Chat Completions URLs,
  keep an existing Images URL as-is, and fail closed on opaque full URLs.
- Record `usage.input_tokens` / `output_tokens` through the Codex usage parser.

Fixes #5429
Fixes #6745
feat(pricing): add Gemini 3.8 Flash pricing (#7164)

Seeds the built-in pricing row for Gemini 3.8 Flash at 0.75 / 3.75 input/output per million tokens, 0.075 cache read, no cache-write charge, plus a regression test locking all four fields.

Values verified against Google's own pricing page (introductory price through 2026-12-31; list price 1.50 / 7.50 / 0.15 from 2027-01-01) and models.dev.
feat(provider): extend PPIO presets for Pi (#6870)

Add a PPIO provider…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and the tests assert each
product's line-up in order so future drift is caught.

Declare thinking control alongside the capabilities rather than leaving it to
defaults: the 31 DeepSeek entries carry a Tencent-specific compat so selecting
"off" actually disables thinking instead of silently billing reasoning tokens,
the eight Kimi K2.7 Code entries no longer offer an "off" level the model
cannot honour, and kimi-k2.7-code-highspeed is marked image-capable so
tool-returned screenshots are no longer dropped.

Drop minimax-m2.5 from every app: it is gone from Tencent's model tables and
the platform is retiring it.
fix(codex): proxy image generations endpoint (#7036)

Codex's built-in ImageGen calls the legacy OpenAI Images API, but the local
proxy only registered Responses / Chat Completions / Compact / Alpha Search
routes, so `/v1/images/generations` returned an empty 404 under Codex routing.

- Register `/images/generations` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them as a Codex standalone passthrough shared with Alpha Search.
- Generalize the Alpha Search full-URL rewrite into
  `rewrite_codex_standalone_full_url`: derive the sibling
  `/images/generations` from full Responses / Compact / Chat Completions URLs,
  keep an existing Images URL as-is, and fail closed on opaque full URLs.
- Record `usage.input_tokens` / `output_tokens` through the Codex us…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and the tests assert each
product's line-up in order so future drift is caught.

Declare thinking control alongside the capabilities rather than leaving it to
defaults: the 31 DeepSeek entries carry a Tencent-specific compat so selecting
"off" actually disables thinking instead of silently billing reasoning tokens,
the eight Kimi K2.7 Code entries no longer offer an "off" level the model
cannot honour, and kimi-k2.7-code-highspeed is marked image-capable so
tool-returned screenshots are no longer dropped.

Drop minimax-m2.5 from every app: it is gone from Tencent's model tables and
the platform is retiring it.
fix(codex): proxy image generations endpoint (#7036)

Codex's built-in ImageGen calls the legacy OpenAI Images API, but the local
proxy only registered Responses / Chat Completions / Compact / Alpha Search
routes, so `/v1/images/generations` returned an empty 404 under Codex routing.

- Register `/images/generations` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them as a Codex standalone passthrough shared with Alpha Search.
- Generalize the Alpha Search full-URL rewrite into
  `rewrite_codex_standal…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
- 删除 6 个文件:commands/stream_check.rs、services/stream_check.rs、
  database/dao/stream_check.rs、hooks/useStreamCheck.ts、lib/api/connectivity-check.ts、
  components/usage/ConnectivityCheckConfigPanel.tsx
- 移除 mod.rs 声明、lib.rs generate_handler! 注册、cleanup_old_stream_check_logs 调用
- 从 SYNC_SKIP_TABLES / SYNC_PRESERVE_TABLES 移除 stream_check_logs(表结构保留在 schema.rs)
- 更新 backup.rs 测试:移除 stream_check_logs fixture 与断言
- 更新 SettingsPage:移除 ConnectivityCheckConfigPanel AccordionItem 与 FlaskConical 导入
- i18n:删除 streamCheck.* 命名空间(zh/en/ja/zh-TW 四语言)
- 更新注释:移除 stream_check 旧引用(forwarder.rs、provider.rs、userAgentPresets.ts、
  connectivity_test.rs)
- 修复 4 个 pre-existing clippy lint(connectivity_test: matches! 宏、unused_unit、
  derive Default)
- 移除 ProviderList.test.tsx 中已失效的 useStreamCheck mock

chore(connectivity-test): 勾选 Task 7 / OpenSpec 2.4 3.1-3.3

feat(connectivity-test): 供应商列表集成(弹窗入口 + 批量探针徽标)

chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
chore(connectivity-test): 勾选 Task 9 / OpenSpec 5.1-5.2 + verify 报告

chore(connectivity-test): 勾选 Task 8 / OpenSpec 4.1-4.3

refactor(connectivity-test): 移除旧 stream_check 可达性探测全链路(保留表结构)

- 删除 6 个文件:commands/stream_check.rs、services/stream_check.rs、
  database/dao/stream_check.rs、hooks/useStreamCheck.ts、lib/api/connectivity-check.ts、
  components/usage/ConnectivityCheckConfigPanel.tsx
- 移除 mod.rs 声明、lib.rs generate_handler! 注册、cleanup_old_stream_check_logs 调用
- 从 SYNC_SKIP_TABLES / SYNC_PRESERVE_TABLES 移除 stream_check_logs(表结构保留在 schema.rs)
- 更新 backup.rs 测试:移除 stream_check_logs fixture 与断言
- 更新 SettingsPage:移除 ConnectivityCheckConfigPanel AccordionItem 与 FlaskConical 导入
- i18n:删除 streamCheck.* 命名空间(zh/en/ja/zh-TW 四语言)
- 更新注释:移除 stream_check 旧引用(forwarder.rs、provider.rs、userAgentPresets.ts、
  connectivity_test.rs)
- 修复 4 个 pre-existing clippy lint(connectivity_test: matches! 宏、unused_unit、
  derive Default)
- 移除 ProviderList.test.tsx 中已失效的 useStreamCheck mock

chore(connectivity-test): 勾选 Task 7 / OpenSpec 2.4 3.1-3.3

feat(connectivity-test): 供应商列表集成(弹窗入口 + 批量探针徽标)

chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-yo…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
- 弹窗抬至 alert 层(z-[60]):打开连通性测试时 header 切换栏正确变灰,
  详情子弹窗同步抬层(同 z + 更晚挂载 portal 在上)
- 批量检测入口移至 header 图标组(状态提升到 App,ProviderList 经
  props 读取探针结果),不再另起一行
- 批量检测可停止:runId 代次守卫(Tauri invoke 无法真正取消),再次
  点击即清除 waiting/running 徽标、丢弃在途迟到写入、不再拉起排队项
- 弹窗打开自动勾选模型:已保存默认测试模型 → 供应商当前模型
  (claude env.ANTHROPIC_MODEL / codex config.toml model,新增
  currentProviderModelId 与后端同口径)→ 清单首个

前端全量 141 文件 / 1106 测试全绿;typecheck clean

chore: archive per-model-connectivity-test

fix(connectivity-test): shouldShowTestEntry 增判 providerType + 收尾清理

chore(connectivity-test): 勾选 Task 9 / OpenSpec 5.1-5.2 + verify 报告

chore(connectivity-test): 勾选 Task 8 / OpenSpec 4.1-4.3

refactor(connectivity-test): 移除旧 stream_check 可达性探测全链路(保留表结构)

- 删除 6 个文件:commands/stream_check.rs、services/stream_check.rs、
  database/dao/stream_check.rs、hooks/useStreamCheck.ts、lib/api/connectivity-check.ts、
  components/usage/ConnectivityCheckConfigPanel.tsx
- 移除 mod.rs 声明、lib.rs generate_handler! 注册、cleanup_old_stream_check_logs 调用
- 从 SYNC_SKIP_TABLES / SYNC_PRESERVE_TABLES 移除 stream_check_logs(表结构保留在 schema.rs)
- 更新 backup.rs 测试:移除 stream_check_logs fixture 与断言
- 更新 SettingsPage:移除 ConnectivityCheckConfigPanel AccordionItem 与 FlaskConical 导入
- i18n:删除 streamCheck.* 命名空间(zh/en/ja/zh-TW 四语言)
- 更新注释:移除 stream_check 旧引用(forwarder.rs、provider.rs、userAgentPresets.ts、
  connectivity_test.rs)
- 修复 4 个 pre-existing clippy lint(connectivity_test: matches! 宏、unused_unit、
  derive Default)
- 移除 ProviderList.test.tsx 中已失效的 useStreamCheck mock

chore(connectivity-test): 勾选 Task 7 / OpenSpec 2.4 3.1-3.3

feat(connectivity-test): 供应商列表集成(弹窗入口 + 批量探针徽标)

chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude De…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
- Rust Provider/UniversalProvider 新增 website_url_2(serde websiteUrl2,skip none)
- SQLite providers 表新增 website_url_2 列(schema v18→v19 幂等迁移 + create_tables 补列 + 旧 config.json 迁移)
- DAO SELECT/INSERT/UPDATE/replace 全链路带出新列
- 表单共享 BasicFormFields + 统一供应商表单第二输入框;zod 同第一链接规则校验与回填
- ProviderCard 主页双链接横排、两空格分隔、逐链接 openExternal 打开默认浏览器
- 搜索匹配、复制供应商、统一供应商生成 claude/codex/gemini 随行透传
- gemini_auth packycode 认证检测纳入 website_url_2;ClaudeDesktop/GrokBuild 预设切换清空第二链接;卡片同值链接去重
- 4 语言 i18n + 前端/前端单测覆盖

Comet change dual-website-links-homepage(19/19 验收通过,归档)

feat(connectivity-test): 拉取模型列表改为「搜索添加」选择器交互

- 新增共享组件 SearchableModelMultiPicker:搜索过滤 + 多选 + 已在列表标记 + 已选计数
- ConnectivityTestDialog 获取模型列表后不再全量追加合并去重,改为会话内缓存完整列表并自动弹出一次选择器;用户搜索挑选后添加进表格并自动勾选
- 重新拉取只刷新缓存,已添加进表格的行保留;拉取结果仍仅会话内有效,不写回供应商配置
- 四语言 i18n 新增 searchAdd / addToTest / alreadyInList / selectedCount
- 同步更新 ConnectivityTestDialog 测试并新增 SearchableModelMultiPicker 测试

feat(connectivity-test): 探测目标按供应商 API 格式对齐真实转发链路

- build_claude 按 get_claude_api_format 分派端点与请求体:openai_chat → /v1/chat/completions(复用 transform_claude_request_for_api_format,流式注入 stream_options.include_usage)、openai_responses → /v1/responses、gemini_native → /v1beta/models/{model}:{method}(resolve_gemini_native_url)、anthropic 保持 /v1/messages 并补 anthropic-version/beta 头
- build_codex 按 should_convert_codex_responses_to_chat/anthropic 分派:chat → /chat/completions(responses_to_chat_completions_with_reasoning)、anthropic → /v1/messages(responses_request_to_anthropic,含 max_output_tokens 注入与 [1m] 剥离 + context-1m beta 头)
- 鉴权改用 ClaudeAdapter/CodexAdapter 的 extract_auth + get_auth_headers(与真实出站请求同源:AUTH_TOKEN→Bearer、ANTHROPIC_API_KEY→x-api-key、gemini→x-goog-api-key、GeminiCli OAuth→Bearer、OpenRouter→Bearer、Codex 按 apiKeyField)
- 转换前剥离 [1m] 标记;meta.isFullUrl 或 base 已是完整端点时直接使用 base_url;自定义 body 浅合并移至转换后的最终协议体
- 新增 18 个目标构造断言单测;Comet 归档产物同步提交

chore: archive connectivity-test-optimizations

feat(connectivity-test): 单一模型表格 + 实时拉取模型 + 勾选即测试范围 + 新默认提示词

- 弹窗合并为单一模型表格:打开即全部模型行(初始待测试)、行勾选 + 表头全选(含半选态)、5 格统计卡,移除上方独立选择区
- 获取模型列表:凭据提取抽为共享模块 src/utils/providerCredentials.ts(ModelQuickSwitchDialog 同步改引用),复用 fetch_models_for_config 拉取 /v1/models,按 ID 追加合并去重、仅会话内有效
- 默认提示词改为「你好,你可以帮我做什么事情」:前后端常量同步,merge 差异判断基准跟随
- 勾选即测试范围:后端 connectivity_test_provider_models 新增 modelIds 参数(None/空白回退静态清单),前端逐模型独立 invoke 并行 + 行级流式更新
- 一轮测完自动勾选失败项,全部成功保持原勾选

fix(connectivity-test): UI 四项修复

- 弹窗抬至 alert 层(z-[60]):打开连通性测试时 header 切换栏正确变灰,
  详情子弹窗同步抬层(同 z + 更晚挂载 portal 在上)
- 批量检测入口移至 header 图标组(状态提升到 App,ProviderList 经
  props 读取探针结果),不再另起一行
- 批量检测可停止:runId 代次守卫(Tauri invoke 无法真正取消),再次
  点击即清除 waiting/running 徽标、丢弃在途迟到写入、不再拉起排队项
- 弹窗打开自动勾选模型:已保存默认测试模型 → 供应商当前模型
  (claude env.ANTHROPIC_MODEL / codex config.toml model,新增
  currentProviderModelId 与后端同口径)→ 清单首个

前端全量 141 文件 / 1106 测试全绿;typecheck clean

chore: archive per-model-connectivity-test

fix(connectivity-test): shouldShowTestEntry 增判 providerType + 收尾清理

chore(connectivity-test): 勾选 Task 9 / OpenSpec 5.1-5.2 + verify 报告

chore(connectivity-test): 勾选 Task 8 / OpenSpec 4.1-4.3

refactor(connectivity-test): 移除旧 stream_check 可达性探测全链路(保留表结构)

- 删除 6 个文件:commands/stream_check.rs、services/stream_check.rs、
  database/dao/stream_check.rs、hooks/useStreamCheck.ts、lib/api/connectivity-check.ts、
  components/usage/ConnectivityCheckConfigPanel.tsx
- 移除 mod.rs 声明、lib.rs generate_handler! 注册、cleanup_old_stream_check_logs 调用
- 从 SYNC_SKIP_TABLES / SYNC_PRESERVE_TABLES 移除 stream_check_logs(表结构保留在 schema.rs)
- 更新 backup.rs 测试:移除 stream_check_logs fixture 与断言
- 更新 SettingsPage:移除 ConnectivityCheckConfigPanel AccordionItem 与 FlaskConical 导入
- i18n:删除 streamCheck.* 命名空间(zh/en/ja/zh-TW 四语言)
- 更新注释:移除 stream_check 旧引用(forwarder.rs、provider.rs、userAgentPresets.ts、
  connectivity_test.rs)
- 修复 4 个 pre-existing clippy lint(connectivity_test: matches! 宏、unused_unit、
  derive Default)
- 移除 ProviderList.test.tsx 中已失效的 useStreamCheck mock

chore(connectivity-test): 勾选 Task 7 / OpenSpec 2.4 3.1-3.3

feat(connectivity-test): 供应商列表集成(弹窗入口 + 批量探针徽标)

chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order tes…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
- tray.rs 新增 show_main_window/hide_main_window/toggle_main_window 助手:
  显示语义与"打开主界面"一致,隐藏与"关闭到托盘"平台处理一致
  (Windows set_skip_taskbar、macOS apply_tray_policy)
- lib.rs 托盘 show_menu_on_left_click 改为 false,左键 Click(Left, Up)
  触发切换(过滤 Down/Up 双事件),右键仍弹菜单;Enter/Click 用量刷新保持
- show_main 菜单事件与 macOS Reopen 复用 show_main_window

docs(readme): 更新日志补充连通性测试、双官网链接与托盘左键切换

feat(model-quick-switch): 获取模型列表成功后自动展开选择器

SearchableModelPicker 增加可选 open/onOpenChange 受控展开(不传保持内部自管,
表单等既有用法不受影响);快捷切换弹窗拉取成功且列表非空时直接展开,关闭弹窗复位。

chore(native): archive fix-quick-switch-1m

chore(comet): 记录 fix-quick-switch-1m 验收通过与归档状态

fix(model-quick-switch): 补齐 Haiku 显示名同步,支持不选模型仅翻转 1M

- 显示名同步映射补上 ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME(沿用空/旧base/旧base[1M] 覆盖、自定义保留语义)
- 新增 setClaudeOneMInSettings:仅原地翻转 1M 标记,不改各角色模型 base 与显示名
- 弹窗打开时 1M 开关按供应商当前状态初始化,避免已开 1M 被静默剥离
- claude 未选模型且 1M 状态有变化时允许保存;无可应用变化时保存禁用
- 补单测与四语言 toast 文案

feat(provider-website-links): 供应商支持第二个官网链接,主页横排双链接

- Rust Provider/UniversalProvider 新增 website_url_2(serde websiteUrl2,skip none)
- SQLite providers 表新增 website_url_2 列(schema v18→v19 幂等迁移 + create_tables 补列 + 旧 config.json 迁移)
- DAO SELECT/INSERT/UPDATE/replace 全链路带出新列
- 表单共享 BasicFormFields + 统一供应商表单第二输入框;zod 同第一链接规则校验与回填
- ProviderCard 主页双链接横排、两空格分隔、逐链接 openExternal 打开默认浏览器
- 搜索匹配、复制供应商、统一供应商生成 claude/codex/gemini 随行透传
- gemini_auth packycode 认证检测纳入 website_url_2;ClaudeDesktop/GrokBuild 预设切换清空第二链接;卡片同值链接去重
- 4 语言 i18n + 前端/前端单测覆盖

Comet change dual-website-links-homepage(19/19 验收通过,归档)

feat(connectivity-test): 拉取模型列表改为「搜索添加」选择器交互

- 新增共享组件 SearchableModelMultiPicker:搜索过滤 + 多选 + 已在列表标记 + 已选计数
- ConnectivityTestDialog 获取模型列表后不再全量追加合并去重,改为会话内缓存完整列表并自动弹出一次选择器;用户搜索挑选后添加进表格并自动勾选
- 重新拉取只刷新缓存,已添加进表格的行保留;拉取结果仍仅会话内有效,不写回供应商配置
- 四语言 i18n 新增 searchAdd / addToTest / alreadyInList / selectedCount
- 同步更新 ConnectivityTestDialog 测试并新增 SearchableModelMultiPicker 测试

feat(connectivity-test): 探测目标按供应商 API 格式对齐真实转发链路

- build_claude 按 get_claude_api_format 分派端点与请求体:openai_chat → /v1/chat/completions(复用 transform_claude_request_for_api_format,流式注入 stream_options.include_usage)、openai_responses → /v1/responses、gemini_native → /v1beta/models/{model}:{method}(resolve_gemini_native_url)、anthropic 保持 /v1/messages 并补 anthropic-version/beta 头
- build_codex 按 should_convert_codex_responses_to_chat/anthropic 分派:chat → /chat/completions(responses_to_chat_completions_with_reasoning)、anthropic → /v1/messages(responses_request_to_anthropic,含 max_output_tokens 注入与 [1m] 剥离 + context-1m beta 头)
- 鉴权改用 ClaudeAdapter/CodexAdapter 的 extract_auth + get_auth_headers(与真实出站请求同源:AUTH_TOKEN→Bearer、ANTHROPIC_API_KEY→x-api-key、gemini→x-goog-api-key、GeminiCli OAuth→Bearer、OpenRouter→Bearer、Codex 按 apiKeyField)
- 转换前剥离 [1m] 标记;meta.isFullUrl 或 base 已是完整端点时直接使用 base_url;自定义 body 浅合并移至转换后的最终协议体
- 新增 18 个目标构造断言单测;Comet 归档产物同步提交

chore: archive connectivity-test-optimizations

feat(connectivity-test): 单一模型表格 + 实时拉取模型 + 勾选即测试范围 + 新默认提示词

- 弹窗合并为单一模型表格:打开即全部模型行(初始待测试)、行勾选 + 表头全选(含半选态)、5 格统计卡,移除上方独立选择区
- 获取模型列表:凭据提取抽为共享模块 src/utils/providerCredentials.ts(ModelQuickSwitchDialog 同步改引用),复用 fetch_models_for_config 拉取 /v1/models,按 ID 追加合并去重、仅会话内有效
- 默认提示词改为「你好,你可以帮我做什么事情」:前后端常量同步,merge 差异判断基准跟随
- 勾选即测试范围:后端 connectivity_test_provider_models 新增 modelIds 参数(None/空白回退静态清单),前端逐模型独立 invoke 并行 + 行级流式更新
- 一轮测完自动勾选失败项,全部成功保持原勾选

fix(connectivity-test): UI 四项修复

- 弹窗抬至 alert 层(z-[60]):打开连通性测试时 header 切换栏正确变灰,
  详情子弹窗同步抬层(同 z + 更晚挂载 portal 在上)
- 批量检测入口移至 header 图标组(状态提升到 App,ProviderList 经
  props 读取探针结果),不再另起一行
- 批量检测可停止:runId 代次守卫(Tauri invoke 无法真正取消),再次
  点击即清除 waiting/running 徽标、丢弃在途迟到写入、不再拉起排队项
- 弹窗打开自动勾选模型:已保存默认测试模型 → 供应商当前模型
  (claude env.ANTHROPIC_MODEL / codex config.toml model,新增
  currentProviderModelId 与后端同口径)→ 清单首个

前端全量 141 文件 / 1106 测试全绿;typecheck clean

chore: archive per-model-connectivity-test

fix(connectivity-test): shouldShowTestEntry 增判 providerType + 收尾清理

chore(connectivity-test): 勾选 Task 9 / OpenSpec 5.1-5.2 + verify 报告

chore(connectivity-test): 勾选 Task 8 / OpenSpec 4.1-4.3

refactor(connectivity-test): 移除旧 stream_check 可达性探测全链路(保留表结构)

- 删除 6 个文件:commands/stream_check.rs、services/stream_check.rs、
  database/dao/stream_check.rs、hooks/useStreamCheck.ts、lib/api/connectivity-check.ts、
  components/usage/ConnectivityCheckConfigPanel.tsx
- 移除 mod.rs 声明、lib.rs generate_handler! 注册、cleanup_old_stream_check_logs 调用
- 从 SYNC_SKIP_TABLES / SYNC_PRESERVE_TABLES 移除 stream_check_logs(表结构保留在 schema.rs)
- 更新 backup.rs 测试:移除 stream_check_logs fixture 与断言
- 更新 SettingsPage:移除 ConnectivityCheckConfigPanel AccordionItem 与 FlaskConical 导入
- i18n:删除 streamCheck.* 命名空间(zh/en/ja/zh-TW 四语言)
- 更新注释:移除 stream_check 旧引用(forwarder.rs、provider.rs、userAgentPresets.ts、
  connectivity_test.rs)
- 修复 4 个 pre-existing clippy lint(connectivity_test: matches! 宏、unused_unit、
  derive Default)
- 移除 ProviderList.test.tsx 中已失效的 useStreamCheck mock

chore(connectivity-test): 勾选 Task 7 / OpenSpec 2.4 3.1-3.3

feat(connectivity-test): 供应商列表集成(弹窗入口 + 批量探针徽标)

chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
重放 fork 提交到 upstream/main(f3b18df1,v3.20.2)后统一版本号。
Cargo.lock 同步移除 updater 依赖树(fork 已禁用应用内更新器)。

chore(native): archive tray-left-click-toggle-window

chore(comet): 记录 tray-left-click-toggle-window 验收通过与归档状态

feat(tray): 托盘图标左键单击切换主窗口显示/隐藏

- tray.rs 新增 show_main_window/hide_main_window/toggle_main_window 助手:
  显示语义与"打开主界面"一致,隐藏与"关闭到托盘"平台处理一致
  (Windows set_skip_taskbar、macOS apply_tray_policy)
- lib.rs 托盘 show_menu_on_left_click 改为 false,左键 Click(Left, Up)
  触发切换(过滤 Down/Up 双事件),右键仍弹菜单;Enter/Click 用量刷新保持
- show_main 菜单事件与 macOS Reopen 复用 show_main_window

docs(readme): 更新日志补充连通性测试、双官网链接与托盘左键切换

feat(model-quick-switch): 获取模型列表成功后自动展开选择器

SearchableModelPicker 增加可选 open/onOpenChange 受控展开(不传保持内部自管,
表单等既有用法不受影响);快捷切换弹窗拉取成功且列表非空时直接展开,关闭弹窗复位。

chore(native): archive fix-quick-switch-1m

chore(comet): 记录 fix-quick-switch-1m 验收通过与归档状态

fix(model-quick-switch): 补齐 Haiku 显示名同步,支持不选模型仅翻转 1M

- 显示名同步映射补上 ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME(沿用空/旧base/旧base[1M] 覆盖、自定义保留语义)
- 新增 setClaudeOneMInSettings:仅原地翻转 1M 标记,不改各角色模型 base 与显示名
- 弹窗打开时 1M 开关按供应商当前状态初始化,避免已开 1M 被静默剥离
- claude 未选模型且 1M 状态有变化时允许保存;无可应用变化时保存禁用
- 补单测与四语言 toast 文案

feat(provider-website-links): 供应商支持第二个官网链接,主页横排双链接

- Rust Provider/UniversalProvider 新增 website_url_2(serde websiteUrl2,skip none)
- SQLite providers 表新增 website_url_2 列(schema v18→v19 幂等迁移 + create_tables 补列 + 旧 config.json 迁移)
- DAO SELECT/INSERT/UPDATE/replace 全链路带出新列
- 表单共享 BasicFormFields + 统一供应商表单第二输入框;zod 同第一链接规则校验与回填
- ProviderCard 主页双链接横排、两空格分隔、逐链接 openExternal 打开默认浏览器
- 搜索匹配、复制供应商、统一供应商生成 claude/codex/gemini 随行透传
- gemini_auth packycode 认证检测纳入 website_url_2;ClaudeDesktop/GrokBuild 预设切换清空第二链接;卡片同值链接去重
- 4 语言 i18n + 前端/前端单测覆盖

Comet change dual-website-links-homepage(19/19 验收通过,归档)

feat(connectivity-test): 拉取模型列表改为「搜索添加」选择器交互

- 新增共享组件 SearchableModelMultiPicker:搜索过滤 + 多选 + 已在列表标记 + 已选计数
- ConnectivityTestDialog 获取模型列表后不再全量追加合并去重,改为会话内缓存完整列表并自动弹出一次选择器;用户搜索挑选后添加进表格并自动勾选
- 重新拉取只刷新缓存,已添加进表格的行保留;拉取结果仍仅会话内有效,不写回供应商配置
- 四语言 i18n 新增 searchAdd / addToTest / alreadyInList / selectedCount
- 同步更新 ConnectivityTestDialog 测试并新增 SearchableModelMultiPicker 测试

feat(connectivity-test): 探测目标按供应商 API 格式对齐真实转发链路

- build_claude 按 get_claude_api_format 分派端点与请求体:openai_chat → /v1/chat/completions(复用 transform_claude_request_for_api_format,流式注入 stream_options.include_usage)、openai_responses → /v1/responses、gemini_native → /v1beta/models/{model}:{method}(resolve_gemini_native_url)、anthropic 保持 /v1/messages 并补 anthropic-version/beta 头
- build_codex 按 should_convert_codex_responses_to_chat/anthropic 分派:chat → /chat/completions(responses_to_chat_completions_with_reasoning)、anthropic → /v1/messages(responses_request_to_anthropic,含 max_output_tokens 注入与 [1m] 剥离 + context-1m beta 头)
- 鉴权改用 ClaudeAdapter/CodexAdapter 的 extract_auth + get_auth_headers(与真实出站请求同源:AUTH_TOKEN→Bearer、ANTHROPIC_API_KEY→x-api-key、gemini→x-goog-api-key、GeminiCli OAuth→Bearer、OpenRouter→Bearer、Codex 按 apiKeyField)
- 转换前剥离 [1m] 标记;meta.isFullUrl 或 base 已是完整端点时直接使用 base_url;自定义 body 浅合并移至转换后的最终协议体
- 新增 18 个目标构造断言单测;Comet 归档产物同步提交

chore: archive connectivity-test-optimizations

feat(connectivity-test): 单一模型表格 + 实时拉取模型 + 勾选即测试范围 + 新默认提示词

- 弹窗合并为单一模型表格:打开即全部模型行(初始待测试)、行勾选 + 表头全选(含半选态)、5 格统计卡,移除上方独立选择区
- 获取模型列表:凭据提取抽为共享模块 src/utils/providerCredentials.ts(ModelQuickSwitchDialog 同步改引用),复用 fetch_models_for_config 拉取 /v1/models,按 ID 追加合并去重、仅会话内有效
- 默认提示词改为「你好,你可以帮我做什么事情」:前后端常量同步,merge 差异判断基准跟随
- 勾选即测试范围:后端 connectivity_test_provider_models 新增 modelIds 参数(None/空白回退静态清单),前端逐模型独立 invoke 并行 + 行级流式更新
- 一轮测完自动勾选失败项,全部成功保持原勾选

fix(connectivity-test): UI 四项修复

- 弹窗抬至 alert 层(z-[60]):打开连通性测试时 header 切换栏正确变灰,
  详情子弹窗同步抬层(同 z + 更晚挂载 portal 在上)
- 批量检测入口移至 header 图标组(状态提升到 App,ProviderList 经
  props 读取探针结果),不再另起一行
- 批量检测可停止:runId 代次守卫(Tauri invoke 无法真正取消),再次
  点击即清除 waiting/running 徽标、丢弃在途迟到写入、不再拉起排队项
- 弹窗打开自动勾选模型:已保存默认测试模型 → 供应商当前模型
  (claude env.ANTHROPIC_MODEL / codex config.toml model,新增
  currentProviderModelId 与后端同口径)→ 清单首个

前端全量 141 文件 / 1106 测试全绿;typecheck clean

chore: archive per-model-connectivity-test

fix(connectivity-test): shouldShowTestEntry 增判 providerType + 收尾清理

chore(connectivity-test): 勾选 Task 9 / OpenSpec 5.1-5.2 + verify 报告

chore(connectivity-test): 勾选 Task 8 / OpenSpec 4.1-4.3

refactor(connectivity-test): 移除旧 stream_check 可达性探测全链路(保留表结构)

- 删除 6 个文件:commands/stream_check.rs、services/stream_check.rs、
  database/dao/stream_check.rs、hooks/useStreamCheck.ts、lib/api/connectivity-check.ts、
  components/usage/ConnectivityCheckConfigPanel.tsx
- 移除 mod.rs 声明、lib.rs generate_handler! 注册、cleanup_old_stream_check_logs 调用
- 从 SYNC_SKIP_TABLES / SYNC_PRESERVE_TABLES 移除 stream_check_logs(表结构保留在 schema.rs)
- 更新 backup.rs 测试:移除 stream_check_logs fixture 与断言
- 更新 SettingsPage:移除 ConnectivityCheckConfigPanel AccordionItem 与 FlaskConical 导入
- i18n:删除 streamCheck.* 命名空间(zh/en/ja/zh-TW 四语言)
- 更新注释:移除 stream_check 旧引用(forwarder.rs、provider.rs、userAgentPresets.ts、
  connectivity_test.rs)
- 修复 4 个 pre-existing clippy lint(connectivity_test: matches! 宏、unused_unit、
  derive Default)
- 移除 ProviderList.test.tsx 中已失效的 useStreamCheck mock

chore(connectivity-test): 勾选 Task 7 / OpenSpec 2.4 3.1-3.3

feat(connectivity-test): 供应商列表集成(弹窗入口 + 批量探针徽标)

chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(pro…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
修复 CI Frontend Checks 的 Check formatting 步骤失败
(App.tsx、ConnectivityTestDialog、ProviderCard、ProviderList、
provider schema 共 5 个文件)。

style: apply cargo fmt to connectivity-test era backend files

修复 CI 的 Check Rust formatting 步骤失败(connectivity_test、
backup、tests、provider 等文件格式不符合 rustfmt 规范)。

chore(native): archive sync-upstream

docs(fork): update rebase whitelist for connectivity-test era fork features

- 白名单补充连通性测试前后端、模型快捷切换、双官网链接测试、
  CI 裁剪版、README/CHANGELOG、.comet/superpowers 产物等 fork 专属文件
- 新增共享文件 fork 专属语义表(托盘左键切换、禁用更新器、hidden 字段等)
- 修正 §3.1 rebase 方向说明:rebase 中 fork 侧是 --theirs 而非 --ours
  (v3.20.2 同步实测确认,与 merge 语义相反)

chore: bump fork version to 3.20.2-1 after upstream v3.20.2 rebase

重放 fork 提交到 upstream/main(f3b18df1,v3.20.2)后统一版本号。
Cargo.lock 同步移除 updater 依赖树(fork 已禁用应用内更新器)。

chore(native): archive tray-left-click-toggle-window

chore(comet): 记录 tray-left-click-toggle-window 验收通过与归档状态

feat(tray): 托盘图标左键单击切换主窗口显示/隐藏

- tray.rs 新增 show_main_window/hide_main_window/toggle_main_window 助手:
  显示语义与"打开主界面"一致,隐藏与"关闭到托盘"平台处理一致
  (Windows set_skip_taskbar、macOS apply_tray_policy)
- lib.rs 托盘 show_menu_on_left_click 改为 false,左键 Click(Left, Up)
  触发切换(过滤 Down/Up 双事件),右键仍弹菜单;Enter/Click 用量刷新保持
- show_main 菜单事件与 macOS Reopen 复用 show_main_window

docs(readme): 更新日志补充连通性测试、双官网链接与托盘左键切换

feat(model-quick-switch): 获取模型列表成功后自动展开选择器

SearchableModelPicker 增加可选 open/onOpenChange 受控展开(不传保持内部自管,
表单等既有用法不受影响);快捷切换弹窗拉取成功且列表非空时直接展开,关闭弹窗复位。

chore(native): archive fix-quick-switch-1m

chore(comet): 记录 fix-quick-switch-1m 验收通过与归档状态

fix(model-quick-switch): 补齐 Haiku 显示名同步,支持不选模型仅翻转 1M

- 显示名同步映射补上 ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME(沿用空/旧base/旧base[1M] 覆盖、自定义保留语义)
- 新增 setClaudeOneMInSettings:仅原地翻转 1M 标记,不改各角色模型 base 与显示名
- 弹窗打开时 1M 开关按供应商当前状态初始化,避免已开 1M 被静默剥离
- claude 未选模型且 1M 状态有变化时允许保存;无可应用变化时保存禁用
- 补单测与四语言 toast 文案

feat(provider-website-links): 供应商支持第二个官网链接,主页横排双链接

- Rust Provider/UniversalProvider 新增 website_url_2(serde websiteUrl2,skip none)
- SQLite providers 表新增 website_url_2 列(schema v18→v19 幂等迁移 + create_tables 补列 + 旧 config.json 迁移)
- DAO SELECT/INSERT/UPDATE/replace 全链路带出新列
- 表单共享 BasicFormFields + 统一供应商表单第二输入框;zod 同第一链接规则校验与回填
- ProviderCard 主页双链接横排、两空格分隔、逐链接 openExternal 打开默认浏览器
- 搜索匹配、复制供应商、统一供应商生成 claude/codex/gemini 随行透传
- gemini_auth packycode 认证检测纳入 website_url_2;ClaudeDesktop/GrokBuild 预设切换清空第二链接;卡片同值链接去重
- 4 语言 i18n + 前端/前端单测覆盖

Comet change dual-website-links-homepage(19/19 验收通过,归档)

feat(connectivity-test): 拉取模型列表改为「搜索添加」选择器交互

- 新增共享组件 SearchableModelMultiPicker:搜索过滤 + 多选 + 已在列表标记 + 已选计数
- ConnectivityTestDialog 获取模型列表后不再全量追加合并去重,改为会话内缓存完整列表并自动弹出一次选择器;用户搜索挑选后添加进表格并自动勾选
- 重新拉取只刷新缓存,已添加进表格的行保留;拉取结果仍仅会话内有效,不写回供应商配置
- 四语言 i18n 新增 searchAdd / addToTest / alreadyInList / selectedCount
- 同步更新 ConnectivityTestDialog 测试并新增 SearchableModelMultiPicker 测试

feat(connectivity-test): 探测目标按供应商 API 格式对齐真实转发链路

- build_claude 按 get_claude_api_format 分派端点与请求体:openai_chat → /v1/chat/completions(复用 transform_claude_request_for_api_format,流式注入 stream_options.include_usage)、openai_responses → /v1/responses、gemini_native → /v1beta/models/{model}:{method}(resolve_gemini_native_url)、anthropic 保持 /v1/messages 并补 anthropic-version/beta 头
- build_codex 按 should_convert_codex_responses_to_chat/anthropic 分派:chat → /chat/completions(responses_to_chat_completions_with_reasoning)、anthropic → /v1/messages(responses_request_to_anthropic,含 max_output_tokens 注入与 [1m] 剥离 + context-1m beta 头)
- 鉴权改用 ClaudeAdapter/CodexAdapter 的 extract_auth + get_auth_headers(与真实出站请求同源:AUTH_TOKEN→Bearer、ANTHROPIC_API_KEY→x-api-key、gemini→x-goog-api-key、GeminiCli OAuth→Bearer、OpenRouter→Bearer、Codex 按 apiKeyField)
- 转换前剥离 [1m] 标记;meta.isFullUrl 或 base 已是完整端点时直接使用 base_url;自定义 body 浅合并移至转换后的最终协议体
- 新增 18 个目标构造断言单测;Comet 归档产物同步提交

chore: archive connectivity-test-optimizations

feat(connectivity-test): 单一模型表格 + 实时拉取模型 + 勾选即测试范围 + 新默认提示词

- 弹窗合并为单一模型表格:打开即全部模型行(初始待测试)、行勾选 + 表头全选(含半选态)、5 格统计卡,移除上方独立选择区
- 获取模型列表:凭据提取抽为共享模块 src/utils/providerCredentials.ts(ModelQuickSwitchDialog 同步改引用),复用 fetch_models_for_config 拉取 /v1/models,按 ID 追加合并去重、仅会话内有效
- 默认提示词改为「你好,你可以帮我做什么事情」:前后端常量同步,merge 差异判断基准跟随
- 勾选即测试范围:后端 connectivity_test_provider_models 新增 modelIds 参数(None/空白回退静态清单),前端逐模型独立 invoke 并行 + 行级流式更新
- 一轮测完自动勾选失败项,全部成功保持原勾选

fix(connectivity-test): UI 四项修复

- 弹窗抬至 alert 层(z-[60]):打开连通性测试时 header 切换栏正确变灰,
  详情子弹窗同步抬层(同 z + 更晚挂载 portal 在上)
- 批量检测入口移至 header 图标组(状态提升到 App,ProviderList 经
  props 读取探针结果),不再另起一行
- 批量检测可停止:runId 代次守卫(Tauri invoke 无法真正取消),再次
  点击即清除 waiting/running 徽标、丢弃在途迟到写入、不再拉起排队项
- 弹窗打开自动勾选模型:已保存默认测试模型 → 供应商当前模型
  (claude env.ANTHROPIC_MODEL / codex config.toml model,新增
  currentProviderModelId 与后端同口径)→ 清单首个

前端全量 141 文件 / 1106 测试全绿;typecheck clean

chore: archive per-model-connectivity-test

fix(connectivity-test): shouldShowTestEntry 增判 providerType + 收尾清理

chore(connectivity-test): 勾选 Task 9 / OpenSpec 5.1-5.2 + verify 报告

chore(connectivity-test): 勾选 Task 8 / OpenSpec 4.1-4.3

refactor(connectivity-test): 移除旧 stream_check 可达性探测全链路(保留表结构)

- 删除 6 个文件:commands/stream_check.rs、services/stream_check.rs、
  database/dao/stream_check.rs、hooks/useStreamCheck.ts、lib/api/connectivity-check.ts、
  components/usage/ConnectivityCheckConfigPanel.tsx
- 移除 mod.rs 声明、lib.rs generate_handler! 注册、cleanup_old_stream_check_logs 调用
- 从 SYNC_SKIP_TABLES / SYNC_PRESERVE_TABLES 移除 stream_check_logs(表结构保留在 schema.rs)
- 更新 backup.rs 测试:移除 stream_check_logs fixture 与断言
- 更新 SettingsPage:移除 ConnectivityCheckConfigPanel AccordionItem 与 FlaskConical 导入
- i18n:删除 streamCheck.* 命名空间(zh/en/ja/zh-TW 四语言)
- 更新注释:移除 stream_check 旧引用(forwarder.rs、provider.rs、userAgentPresets.ts、
  connectivity_test.rs)
- 修复 4 个 pre-existing clippy lint(connectivity_test: matches! 宏、unused_unit、
  derive Default)
- 移除 ProviderList.test.tsx 中已失效的 useStreamCheck mock

chore(connectivity-test): 勾选 Task 7 / OpenSpec 2.4 3.1-3.3

feat(connectivity-test): 供应商列表集成(弹窗入口 + 批量探针徽标)

chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 19…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
…itHub

Rebase fork main onto upstream/main (v3.20.3, 06082e18, 33 new commits)
while keeping all fork modifications: connectivity test, model quick
switch, sidebar panel visibility, preset filtering, tray left-click
toggle, dual website links, disabled in-app updater, fork CI.

- Bump fork version to 3.20.3-1 (package.json, tauri.conf.json,
  Cargo.toml, Cargo.lock).
- Merged upstream's v18->v19 schema migration with the fork's
  website_url_2 column migration in the same migration arm.
- Added mcode entry to forkOfficialAllowlist (no official presets,
  same treatment as hermes/openclaw/opencode/pi).
- Changed the top-left CC Switch brand link target from
  https://ccswitch.io to https://github.com/farion1231/cc-switch.
- Updated HOW_TO_REBASE_UPSTREAM.md whitelist with the brand link
  override.

style: apply prettier to files flagged by CI format check

修复 CI Frontend Checks 的 Check formatting 步骤失败
(App.tsx、ConnectivityTestDialog、ProviderCard、ProviderList、
provider schema 共 5 个文件)。

style: apply cargo fmt to connectivity-test era backend files

修复 CI 的 Check Rust formatting 步骤失败(connectivity_test、
backup、tests、provider 等文件格式不符合 rustfmt 规范)。

chore(native): archive sync-upstream

docs(fork): update rebase whitelist for connectivity-test era fork features

- 白名单补充连通性测试前后端、模型快捷切换、双官网链接测试、
  CI 裁剪版、README/CHANGELOG、.comet/superpowers 产物等 fork 专属文件
- 新增共享文件 fork 专属语义表(托盘左键切换、禁用更新器、hidden 字段等)
- 修正 §3.1 rebase 方向说明:rebase 中 fork 侧是 --theirs 而非 --ours
  (v3.20.2 同步实测确认,与 merge 语义相反)

chore: bump fork version to 3.20.2-1 after upstream v3.20.2 rebase

重放 fork 提交到 upstream/main(f3b18df1,v3.20.2)后统一版本号。
Cargo.lock 同步移除 updater 依赖树(fork 已禁用应用内更新器)。

chore(native): archive tray-left-click-toggle-window

chore(comet): 记录 tray-left-click-toggle-window 验收通过与归档状态

feat(tray): 托盘图标左键单击切换主窗口显示/隐藏

- tray.rs 新增 show_main_window/hide_main_window/toggle_main_window 助手:
  显示语义与"打开主界面"一致,隐藏与"关闭到托盘"平台处理一致
  (Windows set_skip_taskbar、macOS apply_tray_policy)
- lib.rs 托盘 show_menu_on_left_click 改为 false,左键 Click(Left, Up)
  触发切换(过滤 Down/Up 双事件),右键仍弹菜单;Enter/Click 用量刷新保持
- show_main 菜单事件与 macOS Reopen 复用 show_main_window

docs(readme): 更新日志补充连通性测试、双官网链接与托盘左键切换

feat(model-quick-switch): 获取模型列表成功后自动展开选择器

SearchableModelPicker 增加可选 open/onOpenChange 受控展开(不传保持内部自管,
表单等既有用法不受影响);快捷切换弹窗拉取成功且列表非空时直接展开,关闭弹窗复位。

chore(native): archive fix-quick-switch-1m

chore(comet): 记录 fix-quick-switch-1m 验收通过与归档状态

fix(model-quick-switch): 补齐 Haiku 显示名同步,支持不选模型仅翻转 1M

- 显示名同步映射补上 ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME(沿用空/旧base/旧base[1M] 覆盖、自定义保留语义)
- 新增 setClaudeOneMInSettings:仅原地翻转 1M 标记,不改各角色模型 base 与显示名
- 弹窗打开时 1M 开关按供应商当前状态初始化,避免已开 1M 被静默剥离
- claude 未选模型且 1M 状态有变化时允许保存;无可应用变化时保存禁用
- 补单测与四语言 toast 文案

feat(provider-website-links): 供应商支持第二个官网链接,主页横排双链接

- Rust Provider/UniversalProvider 新增 website_url_2(serde websiteUrl2,skip none)
- SQLite providers 表新增 website_url_2 列(schema v18→v19 幂等迁移 + create_tables 补列 + 旧 config.json 迁移)
- DAO SELECT/INSERT/UPDATE/replace 全链路带出新列
- 表单共享 BasicFormFields + 统一供应商表单第二输入框;zod 同第一链接规则校验与回填
- ProviderCard 主页双链接横排、两空格分隔、逐链接 openExternal 打开默认浏览器
- 搜索匹配、复制供应商、统一供应商生成 claude/codex/gemini 随行透传
- gemini_auth packycode 认证检测纳入 website_url_2;ClaudeDesktop/GrokBuild 预设切换清空第二链接;卡片同值链接去重
- 4 语言 i18n + 前端/前端单测覆盖

Comet change dual-website-links-homepage(19/19 验收通过,归档)

feat(connectivity-test): 拉取模型列表改为「搜索添加」选择器交互

- 新增共享组件 SearchableModelMultiPicker:搜索过滤 + 多选 + 已在列表标记 + 已选计数
- ConnectivityTestDialog 获取模型列表后不再全量追加合并去重,改为会话内缓存完整列表并自动弹出一次选择器;用户搜索挑选后添加进表格并自动勾选
- 重新拉取只刷新缓存,已添加进表格的行保留;拉取结果仍仅会话内有效,不写回供应商配置
- 四语言 i18n 新增 searchAdd / addToTest / alreadyInList / selectedCount
- 同步更新 ConnectivityTestDialog 测试并新增 SearchableModelMultiPicker 测试

feat(connectivity-test): 探测目标按供应商 API 格式对齐真实转发链路

- build_claude 按 get_claude_api_format 分派端点与请求体:openai_chat → /v1/chat/completions(复用 transform_claude_request_for_api_format,流式注入 stream_options.include_usage)、openai_responses → /v1/responses、gemini_native → /v1beta/models/{model}:{method}(resolve_gemini_native_url)、anthropic 保持 /v1/messages 并补 anthropic-version/beta 头
- build_codex 按 should_convert_codex_responses_to_chat/anthropic 分派:chat → /chat/completions(responses_to_chat_completions_with_reasoning)、anthropic → /v1/messages(responses_request_to_anthropic,含 max_output_tokens 注入与 [1m] 剥离 + context-1m beta 头)
- 鉴权改用 ClaudeAdapter/CodexAdapter 的 extract_auth + get_auth_headers(与真实出站请求同源:AUTH_TOKEN→Bearer、ANTHROPIC_API_KEY→x-api-key、gemini→x-goog-api-key、GeminiCli OAuth→Bearer、OpenRouter→Bearer、Codex 按 apiKeyField)
- 转换前剥离 [1m] 标记;meta.isFullUrl 或 base 已是完整端点时直接使用 base_url;自定义 body 浅合并移至转换后的最终协议体
- 新增 18 个目标构造断言单测;Comet 归档产物同步提交

chore: archive connectivity-test-optimizations

feat(connectivity-test): 单一模型表格 + 实时拉取模型 + 勾选即测试范围 + 新默认提示词

- 弹窗合并为单一模型表格:打开即全部模型行(初始待测试)、行勾选 + 表头全选(含半选态)、5 格统计卡,移除上方独立选择区
- 获取模型列表:凭据提取抽为共享模块 src/utils/providerCredentials.ts(ModelQuickSwitchDialog 同步改引用),复用 fetch_models_for_config 拉取 /v1/models,按 ID 追加合并去重、仅会话内有效
- 默认提示词改为「你好,你可以帮我做什么事情」:前后端常量同步,merge 差异判断基准跟随
- 勾选即测试范围:后端 connectivity_test_provider_models 新增 modelIds 参数(None/空白回退静态清单),前端逐模型独立 invoke 并行 + 行级流式更新
- 一轮测完自动勾选失败项,全部成功保持原勾选

fix(connectivity-test): UI 四项修复

- 弹窗抬至 alert 层(z-[60]):打开连通性测试时 header 切换栏正确变灰,
  详情子弹窗同步抬层(同 z + 更晚挂载 portal 在上)
- 批量检测入口移至 header 图标组(状态提升到 App,ProviderList 经
  props 读取探针结果),不再另起一行
- 批量检测可停止:runId 代次守卫(Tauri invoke 无法真正取消),再次
  点击即清除 waiting/running 徽标、丢弃在途迟到写入、不再拉起排队项
- 弹窗打开自动勾选模型:已保存默认测试模型 → 供应商当前模型
  (claude env.ANTHROPIC_MODEL / codex config.toml model,新增
  currentProviderModelId 与后端同口径)→ 清单首个

前端全量 141 文件 / 1106 测试全绿;typecheck clean

chore: archive per-model-connectivity-test

fix(connectivity-test): shouldShowTestEntry 增判 providerType + 收尾清理

chore(connectivity-test): 勾选 Task 9 / OpenSpec 5.1-5.2 + verify 报告

chore(connectivity-test): 勾选 Task 8 / OpenSpec 4.1-4.3

refactor(connectivity-test): 移除旧 stream_check 可达性探测全链路(保留表结构)

- 删除 6 个文件:commands/stream_check.rs、services/stream_check.rs、
  database/dao/stream_check.rs、hooks/useStreamCheck.ts、lib/api/connectivity-check.ts、
  components/usage/ConnectivityCheckConfigPanel.tsx
- 移除 mod.rs 声明、lib.rs generate_handler! 注册、cleanup_old_stream_check_logs 调用
- 从 SYNC_SKIP_TABLES / SYNC_PRESERVE_TABLES 移除 stream_check_logs(表结构保留在 schema.rs)
- 更新 backup.rs 测试:移除 stream_check_logs fixture 与断言
- 更新 SettingsPage:移除 ConnectivityCheckConfigPanel AccordionItem 与 FlaskConical 导入
- i18n:删除 streamCheck.* 命名空间(zh/en/ja/zh-TW 四语言)
- 更新注释:移除 stream_check 旧引用(forwarder.rs、provider.rs、userAgentPresets.ts、
  connectivity_test.rs)
- 修复 4 个 pre-existing clippy lint(connectivity_test: matches! 宏、unused_unit、
  derive Default)
- 移除 ProviderList.test.tsx 中已失效的 useStreamCheck mock

chore(connectivity-test): 勾选 Task 7 / OpenSpec 2.4 3.1-3.3

feat(connectivity-test): 供应商列表集成(弹窗入口 + 批量探针徽标)

chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four READ…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
新建/编辑供应商表单里,可从其他已启用应用的已有供应商一次性带入
名称、备注、官网链接、API Key、请求地址、默认(兜底)模型名与上游
API 格式,其余配置由用户自行填写。10 个应用的配置形状各不相同,
映射层按目标应用分别落键;编辑模式只覆盖这三个键位,配置对象其余
部分(providerKey、模型表、Codex modelCatalog、Gemini 顶层 config、
env/options/auth 其他键含 Codex 登录态键)原样保留。

- src/utils/providerImport.ts:读侧按应用取可迁移字段;写侧拆成
  buildImportedSettingsConfig(新建,按目标应用「自定义」模板重建)
  与 patchImportedSettingsConfig(编辑,就地覆盖)两条路径
- src/components/providers/forms/ProviderImportEntry.tsx:来源应用
  按钮行 + 供应商选择弹窗,预告「本次将写入」的字段,不渲染 API Key
- useProviderImportSources / useProviderImportApply:来源枚举与表单
  写入;ProviderPresetSelector 增加 extraActions 插槽
- src/utils/piProviderConfig.ts:buildPiSettingsConfig 自
  PiProviderForm 抽出,供映射层复用
- i18n 四语言新增 providerImport 文案

release 工作流改为直接以正式版发布(prerelease: false),不再先挂
预发布等人工提升;注意这同时移除了那道人工放行关卡,tag 一推发布
即对 /releases/latest、下载页和应用内更新器可见。sync-r2 不受影响,
其 released 触发器对直接以 stable 发布同样触发。

chore(native): archive cross-app-provider-import

chore(native): archive sync-upstream-3203

chore: sync fork with upstream v3.20.3 and rebrand top-left link to GitHub

Rebase fork main onto upstream/main (v3.20.3, 06082e18, 33 new commits)
while keeping all fork modifications: connectivity test, model quick
switch, sidebar panel visibility, preset filtering, tray left-click
toggle, dual website links, disabled in-app updater, fork CI.

- Bump fork version to 3.20.3-1 (package.json, tauri.conf.json,
  Cargo.toml, Cargo.lock).
- Merged upstream's v18->v19 schema migration with the fork's
  website_url_2 column migration in the same migration arm.
- Added mcode entry to forkOfficialAllowlist (no official presets,
  same treatment as hermes/openclaw/opencode/pi).
- Changed the top-left CC Switch brand link target from
  https://ccswitch.io to https://github.com/farion1231/cc-switch.
- Updated HOW_TO_REBASE_UPSTREAM.md whitelist with the brand link
  override.

style: apply prettier to files flagged by CI format check

修复 CI Frontend Checks 的 Check formatting 步骤失败
(App.tsx、ConnectivityTestDialog、ProviderCard、ProviderList、
provider schema 共 5 个文件)。

style: apply cargo fmt to connectivity-test era backend files

修复 CI 的 Check Rust formatting 步骤失败(connectivity_test、
backup、tests、provider 等文件格式不符合 rustfmt 规范)。

chore(native): archive sync-upstream

docs(fork): update rebase whitelist for connectivity-test era fork features

- 白名单补充连通性测试前后端、模型快捷切换、双官网链接测试、
  CI 裁剪版、README/CHANGELOG、.comet/superpowers 产物等 fork 专属文件
- 新增共享文件 fork 专属语义表(托盘左键切换、禁用更新器、hidden 字段等)
- 修正 §3.1 rebase 方向说明:rebase 中 fork 侧是 --theirs 而非 --ours
  (v3.20.2 同步实测确认,与 merge 语义相反)

chore: bump fork version to 3.20.2-1 after upstream v3.20.2 rebase

重放 fork 提交到 upstream/main(f3b18df1,v3.20.2)后统一版本号。
Cargo.lock 同步移除 updater 依赖树(fork 已禁用应用内更新器)。

chore(native): archive tray-left-click-toggle-window

chore(comet): 记录 tray-left-click-toggle-window 验收通过与归档状态

feat(tray): 托盘图标左键单击切换主窗口显示/隐藏

- tray.rs 新增 show_main_window/hide_main_window/toggle_main_window 助手:
  显示语义与"打开主界面"一致,隐藏与"关闭到托盘"平台处理一致
  (Windows set_skip_taskbar、macOS apply_tray_policy)
- lib.rs 托盘 show_menu_on_left_click 改为 false,左键 Click(Left, Up)
  触发切换(过滤 Down/Up 双事件),右键仍弹菜单;Enter/Click 用量刷新保持
- show_main 菜单事件与 macOS Reopen 复用 show_main_window

docs(readme): 更新日志补充连通性测试、双官网链接与托盘左键切换

feat(model-quick-switch): 获取模型列表成功后自动展开选择器

SearchableModelPicker 增加可选 open/onOpenChange 受控展开(不传保持内部自管,
表单等既有用法不受影响);快捷切换弹窗拉取成功且列表非空时直接展开,关闭弹窗复位。

chore(native): archive fix-quick-switch-1m

chore(comet): 记录 fix-quick-switch-1m 验收通过与归档状态

fix(model-quick-switch): 补齐 Haiku 显示名同步,支持不选模型仅翻转 1M

- 显示名同步映射补上 ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME(沿用空/旧base/旧base[1M] 覆盖、自定义保留语义)
- 新增 setClaudeOneMInSettings:仅原地翻转 1M 标记,不改各角色模型 base 与显示名
- 弹窗打开时 1M 开关按供应商当前状态初始化,避免已开 1M 被静默剥离
- claude 未选模型且 1M 状态有变化时允许保存;无可应用变化时保存禁用
- 补单测与四语言 toast 文案

feat(provider-website-links): 供应商支持第二个官网链接,主页横排双链接

- Rust Provider/UniversalProvider 新增 website_url_2(serde websiteUrl2,skip none)
- SQLite providers 表新增 website_url_2 列(schema v18→v19 幂等迁移 + create_tables 补列 + 旧 config.json 迁移)
- DAO SELECT/INSERT/UPDATE/replace 全链路带出新列
- 表单共享 BasicFormFields + 统一供应商表单第二输入框;zod 同第一链接规则校验与回填
- ProviderCard 主页双链接横排、两空格分隔、逐链接 openExternal 打开默认浏览器
- 搜索匹配、复制供应商、统一供应商生成 claude/codex/gemini 随行透传
- gemini_auth packycode 认证检测纳入 website_url_2;ClaudeDesktop/GrokBuild 预设切换清空第二链接;卡片同值链接去重
- 4 语言 i18n + 前端/前端单测覆盖

Comet change dual-website-links-homepage(19/19 验收通过,归档)

feat(connectivity-test): 拉取模型列表改为「搜索添加」选择器交互

- 新增共享组件 SearchableModelMultiPicker:搜索过滤 + 多选 + 已在列表标记 + 已选计数
- ConnectivityTestDialog 获取模型列表后不再全量追加合并去重,改为会话内缓存完整列表并自动弹出一次选择器;用户搜索挑选后添加进表格并自动勾选
- 重新拉取只刷新缓存,已添加进表格的行保留;拉取结果仍仅会话内有效,不写回供应商配置
- 四语言 i18n 新增 searchAdd / addToTest / alreadyInList / selectedCount
- 同步更新 ConnectivityTestDialog 测试并新增 SearchableModelMultiPicker 测试

feat(connectivity-test): 探测目标按供应商 API 格式对齐真实转发链路

- build_claude 按 get_claude_api_format 分派端点与请求体:openai_chat → /v1/chat/completions(复用 transform_claude_request_for_api_format,流式注入 stream_options.include_usage)、openai_responses → /v1/responses、gemini_native → /v1beta/models/{model}:{method}(resolve_gemini_native_url)、anthropic 保持 /v1/messages 并补 anthropic-version/beta 头
- build_codex 按 should_convert_codex_responses_to_chat/anthropic 分派:chat → /chat/completions(responses_to_chat_completions_with_reasoning)、anthropic → /v1/messages(responses_request_to_anthropic,含 max_output_tokens 注入与 [1m] 剥离 + context-1m beta 头)
- 鉴权改用 ClaudeAdapter/CodexAdapter 的 extract_auth + get_auth_headers(与真实出站请求同源:AUTH_TOKEN→Bearer、ANTHROPIC_API_KEY→x-api-key、gemini→x-goog-api-key、GeminiCli OAuth→Bearer、OpenRouter→Bearer、Codex 按 apiKeyField)
- 转换前剥离 [1m] 标记;meta.isFullUrl 或 base 已是完整端点时直接使用 base_url;自定义 body 浅合并移至转换后的最终协议体
- 新增 18 个目标构造断言单测;Comet 归档产物同步提交

chore: archive connectivity-test-optimizations

feat(connectivity-test): 单一模型表格 + 实时拉取模型 + 勾选即测试范围 + 新默认提示词

- 弹窗合并为单一模型表格:打开即全部模型行(初始待测试)、行勾选 + 表头全选(含半选态)、5 格统计卡,移除上方独立选择区
- 获取模型列表:凭据提取抽为共享模块 src/utils/providerCredentials.ts(ModelQuickSwitchDialog 同步改引用),复用 fetch_models_for_config 拉取 /v1/models,按 ID 追加合并去重、仅会话内有效
- 默认提示词改为「你好,你可以帮我做什么事情」:前后端常量同步,merge 差异判断基准跟随
- 勾选即测试范围:后端 connectivity_test_provider_models 新增 modelIds 参数(None/空白回退静态清单),前端逐模型独立 invoke 并行 + 行级流式更新
- 一轮测完自动勾选失败项,全部成功保持原勾选

fix(connectivity-test): UI 四项修复

- 弹窗抬至 alert 层(z-[60]):打开连通性测试时 header 切换栏正确变灰,
  详情子弹窗同步抬层(同 z + 更晚挂载 portal 在上)
- 批量检测入口移至 header 图标组(状态提升到 App,ProviderList 经
  props 读取探针结果),不再另起一行
- 批量检测可停止:runId 代次守卫(Tauri invoke 无法真正取消),再次
  点击即清除 waiting/running 徽标、丢弃在途迟到写入、不再拉起排队项
- 弹窗打开自动勾选模型:已保存默认测试模型 → 供应商当前模型
  (claude env.ANTHROPIC_MODEL / codex config.toml model,新增
  currentProviderModelId 与后端同口径)→ 清单首个

前端全量 141 文件 / 1106 测试全绿;typecheck clean

chore: archive per-model-connectivity-test

fix(connectivity-test): shouldShowTestEntry 增判 providerType + 收尾清理

chore(connectivity-test): 勾选 Task 9 / OpenSpec 5.1-5.2 + verify 报告

chore(connectivity-test): 勾选 Task 8 / OpenSpec 4.1-4.3

refactor(connectivity-test): 移除旧 stream_check 可达性探测全链路(保留表结构)

- 删除 6 个文件:commands/stream_check.rs、services/stream_check.rs、
  database/dao/stream_check.rs、hooks/useStreamCheck.ts、lib/api/connectivity-check.ts、
  components/usage/ConnectivityCheckConfigPanel.tsx
- 移除 mod.rs 声明、lib.rs generate_handler! 注册、cleanup_old_stream_check_logs 调用
- 从 SYNC_SKIP_TABLES / SYNC_PRESERVE_TABLES 移除 stream_check_logs(表结构保留在 schema.rs)
- 更新 backup.rs 测试:移除 stream_check_logs fixture 与断言
- 更新 SettingsPage:移除 ConnectivityCheckConfigPanel AccordionItem 与 FlaskConical 导入
- i18n:删除 streamCheck.* 命名空间(zh/en/ja/zh-TW 四语言)
- 更新注释:移除 stream_check 旧引用(forwarder.rs、provider.rs、userAgentPresets.ts、
  connectivity_test.rs)
- 修复 4 个 pre-existing clippy lint(connectivity_test: matches! 宏、unused_unit、
  derive Default)
- 移除 ProviderList.test.tsx 中已失效的 useStreamCheck mock

chore(connectivity-test): 勾选 Task 7 / OpenSpec 2.4 3.1-3.3

feat(connectivity-test): 供应商列表集成(弹窗入口 + 批量探针徽标)

chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
it…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
设置页可隐藏主页顶栏批量检测入口;关闭时停止进行中的探测。旧数据缺字段时默认显示。README 补上该开关,以及从其他应用导入供应商。

chore(native): archive sync-upstream-latest

chore(native): record sync-upstream-latest verification state

Commit the Runtime-managed comet-state.yaml and verification.md so the
working tree is clean after the v3.20.4 upstream sync.

chore: sync fork with upstream v3.20.4

Rebase fork main onto upstream/main (v3.20.4, 8e478b2b, 40 new commits)
while keeping all fork modifications: connectivity test, model quick
switch, sidebar panel visibility, preset filtering, tray left-click
toggle, dual website links, cross-app provider import, disabled in-app
updater, fork CI.

- Bump fork version to 3.20.4-1 (package.json, tauri.conf.json,
  Cargo.toml, Cargo.lock).
- Keep vitest testTimeout at 30s so full provider-form jsdom tests stop
  flaking past the 5s default.
- Resolved conflicts: version files kept on the fork side, dropped the
  duplicate GitHub button in AboutSection (upstream now ships its own
  star prompt), and restored .comet/config.yaml with native enabled.
- Updated HOW_TO_REBASE_UPSTREAM.md whitelist with the cross-app import
  files, searchable model pickers, and the dev config.

feat(provider-import): 从其他已启用应用导入供应商配置

新建/编辑供应商表单里,可从其他已启用应用的已有供应商一次性带入
名称、备注、官网链接、API Key、请求地址、默认(兜底)模型名与上游
API 格式,其余配置由用户自行填写。10 个应用的配置形状各不相同,
映射层按目标应用分别落键;编辑模式只覆盖这三个键位,配置对象其余
部分(providerKey、模型表、Codex modelCatalog、Gemini 顶层 config、
env/options/auth 其他键含 Codex 登录态键)原样保留。

- src/utils/providerImport.ts:读侧按应用取可迁移字段;写侧拆成
  buildImportedSettingsConfig(新建,按目标应用「自定义」模板重建)
  与 patchImportedSettingsConfig(编辑,就地覆盖)两条路径
- src/components/providers/forms/ProviderImportEntry.tsx:来源应用
  按钮行 + 供应商选择弹窗,预告「本次将写入」的字段,不渲染 API Key
- useProviderImportSources / useProviderImportApply:来源枚举与表单
  写入;ProviderPresetSelector 增加 extraActions 插槽
- src/utils/piProviderConfig.ts:buildPiSettingsConfig 自
  PiProviderForm 抽出,供映射层复用
- i18n 四语言新增 providerImport 文案

release 工作流改为直接以正式版发布(prerelease: false),不再先挂
预发布等人工提升;注意这同时移除了那道人工放行关卡,tag 一推发布
即对 /releases/latest、下载页和应用内更新器可见。sync-r2 不受影响,
其 released 触发器对直接以 stable 发布同样触发。

chore(native): archive cross-app-provider-import

chore(native): archive sync-upstream-3203

chore: sync fork with upstream v3.20.3 and rebrand top-left link to GitHub

Rebase fork main onto upstream/main (v3.20.3, 06082e18, 33 new commits)
while keeping all fork modifications: connectivity test, model quick
switch, sidebar panel visibility, preset filtering, tray left-click
toggle, dual website links, disabled in-app updater, fork CI.

- Bump fork version to 3.20.3-1 (package.json, tauri.conf.json,
  Cargo.toml, Cargo.lock).
- Merged upstream's v18->v19 schema migration with the fork's
  website_url_2 column migration in the same migration arm.
- Added mcode entry to forkOfficialAllowlist (no official presets,
  same treatment as hermes/openclaw/opencode/pi).
- Changed the top-left CC Switch brand link target from
  https://ccswitch.io to https://github.com/farion1231/cc-switch.
- Updated HOW_TO_REBASE_UPSTREAM.md whitelist with the brand link
  override.

style: apply prettier to files flagged by CI format check

修复 CI Frontend Checks 的 Check formatting 步骤失败
(App.tsx、ConnectivityTestDialog、ProviderCard、ProviderList、
provider schema 共 5 个文件)。

style: apply cargo fmt to connectivity-test era backend files

修复 CI 的 Check Rust formatting 步骤失败(connectivity_test、
backup、tests、provider 等文件格式不符合 rustfmt 规范)。

chore(native): archive sync-upstream

docs(fork): update rebase whitelist for connectivity-test era fork features

- 白名单补充连通性测试前后端、模型快捷切换、双官网链接测试、
  CI 裁剪版、README/CHANGELOG、.comet/superpowers 产物等 fork 专属文件
- 新增共享文件 fork 专属语义表(托盘左键切换、禁用更新器、hidden 字段等)
- 修正 §3.1 rebase 方向说明:rebase 中 fork 侧是 --theirs 而非 --ours
  (v3.20.2 同步实测确认,与 merge 语义相反)

chore: bump fork version to 3.20.2-1 after upstream v3.20.2 rebase

重放 fork 提交到 upstream/main(f3b18df1,v3.20.2)后统一版本号。
Cargo.lock 同步移除 updater 依赖树(fork 已禁用应用内更新器)。

chore(native): archive tray-left-click-toggle-window

chore(comet): 记录 tray-left-click-toggle-window 验收通过与归档状态

feat(tray): 托盘图标左键单击切换主窗口显示/隐藏

- tray.rs 新增 show_main_window/hide_main_window/toggle_main_window 助手:
  显示语义与"打开主界面"一致,隐藏与"关闭到托盘"平台处理一致
  (Windows set_skip_taskbar、macOS apply_tray_policy)
- lib.rs 托盘 show_menu_on_left_click 改为 false,左键 Click(Left, Up)
  触发切换(过滤 Down/Up 双事件),右键仍弹菜单;Enter/Click 用量刷新保持
- show_main 菜单事件与 macOS Reopen 复用 show_main_window

docs(readme): 更新日志补充连通性测试、双官网链接与托盘左键切换

feat(model-quick-switch): 获取模型列表成功后自动展开选择器

SearchableModelPicker 增加可选 open/onOpenChange 受控展开(不传保持内部自管,
表单等既有用法不受影响);快捷切换弹窗拉取成功且列表非空时直接展开,关闭弹窗复位。

chore(native): archive fix-quick-switch-1m

chore(comet): 记录 fix-quick-switch-1m 验收通过与归档状态

fix(model-quick-switch): 补齐 Haiku 显示名同步,支持不选模型仅翻转 1M

- 显示名同步映射补上 ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME(沿用空/旧base/旧base[1M] 覆盖、自定义保留语义)
- 新增 setClaudeOneMInSettings:仅原地翻转 1M 标记,不改各角色模型 base 与显示名
- 弹窗打开时 1M 开关按供应商当前状态初始化,避免已开 1M 被静默剥离
- claude 未选模型且 1M 状态有变化时允许保存;无可应用变化时保存禁用
- 补单测与四语言 toast 文案

feat(provider-website-links): 供应商支持第二个官网链接,主页横排双链接

- Rust Provider/UniversalProvider 新增 website_url_2(serde websiteUrl2,skip none)
- SQLite providers 表新增 website_url_2 列(schema v18→v19 幂等迁移 + create_tables 补列 + 旧 config.json 迁移)
- DAO SELECT/INSERT/UPDATE/replace 全链路带出新列
- 表单共享 BasicFormFields + 统一供应商表单第二输入框;zod 同第一链接规则校验与回填
- ProviderCard 主页双链接横排、两空格分隔、逐链接 openExternal 打开默认浏览器
- 搜索匹配、复制供应商、统一供应商生成 claude/codex/gemini 随行透传
- gemini_auth packycode 认证检测纳入 website_url_2;ClaudeDesktop/GrokBuild 预设切换清空第二链接;卡片同值链接去重
- 4 语言 i18n + 前端/前端单测覆盖

Comet change dual-website-links-homepage(19/19 验收通过,归档)

feat(connectivity-test): 拉取模型列表改为「搜索添加」选择器交互

- 新增共享组件 SearchableModelMultiPicker:搜索过滤 + 多选 + 已在列表标记 + 已选计数
- ConnectivityTestDialog 获取模型列表后不再全量追加合并去重,改为会话内缓存完整列表并自动弹出一次选择器;用户搜索挑选后添加进表格并自动勾选
- 重新拉取只刷新缓存,已添加进表格的行保留;拉取结果仍仅会话内有效,不写回供应商配置
- 四语言 i18n 新增 searchAdd / addToTest / alreadyInList / selectedCount
- 同步更新 ConnectivityTestDialog 测试并新增 SearchableModelMultiPicker 测试

feat(connectivity-test): 探测目标按供应商 API 格式对齐真实转发链路

- build_claude 按 get_claude_api_format 分派端点与请求体:openai_chat → /v1/chat/completions(复用 transform_claude_request_for_api_format,流式注入 stream_options.include_usage)、openai_responses → /v1/responses、gemini_native → /v1beta/models/{model}:{method}(resolve_gemini_native_url)、anthropic 保持 /v1/messages 并补 anthropic-version/beta 头
- build_codex 按 should_convert_codex_responses_to_chat/anthropic 分派:chat → /chat/completions(responses_to_chat_completions_with_reasoning)、anthropic → /v1/messages(responses_request_to_anthropic,含 max_output_tokens 注入与 [1m] 剥离 + context-1m beta 头)
- 鉴权改用 ClaudeAdapter/CodexAdapter 的 extract_auth + get_auth_headers(与真实出站请求同源:AUTH_TOKEN→Bearer、ANTHROPIC_API_KEY→x-api-key、gemini→x-goog-api-key、GeminiCli OAuth→Bearer、OpenRouter→Bearer、Codex 按 apiKeyField)
- 转换前剥离 [1m] 标记;meta.isFullUrl 或 base 已是完整端点时直接使用 base_url;自定义 body 浅合并移至转换后的最终协议体
- 新增 18 个目标构造断言单测;Comet 归档产物同步提交

chore: archive connectivity-test-optimizations

feat(connectivity-test): 单一模型表格 + 实时拉取模型 + 勾选即测试范围 + 新默认提示词

- 弹窗合并为单一模型表格:打开即全部模型行(初始待测试)、行勾选 + 表头全选(含半选态)、5 格统计卡,移除上方独立选择区
- 获取模型列表:凭据提取抽为共享模块 src/utils/providerCredentials.ts(ModelQuickSwitchDialog 同步改引用),复用 fetch_models_for_config 拉取 /v1/models,按 ID 追加合并去重、仅会话内有效
- 默认提示词改为「你好,你可以帮我做什么事情」:前后端常量同步,merge 差异判断基准跟随
- 勾选即测试范围:后端 connectivity_test_provider_models 新增 modelIds 参数(None/空白回退静态清单),前端逐模型独立 invoke 并行 + 行级流式更新
- 一轮测完自动勾选失败项,全部成功保持原勾选

fix(connectivity-test): UI 四项修复

- 弹窗抬至 alert 层(z-[60]):打开连通性测试时 header 切换栏正确变灰,
  详情子弹窗同步抬层(同 z + 更晚挂载 portal 在上)
- 批量检测入口移至 header 图标组(状态提升到 App,ProviderList 经
  props 读取探针结果),不再另起一行
- 批量检测可停止:runId 代次守卫(Tauri invoke 无法真正取消),再次
  点击即清除 waiting/running 徽标、丢弃在途迟到写入、不再拉起排队项
- 弹窗打开自动勾选模型:已保存默认测试模型 → 供应商当前模型
  (claude env.ANTHROPIC_MODEL / codex config.toml model,新增
  currentProviderModelId 与后端同口径)→ 清单首个

前端全量 141 文件 / 1106 测试全绿;typecheck clean

chore: archive per-model-connectivity-test

fix(connectivity-test): shouldShowTestEntry 增判 providerType + 收尾清理

chore(connectivity-test): 勾选 Task 9 / OpenSpec 5.1-5.2 + verify 报告

chore(connectivity-test): 勾选 Task 8 / OpenSpec 4.1-4.3

refactor(connectivity-test): 移除旧 stream_check 可达性探测全链路(保留表结构)

- 删除 6 个文件:commands/stream_check.rs、services/stream_check.rs、
  database/dao/stream_check.rs、hooks/useStreamCheck.ts、lib/api/connectivity-check.ts、
  components/usage/ConnectivityCheckConfigPanel.tsx
- 移除 mod.rs 声明、lib.rs generate_handler! 注册、cleanup_old_stream_check_logs 调用
- 从 SYNC_SKIP_TABLES / SYNC_PRESERVE_TABLES 移除 stream_check_logs(表结构保留在 schema.rs)
- 更新 backup.rs 测试:移除 stream_check_logs fixture 与断言
- 更新 SettingsPage:移除 ConnectivityCheckConfigPanel AccordionItem 与 FlaskConical 导入
- i18n:删除 streamCheck.* 命名空间(zh/en/ja/zh-TW 四语言)
- 更新注释:移除 stream_check 旧引用(forwarder.rs、provider.rs、userAgentPresets.ts、
  connectivity_test.rs)
- 修复 4 个 pre-existing clippy lint(connectivity_test: matches! 宏、unused_unit、
  derive Default)
- 移除 ProviderList.test.tsx 中已失效的 useStreamCheck mock

chore(connectivity-test): 勾选 Task 7 / OpenSpec 2.4 3.1-3.3

feat(connectivity-test): 供应商列表集成(弹窗入口 + 批量探针徽标)

chore(connectivity-test): 勾选 Task 6 / OpenSpec 2.2-2.3

fix(connectivity-test): 模型列表解析与后端对齐(每条目首个匹配,提取共享 helper)

feat(connectivity-test): 连通性测试弹窗/详情/徽标组件与四语言文案

chore(connectivity-test): 勾选 Task 5 / OpenSpec 2.1

test(connectivity-test): 锁定 toBackendParams 字段映射(customHeaders/customBody 等)

feat(connectivity-test): 前端命令绑定与测试/探针状态 hooks

chore(connectivity-test): 勾选 Task 4 / OpenSpec 1.4

feat(connectivity-test): 测试参数持久化纯函数(缺省即默认值)

chore(connectivity-test): 勾选 Task 3 / OpenSpec 1.3

feat(connectivity-test): Tauri 命令与注册,官方/动态端点过滤,sanitize 隔离私有字段

chore(connectivity-test): 勾选 Task 2 / OpenSpec 1.2

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

feat(connectivity-test): 后端直连执行器(reqwest+SSE,总/空闲超时,度量与错误识别)

chore(connectivity-test): 勾选 Task 1 / OpenSpec 1.1

fix(connectivity-test): claude 鉴权对齐 adapter 顶层 apiKey/api_key 兜底

feat(connectivity-test): 新增后端连通性测试服务类型/模型解析/协议构造

chore: archive model-quick-switch-picker-scroll

chore: model-quick-switch-picker-scroll 验证报告与状态元数据

fix: 模型快捷切换弹窗内模型列表滚轮无法滚动

模态 Dialog 的 RemoveScroll 会对 shards 外的 portaled PopoverContent
wheel 事件无条件 preventDefault。在 SearchableModelPicker 的
PopoverContent 捕获阶段阻断 wheel 传播,恢复原生滚动。

chore: bump fork version to 3.20.1-1 after upstream v3.20.1 rebase

fix(form): only auto-expand Claude advanced options on divergent model mapping

Uniform mapping produced by the quick-set button (all roles equal to the
fallback model) no longer counts as an advanced value, so the advanced
section stays collapsed on edit. A 1M marker is stripped before comparing
so marked roles are not misjudged as divergent. Truly customized mappings
still auto-expand the section.

feat: align fallback-model quick-set buttons with model input right edge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

docs: update README with fork build notes and quick-move provider changes

chore: archive comet-native claude-model-quick-access

feat: surface Claude fallback model as quick-access section below endpoint

Move the default fallback model (ANTHROPIC_MODEL) block out of the
collapsed Advanced Options into a quick-access section directly below
the request endpoint: input + 1M checkbox + quick-set + fetch-models
buttons are all reachable without expanding.

- header toggle-all "declare 1M" now includes the fallback model
  (Haiku still excluded); partial selection shows indeterminate state
- drop the duplicated quick-set/fetch buttons and the old fallback
  block inside Advanced Options
- exclude claudeModel from hasAnyAdvancedValue so a fallback-only
  value no longer auto-expands Advanced Options
- quick-set priority, env write semantics, and OAuth preset branches
  unchanged; add 4 component tests covering the new layout

chore: archive comet-native provider-right-click-top-bottom

Enable native workflow in .comet/config.yaml and move the Comet managed
state block in .gitignore so the exclusion rules stay grouped with the
other fork tool artifacts.

feat: add provider right-click move-to-top/bottom and insert new providers at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini c…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and the tests assert each
product's line-up in order so future drift is caught.

Declare thinking control alongside the capabilities rather than leaving it to
defaults: the 31 DeepSeek entries carry a Tencent-specific compat so selecting
"off" actually disables thinking instead of silently billing reasoning tokens,
the eight Kimi K2.7 Code entries no longer offer an "off" level the model
cannot honour, and kimi-k2.7-code-highspeed is marked image-capable so
tool-returned screenshots are no longer dropped.

Drop minimax-m2.5 from every app: it is gone from Tencent's model tables and
the platform is retiring it.
fix(codex): proxy image generations endpoint (#7036)

Codex's built-in ImageGen calls the legacy OpenAI Images API, but the local
proxy only registered Responses / Chat Completions / Compact / Alpha Search
routes, so `/v1/images/generations` returned an empty 404 under Codex routing.

- Register `/images/generations` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them as a Codex standalone passthrough shared with Alpha Search.
- Generalize the Alpha Search full-URL rewrite into
  `rewrite_codex_standalone_full_url`: derive the sibling
  `/images/generations` from full Responses / Compact / Chat Completions URLs,
  keep an existing Images URL as-is, and fail closed on opaque full URLs.
- Record `usage.input_tokens` / `output_tokens` through the Codex usage parser.

Fixes #5429
Fixes #6745
feat(pricing): add Gemini 3.8 Flash pricing (#7164)

Seeds the built-in pricing row for Gemini 3.8 Flash at 0.75 / 3.75 input/output per million tokens, 0.075 cache read, no cache-write charge, plus a regression test locking all four fields.

Values verified against Google's own pricing page (introductory price through 2026-12-31; list price 1.50 / 7.50 / 0.15 from 2027-01-01) and models.dev.
feat(provider): extend PPIO presets for Pi (#6870)

Add a PPIO provider preset for Pi using the OpenAI-compatible endpoint,
and pin an explicit models URL on the Claude Code PPIO preset so model
discovery resolves against https://api.ppio.com/openai/v1/models instead
of the derived candidates, which all return 404.

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 Flash pricing (#7163)
fix(proxy): enable parallel tool calls for Codex OAuth (#7024)

Codex OAuth requests defaulted parallel_tool_calls to false when the incoming
Anthropic request carried no explicit value, forcing Claude Code into one tool
call per model turn. Default it to true (matching codex-rs), map Anthropic
tool_choice.disable_parallel_tool_use to the inverse Responses value so explicit
serial execution is preserved, and add regression coverage for both directions.

Carries #5722 forward on top of current main.

Fixes #5719

Co-authored-by: 太一 <taiyi@douyu.tv>
feat(pricing): add GPT-6 Astra pricing (#7162)
fix(codex): resolve input_modalities for unknown models in vendor catalog (#6750)

When a model slug is not in the bundled DeepSeek official catalog, the vendor path clones the flagship entry and previously inherited its text-only input_modalities. Resolve modalities for unmatched slugs through the same registry/fail-open logic the non-vendor path uses, so a vision variant such as deepseek-v4-flash-vision-exp is no longer declared text-only. Explicit user inputModalities still win; matched models keep the vendor's declaration verbatim.

Fixes #6725
fix(claude): expose Opus 5 and Sonnet 5 in takeover mode (#5882)

Bump the stable client-facing role aliases that takeover mode writes to
Claude Code: Opus claude-opus-4-8 -> claude-opus-5 and Sonnet
claude-sonnet-4-6 -> claude-sonnet-5, so the /model menu matches the
Claude Desktop default routes and preset defaults that already moved to
Opus 5 / Sonnet 5. Add "opus-5" to the adaptive-thinking classifier so
the Bedrock thinking optimizer, which runs on the client alias before
model mapping, keeps sending adaptive thinking instead of the removed
budget_tokens.

Routing is unaffected: the proxy maps client aliases by role keyword, and
the aliases are rewritten automatically the next time the proxy starts.

Fixes #5876

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 to built-in model pricing table (#6591)

Seed glm-5.3 at $1.4/$4.4 per MTok with $0.26 cache read, matching the
official Z.ai list price (identical to the GLM-5.2 tier):
https://docs.z.ai/guides/overview/pricing

New model id, so seed only; no repair entry and no SCHEMA_VERSION bump.
Existing installs pick the row up on next launch via INSERT OR IGNORE, and
the startup cost backfill re-prices historical glm-5.3 rows recorded at $0.
fix(codex): align OAuth client identity for GPT-6 (#7132)

Requests for gpt-6-astra through the Claude-to-Codex OAuth takeover route advertised Codex 0.144.1, below the model's 0.153.0 minimal_client_version, so ChatGPT's backend rejected them with HTTP 400 "requires a newer version of Codex". Bump the advertised client version to 0.153.4 and share the originator/version constants between generation and model discovery, which previously sent cc-switch's own package version and originator.

Two regression tests pin the generation headers and the model-list request to a version at or above the gpt-6-astra floor.

Refs #7129
fix(codex): disable supports_search_tool for DeepSeek catalog models (#6653)

DeepSeek's Responses API has no tool_search support, but the bundled
official catalog declared supports_search_tool=true for deepseek-v4-pro
and deepseek-v4-flash. Codex gates deferred/namespaced MCP tools on
this flag (search_tool_enabled = supports_search_tool &&
namespace_tools), so MCP tools were hidden behind a tool_search the
gateway cannot serve — no MCP tool could ever be called.

Set the flag to false for both models so Codex lists MCP tools inline.
Hosted web_search is unaffected (gated by provider capabilities, not
this flag) and DeepSeek's server-side web_search still works.

Fixes #6647
fix(codex): move $ref siblings into allOf for Moonshot/Kimi chat upstreams (#6863)

Moonshot's Chat Completions validator (api.moonshot.cn, api.moonshot.ai and the Kimi For Coding endpoint api.kimi.com) rejects a `$ref` that carries sibling keywords. Codex Desktop's built-in tool schemas carry exactly that shape, so every desktop turn routed through the local proxy to Kimi failed with HTTP 400.

When the resolved upstream host is Moonshot/Kimi, each `$ref` with siblings is moved into `allOf` after the Responses -> Chat conversion and the siblings stay in place. The traversal only enters schema-valued keywords and the rewrite is idempotent. Every other provider keeps byte-identical tool schemas, and the Codex -> Anthropic path is left alone because Moonshot's Anthropic-compatible endpoint accepts the original shape.

Fixes #6867

Co-authored-by: Jason <farion1231@gmail.com>
fix(codex): point Zhipu GLM presets at the official Responses endpoint (#6957)

Zhipu documents three separate base URLs per site (Anthropic /api/anthropic,
Chat /api/coding/paas/v4, Responses /api/v1) and warns that the wrong one
cannot use Coding Plan quota. Codex direct-connect speaks Responses, so the
presets must target /api/v1; the 400 in #6944 (`unknown variant custom`)
came from the Chat endpoint's strict legacy gateway.

- Switch the Zhipu GLM / Zhipu GLM en presets to apiFormat openai_responses
  (NativeResponses catalog: shell_command edits, no freeform apply_patch)
…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and the tests assert each
product's line-up in order so future drift is caught.

Declare thinking control alongside the capabilities rather than leaving it to
defaults: the 31 DeepSeek entries carry a Tencent-specific compat so selecting
"off" actually disables thinking instead of silently billing reasoning tokens,
the eight Kimi K2.7 Code entries no longer offer an "off" level the model
cannot honour, and kimi-k2.7-code-highspeed is marked image-capable so
tool-returned screenshots are no longer dropped.

Drop minimax-m2.5 from every app: it is gone from Tencent's model tables and
the platform is retiring it.
fix(codex): proxy image generations endpoint (#7036)

Codex's built-in ImageGen calls the legacy OpenAI Images API, but the local
proxy only registered Responses / Chat Completions / Compact / Alpha Search
routes, so `/v1/images/generations` returned an empty 404 under Codex routing.

- Register `/images/generations` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them as a Codex standalone passthrough shared with Alpha Search.
- Generalize the Alpha Search full-URL rewrite into
  `rewrite_codex_standalone_full_url`: derive the sibling
  `/images/generations` from full Responses / Compact / Chat Completions URLs,
  keep an existing Images URL as-is, and fail closed on opaque full URLs.
- Record `usage.input_tokens` / `output_tokens` through the Codex usage parser.

Fixes #5429
Fixes #6745
feat(pricing): add Gemini 3.8 Flash pricing (#7164)

Seeds the built-in pricing row for Gemini 3.8 Flash at 0.75 / 3.75 input/output per million tokens, 0.075 cache read, no cache-write charge, plus a regression test locking all four fields.

Values verified against Google's own pricing page (introductory price through 2026-12-31; list price 1.50 / 7.50 / 0.15 from 2027-01-01) and models.dev.
feat(provider): extend PPIO presets for Pi (#6870)

Add a PPIO provider preset for Pi using the OpenAI-compatible endpoint,
and pin an explicit models URL on the Claude Code PPIO preset so model
discovery resolves against https://api.ppio.com/openai/v1/models instead
of the derived candidates, which all return 404.

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 Flash pricing (#7163)
fix(proxy): enable parallel tool calls for Codex OAuth (#7024)

Codex OAuth requests defaulted parallel_tool_calls to false when the incoming
Anthropic request carried no explicit value, forcing Claude Code into one tool
call per model turn. Default it to true (matching codex-rs), map Anthropic
tool_choice.disable_parallel_tool_use to the inverse Responses value so explicit
serial execution is preserved, and add regression coverage for both directions.

Carries #5722 forward on top of current main.

Fixes #5719

Co-authored-by: 太一 <taiyi@douyu.tv>
feat(pricing): add GPT-6 Astra pricing (#7162)
fix(codex): resolve input_modalities for unknown models in vendor catalog (#6750)

When a model slug is not in the bundled DeepSeek official catalog, the vendor path clones the flagship entry and previously inherited its text-only input_modalities. Resolve modalities for unmatched slugs through the same registry/fail-open logic the non-vendor path uses, so a vision variant such as deepseek-v4-flash-vision-exp is no longer declared text-only. Explicit user inputModalities still win; matched models keep the vendor's declaration verbatim.

Fixes #6725
fix(claude): expose Opus 5 and Sonnet 5 in takeover mode (#5882)

Bump the stable client-facing role aliases that takeover mode writes to
Claude Code: Opus claude-opus-4-8 -> claude-opus-5 and Sonnet
claude-sonnet-4-6 -> claude-sonnet-5, so the /model menu matches the
Claude Desktop default routes and preset defaults that already moved to
Opus 5 / Sonnet 5. Add "opus-5" to the adaptive-thinking classifier so
the Bedrock thinking optimizer, which runs on the client alias before
model mapping, keeps sending adaptive thinking instead of the removed
budget_tokens.

Routing is unaffected: the proxy maps client aliases by role keyword, and
the aliases are rewritten automatically the next time the proxy starts.

Fixes #5876

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 to built-in model pricing table (#6591)

Seed glm-5.3 at $1.4/$4.4 per MTok with $0.26 cache read, matching the
official Z.ai list price (identical to the GLM-5.2 tier):
https://docs.z.ai/guides/overview/pricing

New model id, so seed only; no repair entry and no SCHEMA_VERSION bump.
Existing installs pick the row up on next launch via INSERT OR IGNORE, and
the startup cost backfill re-prices historical glm-5.3 rows recorded at $0.
fix(codex): align OAuth client identity for GPT-6 (#7132)

Requests for gpt-6-astra through the Claude-to-Codex OAuth takeover route advertised Codex 0.144.1, below the model's 0.153.0 minimal_client_version, so ChatGPT's backend rejected them with HTTP 400 "requires a newer version of Codex". Bump the advertised client version to 0.153.4 and share the originator/version constants between generation and model discovery, which previously sent cc-switch's own package version and originator.

Two regression tests pin the generation headers and the model-list request to a version at or above the gpt-6-astra floor.

Refs #7129
fix(codex): disable supports_search_tool for DeepSeek catalog models (#6653)

DeepSeek's Responses API has no tool_search support, but the bundled
official catalog declared supports_search_tool=true for deepseek-v4-pro
and deepseek-v4-flash. Codex gates deferred/namespaced MCP tools on
this flag (search_tool_enabled = supports_search_tool &&
namespace_tools), so MCP tools were hidden behind a tool_search the
gateway cannot serve — no MCP tool could ever be called.

Set the flag to false for both models so Codex lists MCP tools inline.
Hosted web_search is unaffected (gated by provider capabilities, not
this flag) and DeepSeek's server-side web_search still works.

Fixes #6647
fix(codex): move $ref siblings into allOf for Moonshot/Kimi chat upstreams (#6863)

Moonshot's Chat Completions validator (api.moonshot.cn, api.moonshot.ai and the Kimi For Coding endpoint api.kimi.com) rejects a `$ref` that carries sibling keywords. Codex Desktop's built-in tool schemas carry exactly that shape, so every desktop turn rou…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and the tests assert each
product's line-up in order so future drift is caught.

Declare thinking control alongside the capabilities rather than leaving it to
defaults: the 31 DeepSeek entries carry a Tencent-specific compat so selecting
"off" actually disables thinking instead of silently billing reasoning tokens,
the eight Kimi K2.7 Code entries no longer offer an "off" level the model
cannot honour, and kimi-k2.7-code-highspeed is marked image-capable so
tool-returned screenshots are no longer dropped.

Drop minimax-m2.5 from every app: it is gone from Tencent's model tables and
the platform is retiring it.
fix(codex): proxy image generations endpoint (#7036)

Codex's built-in ImageGen calls the legacy OpenAI Images API, but the local
proxy only registered Responses / Chat Completions / Compact / Alpha Search
routes, so `/v1/images/generations` returned an empty 404 under Codex routing.

- Register `/images/generations` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them as a Codex standalone passthrough shared with Alpha Search.
- Generalize the Alpha Search full-URL rewrite into
  `rewrite_codex_standalone_full_url`: derive the sibling
  `/images/generations` from full Responses / Compact / Chat Completions URLs,
  keep an existing Images URL as-is, and fail closed on opaque full URLs.
- Record `usage.input_tokens` / `output_tokens` through the Codex usage parser.

Fixes #5429
Fixes #6745
feat(pricing): add Gemini 3.8 Flash pricing (#7164)

Seeds the built-in pricing row for Gemini 3.8 Flash at 0.75 / 3.75 input/output per million tokens, 0.075 cache read, no cache-write charge, plus a regression test locking all four fields.

Values verified against Google's own pricing page (introductory price through 2026-12-31; list price 1.50 / 7.50 / 0.15 from 2027-01-01) and models.dev.
feat(provider): extend PPIO presets for Pi (#6870)

Add a PPIO provider preset for Pi using the OpenAI-compatible endpoint,
and pin an explicit models URL on the Claude Code PPIO preset so model
discovery resolves against https://api.ppio.com/openai/v1/models instead
of the derived candidates, which all return 404.

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 Flash pricing (#7163)
fix(proxy): enable parallel tool calls for Codex OAuth (#7024)

Codex OAuth requests defaulted parallel_tool_calls to false when the incoming
Anthropic request carried no explicit value, forcing Claude Code into one tool
call per model turn. Default it to true (matching codex-rs), map Anthropic
tool_choice.disable_parallel_tool_use to the inverse Responses value so explicit
serial execution is preserved, and add regression coverage for both directions.

Carries #5722 forward on top of current main.

Fixes #5719

Co-authored-by: 太一 <taiyi@douyu.tv>
feat(pricing): add GPT-6 Astra pricing (#7162)
fix(codex): resolve input_modalities for unknown models in vendor catalog (#6750)

When a model slug is not in the bundled DeepSeek official catalog, the vendor path clones the flagship entry and previously inherited its text-only input_modalities. Resolve modalities for unmatched slugs through the same registry/fail-open logic the non-vendor path uses, so a vision variant such as deepseek-v4-flash-vision-exp is no longer declared text-only. Explicit user inputModalities still win; matched models keep the vendor's declaration verbatim.

Fixes #6725
fix(claude): expose Opus 5 and Sonnet 5 in takeover mode (#5882)

Bump the stable client-facing role aliases that takeover mode writes to
Claude Code: Opus claude-opus-4-8 -> claude-opus-5 and Sonnet
claude-sonnet-4-6 -> claude-sonnet-5, so the /model menu matches the
Claude Desktop default routes and preset defaults that already moved to
Opus 5 / Sonnet 5. Add "opus-5" to the adaptive-thinking classifier so
the Bedrock thinking optimizer, which runs on the client alias before
model mapping, keeps sending adaptive thinking instead of the removed
budget_tokens.

Routing is unaffected: the proxy maps client aliases by role keyword, and
the aliases are rewritten automatically the next time the proxy starts.

Fixes #5876

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 to built-in model pricing table (#6591)

Seed glm-5.3 at $1.4/$4.4 per MTok with $0.26 cache read, matching the
official Z.ai list price (identical to the GLM-5.2 tier):
https://docs.z.ai/guides/overview/pricing

New model id, so seed only; no repair entry and no SCHEMA_VERSION bump.
Existing installs pick the row up on next launch via INSERT OR IGNORE, and
the startup cost backfill re-prices historical glm-5.3 rows recorded at $0.
fix(codex): align OAuth client identity for GPT-6 (#7132)

Requests for gpt-6-astra through the Claude-to-Codex OAuth takeover route advertised Codex 0.144.1, below the model's 0.153.0 minimal_client_version, so ChatGPT's backend rejected them with HTTP 400 "requires a newer version of Codex". Bump the advertised client version to 0.153.4 and share the originator/version constants between generation and model discovery, which previously sent cc-switch's own package version and originator.

Two regression tests pin the generation headers and the model-list request to a version at or above the gpt-6-astra floor.

Refs #7129
fix(codex): disable supports_search_tool for DeepSeek catalog models (#6653)

DeepSeek's Responses API has no tool_search support, but the bundled
official catalog declared supports_search_tool=true for deepseek-v4-pro
and deepseek-v4-flash. Codex gates deferred/namespaced MCP tools on
this flag (search_tool_enabled = supports_search_tool &&
namespace_tools), so MCP tools were hidden behind a tool_search the
gateway cannot serve — no MCP tool could ever be called.

Set the flag to false for both models so Codex lists MCP tools inline.
Hosted web_search is unaffected (gated by provider capabilit…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and the tests assert each
product's line-up in order so future drift is caught.

Declare thinking control alongside the capabilities rather than leaving it to
defaults: the 31 DeepSeek entries carry a Tencent-specific compat so selecting
"off" actually disables thinking instead of silently billing reasoning tokens,
the eight Kimi K2.7 Code entries no longer offer an "off" level the model
cannot honour, and kimi-k2.7-code-highspeed is marked image-capable so
tool-returned screenshots are no longer dropped.

Drop minimax-m2.5 from every app: it is gone from Tencent's model tables and
the platform is retiring it.
fix(codex): proxy image generations endpoint (#7036)

Codex's built-in ImageGen calls the legacy OpenAI Images API, but the local
proxy only registered Responses / Chat Completions / Compact / Alpha Search
routes, so `/v1/images/generations` returned an empty 404 under Codex routing.

- Register `/images/generations` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them as a Codex standalone passthrough shared with Alpha Search.
- Generalize the Alpha Search full-URL rewrite into
  `rewrite_codex_standalone_full_url`: derive the sibling
  `/images/generations` from full Responses / Compact / Chat Completions URLs,
  keep an existing Images URL as-is, and fail closed on opaque full URLs.
- Record `usage.input_tokens` / `output_tokens` through the Codex usage parser.

Fixes #5429
Fixes #6745
feat(pricing): add Gemini 3.8 Flash pricing (#7164)

Seeds the built-in pricing row for Gemini 3.8 Flash at 0.75 / 3.75 input/output per million tokens, 0.075 cache read, no cache-write charge, plus a regression test locking all four fields.

Values verified against Google's own pricing page (introductory price through 2026-12-31; list price 1.50 / 7.50 / 0.15 from 2027-01-01) and models.dev.
feat(provider): extend PPIO presets for Pi (#6870)

Add a PPIO provider preset for Pi using the OpenAI-compatible endpoint,
and pin an explicit models URL on the Claude Code PPIO preset so model
discovery resolves against https://api.ppio.com/openai/v1/models instead
of the derived candidates, which all return 404.

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 Flash pricing (#7163)
fix(proxy): enable parallel tool calls for Codex OAuth (#7024)

Codex OAuth requests defaulted parallel_tool_calls to false when the incoming
Anthropic request carried no explicit value, forcing Claude Code into one tool
call per model turn. Default it to true (matching codex-rs), map Anthropic
tool_choice.disable_parallel_tool_use to the inverse Responses value so explicit
serial execution is preserved, and add regression coverage for both directions.

Carries #5722 forward on top of current main.

Fixes #5719

Co-authored-by: 太一 <taiyi@douyu.tv>
feat(pricing): add GPT-6 Astra pricing (#7162)
fix(codex): resolve input_modalities for unknown models in vendor catalog (#6750)

When a model slug is not in the bundled DeepSeek official catalog, the vendor path clones the flagship entry and previously inherited its text-only input_modalities. Resolve modalities for unmatched slugs through the same registry/fail-open logic the non-vendor path uses, so a vision variant such as deepseek-v4-flash-vision-exp is no longer declared text-only. Explicit user inputModalities still win; matched models keep the vendor's declaration verbatim.

Fixes #6725
fix(claude): expose Opus 5 and Sonnet 5 in takeover mode (#5882)

Bump the stable client-facing role aliases that takeover mode writes to
Claude Code: Opus claude-opus-4-8 -> claude-opus-5 and Sonnet
claude-sonnet-4-6 -> claude-sonnet-5, so the /model menu matches the
Claude Desktop default routes and preset defaults that already moved to
Opus 5 / Sonnet 5. Add "opus-5" to the adaptive-thinking classifier so
the Bedrock thinking optimizer, which runs on the client alias before
model mapping, keeps sending adaptive thinking instead of the removed
budget_tokens.

Routing is unaffected: the proxy maps client aliases by role keyword, and
the aliases are rewritten automatically the next time the proxy starts.

Fixes #5876

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 to built-in model pricing table (#6591)

Seed glm-5.3 at $1.4/$4.4 per MTok with $0.26 cache read, matching the
official Z.ai list price (identical to the GLM-5.2 tier):
https://docs.z.ai/guides/overview/pricing

New model id, so seed only; no repair entry and no SCHEMA_VERSION bump.
Existing installs pick the row up on next launch via INSERT OR IGNORE, and
the startup cost backfill re-prices historical glm-5.3 rows recorded at $0.
fix(codex): align OAuth client identity for GPT-6 (#7132)

Requests for gpt-6-astra through the Claude-to-Codex OAuth takeover route advertised Codex 0.144.1, below the model's 0.153.0 minimal_client_version, so ChatGPT's backend rejected them with HTTP 400 "requires a newer version of Codex". Bump the advertised client version to 0.153.4 and share the originator/version constants between generation and model discovery, which previously sent cc-switch's own package version and originator.

Two regression tests pin the generation headers and the model-list request to a version at or above the gpt-6-astra floor.

Refs #7129
fix(codex): disable supports_search_tool for DeepSeek catalog models (#6653)

DeepSeek's Responses API has no tool_search support, but the bundled
official catalog declared supports_search_tool=true for deepseek-v4-pro
and deepseek-v4-flash. Codex gates…
tunecc added a commit to tunecc/cc-switch that referenced this pull request Oct 7, 2026
…iders at second position

- ProviderList: context menu with quick move-to-top/move-to-bottom, reusing
  update_providers_sort_order and refreshing tray menu like drag sort
- useAddProviderMutation: default insert at index 1 with explicit sortIndex
  materialization for existing items (avoids defined-first sort trap)
- i18n: quickMove* strings for zh/en/zh-TW/ja

ci: stop flaky WSL2 linker job from blocking main

The backend-windows-wsl2 job intermittently fails with LNK1327 /
mt.exe c1010070 when the Windows runner's mt.exe cannot parse a
manifest temp file on the WSL2 UNC TEMP path. The same flaky failure
occurs upstream and is unrelated to fork changes. Mark the job
continue-on-error so main stops going red on it; the result stays
visible in the Actions UI.

style: apply prettier to files touched by recent comet archives

Frontend CI format:check failed on 6 files modified by the app-name
and model-quick-switch archive commits plus the updater-disable commit.
Run prettier --write so format:check passes again.

chore: archive ccs-fork-app-name-cc-switch

chore: archive ccs-model-quick-switch-ui-polish

docs: rewrite README to document fork differences vs upstream

README 从上游完整功能介绍改为仅记录 fork 与上游的差别,完整功能
文档指回上游。涵盖 8 项 fork 魔改:构建标识/开发预览、预设精简、
Claude 表单调整、模型搜索移植、主页模型快速切换、侧边栏面板显隐、
关闭应用内更新、打包范围,并说明版本号与 rebase 同步流程。

feat: disable in-app updater, point users to GitHub releases

移除 tauri-plugin-updater 依赖与自动更新链路,改为统一引导用户到
GitHub Releases 手动下载新版。

- 前端:updater.ts 恒返回 up-to-date;UpdateContext 取消启动自检;
  AboutSection「检查更新」直接打开 fork releases 页;DatabaseUpgrade
  恢复界面去掉下载进度条,改为打开发布页。
- 后端:移除 updater 插件注册与 UpdaterExt 调用;
  install_update_and_restart 降级为空壳,check_app_update_available
  返回当前版本号;check_for_updates 指向 fork releases;capabilities
  去掉 updater:default 权限。
- 打包:tauri.conf.json 删 plugins.updater、createUpdaterArtifacts=false;
  Cargo.toml / package.json 删 updater 依赖,Cargo.lock / pnpm-lock
  同步重生成。
- CI:release.yml 移除签名收集与 assemble-latest-json job,删除
  .tar.gz/.sig 处理,release body 去掉 updater 说明。

版本号统一为 3.20.0-1(合法 semver 且 MSI 第 4 位为 1)。
Cargo.lock / pnpm-lock.yaml 已随依赖移除重生成,cargo check 通过。

ci: build only Windows x64 and macOS arm64, unsigned

feat: gate dev panel behind dev-only env flag

chore(comet): archive ccs-sidebar-panels-visibility change

fix: apply skills visibility guard to hermes branch toolbar button

feat: include prompts in sidebar panel visibility settings

fix: persist visibleSidebarPanels setting in backend AppSettings

style: use compact pill toggles for sidebar panel visibility

feat: add sidebar panel visibility switches to settings

feat: add sidebar panel visibility settings and wire into toolbar buttons

chore(comet): archive ccs-home-model-display change

fix(fork): read grokbuild api key from config TOML in quick switch dialog

feat(fork): align model badge with upstream extractModelBadge

feat(fork): show current model badge and quick-switch entry on provider cards

feat(fork): add model quick switch dialog

feat(fork): add provider model read/write utils with tests

chore(comet): archive ccs-model-search-port change

feat(i18n): add model search picker keys for all locales

feat(form): port SearchableModelPicker from upstream for searchable model selection

chore(comet): archive ccs-claude-form-layout change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-claude-form-layout/
- Sync claude-form-layout spec to main specs
- Annotate design doc as archived
- Add verification report

fix(i18n): update advanced options hint, add ja/zh-TW toggle-all, drop dead apiFormatHint key

fix(form): correct 1M toggle-all alignment direction (ml-1)

fix(form): align 1M toggle-all checkbox with row checkboxes

refactor(form): move api format select into EndpointField rightSlot

fix(form): widen api format select to fit option text

feat(form): add 1M toggle-all checkbox in model mapping header

feat(form): move api format select next to endpoint input

chore(comet): archive ccs-provider-presets-trim change

- Move change artifacts to docs/openspec/changes/archive/2026-08-19-ccs-provider-presets-trim/
- Sync provider-presets-visibility spec to main specs
- Annotate design doc as archived
- Add verification report

docs(fork): add presets-trim files to rebase whitelist

test(fork): mock IS_FORK_BUILD false in unit tests for upstream-form semantics

feat(fork): wire official preset filter into provider forms

feat(fork): add hidden field to preset interfaces

feat(fork): add official preset allowlist and filter util

chore(comet): archive ccs-fork-scaffolding change

- Move change artifacts to docs/openspec/changes/archive/2026-08-18-ccs-fork-scaffolding/
- Sync fork-build-identity spec to main specs
- Annotate design doc as archived
- Add verification report

feat(fork): add dev config with isolated identifier and config dir

docs(fork): complete rebase whitelist with all fork-touched files

fix(fork): guard setTitle with isTauri in test/non-tauri env

fix(fork): define __CCS_FORK_BUILD__ in vitest config

feat(fork): add DevPanel and settings footer entry

feat(fork): set macOS window title to CC Switch (Fork) on startup

feat(fork): inject __CCS_FORK_BUILD__ compile-time constant

feat(fork): bump version to 3.19.2-fork.1 and rename product to CC Switch (Fork)

docs(fork): add upstream rebase workflow and ignore tool artifacts

fix(proxy): accept the whole grok-4.x (x>=5) family in the reasoning-effort whitelist (#7369)

Replace the verbatim grok-4.5 / grok-4.6 entries in supports_reasoning_effort with a rule that parses the grok-4.x minor version and accepts x >= 5, mirroring the existing GPT-5+ rule. This covers grok-4.7 (released 2026-09-21), whose reasoning effort was previously dropped on the Claude -> Chat, Claude -> Responses and Codex Responses -> Chat conversion paths, and lets future releases pass without another whitelist edit. The grok-build-* family is retained for saved providers.

Co-authored-by: allenxu09 <171831965+allenxu09@users.noreply.github.com>
docs(release): add v3.20.4 release notes

chore(release): v3.20.4

feat(pricing): add Grok 4.7 and GLM-5.3-FlashX

Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.

fix(i18n): remove outdated OpenCode Go referral offer

fix: add SudoCode.chat alternate API endpoint

feat(settings): add a GitHub star prompt to the About card

Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.

- Show a one-line prompt in the primary color that opens the repo,
  followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
  on it, tint it with the primary color (kept below the solid "check
  for updates" action), and add a slowly spinning star that stops
  under prefers-reduced-motion
- Add settings.starPrompt to all four locales

feat(presets): add Soshow aggregator presets

Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.

- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
  Model API doc requires for the Claude protocol, and points the Haiku tier at
  claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
  models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
  exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
  Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.

fix(ci): stabilize frontend tests and verify renderer build

fix(mcode): refresh provider state after removal (#7578)

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
fix(lib): reset skip_taskbar before window.show() across entry points (#6348)

5处 window.show() 调用中 4处没有在 show() 前重置 Windows 的 skip_taskbar,导致以下场景出现窗口可见但不在任务栏、点最小化后窗口消失的 bug:

- single_instance handler (lib.rs:360-361):当原进程因 silent_startup / 关闭到托盘等处于 skip_taskbar(true) 状态时,从快捷方式/开始菜单唤起已运行实例触发。

- handle_deeplink_url 的 focus_main_window 分支 (lib.rs:256):通过 ccswitch:// URL 唤起时。

- DB 恢复流程 (lib.rs:556):数据库版本不匹配时显示恢复界面。

- RunEvent::Opened 处理器 (lib.rs:1803):系统接收到 deep-link URL 时。

修复后的行为与 tray.rs::handle_tray_menu_event::show_main 分支一致(那里已有相同的 set_skip_taskbar(false) 调用)。

经用户测试验证:autostart + silent_startup + 从开始菜单/快捷方式启动的场景下,最小化按钮恢复正常工作。
fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

try_get_version_wsl runs "${SHELL:-sh}" -lic '<tool> --version' through
wsl.exe and takes the first x.y.z anywhere in stdout. A login+interactive
shell runs the distro's startup files first, so their output shares that
stdout. On Ubuntu, /etc/profile.d/update-motd.sh prints the MOTD once a day,
starting with "Welcome to Ubuntu 24.04.4 LTS", and the card then showed
24.04.4 as the Claude Code version. The once-a-day stamp makes it appear
after the first probe of the day and vanish on refresh.

The payload now prints a sentinel before running the tool, and parsing looks
only at what follows its last occurrence. The last occurrence handles the
-lic || -lc || -c fallback chain printing it several times; matching it as a
substring handles OSC sequences glued in front of it. If the sentinel is
absent (the shell failed before the payload ran), the output is used as
before. The stderr fallback for tools that print their version there is kept.

Fixes #7347

Co-authored-by: lyfuci <lyfuci11@gmail.com>
fix(claude): hide session URL attribution (#7053)

* fix(claude): hide session URL attribution

* docs(claude): document session URL attribution
fix(ui): reset provider scroll when switching apps (#5211)
fix(pi): fix provider logo (#6826)
fix(i18n): use the Pi form's own term for the zh-TW duplicate-key message (#7187)

Follow-up to the nit on #6768. In zh-TW the Pi form labels the field
供應商識別碼 and says 供應商識別碼為必填 when it is empty, but the
duplicate message I added said 供應商標識 — the term Hermes, OpenCode and
OpenClaw use for their own field. One form, two names for the same thing.

The other three locales already keep the label and both messages on one
term, so this makes zh-TW match the pattern rather than introduce one:

  en     Provider key / Provider key is required / This provider key already exists
  ja     プロバイダーキー / プロバイダーキーを入力してください / このプロバイダーキーは既に存在します
  zh     供应商标识 / 供应商标识不能为空 / 该供应商标识已存在
  zh-TW  供應商識別碼 / 供應商識別碼為必填 / 該供應商標識已存在  <- the odd one out

hermes.form.providerKeyDuplicate carries the same sentence and is left
alone: its own label is 供應商標識, so it is already consistent.

Verified: managementListLocales 7/7. Full unit suite 1085/1087, the same
two App.test.tsx integration cases failing before and after.
fix(proxy): strip stop param for GitHub Copilot to unblock auto mode classifier (#5404)

Claude Code's auto mode classifier sends `stop_sequences` on every
safety check. On the GitHub Copilot path the Anthropic -> OpenAI Chat
conversion mapped it to `stop`, and Copilot's Chat Completions route
for Claude models answers that request with HTTP 400, so every
classifier call failed while normal chat kept working.

- Remove the converted `stop` field only when the provider is GitHub
  Copilot; other OpenAI-compatible upstreams keep it.
- Copilot detection in the transform now also covers providers that
  are recognised only through `env.ANTHROPIC_BASE_URL`.
- Regression tests for the Copilot (meta and env URL) and generic
  provider cases.

Fixes #5175
fix(codex): keep stored auth when the live snapshot has no credential (#7434)

Editing the current Codex provider seeds the form from the live ~/.codex
snapshot, and switching away backfills that snapshot into the DB row. When a
provider table declares its own credential source (an Authorization header,
env_key, auth/aws), no bearer token is injected into config.toml and the
shared auth.json is deleted on third-party switches, so the live snapshot is
permanently `{ auth: {}, config }` while auth.OPENAI_API_KEY in the DB is the
only copy of the key. Both paths treated that empty live auth as
authoritative and silently erased the stored key, which in turn made the
usage-script modal misread the provider as an official subscription and
reset the saved script.

- Treat a credential-less live auth (missing auth.json or the bare
  auth_mode logout marker) as an absent field in both the edit dialog and
  the switch-away backfill, keeping the stored auth template. Live still
  wins whenever it carries material, and official providers keep the live
  login as their source of truth.
- Gate the official exemption on is_codex_official_provider /
  resolveCodexOfficialIdentity rather than the category alone: a legacy
  fixed official card without a persisted category is still written as
  official, so the fallback must not resurrect a logged-out login on
  switch-back.
- Cover both layers with regression tests, including the category-less
  official shapes.

Fixes #7433
Supersedes #7492

Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): lift additional_tools input carriers on the Codex chat conversion (#7454)

Codex talks to third-party providers over its Responses Lite transport,
which ships the request's extra tools in an `additional_tools` input
carrier: `{"type":"additional_tools","role":"developer","tools":[...]}`,
a role but no `content`. The Responses->Chat conversion let that carrier
fall through the generic "item with a role is a message" arm and emitted
`{"role":"system","content":null}` at messages[1], which strict Chat
gateways reject with `messages[1]: missing field "content"` (or
`data did not match any variant of ...SystemMessageContent`). The tools
carried inside it were also dropped on the Chat and Anthropic conversions.

- Skip `additional_tools` items in the Chat message walk; a carrier
  declares tools, it is not a message.
- Collect carrier tools through the existing CodexToolContext registry
  (dedup, namespace flattening, custom-tool conversion), which also feeds
  the Anthropic conversion and the response-side name round-trip.
- Requests without a carrier convert byte-for-byte as before.

Known boundary: `custom` children inside a namespace tool
(`functions.exec`) are still not lifted; that needs its own response-side
name round-trip and is left for a follow-up.

Diagnosed independently in #6158 by BigStrongSun (fix in #6159) and in
fixture.

Fixes #7451
Fixes #6158
Refs #6033
Refs #6776
Supersedes #6159
fix(proxy): normalize Codex detail:original images for Chat gateways (#7476)

Codex marks tool-returned images with detail:"original" whenever the model
catalog advertises supports_image_detail_original, a Responses-only value.
The Responses->Chat conversion copied it verbatim into image_url.detail, and
strict OpenAI-compatible Chat gateways (Ark/Doubao, SenseNova, GLM, vLLM, ...)
reject the whole request with 400. Because Codex replays the full rollout on
every turn, one poisoned view_image result made the session fail permanently,
including plain-text turns.

Two layers:

- tool_media::merge_top_level_detail downgrades original -> auto, so already
  poisoned rollouts and Codex versions before 0.155.0 (which do not normalize
  replayed history themselves) recover immediately. auto/low/high are untouched.
- codex_catalog_model_entry always writes supports_image_detail_original:false
  on the ProxyChat profile, so Codex stops emitting original at the source once
  the catalog is regenerated. NativeResponses/Anthropic profiles and the DeepSeek
  official catalog are unaffected.

Top-level user images were never affected: responses_content_to_chat_content
drops the part-level detail entirely.

Fixes #6495
Fixes #7458
Refs #6569
Refs #5805
fix(omo): detect unified config on the WSL side when the OpenCode dir is in WSL (#7550)

When the OpenCode config directory points into WSL
(\\wsl.localhost\<distro>\home\<user>\.config\opencode), OMO >= 4.19.3 keeps
its unified config in the WSL-side home (~/.omo/omo.jsonc). Detection only
probed the Windows home, never found it, and silently fell back to the legacy
per-plugin file that OMO no longer reads, so provider switches looked
successful but had no effect.

- Add derive_wsl_home_dir to derive the WSL-side home from a WSL UNC path
  (home/<user> or root); non-WSL and non-home paths return None.
- Probe a list of home candidates for the unified config, WSL-side home
  first and the OS home as a fallback, shared by the read, write and delete
  paths.
- Detection only: when no unified config exists on either side the legacy
  fallback is unchanged.

Fixes #7363
Supersedes #7367
docs(partner): add a FluxA purchase link and tighten its sponsor blurb

The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.

fix(claude-desktop): expose the 1M context variant for DeepSeek V4 routes

The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.

Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.

feat(presets): add FluxA Token Plan partner presets

FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.

- Claude and Claude Desktop use the Anthropic-compatible base
  api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
  OpenClaw, Hermes and Pi use the OpenAI-compatible
  api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
  entries it marks "Coming soon". kimi-k2.6 is added on top because the
  sponsor copy names Kimi; its id and 262144 context window come from
  FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
  international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
  Token Plan presets, and the preset joins the existing regression guard
  that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
  locales, and the sponsor row in all four READMEs.

fix(presets): correct AICodeWith Codex-family endpoint to /v1

The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.

Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.

Existing providers created from the old preset keep the stored URL and
need to be edited manually.

fix(ui): render monochrome preset icons in the foreground color

Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.

Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.

docs(user-manual): point Chat-only Codex examples at plan endpoints

Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.

fix(pricing): restore DeepSeek V4 Pro price and seed Qwen3.8 and Hy4 models

- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
  2026-09-11 move to the V4.1 Flash price anticipated a cutover that
  DeepSeek retracted on 2026-09-12. Replace that repair entry with one
  that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
  the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
  qwen3.8-27b (Alibaba Cloud international list prices).

fix(presets): move MiniMax CN and BaiLing to their current official endpoints

- MiniMax CN: the remaining apps switch from api.minimaxi.com to
  api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
  both hosts and now matches on host labels (frontend, and the global
  host in the backend), so look-alike hosts and URLs that only carry the
  domain in their path are ignored. The quota query keeps
  api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
  api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
  since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
  is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
  keeps serving V4 Pro after September 14, 2026.

feat(codex): refresh CN presets from the Responses API audit

- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
  text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
  declare deepseek-v4-pro text-only, matching the bundled official
  catalog.
- Add native Responses presets for StepFun API (CN/intl,
  step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
  Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
  qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
  conservatively on Qianfan and Astron, whose Responses docs document no
  hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
  so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
  listed, but TokenHub marks Kimi K2.7 Code unsupported and several
  models as server-side Chat conversions, and the catalog levels were
  verified on Chat only.

test(claude-desktop): run Linux path tests on unix hosts and cover Linux in provider sync tests

Follow-up to #7331 (Claude Desktop 3P configuration on Linux):

- Compile the Linux config-dir resolver and its three unit tests under
  any unix test build so they run on macOS as well as CI ubuntu. The
  gate is unix rather than test because the assertions use POSIX
  absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
  Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
  the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
  Linux and note that the Flatpak build always uses the host ~/.config.

fix(ui): stop expanding stale cached usage tiers on ineligible provider cards

A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.

Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.

fix(skills): 支持 skillId 与目录名不一致的技能安装和更新 (#6381)

* fix(skills): resolve mismatched skillId directories

* fix(skills): preserve source path during updates

* fix(skills): refresh source URL after repository relocation

* test(skills): isolate relocation fixture from legacy home
feat(opencode): 支持从获取的模型列表中搜索并批量添加所选模型 (#7515)

* feat: implement fetched models selection and UI components

* fix(opencode): prevent model search from submitting provider form
Add Kimi Global preset variants (kimi.ai platform + api endpoints) (#7526)

Add a "Kimi Global" twin immediately after the domestic "Kimi" API preset and a "Kimi For Coding Global" twin after the domestic "Kimi For Coding" preset, in every preset file that ships them (claude, claude-desktop, codex, hermes, openclaw, opencode, pi).

"Kimi Global" points to the international open platform https://platform.kimi.ai?aff=cc-switch (apiKeyUrl mirrored as https://platform.kimi.ai/console/api-keys?aff=cc-switch) and swaps the endpoint host api.moonshot.cn for api.moonshot.ai with each file's path unchanged (/v1 or /anthropic).

"Kimi For Coding Global" points to the international site https://www.kimi.ai/code/?aff=cc-switch and swaps the endpoint host api.kimi.com for api.kimi.ai with the path unchanged (e.g. https://api.kimi.ai/coding/v1).

Domestic presets are unchanged; category, icons, apiFormat, model catalogs, endpointCandidates (host swapped), partnerPromotionKey and settings otherwise mirror the domestic blocks exactly.

primePartner stays only on the domestic presets so the new variants do not alter prime-partner ordering.

The Pi twins get distinct providerKey values ("cc-switch-kimi-global", "cc-switch-kimi-for-coding-global") because providerKey is the preset-level primary key and must not collide with the domestic presets.
Rebalance sponsor CTAs around Kimi Code plan with dual-region links (#7522)

Kimi Code plan purchases have resumed, so the README sponsor sections go back to leading with the coding plan instead of the API-only flow written during the purchase pause.
All four locales (EN/ZH/JA/DE) now present Kimi Code plan (中文站 | Global) as the primary CTA and the Kimi Open Platform API (中文站 | Global, with the first-top-up bonus) as the secondary one, mirroring the domestic|global split already used for API links.
The aff and track_id values are unchanged.
fix(prompts): refresh active prompts after external file edits (#7194)

Reload the enabled prompt from its live file when the Prompts list loads, and refresh the open panel when the window regains focus, so external edits to CLAUDE.md / AGENTS.md show up without a restart.

- Keep saved templates available when the live file cannot be read.
- Skip the backfill for empty or whitespace-only files, matching enable_prompt.
- Skip the backfill while a sync/restore holds the sync lock, so a list load cannot write a stale live file into a freshly restored database.
fix(skills): raise archive entry limit so large skill repos install (#7489)

Installing hugohe3/ppt-master from skills.sh failed with
ARCHIVE_TOO_MANY_ENTRIES: the GitHub archive we download is the whole
repository (13,248 entries), and the 10,000-entry cap added in #5811
treated it as a zip bomb.

- Raise MAX_ARCHIVE_ENTRIES to 30,000 for both the remote archive and
  local ZIP paths, and align WebDAV's MAX_EXTRACT_ENTRIES so a skills.zip
  containing such a skill still restores.
- Charge every file entry at least one 4 KiB block against the existing
  512 MiB budget, mirroring directory accounting, so empty-file archives
  are bounded by the byte budget rather than by the entry count alone.
- Add tests for a 13k-entry repo archive and for the per-file minimum
  charge.

Fixes: https://github.com/farion1231/cc-switch/issues/7475
feat: add MiniMax Code harness support (#7383)

Add MiniMax Code (mcode) as a managed application: provider and model management through the native custom_provider configuration, MCP servers, Skills, global instructions (AGENTS.md), read-only local session history with resume, and usage import from the native runtime database. Native files shared by the TUI and desktop are written under the native lock with rollback when the database commit fails, unrelated native configuration is preserved, and the data directory honors MINIMAX_DATA_DIR and MAVIS_DATA_DIR. Deleting sessions stays in the native application because they are coupled to runtime state.
fix(codex): recover stale account bindings during takeover (#7395)

After deleting a managed ChatGPT account and logging in again, a Codex provider can still reference the old local account ID, which made route switching, takeover, startup restoration and rebinding fail permanently.

Distinguish a missing outgoing account from an existing account with no matching live token. A missing account only releases its ownership marker after the persisted account store is verified; the target write policy stays in force. A stale target now reports an account-selection instruction instead of a bare not-found error, including when takeover is already enabled.

Fixes #7392
feat(claude-desktop): support 3P configuration on Linux (#7331)

Add Claude Desktop 3P configuration support on Linux. Resolve the config root from an absolute XDG_CONFIG_HOME, falling back to ~/.config; when CC Switch itself runs as a Flatpak, use the host ~/.config instead of the sandbox-private XDG_CONFIG_HOME. Update the en/zh/ja user manuals with Linux paths and the Flatpak boundary.

Fixes #4855
fix(proxy): omit missing tool descriptions instead of serializing null (#7319)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(proxy): keep reasoning effort for grok-4.6 and pass xhigh through verbatim (#7318)

Co-authored-by: SailingLoong <309827397+SailingLoong@users.noreply.github.com>
fix(models): support Zhipu OpenAI Responses model lists (#7330)

Zhipu's Responses endpoint (open.bigmodel.cn/api/v1) serves its model list in the Codex remote catalog shape (models[].slug) instead of the OpenAI-compatible data[].id shape, so the Codex form's model fetch came back empty. The parser now falls back to models[].slug when data is absent.
fix(misc): fetch npm dist-tags from the dedicated endpoint with the probe timeout (#7346)

The tool version probe requested the full npm packument for each package (metadata for every historical version, 14-25 MB uncompressed for codex / opencode / openclaw) only to read its dist-tags, and it was the only latest-version probe without LATEST_PROBE_TIMEOUT, so it fell back to the proxy client's 600 s total timeout and the cards sat on "loading" for minutes on slow links while the large downloads starved the Hermes GitHub probe into "unknown".

Switch fetch_npm_dist_tags to registry.npmjs.org/-/package/{package}/dist-tags, whose body is the dist-tags object itself (tens of bytes to a few KB), escape scoped names as %2f, and apply the same 15 s probe timeout as the GitHub and PyPI probes. No data is lost: the only consumer reads latest and the tool's prerelease tags.

Fixes #7339
feat(usage): display output tokens per second in request log table and detail panel (#3369)

Show tokens/s next to the output token count in the request log table and the request detail panel.

The generation window prefers an explicit duration when present, otherwise latency minus first-token time for streaming requests, and falls back to full latency for non-streaming requests. Rows without a positive token count or window show nothing.

Closes #5936
fix(presets): update APIKEY.FUN URLs to apikey.fan

docs(release): add v3.20.3 release notes

chore(release): v3.20.3

fix(pricing): move retired DeepSeek V4 family to the V4.1 Flash tier

DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.

- Seed `deepseek-flash` (the only currently recommended id) and
  `deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
  cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
  with chained repair guards ordered after the 2026-08-16 peak/off-peak
  rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
  an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
  lands on a vision-capable model, so the image sanitizer must fail open.
  Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
  including the two media_sanitizer tests #7286 had just pointed at it.

fix(presets): declare third-party Token Plan DeepSeek V4 rows text-only

DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.

Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).

Follow-up to #7283.

feat(presets): rename DouBaoSeed preset to localized Volcengine Doubao

Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.

Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.

Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.

fix(codex): skip unchanged incomplete rollout tails

Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.

Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.

fix(claude): align quick model selection with form order

fix(tray): show bound ChatGPT account quota for managed Codex cards

Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.

- UsageCache: add an account-keyed `codex_oauth` snapshot map
  (`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
  under the resolved account id and schedule a tray label refresh.
  Transport errors keep the last good snapshot; auth/HTTP failures replace
  it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
  path share one decision. Managed Codex cards read only their bound
  account's snapshot (never a provider-scoped or CLI snapshot as fallback),
  default to enabled when no usage toggle has been saved (matching
  ProviderCard), and the fixed official card takes the managed path too
  when it carries a binding. Rebinding a card switches to the new
  account's snapshot immediately and a late response for the previous
  account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
  rendering across switch, rebind, late response, disabled and expired
  states, and the native fixed card still using the CLI subscription.

Fixes #7267

feat(claude): add "Disable Artifact Tool" quick toggle to provider editor

Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.

Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.

feat(codex): refresh aggregator model presets and catalogs

feat(codex): switch Kimi presets to native Responses direct-connect

Both Kimi open platform (api.moonshot.cn/v1) and Kimi For Coding
(api.kimi.com/coding/v1) now serve /v1/responses natively and their
official Codex guides require wire_api = "responses", so the two presets
move from openai_chat (proxy Responses->Chat conversion, routing takeover
required) to openai_responses (Codex connects directly). Verified against
both endpoints with real keys using the exact Codex 0.153.4 request shape
(include reasoning.encrypted_content, reasoning.summary, hosted web_search,
replayed reasoning items) and with codex exec end-to-end tool loops.

- kimi-k3 becomes the default open-platform model and first catalog row;
  kimi-k2.7-code stays as a second row (it also answers on /v1/responses,
  although the OpenAPI only lists kimi-k3)
- drop codexChatReasoning (Chat-conversion only) from both presets and
  promptCacheRouting from Kimi For Coding (Codex sends prompt_cache_key
  itself on the native path; the proxy auto-detects the host anyway)
- declare supportsParallelToolCalls on every Kimi row, mirroring the
  official Kimi Code models.json
- no reasoning default is declared: config.toml model_reasoning_effort
  wins over the catalog default, which only marks the /model picker
- move the two presets from the Chat list to the native list in the
  preset tests; invert the prompt-cache-routing assertion
- refresh the user manual (zh/en/ja) so the "Chat-only" examples name
  providers that still use Chat routing, and correct the stale
  userAgentPresets note about Kimi Code rejecting codex-cli

No backend change. Existing Kimi cards keep their stored openai_chat
format and continue to work through routing; switch a card's upstream
format to Responses (or re-add the preset) to connect directly.

fix(usage): show Claude Fable weekly limit from the usage API limits array

The Claude OAuth usage API now reports model-scoped weekly limits in a
top-level `limits[]` array (`kind: "weekly_scoped"`, `scope.model.display_name`,
`percent`) instead of dedicated top-level windows. The parser only read the
legacy windows, so the Fable limit never showed up in the provider card or
the tray.

- Extract `parse_claude_quota` and parse `limits[]` into `seven_day_fable`,
  `seven_day_opus` and `seven_day_sonnet`; scoped rows override the legacy
  window of the same name, duplicates and malformed rows are skipped, and
  legacy windows / extra usage / unknown windows are preserved.
- Tray: give Fable its own label group so it is not merged into the weekly
  max value.
- Frontend: add the `seven_day_fable` label ("Fable") in all four locales.
- Tests: parser edge cases, tray summary for both quota and flattened script
  paths, and footer rendering for inline/expanded layouts and locales.

fix(codex): mirror vision-capable DeepSeek catalog and un-gate the legacy v4-flash alias (#7286)

Refresh the bundled DeepSeek official Codex catalog to the vendor's current models.json (slug deepseek-flash, image input modality, supports_image_detail_original) and remove deepseek-v4-flash from the confirmed text-only registry: the legacy id is still accepted by DeepSeek and routed to the vision-capable V4.1 Flash, so both the proxy media sanitizer and the generated Codex catalog must fail open for it. deepseek-v4-pro stays text-only per the vendor catalog.

Fixes #7283
fix(proxy): clamp sub-floor max_tokens to the Responses API minimum (#7287)

The Responses API rejects max_output_tokens below 16, but Anthropic clients legitimately send tiny probe budgets (Claude Desktop's model-availability probe uses max_tokens=1). The Anthropic→Responses conversion copied the value verbatim, so strict upstreams returned 400 and Claude Desktop reported the mapped model as unavailable under local routing.

Clamp 1..=15 up to 16 during conversion; 16+, 0 and non-integer values keep their pass-through semantics. Normal session requests convert byte-for-byte as before, and the Codex OAuth path still strips max_output_tokens afterwards.

Fixes #7103
fix(codex): coalesce adjacent commentary with pending tool calls (#7280)

The Codex Responses -> Chat Completions converter emitted two consecutive
assistant messages when one Responses model turn contained a commentary
message directly followed by function_call items. Chat upstreams imitate
the text-only assistant message as a complete turn and return
finish_reason=stop before the expected tool call, so long agent tasks end
after a progress update.

Merge pending tool calls into the directly adjacent assistant message when
it carries no tool_calls yet, attach pending reasoning per segment so
parallel calls do not duplicate it, and keep the new-message path for every
other boundary shape (user/tool boundaries, prior tool-call batches, media
flushes). The reasoning placeholder backfill still applies to coalesced
calls without reasoning.

Fixes #6529
Supersedes #6530

Co-authored-by: BigStrongSun <BigStrongSun@users.noreply.github.com>
fix(proxy): skip empty reasoning_content placeholders in Anthropic SSE streaming (#7227)

Some OpenAI-compatible upstreams (GLM, Qwen, DeepSeek-V4-Pro via ModelScope/DashScope,
Bailian, JD Cloud, ...) keep a `reasoning_content: ""` placeholder in every content
chunk instead of omitting the field. The reasoning branch of the OpenAI Chat -> Anthropic
SSE converter only checked for presence, and it shares `current_non_tool_block_type`
with the content branch, so every content chunk closed the open text block, opened an
empty thinking block, then reopened a text block. N content chunks became 2N content
blocks, half of them empty thinking blocks, which Claude Code rendered as one line per
token with dozens of empty Thought blocks and bloated the session JSONL.

Filter empty reasoning before entering the branch, matching the `!content.is_empty()`
guard the content branch already had and the non-streaming `openai_to_anthropic`
converter. Two regression tests pin block index, type and order: the placeholder shape
must yield a single text block and no thinking_delta at all, and real reasoning must
still yield one thinking block followed by one text block.

Fixes #5028
Fixes #4404
fix(codex): detect growing rollouts with a persisted byte cursor (#7219)

Codex keeps the rollout append handle open for the whole session, so on Windows NTFS the file mtime does not advance while the file grows and the pure-mtime gate skipped growing rollouts forever. Persist the observed byte length in session_log_sync.last_byte_offset for Codex rows and skip a file only when both mtime and size are unchanged. The parser now splits records on newlines manually so an incomplete trailing record is retried on the next pass without advancing the line cursor.

Fixes #6060
fix(codex): proxy image edits and Images API follow-ups (#7177)

* fix(codex): proxy image edits and Images API follow-ups

Follow-ups to #7036 for Codex's built-in ImageGen over the local proxy:

- Register `/images/edits` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them through the shared Codex standalone passthrough.
  ImageGen posts to `images/edits` whenever a call references existing
  images, and the router returned an empty 404 for it.
- Add `CodexStandaloneEndpoint::ImagesEdits`. Both Images endpoints share
  one full-URL suffix table, so a complete Responses / Compact / Chat
  Completions / Images URL derives the sibling endpoint (and vice versa)
  while opaque full URLs keep failing closed.
- Apply the standalone sibling rewrite when the base URL is pasted as a
  complete endpoint with the full-URL switch off, mirroring
  `codex_chat_base_is_full_endpoint`; standalone requests no longer become
  `.../chat/completions/images/generations`.
- Parse Images API streaming usage: `image_generation.completed` carries
  `usage` on the event top level, so scan for `usage.input_tokens` before
  falling back to the Chat Completions stream parser.

Related #7036

* fix(codex): match standalone URL suffixes case-insensitively

Keep endpoint detection and sibling rewriting consistent while preserving the original URL prefix and query. Cover mixed-case Images and Alpha Search endpoints through URL and proxy regression cases.

---------

Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
fix(proxy): preserve per-app settings on shutdown and port allocation (#7210)

On normal exit, stop_with_restore_keep_state() called the legacy global
proxy-config writer to clear the long-retired live_takeover_active flag.
That writer reads the Claude row and runs an UPDATE without a WHERE clause,
so every shutdown copied Claude's max_retries and the three timeout fields
onto the Codex, Gemini and Grok Build rows — and the UPDATE never carried
live_takeover_active in the first place, so the step had no effect beyond
the clobber. The ephemeral listen-port path did the same on every proxy
start.

Drop the dead write-back (live-config restore, backup cleanup and health
reset are unchanged) and persist the resolved ephemeral port through the
global-config interface, which only touches the shared listen/logging
columns. Two regression tests seed distinct per-app settings for all four
apps and assert they survive both paths.

Fixes #7204
fix(provider): preserve child metadata during universal sync (#7212)

Syncing a universal provider regenerates its Claude/Codex/Gemini child
providers. The child's `settings_config` was merged with the existing row,
but `meta`, `created_at` and `sort_index` were taken from the generated
object, and the universal provider never carries any of them from the UI.
The UPDATE therefore wiped the child's usage script, common-config opt-out,
endpoint auto-select and other per-app settings to `{}` and reset the sort
index to NULL, which pushed the card to the bottom of the list and looked
like the provider had been deleted and re-created.

Keep the existing child's `meta`, `created_at` and `sort_index` when the
row already exists. Name, base URL, key, models, website and notes are
still driven by the universal provider, and first-time creation still
inherits the parent's metadata.

Fixes #7134
feat(presets): rebrand DashScope as 千问AI平台 and refresh Qwen models (#7183)

Rename the domestic Bailian presets to 千问AI平台 with a dedicated icon, refresh the Qwen family to the 3.8 generation across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, OpenCode and Pi, add the domestic Token Plan presets, restore the international QwenCloud Coding Plan presets, and align OpenClaw/OpenCode/Pi model metadata with the official client docs.
fix(codex): honor proxy URL when model_provider is omitted (#7263)

When a Codex provider card's TOML has no `model_provider`, Codex falls back to
its built-in `openai` provider, but takeover wrote the proxy address as a
top-level `base_url` that Codex never reads, so requests bypassed the local
proxy and went straight to api.openai.com.

Treat the missing selector as the built-in `openai` provider and write the
proxy address to `openai_base_url`; the existing legacy-reroute migration then
turns it into a `cc-switch` custom provider table carrying the `PROXY_MANAGED`
bearer, the same shape every other third-party takeover uses. Explicit
provider selections are unchanged.

Refs #6256, #7217
fix(provider): update MiniMax defaults to M3 and remove expired offers (#7255)

* fix(provider): refresh MiniMax M3 presets and remove expired promotions

* fix(provider): complete MiniMax M3 capability declarations

---------

Co-authored-by: saladday <1203511142@qq.com>
chore(provider): remove Atlas Cloud sponsor status, keep presets

Atlas Cloud is no longer a sponsor. Drop the sponsor identity everywhere
while keeping the provider presets themselves intact:

- README (en/zh/ja/de): remove the Atlas Cloud row from the sponsor table
  and delete the now-unreferenced banner image
- Presets (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes,
  Pi): strip `isPartner` / `partnerPromotionKey` from the AtlasCloud entry
  and move it from the sponsor group to the non-sponsor group (endpoints,
  models and icon unchanged)
- i18n (zh/en/ja/zh-TW): remove `providerForm.partnerPromotion.atlascloud`

docs(release): add v3.20.2 release notes

chore(release): v3.20.2

feat(provider): add SoleAPI sponsor presets across all nine apps

SoleAPI (soleapi.com) joins as a sponsor, placed directly above Micu in
the sponsor order (preset files and the four README sponsor tables stay
in sync).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the
  vendor's CC Switch setup page: Anthropic/Gemini-style clients use the
  bare host, OpenAI-style clients (Codex, Grok, Responses) use /v1.
- Haiku's upstream ID carries a date suffix
  (claude-haiku-4-5-20251001), so the Desktop preset uses mappedRoutes
  and the multi-model presets list the dated ID.
- Codex defaults to gpt-5.6-sol (vendor config.toml), Gemini to
  gemini-3.8-flash.
- No OpenClaw/Hermes cost fields: site prices are official rates times
  a per-channel multiplier and change with promotions.
- Referral link only in apiKeyUrl and README hrefs; request endpoints
  stay bare.
- Inline currentColor icon traced from the vendor SVG, metadata entry,
  four-locale partnerPromotion copy, and a 1920x786 banner.

fix(codex): proxy image edits endpoint

Codex's built-in ImageGen switches from `/images/generations` to
`/images/edits` whenever it references existing images (explicit paths or
the last N generated images). After #7036 the generations route is proxied,
but edits still hit an empty local route and the tool fails with 404.

Add `CodexStandaloneEndpoint::ImagesEdits` and route the four local aliases
through the same standalone passthrough as generations. The full-URL sibling
derivation now uses a shared suffix table for the Images family, so a
provider configured with either `/images/generations` or `/images/edits` as
a complete URL derives the other route; opaque URLs still fail closed.

Tests cover the effective-endpoint match, full-URL rewrites in both
directions, and an end-to-end alias test that asserts auth replacement,
body preservation (data-URL images) and usage logging with the real Images
API `input_tokens_details` shape.

fix(provider): pin models URL for JieKou and Novita Claude presets

Both serve the Anthropic-compatible API under /anthropic but expose the
OpenAI-compatible model list under /openai/v1. The candidates derived
from the base URL (/anthropic/v1/models, and /v1/models and /models at
the stripped root) all return 404, so "Fetch models" could never
succeed for either provider.

Also drop the JieKou assertion that pinned the absent modelsUrl, which
locked the broken behaviour in.

feat(pricing): refresh seed for September 2026 vendor price changes

Audit run of /jason-update-model against models.dev, every change confirmed
on the vendor's own pricing page.

New row:
- qwen3.8-flash: 0.15 / 0.47 flat across the 1M window, cache 0.016 / 0.20.

The other two rows this audit found have since landed through contributed
PRs, so only the annotations remain here:
- gpt-6-astra came in via #7162; this commit keeps its literal "12.5" and
  adds the note that low/medium/high/xhigh fall back to the base row.
- gemini-3.8-flash came in via #7164; this commit adds the note that
  0.75 / 3.75 / 0.075 is introductory through 2026-12-31 and reverts to
  the 1.50 / 7.50 / 0.15 list price on 2027-01-01.

Price changes (seed + repair, guarded on the previous values):
- gemini-3.6-flash: Google now lists the same 0.75 / 3.75 / 0.075
  introductory price through 2026-12-31.
- gpt-5.6-sol plus the bare gpt-5.6 row and its five effort-suffix rows:
  promotional 4 / 20 / 0.40 / 5, "available at least through November 21,
  2026". The alias rows must move in lockstep because exact-id lookup
  runs before suffix stripping.
- minimax-m2, minimax-m2.1, minimax-m2.5: the official pay-as-you-go
  page lists all three at 0.3 / 1.2 with cache read 0.03 and write 0.375.

The new repair entries are appended after the existing chain so old
databases hop through the intermediate prices (gpt-5.6-sol cache-write
backfill, minimax-m2.5 0.12 -> 0.15). The repair-order test now locks
both chains alongside the DeepSeek one.

fix(codex): stamp requires_openai_auth on takeover writes to match the live login state

With official-login preservation off (the default), a direct switch to a
third-party provider deletes ~/.codex/auth.json. Enabling proxy takeover
afterwards rebuilt config.toml from the stored card, which still carried the
pre-0.149 `requires_openai_auth = true`, so Codex >= 0.149 trapped the TUI in
the login screen even though the proxy placeholder bearer token was in place.
The same gap hit hot switches after `codex logout` mid-takeover.

The takeover writer now stamps the active custom table's flag from the login
state Codex itself would observe, mirroring the direct-switch plan:

- Resolve the auth mode exactly like `AuthDotJson::resolved_mode` (explicit
  `auth_mode` first, then personal_access_token > bedrock_api_key >
  bedrock_access_keys > OPENAI_API_KEY > ChatGPT) before checking the
  credential, so Bedrock credentials next to a stale API key are never
  promoted to an OpenAI login (`account_state` would fail with
  UnsupportedBedrockApiKeyAuth and abort TUI startup).
- Decide `cli_auth_credentials_store` before touching auth.json: keyring and
  auto stores are undecidable from disk (the card's flag is left alone),
  ephemeral is always signed out, and only the file store reads the file.
  A missing, unreadable or corrupt file means signed out, as it does for
  Codex, and no longer fails the takeover write.
- Proxy-injected OAuth cards (xai_oauth, github_copilot) keep the neutralized
  `false` regardless of what is on disk; the official login is never their
  credential.

Official passthrough and managed-official branches are untouched.

fix(updater): show the real reason when an update check fails (#6482)

The updater plugin rejects with a plain string, so `err instanceof Error`
never matched and the real failure reason was replaced by the generic
fallback message. Use the existing extractErrorMessage helper, and pass
the recovered message into the check-update failure toast so it actually
reaches the user instead of stopping at an unread context state.

Co-authored-by: Jason <farion1231@gmail.com>
docs: fix the locales path in the README directory trees (#6100)

All four READMEs showed locales/ as a direct child of src/, but the
translations live at src/i18n/locales/. Split it into two tree nodes so it
matches how the rest of the tree is drawn. Also fixes a CLaude -> Claude
typo in README_ZH.md.

Co-authored-by: Jason <farion1231@gmail.com>
fix(claude): exclude workflow journal.jsonl from session list

Claude Code's workflow feature writes subagents/workflows/wf_*/journal.jsonl inside the session directory. The Claude session scanner only excluded agent-* files, so each journal parsed as an empty ghost session titled "journal". Exclude journal.jsonl by file name and add regression tests.

Fixes #6042
fix(i18n): add the missing pi.form.providerKeyDuplicate and common.collapse keys

translatePiProviderMutationError returns pi.form.providerKeyDuplicate for the Pi backend's duplicate-key error, but the key existed in none of the four locale files, so the toast showed the raw key. common.collapse (aria-label of the thinking-map toggle) was missing the same way. Add both keys to zh/en/ja/zh-TW and lock them in the locale coverage test.
fix(proxy): stop mask_url panicking on multi-byte UTF-8 near the truncation boundary

mask_url's fallback branch (used when the proxy URL fails Url::parse) sliced the raw string at byte 20 and panicked when a multi-byte UTF-8 character straddled that offset, e.g. a CJK string pasted into the global proxy address. Snap the cut to the nearest char boundary at or before 20 and add a regression test.
fix(tests): isolate LOCALAPPDATA so tests stop writing the real Claude Desktop config

On Windows, Claude Desktop config paths resolve from LOCALAPPDATA, which the test home override in tests/support.rs did not redirect, so profile_roundtrip wrote the https://desktop.test fixture into the developer's real Claude Desktop config. Point LOCALAPPDATA at the isolated test home alongside HOME and USERPROFILE.

Fixes #6077
fix(usage): widen pricing model source select to fit localized labels

The pricing model source SelectTrigger in the usage cost settings table was w-28, which leaves about 70px for the label; the English and Japanese labels need about 107px and overflowed the trigger. Widen it to w-40 and drop the h-7 overrides so the trigger and the adjacent Input use the shared h-9 height.
feat(pi): add Tencent TokenHub and Token Plan presets (#7159)

Add eight Tencent presets for Pi, covering the TokenHub pay-as-you-go line
(domestic and international) and the six Token Plan subscription products —
Pi was the only managed app still missing them. Model IDs, ordering and
endpoints follow Tencent's own model lists verbatim, and the tests assert each
product's line-up in order so future drift is caught.

Declare thinking control alongside the capabilities rather than leaving it to
defaults: the 31 DeepSeek entries carry a Tencent-specific compat so selecting
"off" actually disables thinking instead of silently billing reasoning tokens,
the eight Kimi K2.7 Code entries no longer offer an "off" level the model
cannot honour, and kimi-k2.7-code-highspeed is marked image-capable so
tool-returned screenshots are no longer dropped.

Drop minimax-m2.5 from every app: it is gone from Tencent's model tables and
the platform is retiring it.
fix(codex): proxy image generations endpoint (#7036)

Codex's built-in ImageGen calls the legacy OpenAI Images API, but the local
proxy only registered Responses / Chat Completions / Compact / Alpha Search
routes, so `/v1/images/generations` returned an empty 404 under Codex routing.

- Register `/images/generations` aliases (bare, `/v1`, `/v1/v1`, `/codex/v1`)
  and forward them as a Codex standalone passthrough shared with Alpha Search.
- Generalize the Alpha Search full-URL rewrite into
  `rewrite_codex_standalone_full_url`: derive the sibling
  `/images/generations` from full Responses / Compact / Chat Completions URLs,
  keep an existing Images URL as-is, and fail closed on opaque full URLs.
- Record `usage.input_tokens` / `output_tokens` through the Codex usage parser.

Fixes #5429
Fixes #6745
feat(pricing): add Gemini 3.8 Flash pricing (#7164)

Seeds the built-in pricing row for Gemini 3.8 Flash at 0.75 / 3.75 input/output per million tokens, 0.075 cache read, no cache-write charge, plus a regression test locking all four fields.

Values verified against Google's own pricing page (introductory price through 2026-12-31; list price 1.50 / 7.50 / 0.15 from 2027-01-01) and models.dev.
feat(provider): extend PPIO presets for Pi (#6870)

Add a PPIO provider preset for Pi using the OpenAI-compatible endpoint,
and pin an explicit models URL on the Claude Code PPIO preset so model
discovery resolves against https://api.ppio.com/openai/v1/models instead
of the derived candidates, which all return 404.

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 Flash pricing (#7163)
fix(proxy): enable parallel tool calls for Codex OAuth (#7024)

Codex OAuth requests defaulted parallel_tool_calls to false when the incoming
Anthropic request carried no explicit value, forcing Claude Code into one tool
call per model turn. Default it to true (matching codex-rs), map Anthropic
tool_choice.disable_parallel_tool_use to the inverse Responses value so explicit
serial execution is preserved, and add regression coverage for both directions.

Carries #5722 forward on top of current main.

Fixes #5719

Co-authored-by: 太一 <taiyi@douyu.tv>
feat(pricing): add GPT-6 Astra pricing (#7162)
fix(codex): resolve input_modalities for unknown models in vendor catalog (#6750)

When a model slug is not in the bundled DeepSeek official catalog, the vendor path clones the flagship entry and previously inherited its text-only input_modalities. Resolve modalities for unmatched slugs through the same registry/fail-open logic the non-vendor path uses, so a vision variant such as deepseek-v4-flash-vision-exp is no longer declared text-only. Explicit user inputModalities still win; matched models keep the vendor's declaration verbatim.

Fixes #6725
fix(claude): expose Opus 5 and Sonnet 5 in takeover mode (#5882)

Bump the stable client-facing role aliases that takeover mode writes to
Claude Code: Opus claude-opus-4-8 -> claude-opus-5 and Sonnet
claude-sonnet-4-6 -> claude-sonnet-5, so the /model menu matches the
Claude Desktop default routes and preset defaults that already moved to
Opus 5 / Sonnet 5. Add "opus-5" to the adaptive-thinking classifier so
the Bedrock thinking optimizer, which runs on the client alias before
model mapping, keeps sending adaptive thinking instead of the removed
budget_tokens.

Routing is unaffected: the proxy maps client aliases by role keyword, and
the aliases are rewritten automatically the next time the proxy starts.

Fixes #5876

Co-authored-by: Jason <farion1231@gmail.com>
feat(pricing): add GLM-5.3 to built-in model pricing table (#6591)

Seed glm-5.3 at $1.4/$4.4 per MTok with $0.26 cache read, matching the
official Z.ai list price (identical to the GLM-5.2 tier):
https://docs.z.ai/guides/overview/pricing

New model id, so seed only; no repair entry and no SCHEMA_VERSION bump.
Existing installs pick the row up on next launch via INSERT OR IGNORE, and
the startup cost backfill re-prices historical glm-5.3 rows recorded at $0.
fix(codex): align OAuth client identity for…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Backend (Rust/Tauri) proxy Related to proxy/API forwarding

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(codex): CCS 本地路由下内置 ImageGen 返回 404 [Bug] Codex 本地路由未代理 /v1/images/generations,导致图片生成返回 404

5 participants