mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-06-16 13:34:04 +08:00
910ca3b40ea4fca3627306a35c40e8516c45ab8a
1789 Commits
-
chore(sponsors): drop AICoding partner from README, presets and i18n
Remove AICoding (aicoding.sh) sponsor entry from the three README files and the provider preset across all seven app_types (claude, codex, gemini, hermes, opencode, openclaw, claudeDesktop). Also drop the matching partner_promotion.aicoding string in zh/en/ja locales. The icon (src/icons/extracted/index.ts) and the legacy provider id in codex_history_migration.rs are kept intentionally: the former for possible later re-use, the latter so existing users' Codex history still gets bucketed into "custom" on upgrade.
Jason ·
2026-05-25 22:20:34 +08:00 -
chore: satisfy clippy::nonminimal_bool and prettier to unblock CI
- codex_config: rewrite `!(A && !B)` as `!A || B` (De Morgan- equivalent) — also reads more directly as "no OAuth, or already has provider API key". - UsageScriptModal: reflow conditional onto one line per prettier.
Jason ·
2026-05-25 22:20:34 +08:00 -
feat(codex): migrate third-party history provider bucket to unified "custom" ID
Codex CLI filters conversations by model_provider — after normalizing all third-party providers to "custom", old sessions with their original provider IDs become invisible. This migration rewrites JSONL session files and state_5.sqlite threads to use the unified ID. Uses a three-tier trust model for source ID collection: - Known legacy preset whitelist (46 IDs, case-insensitive) - cc-switch-created custom providers (identified by created_at) - User-maintained custom providers are explicitly skipped Adds scanned_history_files flag to prevent stale v1 markers from blocking re-runs with the improved source ID collection logic.
Jason ·
2026-05-25 22:20:34 +08:00 -
fix(quota): sort ZhiPu tiers so missing nextResetTime maps to five_hour bucket
When the 5-hour bucket is at 0% utilization, ZhiPu's API omits nextResetTime. The old i64::MAX sentinel sorted these entries last, causing the weekly bucket to incorrectly claim the five_hour slot.
Jason ·
2026-05-25 22:20:34 +08:00 -
feat(codex): preserve OAuth login state during third-party provider switching
Codex provider switches now only write config.toml for third-party providers, injecting the API key as experimental_bearer_token. The user's auth.json (ChatGPT OAuth tokens) is preserved. Official providers with login material still write auth.json normally. Backfill restores bearer tokens into stored provider auth.OPENAI_API_KEY to maintain canonical shape.
Jason ·
2026-05-25 22:20:34 +08:00 -
feat(settings): unify unix installers on mktemp+bash, fix WSL install missing native installer
Extend the mktemp+bash installer pattern from hermes (commit 20d943be) to claude and opencode, and remove the cfg gate that locked install_command_for to non-Windows targets — Windows host + WSL tools now correctly route through the POSIX install priority instead of falling back to bare npm. Frontend "one-click install commands" displayed in the About section now diverge per platform to match what the backend actually runs. Backend (src-tauri/src/commands/misc.rs): - New CLAUDE_INSTALL_UNIX and OPENCODE_INSTALL_UNIX constants use the same `bash -c 'tmp=$(mktemp) && curl -fsSL .../install.sh -o $tmp && bash $tmp; status=$?; rm -f $tmp; exit $status'` shape as HERMES_INSTALL_UNIX. The shared comment about why we avoid `curl | bash` is now hoisted to the top of all three constants — the constraint (WSL `sh -c` sub-shells don't inherit outer pipefail; default shell may be dash/ash) applies to every shell installer, not just Hermes. - New installer_with_npm_fallback helper deduplicates the "official installer || npm fallback" chain construction; reuses chain_update_commands(LifecycleCommandShell::Posix) so the wiring matches the update-chain logic. - install_command_for split into cross-platform posix_install_command_for + thin #[cfg(not(target_os = "windows"))] wrapper. The cfg gate had been hiding a real bug: on Windows hosts, the WSL branch called wsl_tool_action_shell_command which forwarded install actions to the Windows-target tool_action_shell_command, yielding bare `npm i -g` for claude/opencode even though they have working native POSIX installers. - wsl_tool_action_shell_command now branches on action: Install dispatches to posix_install_command_for (native installer || npm chain); Update keeps the existing behavior. Empty install commands collapse to None so callers error on unsupported tools instead of running an empty command. - build_tool_lifecycle_command pipefail comment updated: the original justification (covering install's `curl | bash` path) no longer applies to any current command, but the directive stays as defense-in-depth for future pipe additions. - build_tool_action_line WSL branch comment updated to reflect the install/update split. Frontend (src/components/settings/AboutSection.tsx): - New posixScriptInstallCommand(url) helper builds the same mktemp+bash string template the backend uses. - New powershellEncodedCommand(script) mirrors the backend's UTF-16 LE + base64 encoder (charCodeAt + String.fromCharCode(lo, hi) + btoa) so the PowerShell EncodedCommand shown to users matches what the backend executes. - ONE_CLICK_INSTALL_COMMANDS split into POSIX_ONE_CLICK_INSTALL_COMMANDS and WINDOWS_ONE_CLICK_INSTALL_COMMANDS, selected by isWindows(): POSIX shows claude/opencode/hermes with the mktemp+bash installer; Windows shows claude/codex/gemini/opencode/openclaw with npm (matching backend static_fallback_command on Windows) and hermes with the PowerShell EncodedCommand. Display now mirrors backend execution per platform, removing the show-vs-run mismatch. - Stale comment in the probe-concurrency note mentioning "curl | bash" updated to "官方 installer". Tests (src-tauri/src/commands/misc.rs): - New wsl_install_uses_posix_install_priority covers three representatives: claude (positive: native installer with npm fallback), opencode (positive), codex (negative: no native installer so falls back to bare `npm i -g`). Reverse-asserts !contains("| bash") to lock out the old pipe form. - claude_install_chains_native_then_npm and opencode_install_chains_native_then_npm strengthened with !parts[0].contains('|') so the native installer portion stays pipe-free even if future edits to the template sneak a pipe back in. Validated: - cargo check --tests: clean - cargo test --lib commands::misc:: : 59 passed, 0 failed - pnpm typecheck: 0 errors - cargo fmt: clean - pnpm format: clean (no files changed)Jason ·
2026-05-25 22:20:34 +08:00 -
feat(settings): self-update first chain, switch hermes to native installer
Promote anchored upgrades to "<bin> update || <package fallback>" for the tools whose CLI knows how to self-update safely, and replace pip-based hermes install/update with the official NousResearch installer scripts. Frontend learns to detect "update reported success but version did not change" so silent upstream no-ops surface as a soft warning. Backend (src-tauri/src/commands/misc.rs): - New prefers_official_update(tool, shell) per-platform allow-list: POSIX {claude, codex, opencode, openclaw}. Windows drops opencode pending anomalyco/opencode#17295 (its silent `upgrade` may prompt for install-source detection and deadlock our lifecycle). gemini is absent from both because `gemini update` is not implemented upstream (google-gemini/gemini-cli#18618 / #16122 OPEN). - New chain_update_commands joins primary/fallback per shell: POSIX `||`, Windows `|| call` — batch transfers control to the `||` RHS without `call`, skipping subsequent tools in multi-tool actions. - New LifecycleCommandShell {Posix, WindowsBatch} threads the target shell through tool_action_shell_command_for_shell, replacing the old hermes-only WSL substitution hack with a uniform mechanism. - anchored_command_from_paths refactored: hermes anchors to `<bin> update`; tools in prefers_official_update chain self-update with the package-manager command as fallback; brew formulae remain brew-managed (never self-update what Homebrew owns); others use the prior npm/volta/bun/pnpm anchoring unchanged. Mirror on Windows. - Hermes commands switch from pip to the official scripts. POSIX/WSL runs `bash -c 'tmp=\$(mktemp) && curl -fsSL .../install.sh -o \$tmp && bash \$tmp; status=\$?; rm -f \$tmp; exit \$status'`. The mktemp + two-step download avoids `curl | bash` because WSL sub-shells inherit neither outer `set -o pipefail` nor a guaranteed bash (default may be dash/ash). Windows uses `powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand <b64>` with a hand-rolled UTF-16 LE base64 encoder for `irm .../install.ps1 | iex`, hiding the PowerShell pipe from cmd.exe. Pip is abandoned because macOS system python3 is often 3.9 while hermes-agent requires >=3.11, and the pyenv `python` shim may not exist — pip errors then misclassify as "command not found". Frontend (src/components/settings/AboutSection.tsx): - New versionUnchangedAfterUpdate four-AND short-circuit detects when the upgrade command succeeded, the tool still reports a version, but that version equals the pre-upgrade version while a different latest_version is known. Guards against upstream no-op updaters that return exit 0 without applying anything (openai/codex#21897). - Soft-failure shape gains kind?: "notRunnable" | "versionUnchanged" so the warning toast title can disambiguate the two cases. - Hermes install command snippet swapped from `python3 -m pip ...` to the official curl-bash one-liner shown to users. i18n (zh / en / ja): - New settings.toolActionVersionUnchangedTitle and settings.toolActionVersionUnchanged, synchronized across all locales. Tests (src-tauri/src/commands/misc.rs): - Anchored upgrade assertions updated to `<bin> update || <pkg>` shape across nvm / volta / bun / homebrew-npm-global / spaces / fnm branches; brew formula explicitly does NOT chain self-update. - New hermes anchor tests replace hermes_has_no_npm_anchor on both POSIX and Windows. WSL hermes tests rewritten for the mktemp+bash flow with `hermes update` fallback. - Reverse-lock tests: gemini static fallback must NOT contain `gemini update`; opencode Windows static fallback must NOT contain `opencode upgrade`; hermes commands must NOT contain `python`/`pip`/`powershell`/forbidden pipes. Validated: - cargo check --tests: clean - pnpm typecheck: 0 errors - cargo fmt: cleanJason ·
2026-05-25 22:20:34 +08:00 -
refactor(settings): drop CLI uninstall command hints, keep conflict diagnostics
The uninstall hint feature added in f6bdd4bb / 89e2339b printed a copy-pasteable shell command (e.g. `brew uninstall <formula>`, `<sibling-npm> rm -g <pkg>; [ -e bin ] && rm -f bin && rm -rf pkg_dir`) under each detected install in the multi-install conflict panel. Even with copy-only-never-execute UX, putting destructive commands one button away from the user is risk we don't want to ship — `rm -rf` under a misclassified path would be unrecoverable. Conflict diagnostics themselves are kept: users still see the warning banner + per-install row (source / path / version / Default badge), so the "you have multiple installs and the upgrade only touches one" signal survives. They just don't get a one-click destructive command. Backend (src-tauri/src/commands/misc.rs): - Remove `uninstall_command_from_paths` (POSIX + Windows pair). - Remove `posix_quote_for_user_shell` and `win_quote_path_for_user_shell` — both were uninstall-only quoters (whitelist + cmd-only forms). `win_quote_path_for_batch` and `shell_single_quote` stay because the anchored upgrade path and `build_shell_cd_command` still use them. - Remove `quoted_sibling_or_bare` and `quoted_sibling_or_bare_with_ext` thin wrappers (only callers were uninstall branches). - Drop `uninstall_command` and `uninstall_command_needs_cmd_hint` from `ToolInstallation`. Drop the eager `infer_install_source` -> `uninstall_command_from_paths` call in `enumerate_tool_installations` (saves ~16μs per probe but the real point is fewer surprises). - Remove 14 `#[test]` cases for uninstall command shape (brew / homebrew-npm-global / nvm / npm-fallback quoting / pnpm / volta / bun / hermes / system / Windows percent-path) plus 4 `win_quote_path_for_user_shell` helper tests and the `expect_user_shell_quoted_path` test mirror. - Drop the `src()` test helper that was only used to feed the deleted uninstall tests (`infer_install_source` is still called directly by surviving tests). - Cross-references in `anchored_command_from_paths` doc to its now-gone uninstall "dual" are stripped. Frontend (src/components/settings/AboutSection.tsx): - Conflicts `<ul>` keeps the diagnostic header + per-install `ToolInstallRow` but loses the inline command box, the trash + copy icons, the per-row PowerShell-call-operator hint, and the `handleCopyCommand` callback. - Remove the `Trash2` lucide import; `Copy` stays (the install-commands copy button at line 1100 still uses it). API (src/lib/api/settings.ts): - Drop `uninstall_command` and `uninstall_command_needs_cmd_hint` from the `ToolInstallation` TS interface (matches the Rust struct change). i18n (zh / en / ja): - Drop `settings.toolUninstallCopyHint`, `settings.toolUninstallCopied`, `settings.toolUninstallPwshHint`. Other `tool*` and `skills.uninstall*` keys are untouched (Skill uninstall is a separate, non-destructive feature for managing the Skills repo and is unaffected). Stats: 858 deletions, 5 insertions across 6 files. Validated: - cargo test --lib: 1322 passed, 0 failed - cargo fmt --check: clean - cargo build: clean - cargo check --tests: clean - pnpm typecheck: 0 errors - JSON.parse on all three locale files: clean
Jason ·
2026-05-25 22:20:34 +08:00 -
feat(settings): backend-generated source-aware uninstall command hints
Move uninstall hint command generation from frontend (which used a lossy `source` string and couldn't tell brew formula from homebrew-npm-global apart) into the Rust backend, where `brew_formula_from_path` + `infer_install_source` together produce correct commands for each install. Conflict UI now reads `inst.uninstall_command` directly. Backend (src-tauri/src/commands/misc.rs): - New `uninstall_command_from_paths` (POSIX + Windows) mirrors the `anchored_command_from_paths` family. POSIX branches on brew formula / volta / bun / pnpm / nvm|fnm|mise|homebrew / system. Windows branches on volta / pnpm / npm. - New `quoted_sibling_or_bare` (POSIX) + `quoted_sibling_or_bare_with_ext` (Windows) deduplicate the `sibling_bin().map(quote).unwrap_or_else(bare)` pattern across all uninstall branches. - New `posix_quote_for_user_shell` whitelists `[A-Za-z0-9._/+=:@-]`; any other character routes through `shell_single_quote`. Replaces `quote_path_if_spaced` on the uninstall side because the fallback chain is destructive (`rm -rf <pkg_dir>`), so a `;` / `$` / backtick in the home dir would have let the chain inject extra commands. - New `win_quote_path_for_user_shell` (no `%` 4x escape) splits from `win_quote_path_for_batch`. Uninstall hints are copy-paste targets, not `.bat + call` payloads, so the two-round percent expansion that motivates the 4x escape doesn't apply. - POSIX `nvm|fnm|mise|homebrew` branch appends physical-delete fallback `; [ -e <bin> ] && rm -f <bin> && rm -rf <pkg_dir>` because nvm v18.20.8 was observed to silently no-op `npm rm -g` (exit 0 with `up to date` message, no filesystem change). Anchored `<node_root>/{bin,lib}` layout assumption documented as branch-specific. - `ToolInstallation` gains `uninstall_command: String` and `uninstall_command_needs_cmd_hint: bool`. The bool is computed as `cfg!(target_os = "windows") && uninstall_command.contains('"')` — a compile-time platform gate avoids false positives from POSIX `shell_single_quote`'s `'"'"'` escape form, which also contains `"`. - `enumerate_tool_installations` computes `source` once and threads it into `uninstall_command_from_paths`, removing a redundant `infer_install_source` call (review N1). Frontend (src/components/settings/AboutSection.tsx + src/lib/api/settings.ts): - Remove the JS helpers `buildUninstallCommand`, `dirOfPath`, `isWindowsPath`, `shellQuote`, `TOOL_NPM_PACKAGES`. Conflict rows now read `inst.uninstall_command` directly. - Render a PowerShell-call-operator hint when `inst.uninstall_command_needs_cmd_hint` is true. Avoids string-match inference from the command (which fails on macOS user names containing `'`, e.g. `o'leary`, because `shell_single_quote`'s escape contains `"`). - `ToolInstallation` TS interface gains the two new fields. i18n (zh/en/ja): - New key `settings.toolUninstallPwshHint` documents the PowerShell `&` (call operator) requirement for quoted paths. Tests: 30 new cases under `commands::misc::tests::anchored_upgrade` cover each POSIX branch (brew formula extraction, npm anchored with fallback, pnpm, volta, bun, hermes), strong-quoting under `;` / `'` paths, the fallback-only-for-anchored-branches inverse guard, plus mirror Windows helpers for the user-shell quoter and a `path%foo%` integration case demonstrating the literal `%` preservation contract. Total `commands::misc` test count: 68.Jason ·
2026-05-25 22:20:34 +08:00 -
feat(settings): extend anchored tool upgrades to Windows native paths
Apply macOS-side anchored upgrades (9984eccb) to Windows. Six tools now upgrade via an absolute path derived from the install actually hit by the command line, instead of bare `npm i -g` landing on PATH-first npm. WSL tools explicitly bypass anchoring (Windows-host paths invalid in distro). Scope: 7+ Windows package managers compress to 3 idioms in practice (Scoop/Chocolatey/winget/nvm-windows/MS Store all just install node; the global-package layer is still npm): - volta -> <sibling>\volta.exe install <pkg> - pnpm -> <sibling>\pnpm.cmd add -g <pkg>@latest - others -> <sibling>\npm.cmd i -g <pkg>@latest Cross-platform: - infer_install_source: add /volta/ (no dot) and /pnpm/ matches so %LOCALAPPDATA%\{Volta,pnpm} route correctly. - parent_dir: recognize both \ and /, take rightmost (Option<usize> Ord lets None<Some, so rfind('\\').max(rfind('/')) auto-picks). - npm_package_for, default_install, installs_anchored_command: promoted from cfg(not(windows)) to all-platforms. - plan_command_for: merged the two cfg branches; Windows+WSL short- circuits to wsl_tool_action_shell_command (not static_fallback) so the command shown to the user matches what build_tool_action_line actually runs. - ToolInstallation: add #[serde(skip)] real: PathBuf, populated by enumerate, read by installs_anchored_command — no more double canonicalize, and closes a TOCTOU window between enumerate and anchor. Windows-only helpers (#[cfg(target_os = "windows")]): - sibling_bin_with_ext reads fs to pick .cmd vs .exe (Node installer drops .cmd, Volta drops .exe; pure string can't disambiguate). - win_double_quote shared primitive; win_quote_path_for_batch handles cmd token-boundary chars (space, &, (, ), ^, ;, <, >, |, ,) by wrapping in quotes, and handles `%` via 4x escape (%%%%) to survive both .bat parser expansion AND `call`'s own second percent-expansion pass (Microsoft `call /?`: "percent (%) expansion is performed on each parameter"). - wsl_tool_action_shell_command wraps tool_action_shell_command so hermes inside WSL gets python3||python instead of Windows-target py||python (py launcher is Windows-only, missing in WSL distros). Wire-up: - build_tool_action_line Windows: WSL -> wsl_tool_action_shell_command + wsl.exe wrap; native update -> enumerate + anchored + static fallback. Everything gets `call ` prefix (.bat needs it for .cmd without replacing the current script; harmless for .exe). Tests: 33 new — 10 platform-agnostic (install_source_classification, parent_dir_cases) + 23 cfg-gated Windows-only (anchored_upgrade_windows, windows_helpers). Anchored expected values use a small expect_quoted_path mirror in the test mod so assertions stay correct on Windows hosts whose %TEMP% itself contains spaces/special chars (e.g. user accounts named "John Doe"); the 7 hardcoded-literal win_quote_* unit tests independently lock the quoting rules themselves, so mirror drift would be caught by either side. Limitation: Windows cfg-gated tests don't run on macOS cargo test. Validated on macOS host: cargo fmt clean, clippy --all-targets 0 warnings, cargo test --lib 1322 passed. Windows CI runs only on release tag (release.yml has windows-2022); production validation needs cargo test on a Windows machine before broad rollout.Jason ·
2026-05-25 22:20:34 +08:00 -
fix(settings): enforce absolute paths for all anchored upgrade branches
- misc: extend sibling_bin derivation to claude/brew/volta/bun in addition to npm. Return Option<String> so the anchored-path invariant cannot be silently violated — when bin_path has no parent directory, propagate None and fall back to the static command at the call site instead of emitting a bare command that depends on the GUI process PATH. - misc: factor out quote_path_if_spaced to keep all five anchored branches consistent when the resolved path contains spaces. - AboutSection: add preflightTools lock + try/finally around probeToolInstallations so fast double-clicks cannot race two probe rounds into concurrent executeRun calls or duplicate confirm dialogs. - Tests: cover sibling_bin None branch and space-quoting for every anchored branch (claude/brew/volta/bun).
Jason ·
2026-05-25 22:20:34 +08:00 -
feat(settings): prefer official installer for tool install with npm fallback
Bare `npm i -g <pkg>` was the install command for every managed tool, but Anthropic and SST now both recommend their native installers over npm. The update path already anchors to native installs (`claude update` and friends), so install was the last place still forcing npm — even when the user's intent was clearly to get the upstream-recommended install. - New `install_command_for(tool)` (non-Windows only): claude and opencode now run the upstream installer with a `|| npm i -g ...@latest` POSIX short-circuit fallback, so the tool still installs if the URL is unreachable. - codex / gemini / openclaw / hermes pass through to the original static command — they have no independent official installer today. - `build_tool_lifecycle_command` now opens `set -o pipefail` alongside `set -e`: without it, `curl ... | bash` where curl fails would have bash exit 0 on empty stdin, silently masking the failure and skipping the `||` fallback. - 6 new `install_strategy` unit tests pin the per-tool install command shape, mirroring the existing anchored_upgrade regression suite. Update routing and Windows install behavior are unchanged.
Jason ·
2026-05-25 22:20:34 +08:00 -
fix(proxy): source takeover model fields from target provider on managed accounts
When a managed-account provider (GitHub Copilot or Codex OAuth) is taken over by the local proxy, the *_MODEL_NAME entries written to Claude live config used to be snapshotted from the live file on disk, which still held the *previous* provider's values during a switch. The result was a stale display name lingering after the switch. Read the model snapshot from the target provider's effective settings (common-config merged) when the provider uses managed-account auth; preserve the existing behavior of reading from the live config for normal providers. The three takeover entry points now resolve the effective provider up front so common-config writes flow through.
Jason ·
2026-05-25 22:20:34 +08:00 -
feat(settings): anchor tool upgrades to the actual install
Rework the About tool-upgrade flow so upgrades target the install the command line is actually using, instead of running bare `npm i -g` and landing on PATH's first npm — which used to move upgrades to the wrong location or fail outright when the resolved bin had no sibling npm. Anchored upgrade command (update path, non-Windows): - claude native installer (~/.local/share/claude/versions/) -> `claude update` - Homebrew formula (canonical path under /Cellar/) -> `brew upgrade <formula>` - volta / bun -> `volta install` / `bun add -g` - Node managers with sibling npm (nvm/fnm/mise/homebrew-npm) -> that directory's npm with `i -g <pkg>@latest` - system / unknown sources (e.g. opencode install.sh under ~/.opencode/bin, ~/go/bin) -> fall back to the bare command, surfaced as anchored=false so the UI shows an honest "default entry not determinable" hint instead of pretending the upgrade is targeted. Multi-install confirmation: - When the probe finds >=2 installs, pop ToolUpgradeConfirmDialog showing each install (source badge + path + version + Default badge) and the resolved command before executing. Top-level text only asserts what is universally true ("won't update all of them; see each tool below"); per-card text adapts to anchored vs unanchored. - Pending upgrade state stores the full upgrade set; the dialog only shows the subset that needs confirmation, so non-conflicting tools in a batch still get upgraded after confirm. Unified probe command: - Merge the previous diagnose_tool_installations and plan_tool_upgrades into a single probe_tool_installations returning installs + is_conflict + needs_confirmation + command + anchored. Diagnose button, post-upgrade auto-diagnose, and pre-upgrade confirmation all share one IPC. The diagnose button's previous Promise.all of 6 IPCs is now a single call. resolve_path_default robustness: - The previous lines().next() was poisoned by interactive .zshrc output (welcome banners, p10k instant prompts, etc.), causing default-install detection to fail and dropping multi-install scenarios into the unanchored fallback even when one install was clearly the native one. - Replaced with first_abs_path_line that scans for the first absolute-path line and ignores noise, mirroring how try_get_version already tolerates banner noise via a regex. Diagnostic staleness fix: - diagnoseToolSilently now clears stale diagnostics in the else branch when the conflict no longer holds (previously only wrote on conflict, never cleared, so externally resolving a conflict left the card stuck on a stale list until the user clicked Diagnose). - Auto-diagnose now triggers after any successful update, not only when the version didn't change, so the card reflects the latest conflict state even when an anchored upgrade succeeds while another install remains. Other: - Shared ToolInstallRow between the conflict list and the confirmation dialog removes the duplicated per-install row JSX. - Drop the new shell_quote_path helper in favor of reusing the file's existing POSIX-correct shell_single_quote, gated on whitespace. - Collapse displayName lookup into a stable module-level helper to remove the `as ToolName` cast and avoid recreating the closure each render. - 17 backend unit tests covering anchored command generation across each source, brew formula extraction, is_conflicting thresholds, default install resolution, and the welcome-banner scenario. - i18n (zh/en/ja): new keys for confirmation dialog title/hint/will-run/confirm-button and the unanchored hint.Jason ·
2026-05-25 22:20:34 +08:00 -
feat(settings): diagnose conflicting tool installations with uninstall hints
Add a "Diagnose installs" action to the About section that enumerates every installation of each managed CLI, flags real conflicts (diverging versions or mixed runnable state), and lists them under the affected tool card. - backend: enumerate_tool_installations + diagnose_tool_installations command, sharing build_tool_search_paths with the single-probe scan; resolve the PATH default via the same login shell as version probing so the "Default" marker matches what the command line (and an upgrade) actually targets - frontend: global diagnose button left of Refresh, per-card conflict list, and a copy-only uninstall command per install -- source-aware (npm/volta/ bun/pip) and anchored to that install's own npm to avoid removing the wrong node version; marked with a red trash icon, never executed - auto-diagnose silently after an upgrade that leaves the version unchanged or installs something that can't run - i18n: zh/en/ja
Jason ·
2026-05-25 22:20:34 +08:00 -
fix(settings): surface "installed but not runnable" state in tool cards
After an install/update command succeeds, re-probe the tool version: if it still can't be detected the tool is installed-but-unrunnable (e.g. Node too old), so report a warning toast and show "installed · can't run" with the cause, instead of a misleading "success" plus a stale version number. Read the backend `installed_but_broken` flag rather than matching error text; the card's current-version line, action button (no useless "install" for a broken tool), and a "check environment" hint all key off it. Drop the now-redundant version-clearing setState (the refresh merge already nulls it), flatten the action ternary, and use extractErrorMessage in the catch path. Adds toolNotRunnable / toolActionInstalledNotRunnable / installedNotRunnable / toolCheckEnv i18n keys (zh/en/ja).
Jason ·
2026-05-25 22:20:34 +08:00 -
fix(misc): probe tool versions faithfully instead of masking unrunnable installs
Unify the three probe paths (try_get_version / try_get_version_wsl / scan_cli_version) on a cross-platform ShellProbe enum that distinguishes "not installed" (shell exit 127) from "installed but --version failed" (e.g. Node too old to run the freshly-upgraded CLI). The found-but-broken case is now reported as-is and no longer falls back to scan_cli_version. Previously the fallback surfaced a stale runnable copy installed elsewhere (e.g. a Homebrew node vs the nvm node the upgrade wrote to), making an upgrade look like it had no effect ("succeeded but version unchanged"). Expose the state via a structured `installed_but_broken` flag on ToolVersion so the frontend no longer infers it by string-matching the error text. Also fixes a WSL path that could mislabel a genuinely-missing tool as broken, and extracts the NOT_INSTALLED constant.Jason ·
2026-05-25 22:20:34 +08:00 -
fix(settings): run batch updates per-tool and lock actions while busy
Update All previously sent every tool to the backend as one script, where set -e (or errorlevel on Windows) aborts the whole batch on the first failure, skipping the rest and not refreshing versions for tools that did succeed. Run each tool as its own call instead: failures are isolated, each tool's version refreshes as soon as it finishes, and the result is summarized in a toast (success / partial / failure) listing which tools failed. Also derive an isAnyBusy flag and disable all install/update buttons, the refresh button and the WSL shell selectors while any action is running, so users can't trigger concurrent global npm/pip writes. Add the toolActionPartial i18n key (zh/en/ja).
Jason ·
2026-05-25 22:20:34 +08:00 -
feat(misc): broaden CLI version detection to PATH and Windows managers
Extend scan_cli_version search paths with the PATH environment variable and common Windows Node/version-manager locations (pnpm, Volta, nvm, Scoop, Yarn). Entries from PATH are skipped when they point at the Microsoft\WindowsApps App Execution Alias directory, which is what previously caused bogus launches on Windows. Add unit tests for PATH dedup, child-dir suffix expansion, and the WindowsApps alias skip.
Jason ·
2026-05-25 22:20:34 +08:00 -
docs: update settings manual for tool management and Hermes
Add Hermes to the environment-check table and app-switcher list, fix the OpenCode install command (opencode-ai), and describe the new update/install flow. Align with the final implementation: drop the removed Install Missing action, note that installs run silently with in-button progress, and rename the section to Manual Install Commands (now collapsible).
Jason ·
2026-05-25 22:20:34 +08:00 -
feat(settings): expand About page into a tool management panel
List all managed apps with current/latest versions, with per-tool install and update buttons plus an Update All action. Installs and updates now run silently: the button shows a spinner for the full duration, versions refresh automatically when done, and failures surface the backend error detail in a toast. While loading, the button keeps its label and only swaps the icon for a spinner, so width stays constant across zh/en/ja instead of jumping when the text changes (e.g. Update -> Updating...). Also collapses and renames the install-commands area to Manual Install Commands, and removes the managed-apps badge row and the Install Missing batch button.
Jason ·
2026-05-25 22:20:34 +08:00 -
feat: add silent install/update lifecycle for managed CLI tools
Expand tool version detection to all six managed apps (including Hermes via PyPI), and add a run_tool_lifecycle_action command that installs or updates CLI tools silently: it captures the subprocess output and blocks until the command actually finishes instead of popping a terminal window (Windows uses CREATE_NO_WINDOW). On failure the last lines of stderr are returned so the UI can surface a useful message. Also restores the Windows version-detection path by gating try_get_version behind cfg(not(windows)) so it no longer risks triggering the App Execution Alias / protocol handler that previously disabled Windows.
Jason ·
2026-05-25 22:20:34 +08:00 -
refactor(proxy): replace panic-prone unwrap/expect with safe patterns
Harden the proxy module against panics by removing optimistic unwrap()/expect() calls in favor of pattern matching and graceful fallbacks: - copilot_optimizer/cache_injector: bind Value::Array/String directly instead of is_array()+as_array().unwrap(); use is_none_or and in-place string mutation - hyper_client: gate the raw-write path with if-let + filter instead of has_cases + unwrap() - gemini_shadow: recover poisoned RwLock via into_inner() rather than panicking, with warn logging - streaming_codex_chat: replace expect("tool state exists") with let-else (return/continue) - merge_tool_results: early-return when messages is absent - sse: fall back to from_utf8_lossy on the UTF-8 boundary slice No behavior change on the happy path; all proxy tests pass.Jason ·
2026-05-25 22:20:34 +08:00 -
test: align stale tests with merged Codex preset and bucket changes
CI on main was red because two tests did not follow intentional changes that were already merged into the codebase: - codexChatProviderPresets.test.ts still expected the "Kimi For Coding" preset, which was removed in 16c3ef3f. Drop the corresponding entry from the expected presets map. - import_export_sync.rs still asserted the legacy per-provider model_provider id ("rightcode"), but 9fac15b8 unified Codex third-party providers into the stable "custom" history bucket. Mirror the assertion update already applied to provider_service.rs. No production code changed; both fixes only update test expectations.Jason ·
2026-05-25 22:20:34 +08:00 -
fix: coerce empty tool-call arguments to {} in Codex Chat transform
No-argument tool calls produced empty argument strings that passed through both the streaming response path and the request transform verbatim. Strict OpenAI-compatible upstreams (e.g. Minimax) reject `arguments: ""` with a 400 "invalid function arguments json string", while lenient ones (OpenAI, Kimi) silently treat it as an empty object. Add canonicalize_tool_arguments / canonicalize_tool_arguments_str helpers that coerce empty/whitespace arguments to "{}" and apply them at the four tool-call argument sites (streaming finalize + three request/response transform paths). Leave function_call_output content untouched, where an empty string is valid.Jason ·
2026-05-25 22:20:34 +08:00 -
docs: document Codex Chat reasoning auto-detection
Add a "Reasoning Auto-Detection" subsection to the Codex local-routing manual (zh/en/ja) with the provider capability matrix, and record the feature plus the streaming-usage and tool-call reasoning backfill fixes in the changelog. Drop the removed Kimi For Coding preset from the changelog's Codex Chat preset list.
Jason ·
2026-05-25 22:20:34 +08:00 -
feat: adaptive reasoning detection for Codex Chat providers
Auto-detect each Chat-routed Codex provider's reasoning interface from its name, base URL, and model, then inject the matching thinking parameter without manual configuration: - Platform-first inference (OpenRouter, SiliconFlow) overrides model rules, since the same model exposes different reasoning controls depending on the hosting platform. - Effort tiers are forwarded only to providers that support them (DeepSeek, OpenRouter, and StepFun's step-3.5-flash-2603); on/off-only providers (Kimi, GLM, Qwen, MiniMax, MiMo, SiliconFlow) drop the level instead of sending a field the upstream rejects. - OpenRouter uses the native reasoning:{effort} object, clamps max to xhigh (its enum has no max), and forwards an explicit effort:"none" so reasoning can be turned off. - StepFun falls back to inference so per-model effort support is honored (the static preset would have forced effort on step-3.5-flash too). Includes the Codex provider-form reasoning controls, i18n strings (zh/en/ja), and response-side reasoning extraction.Jason ·
2026-05-25 22:20:33 +08:00 -
fix: inject stream_options.include_usage for Codex Chat streaming
Responses-to-Chat conversion did not set stream_options.include_usage, so OpenAI-compatible upstreams (kimi/MiniMax, etc.) omitted the trailing usage chunk in streaming mode. This caused token/cost/cache stats to be recorded as zero for third-party streaming requests routed through the Codex Chat path. Inject include_usage when the request is streaming, merging into any client-provided stream_options instead of overwriting it. Add tests for the streaming, non-streaming, and merge cases.
Jason ·
2026-05-25 22:20:33 +08:00 -
feat: remove Kimi For Coding preset from Codex
The Kimi For Coding preset pins users to a single coding-only model (kimi-for-coding), restricting model selection. Drop it from the Codex presets and trim the corresponding row in the trilingual user manual. The general Kimi preset (Moonshot platform) is kept, and presets for other apps are unchanged.
Jason ·
2026-05-25 22:20:33 +08:00 -
fix: backfill placeholder reasoning_content for bare tool-call messages
Thinking models like kimi/Moonshot and DeepSeek reject any assistant message carrying tool_calls without a non-empty reasoning_content. When cross-turn history recovery misses (proxy restart drops the in-memory cache, ambiguous call_id, or a turn produced no reasoning upstream), the converted assistant message has none and the whole request fails with 'reasoning_content is missing in assistant tool call message'. Add a final-stage backfill that runs after all input items are processed, so genuine trailing reasoning items still attach first and a placeholder is only injected when none remains. Mirrors the placeholder behavior in transform::anthropic_to_openai_with_reasoning_content.
Jason ·
2026-05-25 22:20:33 +08:00 -
fix: reclassify ClaudeAPI as aggregator to re-enable model test
ClaudeAPI supports the health/stream check, but the third_party category triggers the isClaudeThirdParty gate in ProviderCard that disables the model test button for third-party Claude providers. Recategorizing as aggregator restores the test action; the partner star is unaffected since it is driven by isPartner, not category. Applied to both the Claude Code and Claude Desktop presets.
Jason ·
2026-05-25 22:20:33 +08:00 -
docs: document Codex Chat provider support in changelog and manual
Backfill the empty [Unreleased] CHANGELOG section with the Codex Chat Completions feature (Chat-to-Responses bridge, 23 third-party presets, model mapping table, Stream Check routing, error-envelope conversion, "custom" history bucket) plus the community fixes landed since v3.15.0. Update the zh/en/ja user manual: split the Codex preset tables by upstream protocol (native Responses vs Chat Completions), add a "Codex Local Routing and Model Mapping" section covering the Needs Local Routing toggle and the model catalog, and note Chat-format probing in the Stream Check guide. Manual wording matches the live UI strings.
Jason ·
2026-05-25 22:20:33 +08:00 -
feat: convert Codex Chat error responses to Responses envelope
The Codex Chat-to-Responses bridge already rewrites successful upstream responses to the Responses shape, but the error branch in handle_codex_chat_to_responses_transform passed Chat-shaped error bodies through untouched (MiniMax base_resp, raw OpenAI Chat error, text/plain "Unauthorized" pages, etc.), leaving Codex clients unable to recognize the error. Add chat_error_to_response_error in transform_codex_chat to regularize all upstream error shapes into the standard {error: {message, type, code, param}} envelope, then wire it through a new handle_codex_chat_error_response that preserves the original HTTP status code. Non-JSON error bodies (HTML, plain text) are wrapped as Value::String and truncated to 1KB at a UTF-8 char boundary to keep diagnostic context without flooding the response. Also fix a pre-existing append-vs-insert pitfall in three rebuilt-body branches (Claude transform, Codex Chat normal, Codex Chat error): http Builder::header is append, so leaking the upstream Content-Type produced two Content-Type headers when the rewritten body was JSON. Remove the upstream value before writing application/json.Jason ·
2026-05-25 22:20:33 +08:00 -
feat: route Codex Chat providers through Stream Check
Codex Chat providers (apiFormat=openai_chat, e.g. DeepSeek, MiniMax, Kimi) were incorrectly probed via /v1/responses by Stream Check, which the upstream rejects. Detect chat routing via codex_provider_uses_chat_completions and issue the probe against /chat/completions with a Chat-shaped body. Also align URL fallback order with the production CodexAdapter::build_url: origin-only base URLs (https://api.deepseek.com) must hit /v1/<endpoint> first; bare /<endpoint> only as a fallback. The previous order made any non-404 error on the bare path (401/400/405) flag the provider as down even though /v1/<endpoint> would have succeeded. - Lift origin-only detection into proxy::providers::is_origin_only_url so both build_url and Stream Check share a single source of truth. - Collapse resolve_codex_stream_urls and resolve_codex_chat_stream_urls into a generic resolve_codex_endpoint_urls(base_url, is_full_url, endpoint), which also gives the Responses path a symmetric "full endpoint without is_full_url flag" fallback for free. - Restrict reasoning_effort propagation in the Chat branch to OpenAI o-series models, mirroring transform_codex_chat's runtime check.
Jason ·
2026-05-25 22:20:33 +08:00 -
refactor: rewrite Codex local routing toggle hints for clarity
Reframe OFF/ON hints as action guidance (when to enable) rather than scenario descriptions, mirroring the Claude Desktop model mapping copy style. Synced across zh/en/ja locales and the component-level defaultValue fallback. The switch label "Needs Local Routing" is kept unchanged to preserve the routing-layer semantics specific to Codex.
Jason ·
2026-05-25 22:20:33 +08:00 -
fix: collapse mid-stream system messages in Codex Responses to Chat conversion
MiniMax's OpenAI-compatible chat endpoint strict-rejects any non-leading role=system message with "invalid params, chat content has invalid message role: system (2013)". The Codex client uses role=developer (and occasionally role=system) to inject collaboration_mode / permissions / skills blocks mid-conversation, and responses_role_to_chat_role maps both to chat's system role. The converted messages array therefore frequently contained system entries past index 0, which DeepSeek and OpenAI tolerate but MiniMax flags as 2013. Collapse all system messages into a single leading system message before returning the chat request. The rewrite preserves every system fragment (joined by "\n\n" in original order) and leaves non-system messages untouched, so it is lossless for permissive backends as well. - Add collapse_system_messages_to_head in transform_codex_chat.rs. - Run it on the messages vector at the end of responses_to_chat_completions before serializing. - Cover the new path with two unit tests: one repros the MiniMax-shaped input (developer items between users) and asserts no system role past index 0; the other verifies non-system order is preserved and content is joined with "\n\n".
Jason ·
2026-05-25 22:20:33 +08:00 -
feat: add Chat Completions routing for 22 Codex third-party presets
Enable openai_chat routing with explicit model catalogs across the major Chinese/Asian providers (DeepSeek, Zhipu GLM, Kimi, MiniMax, Baidu Qianfan, Bailian, StepFun, Volcengine Agent Plan, BytePlus, DouBaoSeed, ModelScope, Longcat, BaiLing, Xiaomi MiMo, SiliconFlow, Novita AI, Nvidia, etc.). Each preset declares its context window so the UI can size catalog rows when the preset is picked. Also lands two consistency fixes uncovered along the way: - Include setCodexCatalogModels in resetCodexConfig's useCallback deps to match the new third parameter it consumes. - Realign TheRouter Codex test to the "custom" model_provider bucket established by the recent third-party unification; the previous assertion predated that refactor and had been failing on HEAD.
Jason ·
2026-05-25 22:20:33 +08:00 -
fix: preserve Codex model catalog on live read of active provider
Editing the currently active Codex provider triggered `read_live_settings`, which returned only { auth, config }, omitting `modelCatalog`. The form's mapping table then initialized to empty, and a subsequent save wiped the DB's `modelCatalog` field — silently destroying user-configured model mappings after every CC Switch restart. The mappings already live on disk as a projection in `~/.codex/cc-switch-model-catalog.json` (pointed at by `config.toml`'s `model_catalog_json`). Reverse-parse that file so live reads return the same shape the save path writes. - Add `read_codex_model_catalog_simplified_from_live` to recover `{ model, displayName?, contextWindow? }` entries from the catalog file. - Skip user-managed external catalogs (filename != cc-switch-model-catalog.json) so we don't downgrade their richer structure to the simplified table. - Squash display_name == slug and context_window == default (config's `model_context_window`, or 128_000 fallback) so blank inputs round-trip back to blank instead of materializing fallback values in the UI. - Collapse all failure modes (missing file, parse error, no matching field) to Ok(None) so the editor stays openable when the projection file is absent or corrupt. - Wire the new function into `read_live_settings`'s Codex branch. - Cover the new pure helpers with 7 unit tests in codex_config::tests.Jason ·
2026-05-25 22:20:33 +08:00 -
feat: unify Codex third-party providers into stable "custom" history bucket
Codex filters resume history by `model_provider`, so switching between provider-specific ids like `rightcode` and `aihubmix` made past sessions appear to vanish. Collapse all third-party providers into a single stable bucket so cross-switch history stays visible. - Normalize live `model_provider` to "custom" on every Codex write (reserved built-in ids like openai/ollama are preserved). - Add device-level one-shot migration that rewrites historical JSONL session files and the `state_5.sqlite` threads table from legacy provider ids into the "custom" bucket. Backs up originals under `~/.cc-switch/backups/codex-history-provider-migration-v1/` and uses the SQLite Backup API for the state DB. - Record completion in `settings.json` under `localMigrations` so the migration is strictly idempotent across launches. - Update Codex provider preset templates to emit `model_provider = "custom"` out of the box.
Jason ·
2026-05-25 22:20:33 +08:00 -
feat: preserve user-selected catalog model in Codex Chat requests
When Codex client sends a model from the catalog (e.g., user selected via /model), preserve that choice instead of always replacing with config.toml's default model. - Check if request model exists in modelCatalog before falling back to config model - Remove CODEX_CHAT_CLIENT_MODEL constant (no longer needed) - Add test coverage for catalog model preservation
Jason ·
2026-05-25 22:20:33 +08:00 -
fix: prevent infinite render loop in Codex model catalog
Break bidirectional sync cycle between catalogRows (child) and catalogModels (parent): - Remove catalogModels from child→parent effect dependencies - Track last sent data in ref to avoid redundant callbacks - Sync ref when parent pushes new data to prevent false positives Fixes severe UI jittering when adding/editing catalog entries.
Jason ·
2026-05-25 22:20:33 +08:00 -
fix: Codex model catalog WYSIWYG and config consolidation
- Remove mergeCodexDefaultCatalogModelForSave implicit injection (P1) The model mapping table is now the single source of truth; no hidden entries are prepended on save. - Sync first catalog row model into config.toml on save Ensures Codex default request model matches the table's first entry instead of retaining a stale template value. - Remove API Format selector from CodexFormFields (P3) wire_api is always 'responses'; the selector confused users into thinking they were changing the upstream protocol. Only the 'Needs Local Routing' toggle remains. - Add restart hint to model mapping i18n text (P2) model_catalog_json is loaded at Codex startup; users are now informed that a restart is needed after changes. - Unify write_codex_live_with_catalog helper (P4) Replaces three scattered prepare+write call sites in config.rs, provider/live.rs, and proxy.rs with a single entry point. - Clean up useCodexConfigState dead state (P3 follow-up) Remove codexModelName, codexContextWindow, codexAutoCompactLimit and their handlers/effects since no component consumes them after the UI consolidation.
Jason ·
2026-05-25 22:20:33 +08:00 -
- Restore Codex Chat reasoning fallback
- Add unique call-id fallback when Codex omits or rewrites previous_response_id. - Keep ambiguous call ids unresolved to avoid cross-request history leaks. - Cover fallback and ambiguity behavior with regression tests.
Jason ·
2026-05-25 22:20:33 +08:00 -
- Optimize Codex Chat cache stability
- Stop deriving Codex session/cache identity from `previous_response_id`. - Canonicalize parseable JSON string payloads in Chat and Responses tool conversions. - Add regression coverage for cache-sensitive conversion behavior.
Jason ·
2026-05-25 22:20:33 +08:00 -
Add Codex Chat-to-Responses bridge
- Add Codex provider API format selection and model mapping for Chat-only upstreams. - Convert Codex Responses requests to Chat Completions and rebuild Chat responses as Responses output. - Preserve reasoning_content across non-streaming, streaming, tool calls, and previous_response_id follow-ups. - Add a bounded Codex Chat history cache for restoring tool calls before tool outputs. - Cover Chat bridge, compact routing, streaming, and history recovery with focused tests.
Jason ·
2026-05-25 22:20:33 +08:00 -
Update 1.2-installation.md (#2949)
增加 Windows 运行后无提示的修复方法
狂飙 ·
2026-05-20 15:04:56 +08:00 -
Jason ·
2026-05-19 16:40:19 +08:00 -
docs: recommend official Homebrew cask installation
- Remove personal tap requirement from all READMEs (en/zh/ja) - Update v3.15.0 release notes to highlight official cask availability - Add celebration banner noting Homebrew official repository inclusion - Simplify installation to single command: brew install --cask cc-switch
Jason ·
2026-05-19 16:14:56 +08:00 -
Handle inline thinking in Codex Chat conversion
- Split leading <think> blocks from Chat content into Responses reasoning output. - Keep assistant text output free of inline thinking tags. - Support streamed inline thinking blocks before normal text deltas. - Add regression coverage for MiniMax-style inline thinking responses.
Jason ·
2026-05-19 11:06:12 +08:00 -
Improve Codex Chat reasoning conversion
- Convert Chat reasoning_content and reasoning fields into Responses reasoning output items. - Emit Responses reasoning summary SSE events for streamed Chat reasoning deltas. - Preserve output ordering when reasoning, text, and tool calls are streamed together. - Add regression coverage for data-only Chat SSE errors, multiple tool calls, and compact routing.
Jason ·
2026-05-19 10:33:24 +08:00