From 425e932b7f2514b4becb1f681459117020469c15 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 6 Aug 2026 12:21:00 +0800 Subject: [PATCH] chore(release): v3.19.2 --- CHANGELOG.md | 57 +++++++++++++++++++++++++++++++++++++++ package.json | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 2 +- 5 files changed, 61 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc7a076fa..34c382696 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,63 @@ All notable changes to CC Switch will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.19.2] - 2026-08-06 + +Development since v3.19.1 is a correctness and hardening pass, with the management UI picking up its two most-requested conveniences. The headline fix is to Codex usage accounting: a rollout file that interleaves several cumulative token counters — a gateway replaying the same snapshot under different rate-limit buckets, or two genuinely distinct counters alternating — could record several times its true usage, and the importer now recognizes both shapes; replaying a real corpus of ~1,900 rollout files lands within 0.001% of an independently computed ideal recount (#3011). A six-part security hardening caps every unbounded read a contributor's audit surfaced — usage scripts, Grok session logs, catalog files, proxy response bodies and their decompression — and the deep-link import dialog now shows two credential fields it previously persisted without rendering. OMO setups regain a working integration on two fronts: when OMO's unified config (`~/.omo/omo.jsonc` or `omo.json`) exists, writes land inside it instead of the legacy file the runtime no longer reads, and the model pickers merge in whatever the installed OpenCode reports at runtime. The MCP, prompt and skill panels gain search, with bulk per-app toggles joining the MCP and skill lists; the Auth Center shows each ChatGPT account's subscription usage inline; and two write-path overhauls — batched SQL backups and batched Codex session imports — cut the worst restore, sync and reimport stalls on large databases. + +**Stats**: 24 commits | 109 files changed | +12,340 insertions | -1,897 deletions + +### Added + +- **Search and Bulk Per-App Toggles in the Management Panels**: The MCP, prompt and skill panels gain a shared search box — Esc clears it, and it only intercepts the global back shortcut while non-empty — and the per-app count badges on the MCP and skill lists become three-state checkbox buttons that enable or disable an app across every listed entry at once. Bulk operations run sequentially rather than in parallel, because each app's live config is a single file and concurrent writes would overwrite each other; failures are collected and reported together. Deliberately, a bulk toggle acts on the full list, not the search-filtered subset. Two database-layer fixes ride along: MCP toggles moved from read-whole-row/save-whole-row to a single-column atomic UPDATE, closing a pre-existing lost-update race when two apps were toggled near-simultaneously, and skill updates re-confirm the record still exists with its install generation unchanged before writing, so a slow update task can no longer resurrect a just-uninstalled skill. The search index is an explicit whitelist — env values and headers are never part of the searchable text. (#5954, #5935) +- **Per-Account Subscription Usage in the Auth Center**: Settings → Auth Center now shows each ChatGPT (Codex OAuth) account's subscription usage inline, reusing the exact query the provider card footer already runs — shared by account id, cached for five minutes, fetched once on mount without polling. Frontend-only change. (#4887) +- **OMO Model Pickers Fed by the Runtime's Own Model List**: The OMO form's model selectors previously offered a static list; they now also run `opencode models` and merge in what the installed OpenCode actually reports. The helper process is sandboxed deliberately: project-level config discovery is disabled and the working directory is pinned to the OpenCode config dir, so opening a form can never execute a project's `.opencode/` plugins; it runs under a 20-second deadline, and on timeout the whole process tree is killed — process groups on macOS/Linux, `taskkill /T` on Windows, and an in-distro `timeout` for WSL installs. Any failure falls back to the static list with a warning toast. (#5522) +- **Built-In Pricing for Qwen3.8 Max**: `qwen3.8-max` is seeded insert-if-absent at $2/$6 per million input/output tokens, with $0.25 cache read and $2.50 cache write (125% of input, the official explicit context-cache rate). As always, a price you already customized is untouched. (#6053) + +### Changed + +- **Partner Roster Maintenance**: The NekoCode and Unity2.ai partner presets are removed from all apps, the README and every locale. The Kimi preset and README entry surface the vendor's first top-up bonus, and RunAPI's in-app promo line moves to a first top-up discount. Qiniu moves next to AIGoCode in preset order. + +### Fixed + +- **Codex Usage Over-Counted When Token Counters Interleaved**: The Codex session importer derived usage deltas from a single high-water mark over cumulative totals, which double-counts when a rollout file interleaves counters — either a gateway replaying the same unchanged snapshot under a different rate-limit id, or two genuinely distinct cumulative counters alternating. Files from the field measured six to eight times their true usage. The importer now prefers each event's explicit last-turn usage and recognizes replayed snapshots by their full token signature; a snapshot is only deduplicated against the same source's own last signature or the immediately preceding token event, so a legitimate counter reset is never swallowed, and the total-only fallback keeps a single global baseline. Replaying a ~1,900-file / 1.7 GB real corpus lands within 0.001% of an independently computed ideal, and the residual differences are legitimate recoveries of counter resets the old code clamped away. Historical rows are deliberately not rewritten — see Upgrade notes. (#3011, #3015) +- **Dropped Chat Tool Calls Now Fail the Turn Instead of Faking Completion**: When a third-party Chat gateway returns tool calls without a function name, the Chat → Responses transform silently discarded them and still reported the turn as `completed` — Codex saw a successful turn with nothing left to do and ended its agent loop without any error, turning a diagnosable upstream failure into a silent stall. The transform now emits `response.failed` (streaming) or a transform error (non-streaming) when every tool call in a turn was dropped and none remains usable, gated on `status == "completed"` so that `finish_reason: length` truncation keeps its own `incomplete` semantics. All three drop sites log structured, content-free fields — call-id presence, argument byte counts, finish reason — so the upstream defect behind #4341 can finally be diagnosed from real traffic. Turns that still contain a valid tool call, text-only turns and truncated turns are unaffected. (#4341) +- **OMO Writes Went to a File OMO No Longer Reads**: OMO 4.19.3 unified its configuration into `~/.omo/omo.jsonc` (falling back to `omo.json`) and its migration renames the legacy per-app file away — after which CC Switch, which only knew the legacy path, kept writing a file outside OMO's config chain, so provider switches appeared to succeed but never took effect. CC Switch now detects the unified file and writes the OpenCode section inside OMO's `"[opencode]"` key — and only there, because OMO validates its root schema strictly and discards the entire file over any unknown root key. Edits preserve the document as JSON5: comments, key order and line endings survive, nothing is written when nothing changed, and every write is verified by re-parsing the output and comparing it semantically against the intended result — if the round-trip would corrupt the document, the write is refused and the file left untouched rather than saved corrupted (see Upgrade notes for the one known trigger). The same change also hardened atomic file writes on Windows for every managed app, replacing the remove-then-rename sequence — which had a brief window where the target file did not exist — with `ReplaceFileW`. (#5945) +- **GitHub Copilot Takeover Left Modern Claude Code Logged Out**: Current Claude Code releases show a confirmation dialog for an unrecognized API key, defaulting to "No (recommended)" — so the `ANTHROPIC_API_KEY` placeholder that Copilot takeover wrote left users in an unauthenticated session unless they overrode the default. Takeover now writes the `ANTHROPIC_AUTH_TOKEN` placeholder instead, which enters the session without any prompt; a provider whose credential field explicitly selects `ANTHROPIC_API_KEY` in the form's advanced options keeps the old behavior. The Copilot forwarding path also gains the `[1M]` context-marker strip that the other forwarding paths already had, so a `claude-*[1M]` model id no longer reaches GitHub's API verbatim. (#5832) +- **Hermes Prompts Were Written to a File Hermes Never Reads**: Hermes loads its persona file `SOUL.md` from `~/.hermes/` and never looks for `AGENTS.md` there — that name is project-level context discovered from the working directory upward. CC Switch's prompt management wrote `~/.hermes/AGENTS.md` from the day Hermes support was added, so enabling a Hermes prompt produced a dead file. Prompts now read and write `~/.hermes/SOUL.md`, and the existing back-fill still applies: a SOUL.md you wrote yourself is imported into the database before being replaced. (#5777) +- **Skill Installs Failed on Repos With a Same-Name Wrapper Directory**: Installing ast-grep's official skill failed with a missing-SKILL.md error: the repository keeps a wrapper directory named like the skill, with the real skill nested below it, and the resolver returned the first directory whose name matched. Source resolution is now anchored on SKILL.md itself — a directory without one is never selected — which also stops update checks against such repositories from reporting a permanent phantom update. (#4141) +- **skills.sh Installs of Nested Skills Produced 404 Documentation Links**: The skills.sh discovery flow reports only a skill's basename; installation resolved the real nested directory, but the stored README link was still derived from the basename guess, yielding a 404. The link is now built from the directory the installer actually resolved. The fix is install-only — records written by earlier versions keep their broken link until the skill is reinstalled, see Upgrade notes. (#6111) +- **Header Actions Clipped When Every App Was Enabled**: With all app tabs, the profile switcher and the takeover toggles visible, the header overflowed and clipped the add-provider button. Primary actions now live in a fixed cluster, and the app switcher is width-aware: apps that no longer fit collapse into a "more" popover, with the active app always kept visible. +- **Idle GPU Use From the Route Status Heartbeat**: The route status indicator's pulse animation ran even when the window was unfocused, keeping the GPU ticking for a purely decorative effect. Window focus now gates the heartbeat through a data attribute and CSS — an unfocused window freezes the animation at full opacity, and reduced-motion preferences disable it entirely. Data polling is unchanged; only the decoration pauses. (#5767) + +### Security + +- **Every Unbounded Read on the Audit's List Is Now Capped, and the Deep-Link Dialog Shows Two Fields It Used to Hide**: A six-part hardening pass. Usage scripts — which can arrive via deep link or a synced database — ran on an unlimited JS runtime, so a `while(true)` hung the backend thread forever; the runtime now enforces a 5-second interrupt, a 16 MiB memory limit and a 256 KiB stack. Grok session-log collection skips files over 50 MiB, bounds directory recursion at 16 levels and no longer follows symlinks, so a symlink cycle under `~/.grok/sessions` can no longer overflow the stack. A `model_catalog_json` entry naming the cc-switch catalog file used to be trusted on filename alone, wherever its absolute path led; it must now resolve inside the Codex config directory — re-checked after `canonicalize`, so a symlink cannot escape — and catalog reads are capped at 32 MiB. Proxy response bodies collected in full — non-streaming responses, error bodies and full-body validation paths — are capped at 128 MiB, enforced as chunks arrive so the connection is dropped the moment the limit is crossed rather than after the fact; streaming paths, passthrough and streamed transforms alike, are never buffered in full and carry no total-size cap. Decompression is budgeted at the decoder's read side across gzip, deflate, zstd and brotli, so a compression bomb cannot expand unchecked; an oversized body maps to a distinct 502 rather than being mistaken for a retryable network error. Finally, the deep-link provider import dialog parsed and persisted `usageAccessToken` and `usageUserId` without ever rendering them; both now display — the token masked — before you approve the import. (#5919) + +### Performance + +- **SQL Backups Export in Batches and Restore in One Transaction**: Two separate costs, one per direction. The dump wrote one INSERT per row, so importing a large backup made SQLite parse, prepare and finalize tens of thousands of individual statements (the dump itself already ran inside one transaction); dumps now emit multi-row INSERTs batched at 200 rows / 1 MB, cutting the statement count by two orders of magnitude and the file size roughly 4×. Separately, after every WebDAV/S3 sync import the tables that stay local are restored row by row, and each row ran as its own implicit transaction — one full journal write-and-fsync cycle per row, which is what made the periodic auto-sync freeze the app on large databases; that restore now runs inside a single transaction. Old single-row exports still import, and the new format stays within limits every shipped SQLite accepts, so backups exchange cleanly across app versions in both directions. This likely alleviates the cross-machine import hangs reported in #2100 — feedback there is welcome. (#6122) +- **Full Codex Usage Reimports Are ~3× Faster, More on Windows**: A full reimport — triggered by importing a pre-v16 SQL backup, a cursor mismatch after a cross-machine restore, or a manual rebuild — pegged one CPU core for minutes on large corpora: every token event was written in its own autocommit transaction, paying a full journal create/fsync/delete cycle per row, and every archived file re-ran a cursor-inheritance query that could not use an index. Token events now commit in batches of 1,000 with the connection lock released between batches so UI queries can interleave; the sync cursor advances in the same transaction as the final batch, so a crash can never leave the cursor ahead of the data; cursors and model pricing are preloaded once per pass; and the hot statements are prepared once and cached. A real corpus of 1,920 rollout files / 1.7 GB reimports in 11.1s instead of 36.3s on macOS (release build); Windows pays several milliseconds of fsync per row, so the absolute win there is an order of magnitude larger. Equivalence was verified by replaying the corpus before and after the change: all 82,000 imported rows byte-identical across every exported column, with identical import/skip counts. + +### Docs + +- **The Codex DeepSeek Routing Guide Now Matches v3.19.1's Direct Connection**: The guide used DeepSeek as its worked example for local Chat routing, which v3.19.1 made obsolete by moving the preset to native Responses. All three languages are updated, and the v3.19.1 release notes' pointer to the guide was corrected. + +### Internal + +- **CI Installs pnpm Through Corepack**: The workflows previously installed pnpm ad hoc; they now enable Corepack and respect the `packageManager` pin in `package.json`, so CI and local installs resolve the same pnpm version. CONTRIBUTING documents the behavior, including that a contributor's first `pnpm` invocation after `corepack enable` downloads and pins the declared version. (#5919) + +### Upgrade notes + +- No database schema migration in this release — `SCHEMA_VERSION` stays at 16, so no pre-migration backup is triggered. +- The Codex interleaved-counter fix corrects accounting going forward only: historical rows are deliberately not rewritten, and no automatic rebuild runs. If your dashboard shows implausibly high Codex figures and your rollout files carry the interleaved pattern, trigger a manual Codex usage rebuild after upgrading — this release's import batching makes that rebuild roughly 3× faster than it would have been. Most installations are unaffected: outside the interleaved pattern, the old and new algorithms agree to within a fraction of a percent. +- skills.sh nested skills installed before this release keep their broken README link; uninstalling and reinstalling the skill rewrites it. Updating the skill in place does not. +- The first WebDAV/S3 sync after upgrading re-uploads `db.sql`, because the dump format changed and the sync protocol hashes it as an opaque artifact — a benign, one-time retransfer. +- The Copilot `AUTH_TOKEN` placeholder applies when takeover next rewrites the live config — switching providers or restarting takeover. If you explicitly set the credential field to `ANTHROPIC_API_KEY` in the provider form's advanced options, that choice is honored unchanged. +- Unified OMO config detection is by file presence, not version: when `~/.omo/omo.jsonc` (or `omo.json`) exists, CC Switch edits it in place; when neither exists, it keeps writing the legacy OpenCode-level file as before. Known limitation: if that file contains block comments (`/* … */`), writes are refused with an error to protect the document — line comments (`//`) are unaffected. Remove block comments to switch providers until the upstream JSON5 writer is fixed. +- Bulk app toggles in the management panels act on the entire list, not the search-filtered subset. +- Proxy responses that must be buffered — non-streaming responses and error bodies — now fail with a 502 when they exceed 128 MiB instead of being forwarded; pass-through streaming responses are unaffected. Normal LLM responses are megabytes at most, so this should only trigger on a malfunctioning upstream; note that this class of failure terminates the request and does not trip failover to the next address. + ## [3.19.1] - 2026-07-31 Development since v3.19.0 is a maintenance pass rather than a feature wave — and the first release in this project's history that deletes more than it adds. Three Chinese Codex gateways move onto native Responses and no longer need local routing takeover: DeepSeek connects straight to `api.deepseek.com` and brings with it a general mechanism for mirroring a vendor's own model catalog verbatim, so the GPT-5 harness and the freeform `apply_patch` registration stay self-consistent instead of collapsing into the neutral template; Volcengine's Ark Coding Plan follows now that the official documentation confirms `/api/coding/v3` serves Responses; and Tencent Hunyuan's TokenHub joins as a new preset. The correctness sweep covers four failures visible in the field: Claude Desktop usage has been counted twice since v3.18.0, once as a proxy row and once as an imported session row (#5938); switching back to the built-in official Codex provider left a third-party key in `auth.json`, so Codex authenticated to the official endpoint with the wrong credential and returned 401 without ever falling through to its login screen; `grok update` failed from Settings with a bare `os error 2` because a GUI-launched app cannot see node; and Grok Build's proxy takeover returned 404 on any non-Responses backend while every request minted a fresh session id (#5677). Rounding it out, nine interface strings that rendered as Simplified Chinese in every language are localized and the Traditional Chinese About page gains the thirty tool-manager strings it was missing, eight models that silently billed at zero gain built-in prices, deep-link import confirmations mask more and truncate less, and 3,166 lines of superseded code plus four npm dependencies are removed. diff --git a/package.json b/package.json index 3942cc444..2fed17c34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cc-switch", - "version": "3.19.1", + "version": "3.19.2", "description": "All-in-One Assistant for Claude Code, Codex & Gemini CLI", "type": "module", "scripts": { diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 5cb0bb568..f2476cf22 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -758,7 +758,7 @@ dependencies = [ [[package]] name = "cc-switch" -version = "3.19.1" +version = "3.19.2" dependencies = [ "anyhow", "arboard", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index ece19cbb5..be3c73c33 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cc-switch" -version = "3.19.1" +version = "3.19.2" description = "All-in-One Assistant for Claude Code, Codex & Gemini CLI" authors = ["Jason Young"] license = "MIT" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 3243ff6ee..948727bf5 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "CC Switch", - "version": "3.19.1", + "version": "3.19.2", "identifier": "com.ccswitch.desktop", "build": { "frontendDist": "../dist",