- Gateway: apicdn.xycai.us primary, apicdn.xyc.ai fallback
- Claude auth uses ANTHROPIC_API_KEY per official docs
- Claude Desktop connects direct with native Anthropic format
- In-app partner promotion only (no README entry), four locales
- Register xycai icon (PNG via iconUrls) with metadata
The former 火山Agentplan preset actually pointed at Coding Plan
endpoints (/api/coding[/v3]). Split it into two presets across all six
app preset files:
- 火山 Agent Plan: /api/plan (Anthropic) and /api/plan/v3 (OpenAI),
with its own activity/agentplan invite link
- 火山 Coding Plan: keeps /api/coding[/v3] and the original
activity/codingplan invite link
Both plans' OpenAI endpoints are covered by the official Codex docs
(wire_api=responses), so the Coding Plan Codex preset also uses native
Responses. Widen the Volcengine usage detection to
volces.com/api/(plan|coding) in both the frontend pattern and the
backend detect_provider mirror; the quota query already auto-probes
both plans (GetAFPUsage then GetCodingPlanUsage). Split the partner
promo copy into volcengine_agentplan / volcengine_codingplan i18n keys
in all four locales, and pin both presets in the codex native
Responses preset test.
FennoAI changed its CC Switch benefit: the previous ¥9.9/month Coding
Plan worth $150 in credits is replaced by a $1.99 Trial Plan worth
$50 in credits, valid for 7 days. Update the in-app promo line in all
four locales and the partner sections of the four READMEs. The 20%
referral reward and the registration link stay unchanged.
Switch RunAPI website, registration and request URLs to runapi.host
across all seven app presets and the four README partner sections.
The previous runapi.co endpoints are retained as endpointCandidates
fallbacks where the preset type supports the field (claude,
claude-desktop, codex, grok-build).
The star was legacy display logic gated on meta.isPartner, which the
generic provider form almost never persisted. The standalone Grok Build
form unconditionally saves meta.isPartner, resurrecting the dead code
path. Partner stars belong only in the preset selector, so drop the
card-level badge and the now-unused officialPartner i18n key from all
four locales.
The official catalog mirror template was extracted from DeepSeek's docs
page with HTML unescaping applied before tag stripping, so literal
angle-bracket text inside the JSON (e.g. "<this good thing>" and the
spaced-path markdown link example) was swallowed as if it were HTML
tags — 8 spots across base_instructions and model_messages for both
models, 86 bytes missing per harness copy. Restore them; the template
is now structurally identical to the current official models.json.
Also refresh the preset comment: deepseek-v4-pro's Codex integration
is live as of 2026-08 (its catalog entry differs from flash only by
priority), so the "switching early errors upstream" warning is gone.
Findings from a models.dev audit of the seeded pricing table:
- Correct grok-4.5 cached input 0.50 -> 0.30 (seed + repair with an
old-value guard). The current xAI list table, models.dev and the billing
observed for grok-4.5-build all state 0.30; 0.50 is grok-4.6's rate.
- Seed grok-4.6 (2 / 6 / 0.50, released 2026-08-12). Both 4.5 and 4.6 double
their rates above a 200K prompt, but the table has no tier column, so the
base tier is stored, consistent with other tiered vendors.
- Seed a deepseek-v4-flash-0731 alias at DeepSeek's official price.
strip_model_date_suffix only strips ISO, 8-digit YYYYMMDD and 6-digit
YYMMDD suffixes, and the prefix fallback only matches longer rows, so this
id resolved to no pricing row and was silently billed at zero.
The GrokBuild backfill test expectations follow the corrected cache-read
price; its cache-inclusive input assertion (450 fresh tokens) is unchanged.
Moonshot/Kimi asked to exit the reasoning-vendor workaround: their
endpoints no longer require thinking replay on tool-call turns, and the
injected placeholders disrupt the model's chain of thought.
- Remove moonshot/kimi from REASONING_VENDOR_HINTS; Kimi now gets clean
passthrough on the anthropic path (no placeholder thinking blocks, no
signature stripping) and no reasoning_content on the openai_chat path.
- DeepSeek/MiMo keep the behavior (documented server-side requirement).
- Flip the two Kimi gating tests into negative regressions and update
stale comments/test model names that still referenced Kimi.
The restore backup is a snapshot taken when takeover starts and is never
refreshed from live afterwards. If the user logged into official ChatGPT
while takeover was active, exit/crash-recovery restored the stale
third-party API-key snapshot verbatim over auth.json, wiping the login on
every restart (and the auto re-takeover on startup looped the clobber).
Restore now arbitrates before writing: whenever live auth.json holds real
login credentials (codex_auth_has_credential_login_material, and not the
proxy placeholder), live wins — the backup's API key is demoted into
config.toml as experimental_bearer_token and the backup auth slot is
dropped, so auth.json is left untouched. Live credentials are always newer
than the snapshot because only Codex itself (login / token self-refresh)
advances them, so even an official-shape backup must not roll live tokens
back. Metadata residue (last_refresh / tokens.account_id) counts neither
as a live login to protect nor as backup material to restore. Both restore
entry points — the with-fallback path (exit / crash recovery / takeover
disable) and the simple path (takeover rebuild and failure rollback) —
share the same protection.
Fixes#6277
Zhipu renamed the quota entry type field from TOKENS_LIMIT to CREDIT_LIMIT on the mainland endpoint, causing all quota tiers to be skipped and the usage panel to show empty. Accept both values (case-insensitive) so old plans and the international endpoint keep working.
Fixes#6153
* refactor(hermes): align provider form hierarchy
* fix(hermes): match model detail label typography
* fix(hermes): mute context length label
* fix(hermes): align model detail field width
* fix(hermes): expose model detail state
The model mapping dropdown (used by Claude Desktop, Codex, and OpenCode
provider forms) lists every fetched model grouped by vendor with no way
to filter. Providers with hundreds of models make the right model hard
to find, as requested in #6275.
Convert ModelDropdown from a plain DropdownMenu to a Popover + Command
combobox (the existing OmoFormFields pattern), adding a fuzzy search
input powered by cmdk:
- non-matching groups auto-hide; an empty state shows when nothing matches
- vendor name is indexed as a keyword, so models can be matched by vendor
as well as by model id
- the { models, onSelect } API is unchanged, so all 5 call sites work as-is
Consolidate two near-identical inline copies of the same grouped
dropdown (ModelInputWithFetch and the Copilot branch in ClaudeFormFields)
onto ModelDropdown so every model picker gains the search and the
duplication is removed.
Add searchModelPlaceholder / searchModelEmpty / searchModelAriaLabel i18n
keys for en, zh, zh-TW, ja.
check_updates trusted the DB-cached content_hash before ever looking at
disk, so a skill whose SSOT directory is gone (e.g. after restoring a DB
backup on another machine) showed "no update" and the missing files were
permanently masked; the only recovery was uninstall + reinstall.
Extract the local-hash decision into local_hash_for_update_check with
the order inverted: validate the untrusted directory name, check that
the SSOT directory actually exists, and only then trust the cached
hash (computing and backfilling it when absent). A missing directory
now yields None, which never equals the remote hash, so the skill
surfaces as "update available" and the existing update_skill flow
rebuilds it — no new download logic.
Invalid directory values keep the pre-fix behavior (fall back to the
cached hash) instead of being reported as updatable, because
update_skill would hard-fail on the same validation gate.
Covered by four unit tests on the extracted helper.
The single backslash in Software\{{manufacturer}}\{{product_name}} was
consumed by Handlebars as a mustache escape, so the MSI wrote a literal
key named Software{{manufacturer}}{{product_name}} directly under HKCU.
Double the backslashes to match every other registry key in the template.
Fixes#6283
Upstream has had grok-4.5 since 2026-08, so 'no Grok models upstream' is no longer accurate.
Exclusion is now a deliberate product decision; enrollment requires separate review.
/v1/messages only accepts x-api-key: switch env from ANTHROPIC_AUTH_TOKEN to ANTHROPIC_API_KEY and add top-level apiKeyField.
Drop apiFormat so Claude Code connects directly (providerNeedsRouting no longer triggers) instead of through the proxy.
Desktop keeps mode: proxy (it cannot rename models itself) but switches to anthropic passthrough.
Add a changes-detection job (dorny/paths-filter, SHA-pinned) and gate
the frontend job, the 3-OS backend matrix, and the WSL2 contract gate
on whether the PR actually touches their inputs. About two thirds of
merged PRs only touch one side, and docs-only PRs now skip CI entirely.
Pushes to main still run everything unconditionally so the caches that
PRs restore from stay warm. Path groups mirror .github/labeler.yml,
plus rust-toolchain.toml which pins the compiler used by the backend
jobs.
* ci: add WSL2 backend checks
* ci: fix WSL2 backend job by keeping MSVC toolchain on native temp
- compile test binaries before pointing TEMP/TMP at the WSL2 UNC path
(mt.exe fails with c1010070/LNK1327 when TEMP is a UNC path)
- inject WSL2 TEMP/TMP per test step instead of via GITHUB_ENV
- use --tests so doctests cannot trigger link steps under the UNC temp
- run the filesystem contract gate before the full serial suite
- drop fmt/clippy steps duplicated from the existing Windows job
* ci: move full WSL2 suite to nightly, keep per-PR contract gate
- the full serial suite needs 50+ minutes on the 9P filesystem
(2026/2343 lib tests finished in 39.5min before the 45min timeout)
- per-PR job now runs only the atomic-write contract (~6min total)
and builds just the lib test binary via --lib
- new nightly workflow (schedule + workflow_dispatch, 90min timeout)
runs the contract plus the full serial suite on the WSL2 home
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix(windows): fall back when WSL rejects atomic replace
* fix(windows): preserve config during WSL fallback
* fix(windows): use rename fallback for WSL UNC
The Some arm of set_codex_model_catalog_json_field unconditionally overwrote model_catalog_json with the cc-switch-owned filename, discarding any user-provided custom catalog path or filename. This mirrors the ownership check already present in the None arm: only claim the pointer when it is absent or already cc-switch-owned, leaving user-managed external catalog files untouched.
Add tests covering both full-path and relative-filename user-owned catalogs in the Some arm.
Full Codex usage reimport (triggered by SQL backup import of pre-v16
backups, cursor mismatch after cross-machine restore, or manual rebuild)
pegged one CPU core for minutes on large corpora. Measured on a real
corpus (1920 rollout files / ~1.7GB / 82k billable events, macOS,
release build):
- full reimport on a disk-backed database: 36.3s -> 11.1s (~3.3x)
- the write path accounts for most of the win: per-event autocommit
under journal_mode=delete paid one full journal create/fsync/delete
cycle per row; Windows pays ~4.6ms per fsync (per #6122
measurements), so the absolute win there is an order of magnitude
larger
Changes:
1. sync_single_codex_file writes token events in batched transactions
(1000 events per commit), releasing the connection lock between
batches so UI queries can interleave. The sync cursor advances
inside the same transaction as the final batch, so a crash can
never leave the cursor ahead of the data; batches committed before
a crash are covered on rescan by the existing request_id PK +
fingerprint dedup.
2. CodexSyncPass preloads all session_log_sync cursors once per pass
(the archived-sessions suffix inheritance query used substr, could
not use an index, and ran a full table scan per archived file) and
caches model pricing lookups per pass instead of one SELECT per
event.
3. The three dedup queries and the insert statement use
prepare_cached; the two dynamically assembled dedup SQL strings are
built once via LazyLock.
Behavioral equivalence verified by replaying the real corpus before
and after the change: all 82k imported rows byte-identical across all
exported columns, with identical import/skip/suspected-duplicate
counts. The replay harness is kept as an ignored test
(replay_real_codex_corpus) for future changes on this path.
* fix(opencode): support unified OMO config
* fix(opencode): couple rollback snapshots to writes
* fix(ci): simplify rollback version type
* fix OMO unified config review issues
* validate OMO round-trip semantics
With every app tab, the profile switcher and the takeover toggles shown,
the header overflowed and clipped the add-provider button. Restructure
the header right side so primary actions live in a fixed shrink-0
cluster, and make AppSwitcher width-aware: apps that no longer fit
collapse into a "more" popover, with the active app always kept visible.
Reorders the Qiniu preset entry to sit directly after AIGoCode across all
eight in-app preset files. Pure block move, no field changes; README sponsor
tables are unaffected since Qiniu is not listed there.
* fix(skills): build readme_url from the resolved source directory
For skills.sh installs, `skill.directory` is just the skillId (the
innermost directory name) and the search-result readme_url is the bare
repo URL, so the persisted doc link lost the real nested path
(e.g. skills/developertools/solutions/<name>) and 404'd (#6111).
- install: derive the doc path from the resolved source directory
(repo-relative, SKILL.md-anchored) ahead of the stale readme_url
extraction and the directory-name fallback.
- update_skill: same derivation, so records written by older versions
self-heal on the next update instead of re-assembling the broken
path from the stale readme_url. repo_branch already follows
used_branch.
- New choose_doc_path / doc_path_for_source helpers with regression
tests; dropping the resolved-path priority fails the tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(skills): harden nested source resolution
* fix(skills): refresh repaired source metadata
* refactor(skills): keep nested readme fix install-only
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: YUZHEthefool <2804776511@qq.com>
* perf(backup): batch dump INSERTs and restore preserved tables in one transaction
Backup import was pathologically slow for databases with large log
tables. Measured on a database with 26k log rows (Windows, debug build):
- local file import (import_sql_string): 28.0s -> 0.39s (~71x)
- WebDAV/S3 sync import (import_sql_string_for_sync): 118.9s -> 0.33s
(~360x)
- export: 367ms/15.4MB -> 193ms/3.9MB (column list no longer repeated
per row)
Root causes, isolated with a phase-level benchmark (kept in the test
suite as ignored perf harnesses):
1. dump_sql emitted one INSERT statement per row. The restore side
parses/prepares/finalizes every statement individually, so import
cost scaled with statement count, not data volume. A control run on
an in-memory database was equally slow, proving this is CPU-bound
parsing, not disk I/O; journal/synchronous pragmas made no
difference. dump_sql now emits multi-row VALUES batches of 200 rows
(~200x fewer statements; SQLite has supported multi-row VALUES since
3.7.11, and both old and new formats import through the same generic
execute_batch, so dumps remain cross-version compatible).
2. restore_tables re-inserted preserved local tables (proxy logs,
stream check logs, usage rollups) with one implicit autocommit
transaction per row against the disk-backed staging database - one
fsync per row, ~4.6ms each on Windows. All restores now run in a
single transaction with the INSERT prepared once per table, which
also makes a mid-restore failure roll back atomically instead of
leaving a half-restored table.
Regression tests:
- dump_sql_batches_rows_into_multi_row_inserts pins the batch shape
(450 rows -> exactly 3 INSERT statements; fails if reverted to
per-row)
- multi_row_dump_round_trips_special_values round-trips quotes,
newlines, commas, CJK, emoji, BLOB storage class, and NULL through
export+import
- existing authorizer / genuine-export / sync-preserve tests still pass
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(backup): preserve SQL dump compatibility
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: YUZHEthefool <2804776511@qq.com>
* fix(security): harden usage script execution and Grok session log collection
Three hardening fixes in v3.19.0 code:
1. **usage_script: cap JS runtime resources and execution time**
`execute_usage_script` used a vanilla `Runtime::new()` with no limits,
so a malicious or buggy script delivered via deeplink or synced DB could
hang the backend thread forever with `while(true)`. Add
`create_script_runtime()` enforcing a 5-second interrupt handler, 16 MiB
memory limit, and 256 KiB stack limit. Regression test confirms an
infinite-loop script is rejected within seconds instead of blocking.
2. **Grok session log: skip oversized files**
`sync_single_grok_file` loaded the entire `updates.jsonl` into memory
with no size cap. Add a 50 MiB limit and skip files that exceed it.
3. **Grok session log: bound directory traversal**
`collect_files_named` recursed without depth limits and followed
symlinks, so a symlink cycle under `~/.grok/sessions` caused a stack
overflow. Add a 16-level depth limit and skip symlinks entirely.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ui): show hidden usage access token and user id in deeplink provider import dialog
The provider import confirmation dialog parsed usageAccessToken and
usageUserId from ccswitch:// URLs and persisted them in
ProviderMeta.usage_script, but never rendered them. Users could not see
these credentials before approving the import.
- Add i18n keys for usageAccessToken/usageUserId in all locales.
- Render both fields in the usage script section; mask the access token
like usageApiKey.
- Add a regression test verifying the dialog displays the masked token
and the user id.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(security): bound Codex model catalog and proxy response bodies
Two issues were found while auditing security boundaries:
1. **Codex model_catalog_json arbitrary file read**
only checked the filename and
accepted any absolute path. A malicious could
point CC Switch at an external file (e.g. ) or a huge
file to exhaust memory.
- Require the resolved path to stay inside
using a new helper (lexical, no filesystem calls).
- Treat Unix-style absolute paths as absolute on Windows.
- Cap catalog reads at 32 MiB.
2. **Unbounded proxy response bodies / compression bombs**
Non-streaming responses and error responses were read with
and fully decompressed without any byte
limit, allowing a malicious upstream to OOM the backend.
- Add with a 128 MiB ceiling.
- Reject oversized bodies and oversized decompressed payloads with
.
- Map the new error variant into the Codex proxy error response.
Regression tests cover: absolute-path containment, traversal rejection,
oversized catalog file, oversized buffered/streamed proxy responses.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ci): set up pnpm via Corepack instead of pnpm/action-setup v6
pnpm/action-setup v6 installs pnpm through a two-step self-installer:
it bootstraps the latest pnpm (11.7.0) via npm, then downloads the
pinned version (10.12.3) from the npm registry to switch. When that
second tarball fetch failed (error 23), the self-installer still
reported "done" and crashed with ENOENT on @pnpm/linux-x64's
package.json, failing the CI setup step.
Replace the action with Node 20's bundled Corepack and pin the version
once via package.json's packageManager field, so setup performs a
single exact-version download with no floating bootstrap. This also
lets release.yml drop its Windows ARM64 special case, since Corepack
has no arch restrictions, and pins the same version for local dev.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(security): close review gaps in proxy body caps, deeplink dialog, catalog path
Address maintainer review on the security PR:
- Proxy: bound the four remaining unbounded forwarder read paths
(non-2xx error body, failover pre-read, both 2xx error-envelope
validators) with bytes_with_limit + bounded decompression.
- Proxy: bytes_with_limit now accumulates Hyper/Reqwest/Streamed bodies
chunk-by-chunk and aborts mid-stream instead of collecting the full
body before comparing; Buffered keeps the post-hoc check.
- Proxy: decompression is budgeted at the decoder (Read::take on
gzip/deflate/zstd, bounded brotli reader) via decompress_body_with_limit,
so bombs are cut off at the byte budget instead of after full expansion,
including stacked-encoding intermediates.
- Deeplink: show the usage section whenever any usage field is present,
matching build_provider_meta persistence — a deeplink carrying only
usageAccessToken/usageUserId no longer imports credentials invisibly.
- Codex catalog: after the lexical containment check, canonicalize the
existing file and re-verify containment so symlinks inside ~/.codex
cannot escape the config dir; the path_is_within doc comment no longer
oversells the lexical check as symlink-safe.
- Grok sync: comment now matches the skip-all-symlinks behavior and each
skip is logged, so sessions behind a symlink are diagnosable.
- Docs: CONTRIBUTING notes the packageManager pnpm pin and Corepack flow.
Regression tests follow the same methodology as round 1: reverting a
guard fails the corresponding test (server write-count assertions prove
the mid-stream abort; a truncated gzip stream distinguishes TooLarge
from read-to-end errors; symlink escape is rejected only after the
canonicalize re-check).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(opencode): load OMO models from runtime opencode models
Surface OAuth/Zen free models in OMO/OMO Slim pickers by running the
installed OpenCode CLI, with a 20s timeout and a toast when discovery fails.
* style: fix rustfmt and prettier for OMO runtime models
* fix(opencode): pass OPENCODE_CONFIG_DIR for runtime model discovery
Honor the configured OpenCode config directory when running
`opencode models`, including WSL UNC path translation. Also drop the
unused run_detected_tool_command wrapper that failed clippy.
* fix(opencode): satisfy Windows Clippy
* fix(opencode): bound runtime model discovery
* fix(opencode): address runtime model review feedback
* fix(ci): parenthesize unsafe kill expression